One, foreword
With the continuous iteration and development of Meituan takeout business, the number of takeout users is also growing at a high speed. In this process, takeout marketing plays a “mainstay” role, because the rapid growth of users can not do without efficient marketing strategy. Due to the changeable market environment and business environment, marketing strategy is often complex and changeable. As the support department of marketing business, marketing technology team needs to respond to the demand changes brought by the change of marketing strategy quickly and efficiently. Therefore, to design and implement a marketing system that is easy to expand and maintain is the goal and required basic skills of Meituan takeout marketing technology team.
This article takes a top-down approach to how design patterns help us build a marketing system that is easy to expand and maintain. This paper will first introduce the relationship between Design pattern and domain-driven Design (HEREAFTER referred to as DDD), and then elaborate the Design pattern used in the introduction of takeout marketing business and its specific practice cases.
Design patterns and domain-driven design
When designing a marketing system, we usually take a top-down approach to deconstructing the business, and for this we introduced DDD. At a strategic level, DDD can guide us through the problem space to solution analysis, mapping business requirements to domain context and mapping relationships between contexts. At the tactical level, DDD can refine the domain context and form effective, detailed domain models to guide engineering practices. A key aspect of domain modeling is to ensure that expanding and changing requirements evolve and evolve within the domain model without model corruption and domain logic spillover. For DDD practice, you can refer to the practice of Domain-driven Design in Internet Business Development published by Meituan technical team.
At the same time, we also need to implement and implement domain models in code engineering. Because code engineering is the direct embodiment of domain model in engineering practice, but also the direct expression of domain model at the technical level. Design pattern, as a bridge between domain model and code engineering, can effectively solve the transformation from domain model to code engineering.
Why do design patterns naturally serve as a bridge between domain models and code engineering? In fact, Eric Evans, author of the 2003 book Domain-Driven Design, explains this in his first book. He argues that different positions affect how people see what a “model” is. Therefore, both domain-driven patterns and design patterns are essentially “patterns” that solve different problems. From a business modeling standpoint, DDD’s patterns address how to model the domain. From the standpoint of code practice, design pattern mainly focuses on the design and implementation of code. Since nature is a pattern, they naturally have something in common.
A “model” is a set of methodologies that have been used or validated over and over again. From an abstract or larger point of view, patterns should be applied to both DDD and design patterns, as long as they fit into usage scenarios and solve real-world problems. In fact, that’s what Evans did. In his book, he explains how Strategy and Composite, two traditional GOF design patterns, solve domain model construction. Therefore, when domain models need to be translated into code engineering, isomorphic patterns can naturally translate domain models into code models.
Iii. Specific cases of design mode in takeaway marketing business
3.1 Why are design patterns needed
Characteristics of marketing business
As mentioned above, the difference between marketing business and other relatively stable businesses such as transactions is that marketing needs will be adjusted with the constant changes of the market, users and environment. It is for this reason that the delivery marketing technology team chose DDD to conduct domain modeling and, where applicable, practice and reflect domain modeling at the level of code engineering with design patterns. This allows domain and code models to evolve healthily and avoid model corruption while supporting business change.
Understand design patterns
Software Design pattern, also known as Design pattern, is a set of repeatedly used, most people know, after classification and cataloging, code Design experience summary. Design patterns are used for reusable code, making it easier for others to understand, ensuring code reliability and program reuse. It can be interpreted as: “There is no design pattern in the world, but as many people use it, they conclude a set of design patterns.”
Design pattern principles
There are seven basic principles of object-oriented design patterns:
- Open Closed Principle (OCP)
- Single Responsibility Principle (SRP)
- Liskov Substitution Principle (LSP)
- Dependency Inversion Principle (DIP)
- Interface Segregation Principle (ISP)
- Composite/Aggregate Reuse Principle (CARP)
- Least Knowledge Principle (LKP) or Law of Demeter (LOD)
Simple understanding is: open closed principle is the general principle, it guides us to be open to expansion, closed to modification; The single responsibility principle guides us to implement a class with a single responsibility; Richter’s substitution principle instructs us not to destroy the inheritance system; The dependency inversion principle instructs us to program for interface; The principle of interface isolation guides us to simplify and simplify interface design. Demeter’s law instructs us to reduce coupling.
Design patterns guide us through these seven principles of how to make good design. However, design pattern is not a set of “clever techniques”, it is a set of methodology, a high cohesion, low coupling design ideas. We can play freely on this basis, and even design a set of design patterns of our own.
Of course, the study of design pattern or the practice of design pattern in engineering must go deep into a specific business scene, and then combine the understanding of business scene and the establishment of domain model, to realize the essence of design pattern thought. Learning or using design patterns outside of concrete business logic is extremely hollow. Next, we’ll explore how design patterns can be used to achieve reusable, maintainable code through the practice of a takeaway marketing business.
3.2 Practice of design pattern in “invitation to order” business
3.2.1 Service Overview
“Order by invitation” is a platform where meituan takeout users invite other users to place orders and give rewards. That is, user A invites user B, and user B gives user A certain cash reward (hereinafter referred to as reward) after placing an order with Meituan. At the same time, in order to coordinate the relationship between cost and benefit, there will be multiple calculation strategies. Invitation ordering background mainly involves two technical points:
- The calculation of reward amount involves different calculation rules.
- The whole process from invitation to reward.
3.2.2 Reward rules and design mode practice
Business modeling
Here is a business logic view of the calculation of reward rules:
From this business logic diagram, you can see the rules for calculating the rebate amount. First of all, according to the user status to determine whether the user meets the reward conditions. If the reward conditions are met, it will continue to judge whether the current user is a new user or an old user, so as to give different reward schemes. The following different incentive schemes are involved:
A new user
- Ordinary rewards (give a fixed amount of rewards)
- Gradient award (give different reward amount according to the number of users invited, the more people invited, the more reward amount)
Old user
- According to the old user user attributes to calculate the amount of reward. In order to evaluate the different effects of inviting new users, there will be a variety of reward mechanisms.
After calculating the reward amount, it also needs to update the user’s bonus information and notify the settlement service to settle the user’s amount. These two modules are the same for all rewards.
As you can see, no matter what kind of user, the overall reward process is the same, the only change is the reward rules. Here, we can refer to the open and closed principle to keep the reward process closed and the reward rules open for possible expansion. We abstract the reward return rules into reward return strategies, that is, different reward return schemes of different user types are regarded as different reward return strategies, and different reward return strategies will produce different reward amount results.
In our domain model, the reward policy is a value object, and we produce the reward policy value object for different users in a factory way. Below we will introduce the engineering implementation of the above domain model, i.e. the practical application of the factory pattern and the policy pattern.
Mode: Factory mode
Factory pattern is divided into factory method pattern and abstract factory pattern. This paper introduces factory method pattern.
Schema definition: Defines an interface for creating objects and lets subclasses decide which class to instantiate. A factory method is a class whose instantiation is deferred to its subclasses.
The factory pattern generic class diagram is as follows:
Let’s use some generic code to explain how to use the factory pattern:
// Abstract product
public abstract class Product {
public abstract void method(a);
}
Define a specific product (multiple specific products can be defined)
class ProductA extends Product {
@Override
public void method(a) {} // Specific execution logic
}
// Abstract factory
abstract class Factory<T> {
abstract Product createProduct(Class<T> c);
}
// Specific factories can produce corresponding products
class FactoryA extends Factory{
@Override
Product createProduct(Class c) {
Product product = (Product) Class.forName(c.getName()).newInstance();
returnproduct; }}Copy the code
Mode: Policy mode
Pattern definition: Define a series of algorithms, encapsulate each algorithm, and they are interchangeable. The policy pattern is an object behavior pattern.
The general class diagram of policy pattern is as follows:
Let’s use some generic code to explain how to use the policy pattern:
// Define a policy interface
public interface Strategy {
void strategyImplementation(a);
}
// Specific policy implementations (multiple specific policy implementations can be defined)
public class StrategyA implements Strategy{
@Override
public void strategyImplementation(a) {
System.out.println("Executing Strategy A"); }}// Encapsulate policies to shield high-level modules from direct access to policies and algorithms, and shield possible policy changes
public class Context {
private Strategy strategy = null;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void doStrategy(a) { strategy.strategyImplementation(); }}Copy the code
The engineering practice
Through the reward business model introduced above, we can see that the main process of reward is the process of choosing different reward strategies, and each reward strategy includes three steps: reward amount calculation, updating user bonus information, and settlement. We can use the factory pattern to produce different policies and use the policy pattern for different policy execution. First, we need to generate n different reward strategies, which are encoded as follows:
// Abstract policy
public abstract class RewardStrategy {
public abstract void reward(long userId);
public void insertRewardAndSettlement(long userId, int reward) {};// Update user information and settlement
}
// New user reward specific strategy A
public class newUserRewardStrategyA extends RewardStrategy {
@Override
public void reward(long userId) {} // Specific calculation logic...
}
// Old users reward specific strategy A
public class OldUserRewardStrategyA extends RewardStrategy {
@Override
public void reward(long userId) {} // Specific calculation logic...
}
// Abstract factory
public abstract class StrategyFactory<T> {
abstract RewardStrategy createStrategy(Class<T> c);
}
// Create specific policies for specific factories
public class FactorRewardStrategyFactory extends StrategyFactory {
@Override
RewardStrategy createStrategy(Class c) {
RewardStrategy product = null;
try {
product = (RewardStrategy) Class.forName(c.getName()).newInstance();
} catch (Exception e) {}
returnproduct; }}Copy the code
Having produced the specific policies through the factory pattern, it is easy to think of using the policy pattern to execute our policies based on our previous introduction. The specific code is as follows:
public class RewardContext {
private RewardStrategy strategy;
public RewardContext(RewardStrategy strategy) {
this.strategy = strategy;
}
public void doStrategy(long userId) {
int rewardMoney = strategy.reward(userId);
insertRewardAndSettlement(long userId, intreward) { insertReward(userId, rewardMoney); settlement(userId); }}}Copy the code
Then we combine the factory model with the strategy model to complete the reward process:
public class InviteRewardImpl {
// Reward main process
public void sendReward(long userId) {
FactorRewardStrategyFactory strategyFactory = new FactorRewardStrategyFactory(); // Create a factory
Invitee invitee = getInviteeByUserId(userId); // Query user information based on the user ID
if (invitee.userType == UserTypeEnum.NEW_USER) { // New user reward policy
NewUserBasicReward newUserBasicReward = (NewUserBasicReward) strategyFactory.createStrategy(NewUserBasicReward.class);
RewardContext rewardContext = new RewardContext(newUserBasicReward);
rewardContext.doStrategy(userId); // Execute a reward return strategy
}if(invitee.userType == UserTypeEnum.OLD_USER){} // Old user reward strategy,...}}Copy the code
The factory method pattern helps us to directly generate a concrete policy object, and the policy pattern helps us to ensure that these policy objects can be switched freely without changing other logic, thus achieving decoupling. Through the combination of these two patterns, when we need to add a RewardStrategy to our system, we only need to implement the RewardStrategy interface, without considering other changes. When we need to change the policy, we simply change the class name of the policy. It not only enhances the scalability of the system, avoids a lot of conditional judgment, but also achieves the purpose of high cohesion and low coupling in a real sense.
3.2.3 Reward return process and design mode practice
Business modeling
When the inviter accepts the inviter’s invitation and places an order, the prize return background receives the inviter’s order record, and the inviter also enters the prize return process. First, we subscribe to the user order message and verify the reward rule for the order. For example, whether to use red envelope order, whether to place an order within the red envelope validity period, whether the order meets a certain discount amount and other conditions. When these conditions are met, we put the order information into a delay queue for subsequent processing. After T+N days, the delayed message is processed to determine whether the user has refunded the order. If no refund is made, the user will be given a reward. If the reward fails, the backstage has the reward compensation process, and the reward is returned again. Its process is shown in the figure below:
We domain-modeled the above business processes:
- After receiving the order message, the user enters the state to be verified.
- After verification, if the verification passes, the user enters the pre-reward state and is put into the delay queue. If the verification fails, the user enters the state of no reward return, and the process ends.
- After T+N days, the delayed message will be processed. If the user does not refund, the user will enter the state of waiting for reward. If the user refunds, the process will be terminated and the user enters the failed state.
- Return the prize, if successful, enter the completed state, end the process. If the reward is not successful, enter the state to be compensated;
- The user to be compensated will trigger the compensation mechanism periodically by the task until the reward is returned successfully and the process is completed.
As you can see, we modeled the multiple steps of the reward process to map to the state of the system. For the representation of system state, the concept of domain events is often used in DDD, and the practice of event traceability is also mentioned. Of course, in design patterns, there is also a code model that can express the state of the system, and that is the state pattern. In the invitation-order system, our main process is reward. For rewards, each state has a different set of actions and operations to perform. Therefore, using state mode can help us to manage and expand the system state and the flow between states uniformly.
Mode: State mode
Schema definition: allows an object to change its behavior when its internal state changes, as if the object changed its class.
The generic class diagram for the state mode is shown below:
When comparing the types of the policy pattern, you will find that they are similar to the class diagram of the state pattern, but there is actually a big difference, which is embodied in concrete Class. The strategy pattern uses the Context to generate a single ConcreteStrategy to act on code, whereas the state pattern uses the Context to organize multiple ConcreteStates into a state transition diagram to implement business logic. Next, let’s use some generic code to explain how to use state patterns:
// Define an abstract state class
public abstract class State {
Context context;
public void setContext(Context context) {
this.context = context;
}
public abstract void handle1(a);
public abstract void handle2(a);
}
// Define state A
public class ConcreteStateA extends State {
@Override
public void handle1(a) {} // Must be handled in this state
@Override
public void handle2(a) {
super.context.setCurrentState(Context.contreteStateB); // Switch to state B
super.context.handle2(); // Execute the task in state B}}// Define state B
public class ConcreteStateB extends State {
@Override
public void handle2(a) {} // What must be done in this state...
@Override
public void handle1(a) {
super.context.setCurrentState(Context.contreteStateA); // Switch to state A
super.context.handle1(); // Execute the task in state A}}// Define a context management environment
public class Context {
public final static ConcreteStateA contreteStateA = new ConcreteStateA();
public final static ConcreteStateB contreteStateB = new ConcreteStateB();
private State CurrentState;
public State getCurrentState(a) {returnCurrentState; }public void setCurrentState(State currentState) {
this.CurrentState = currentState;
this.CurrentState.setContext(this);
}
public void handle1(a) {this.CurrentState.handle1(); }public void handle2(a) {this.CurrentState.handle2();}
}
// Define client execution
public class client {
public static void main(String[] args) {
Context context = new Context();
context.setCurrentState(newContreteStateA()); context.handle1(); context.handle2(); }}Copy the code
The engineering practice
From our introduction to state patterns, we can see that when transitions between states are not very complex, the common state pattern has a lot of state-independent actions that result in a lot of useless code. In our practice, the downstream of a state does not involve particularly many state transitions, so we simplify the state pattern. The current state is only responsible for what the current state does, and the flow of the state is the responsibility of the third party class. Its practice code is as follows:
// The context in which the reward state is executed
public class RewardStateContext {
private RewardState rewardState;
public void setRewardState(RewardState currentState) {this.rewardState = currentState; }public RewardState getRewardState(a) {returnrewardState; }public void echo(RewardStateContext context, Request request) { rewardState.doReward(context, request); }}public abstract class RewardState {
abstract void doReward(RewardStateContext context, Request request);
}
// To be verified
public class OrderCheckState extends RewardState {
@Override
public void doReward(RewardStateContext context, Request request) {
orderCheck(context, request); // Check incoming orders to determine whether coupons are used, whether preferential conditions are met, etc}}// To be compensated
public class CompensateRewardState extends RewardState {
@Override
public void doReward(RewardStateContext context, Request request) {
compensateReward(context, request); // If the user fails to return the reward, it needs to compensate for the reward}}// Return status, return status, success status, failure status
/ /..
public class InviteRewardServiceImpl {
public boolean sendRewardForInvtee(long userId, long orderId) {
Request request = new Request(userId, orderId);
RewardStateContext rewardContext = new RewardStateContext();
rewardContext.setRewardState(new OrderCheckState());
rewardContext.echo(rewardContext, request); // Start to return awards, order verification
// The if-else logic here is just to express the state transition process, not the actual business logic
if (rewardContext.isResultFlag()) { // If the order verification is successful, enter the pre-reward state
rewardContext.setRewardState(new BeforeRewardCheckState());
rewardContext.echo(rewardContext, request);
} else {// If the order verification fails, enter the reward failure process...
rewardContext.setRewardState(new RewardFailedState());
rewardContext.echo(rewardContext, request);
return false;
}
if (rewardContext.isResultFlag()) {// The pre-return prize check is successful, and the process of waiting for return prize is entered...
rewardContext.setRewardState(new SendRewardState());
rewardContext.echo(rewardContext, request);
} else { // If the pre-return check fails, enter the return failure process...
rewardContext.setRewardState(new RewardFailedState());
rewardContext.echo(rewardContext, request);
return false;
}
if (rewardContext.isResultFlag()) { // Successful return, enter the return process,...
rewardContext.setRewardState(new RewardSuccessState());
rewardContext.echo(rewardContext, request);
} else { // Return reward failed, enter return reward compensation phase...
rewardContext.setRewardState(new CompensateRewardState());
rewardContext.echo(rewardContext, request);
}
if (rewardContext.isResultFlag()) { // The compensation is successful and the reward is completed...
rewardContext.setRewardState(new RewardSuccessState());
rewardContext.echo(rewardContext, request);
} else { // The compensation fails and remains in the current state until the compensation succeeds (or manual intervention after multiple compensation failures)
rewardContext.setRewardState(new CompensateRewardState());
rewardContext.echo(rewardContext, request);
}
return true; }}Copy the code
The core of the state mode is encapsulation, which encapsulates the state and the state transformation logic inside the class to achieve, and also embodies the “open and closed principle” and “single responsibility principle”. Each state is a subclass. To change or add a state, you only need to change or add a subclass. In our application scenario, the number of states and state transitions is much more complex than the above example, and the “state mode” avoids a lot of if-else code and makes our logic much clearer. At the same time, because of the good encapsulation and design principles followed by the state mode, we can easily manage various states in complex business scenarios.
3.3 Comment on the practice of design mode in takeaway delivery system
3.3.1 Service Overview
For example, in the take-out channel of the review App, multiple resource slots are reserved for marketing to show users some exquisite and delicious take-out food, in order to increase users’ intention to order take-out. When the user clicks on the entrance of “Meituan Take-out” on the review homepage, the resource bits start to load, and the appropriate display Banner will be screened out through some rules.
3.3.2 Design pattern practice
Business modeling
For the placement business, it is necessary to display resources in these resource bits that match the current user. Its process is shown in the figure below:
As we can see from the process, first the operations personnel will configure the resources to be displayed and the rules to filter the resources. Our resource filtering rules are relatively flexible, which can be reflected in the following three aspects:
- Filtering rules are mostly reusable, but can be extended and changed.
- The filtering rules and sequence for different resource bits are different.
- The filtering rules for the same resource bit may be different at different service stages.
Filtering rules themselves are value objects. We operate these rule value objects in the way of domain service to complete the filtering logic of resource bits. The following figure shows the process of resource bits filtering user feature-related rules:
In order to realize the decoupling of filtering rules, the modification of single rule value object is closed, and the filtering chain composed of rule set is open, we introduce the responsibility chain mode in the domain service of resource bit filtering.
Mode: Chain of responsibility mode
Schema definition: Avoids coupling between the sender and receiver of the request by giving multiple objects the opportunity to process the request. Connect the objects into a chain and pass the request along the chain until an object handles it.
The general class diagram of responsibility chain mode is as follows:
Let’s use some generic code to explain how to use the chain of responsibility pattern:
// Define an abstract handle
public abstract class Handler {
private Handler nextHandler; // point to the next handler
private int level; // The level at which the handler can process
public Handler(int level) {
this.level = level;
}
public void setNextHandler(Handler handler) {
this.nextHandler = handler;
}
// Handle request passing, note final, subclasses cannot be overridden
public final void handleMessage(Request request) {
if (level == request.getRequstLevel()) {
this.echo(request);
} else {
if (this.nextHandler ! =null) {
this.nextHandler.handleMessage(request);
} else {
System.out.println("This is the end of the road."); }}}// Abstract method, subclass implementation
public abstract void echo(Request request);
}
Define a concrete handleA
public class HandleRuleA extends Handler {
public HandleRuleA(int level) {
super(level);
}
@Override
public void echo(Request request) {
System.out.println("I'm handler 1, I'm dealing with rule A."); }}// Define a concrete handleB
public class HandleRuleB extends Handler {} / /...
// Client implementation
class Client {
public static void main(String[] args) {
HandleRuleA handleRuleA = new HandleRuleA(1);
HandleRuleB handleRuleB = new HandleRuleB(2);
handleRuleA.setNextHandler(handleRuleB); // This is the main point, handleA and handleB string together
handleRuleA.echo(newRequest()); }}Copy the code
The engineering practice
The following code shows how to implement this process:
Public abstract class BasicRule<CORE_ITEM, T extends RuleContext<CORE_ITEM>>{evaluate: evaluate; Execute is used to execute specific rules. public abstract boolean evaluate(T context); Public void execute(T context) {} public void execute(T context) {} Public class ServiceAvailableRule extends BasicRule<UserPortrait, UserPortraitRuleContext> { @Override public boolean evaluate(UserPortraitRuleContext context) { TakeawayUserPortraitBasicInfo basicInfo = context.getBasicInfo(); if (basicInfo.isServiceFail()) { return false; } return true; } @override public void execute(UserPortraitRuleContext context) {}} Public class UserGroupRule extends BasicRule<UserPortrait, UserPortraitRuleContext> { @Override public boolean evaluate(UserPortraitRuleContext context) {} @Override public void execute(UserPortraitRuleContext context) { UserPortrait userPortraitPO = context.getData(); if(userPortraitPO.getUserGroup() == context.getBasicInfo().getUserGroup().code) { context.setValid(true); } else { context.setValid(false); }}} // Rule 3: Public class CityInfoRule extends BasicRule<UserPortrait, UserPortraitRuleContext> {} Filtering resources based on user activity, Public class UserPortraitRule extends BasicRule<UserPortrait, <bean name="serviceAvailableRule" <bean name="serviceAvailableRule" class="com.dianping.takeaway.ServiceAvailableRule"/> <bean name="userGroupValidRule" class="com.dianping.takeaway.UserGroupRule"/> <bean name="cityInfoValidRule" class="com.dianping.takeaway.CityInfoRule"/> <bean name="userPortraitRule" class="com.dianping.takeaway.UserPortraitRule"/> <util:list id="userPortraitRuleChain" value-type="com.dianping.takeaway.Rule"> <ref bean="serviceAvailableRule"/> <ref bean="userGroupValidRule"/> <ref /> <ref bean="userPortraitRule"/> </util:list List<BasicRule> userPortraitRuleChain; public void invokeAll(RuleContext ruleContext) { for(Rule rule : userPortraitRuleChain) { rule.evaluate(ruleContext) } } }Copy the code
The most important advantage of the chain of responsibility pattern is decoupling, separating the client from the handler. The client does not need to know which handler handles the event, and the handler does not need to know the entire process of processing. In our system, the filtering rules in the background often change, and there may be transitive relationships between rules. Through the responsibility chain mode, we separate rules from rules, and inject the transitive relationships between rules into the List through Spring to form a chain relationship. When you add a rule, you simply implement the BasicRule interface and add the new rules to Spring in sequence. When deleting, you simply delete the relevant rules, regardless of the rest of the code’s logic. Thus, the flexibility of the code is significantly improved, the efficiency of the code development is improved, and the stability of the system is ensured.
Four,
Starting from marketing business, this paper introduces the transformation from domain model to code engineering, introduces the design mode from DDD, and introduces in detail the implementation of factory method mode, strategy mode, responsibility chain mode and state mode in marketing business. In addition to these four patterns, our code engineering also extensively uses the proxy pattern, singleton pattern, adapter pattern, etc. For example, in our IMPLEMENTATION of DDD anticorrosion layer, the adapter pattern is used to shield the business logic from the interaction of third-party services. I will not elaborate too much for reasons of space.
For marketing business, changing business strategy leads to changing demand is the main problem we face. How to deal with complex and changing requirements is what we must consider when refining the domain model and implementing the code model. DDD and design patterns provide a relatively complete methodology to help us complete domain modeling and engineering implementation. In fact, design patterns are like mirrors that map domain models to code models, effectively improving code reuse, extensibility, and system maintainability.
Of course, design pattern is only a summary of years of experience in the field of software development, any simple or complex design pattern will follow the above seven design principles, as long as we really understand the seven design principles, design pattern for us should not be a difficult thing. However, the use of design patterns does not require us to follow the rules, as long as our code model design follows the above seven principles, we will find that we have already used a design pattern in our design.
5. Reference materials
- Software Design patterns – Baidu Encyclopedia
- Quick Understanding – Six principles of design patterns
- Software design pattern
- Zen of Design Pattern, Qin Xiaobo, China Machine Press
- Domain-driven Design: Coping with Software Core Complexity, Eric Evans, Posts and Telecommunications Press.
- Practice of domain driven design in Internet business development
6. Introduction to the author
Wu Liangliang, joined Meituan Takeout in 2017, development engineer of marketing background team of Meituan Takeout.
Recruitment information
We are looking for front-end, data warehouse, machine learning/data mining algorithm engineers. Please send your resume to [email protected] (email subject: Meituan Waimai – Shanghai).