In vivo marketing automation technology decryption | the opening of our vivo marketing automation platform introduced from overall business architecture, the core business module function design, system architecture and several big core technology.

This is the second in a series of articles, which will analyze in detail how design patterns and related applications can help marketing automation businesses improve system scalability, as well as the thinking and summary of the practice process.

One, the introduction

Marketing business itself is extremely complex and changeable, especially with the booming trend of digital marketing, different marketing strategy decisions will be made in different periods of the market, different stages of the company’s development, different user groups and continuous effect fluctuation iteration.

When faced with changing business scenarios, system scalability is very important. When it comes to system design scalability, design principles and design patterns always come to mind first. However, design mode is not a silver bullet and cannot solve all problems. It is just a method extracted and summarized by predecessors. Only by making reasonable choices and adapting appropriately according to actual business scenarios can developers truly solve problems in actual scenarios and summarize and form their own methodology.

So let’s look at how design patterns help us improve system scalability in the marketing strategy engine.

Second, marketing strategy engine

A brief introduction to the marketing strategy engine: The strategy engine provides different operational capabilities by building visual process components, defining process nodes and automating the execution of active business processes. The core business process mainly includes three parts: operation activity configuration -> operation activity approval -> operation activity execution.

  • Operation activity configuration: Operation personnel configure operation activities in the background of the system. Including activity name, activity time, trigger conditions, activity users and specific push channels (such as SMS, wechat, push).

  • Operational activity approval: quality/supervisor approves operational activity configuration. The approval process involves the configuration of activity approval nodes and personnel, and the configuration of the related callback operation.

  • Operational activity execution: The process by which the system automates the execution of operational activities. That is, the task execution and delivery process of push activities in specific channels such as SMS, wechat and push, including user data preparation, data delivery and push and data effect recovery, etc.

Iii. Specific application of design pattern

3.1 Configuration of operation activities

3.1.1 Factory Mode

The scenario

In general, according to different users and activity scenarios, the operation will make different activity strategies with the help of data analysis, such as the need to create SMS Push strategy, wechat graphic Push strategy, App Push strategy, etc. At this point, we can use the factory mode to centrally manage the creation of specific push policies.

Pattern analysis

In GoF design Patterns: The Foundation of Reusable Object-oriented Software, factory patterns are divided into factory methods and abstract factories, while simple factory patterns (also known as static factory patterns) are considered as a special case of factory methods. However, simple factories and factory methods are more commonly used in real projects because they are relatively simple and easy to understand, and the code is more readable.

Applicable scenarios of simple factories:

  • A. The factory class is responsible for creating fewer objects, and the creation logic in the factory method is simple.

  • B. The client does not need to care about the details of creating the specific object, but only needs to know the type parameters of the factory class passed in.

The factory method applies to the following scenarios:

  • A. The factory class object creation logic is relatively complex, requiring that the factory instantiation be deferred to its concrete factory subclass.

  • B. Suitable for scenarios with frequent demand changes, different factory implementation classes can be used to support new factory creation schemes, which are more in line with the open and closed principle and have better scalability.

Typical code examples

// Abstract product class
public abstract class Product {
    public abstract void method(a);
}
// Specific product category
class ProductA extends Product {
    @Override
    public void method(a) {
        // Specific execution logic}}// Abstract factory template class
abstract class Factory<T> {
    abstract Product createProduct(Class<T> c);
}
// Concrete factory implementation class
class FactoryA extends Factory{
    @Override
    Product createProduct(Class c) {
        Product product = (Product) Class.forName(c.getName()).newInstance();
        returnproduct; }}Copy the code

The actual code

/ * * *@authorChenwangrong * Activity Strategy Factory class */
@Component
@Slf4j
public class ActivityStrategyFactory {
 
    /** * Obtain the policy corresponding to the channel type **@param channelType channelType
     * @return OperationServiceStrategy
     */
    public static ActivityStrategy getActivityStrategy(ChannelTypeEnum channelType) {
 
        ChannelTypeStrategyEnum channelTypeStrategyEnum = ChannelTypeStrategyEnum.getByChannelType(channelType);
        Assert.notNull(channelTypeStrategyEnum , "Specified channelType [channelType=" + channelType + "] Does not exist");
 
        String strategyName= channelTypeStrategyEnum.getHandlerName();
        Assert.notNull(strategyName, "Specified channelType [channelType=" + channelType + "No policy configured");
 
        return (ActivityStrategy)SpringContextHolder.getBean(handlerName);
    }
 
 
    public enum ChannelTypeStrategyEnum {
        /** * SMS channel */
        SMS(ChannelTypeEnum.SMS, "smsActivityStrategy"),
        /** * wechat channel */
        WX_NEWS(ChannelTypeEnum.WX, "wxActivityStrategy"),
        /** * push channel */
        PUSH(ChannelTypeEnum.PUSH, "pushActivityStrategy"),;
 
        private final ChannelTypeEnum channelTypeEnum;
 
        private final String strategyName;
 
        ChannelTypeStrategyEnum (ChannelTypeEnum channelTypeEnum, String strategyName) {
            this.channelTypeEnum = channelTypeEnum;
            this.strategyName= strategyName;
        }
 
 
        public String getStrategyName(a) {
            return strategyName;
        }
 
        public static ChannelTypeStrategyEnum getByChannelType(ChannelTypeEnum channelTypeEnum) {
            for (ChannelTypeStrategyEnum channelTypeStrategyEnum : values()) {
                if (channelTypeEnum == channelTypeStrategyEnum.channelTypeEnum) {
                    returnchannelTypeStrategyEnum ; }}return null; }}}Copy the code

practice

In the actual project code, we use a simple factory pattern (static factory pattern), which uses enumeration (or mapping configuration table) to save the mapping relationship between channel types and specific policy implementation classes, and then combines Spring singleton pattern to create policy classes.

Compared with the factory method pattern, the number of factory classes is reduced under the premise of satisfying the business, and the code is simpler and more applicable.

3.1.2 Template method mode

The scenario

When creating policies for different types of operation activities, you can find that in addition to saving the configuration information of specific activity channels, many operation procedures are the same during the creation process, such as saving the basic configuration information of activities, reporting audit logs, creating work orders for activity approval, and notifying message after creation.

The original practice

/** ** SMS activity ** /
@Service
public class SmsActivityStrategy{
  
    /** * execute channel to send **@param msgParam msgParam
     */
    public ProcessResult createActivity(ActParam param) {
         // Save basic activity information
         saveActBaseConfig(param);
         // Save the SMS activity configuration
         createSmsActivity(param);
         // Audit log reporting...
         // Create activity approval workorder...
         // Message notification...sendNotification(param); }}/** * Push ** /
@Service
public class PushActivityStrategy{
  
    /** * execute channel to send **@param msgParam msgParam
     */
    public ProcessResult createActivity(ActParam param) {
         // Save basic activity information
         saveActBaseConfig(param);
         // Save the Push activity configuration
         createChannelActivity(param);
         // Audit log reporting...
         // Create activity approval workorder...
         // Message notification...sendNotification(param); }}...Copy the code

These operations are required for each activity strategy and the flow of operations is fixed, so they can be extracted into a common flow, where the template method pattern is taken into account.

Pattern analysis

In GoF, Design Patterns: The Foundation of Reusable Object-oriented Software: The template method pattern is to define an algorithm skeleton in a method and defer certain steps to its subclasses. The template method pattern allows subclasses to redefine certain steps of an algorithm without changing the structure of the algorithm.

The “algorithm” referred to above can be understood as business logic, and “algorithm skeleton” is template, the method containing “algorithm skeleton” is template method, which is also the source of the template method pattern name.

The template method pattern applies to scenarios where business logic consists of defined steps in a fixed order. Some methods or implementations may vary from one business to another.

Generally, a logical template and framework are defined through abstract classes, and then the undetermined parts are abstracted into abstract methods and handed over to subclasses for implementation, and the calling logic is still completed in abstract classes.

Typical code examples

/ / template class
public abstract class AbstractTemplate {
 
// Business logic 1
protected abstract void doStep1(a);
// Business logic 2
protected abstract void doStep2(a);
 
// Template method
public void templateMethod(a){
     this.doStep1();
     // Public logic.this.doStep2(); }}// Implement class 1
public class ConcreteClass1  extends AbstractTemplate {
  // Implement business logic 1
  protected void doStep1(a)
  {
     // Business logic processing
  }
 
  // Implement business logic 2
  protected void doStep2(a)
  {
    // Business logic processing}}// Implement class 2
public class ConcreteClass2  extends AbstractTemplate {
  // Implement business logic 1
  protected void doStep1(a)
  {
     // Business logic processing
  }
 
  // Implement business logic 2
  protected void doStep2(a)
  {
    // Business logic processing}}/ / call the class
public class Client {
 public static void main(String[] args)
  {
    AbstractTemplate class1=new ConcreteClass1();
    AbstractTemplate class2=new ConcreteClass2();
   // Call the template methodclass1.templateMethod(); class2.templateMethod(); }}Copy the code

The actual code

/** * activity create template class **@author chenwangrong
 */
@Slf4j
public abstract class AbstractActivityTemplate{
 
    /** * Save the active configuration **@paramParam Activity parameter *@returnProcessResult ProcessResult */
    protected abstract ProcessResult createChannelActivity(ActParam param);
 
    /** * Perform the activity to create **@param msgParam msgParam
     */
    public ProcessResult createActivity(ActParam param) {
         // Save basic activity information
         saveActBaseConfig(param);
         // Save the channel configuration
         createChannelActivity(param);
         // Audit log reporting...
         // Message notification...}}/** ** SMS activity ** /
@Service
public class SmsActivityStrategy extends AbstractActivityTemplate{
  
    /** * Create SMS channel activity configuration **@param msgParam msgParam
     */
    public ProcessResult createChannelActivity(ActParam param) {
         // Save the SMS activity configurationcreateSmsActivity(param); }} (other channel activities are similar, omitted here)/ / call the class
public class Client {
 public static void main(String[] args)
  {
    AbstractActivityTemplate smsActivityStrategy=new SmsActivityStrategy();
    AbstractActivityTemplate pushActivityStrategy=new PushActivityStrategy();
 
    ActParam param = new ActParam();
 
   // Invoke the concrete activity implementation classsmsActivityStrategy.createActivity(param); pushActivityStrategy.createActivity(param); }}Copy the code

practice

The template method pattern serves two purposes: reuse and extension. Reuse means that all subclasses can reuse code for template methods provided in the parent class. Extension refers to the function extension points provided by the framework through the template pattern, so that users can customize the functions of the framework based on the extension points without modifying the framework source code.

Template method is very suitable for scenarios with common business logic processing processes and certain differences in specific processes. The process skeleton can be extracted into template classes and the variable differences can be set as abstract methods to encapsulate the invariant part and expand the variable part.

3.1.3 Policy Mode

The scenario

We have extracted the common process skeleton from the template method pattern above, but there is a problem: the calling class still needs to know exactly which implementation class is instantiated before it can be invoked. That is, each time a new channel activity is added, the caller must modify the invocation logic to add a new activity to implement the initial invocation of the class, obviously not taking advantage of the extensibility of the business.

In the process of creating operational activities, different types of activities will correspond to different creation processes, and callers only need to distinguish according to channel types, regardless of the specific business logic involved. In this case, the policy mode is a better choice.

Pattern analysis

In GoF, Design Patterns: The Foundation of Reusable Object-oriented Software, a policy pattern defines a family of algorithm classes and encapsulates each algorithm individually so that they can be interchangeable. Policy patterns can make changes to algorithms independent of the callers who use them.

Typical code examples

// Policy interface definition
public interface Strategy {
    void doStrategy(a);
}
​
// Policy concrete implementation classes (multiple)
public class StrategyA implements Strategy{
    @Override
    public void doStrategy(a) {}}// Context manipulation class, which blocks direct access to policies by higher-level modules
public class Context {
    private Strategy strategy = null;
​
    public Context(Strategy strategy) {
        this.strategy = strategy;
    }
 
    public void doStrategy(a) { strategy.doStrategy(); }}Copy the code

The actual code

/** * Channel activity create policy interface ** /
public interface ActivityStrategy {
 
    /** * Create channel activity configuration **@paramParam Activity parameter *@return* /
    void createActivity(ActParam param);
}
 
/** * Active template class ** /
@Slf4j
public abstract class AbstractActivityTemplate implements ActivityStrategy {
 
    /** * abstract method: concrete channel activities create ** /
    protected abstract ProcessResult createChannelActivity(ActParam param);
 
    @Override
    public ProcessResult createActivity(ActParam param) {
         // Save basic activity information
         saveActBaseConfig(param);
         // Save the channel configuration
         createChannelActivity(param);
         // Audit log reporting...
         // Message notification...}}/** * SMS push policy implementation class ** /
@Component
public class SmsChannelActivityStrategy extends AbstractActivityTemplate {
    @Override
    public void createChannelActivity(ActParam param) {
        // Save SMS configuration data}} (other channel activities are similar, omitted here)/** * Policy invocation entry ** /
@Slf4j
@Component
public class ActivityContext {
 
   @Resource
   private ActivityStrategyFactory activityStrategyFactory ;
 
      public void create(ActParam param) {
            // Get the policy class corresponding to the specific channel through the previous factory pattern code
            ActivityStrategy strategy = activityStrategyFactory.getActivityStrategy(param.ChannelType);
            // Execute the policystrategy.createActivity(param); }}Copy the code

In the actual coding process, we added the ChannelActivityStrategy as the channel activity creation policy interface, implemented the interface with the template class AbstractActivityTemplate, and combined the factory pattern to create the specific policy, thus combining the three patterns.

practice

Policy mode is often used to eliminate complex if and else complex logic in the process of project development. If there is a new channel activity, you only need to add the activity creation logic of the corresponding channel, which can be very convenient to expand the system business.

During project practices, the factory pattern, template method pattern, and policy pattern are often used together. The template method pattern abstracts the common skeleton of the business process, the policy pattern implements and encapsulates the specific subprocess policy, and the factory pattern creates the subprocess policy.

The combination of multiple modes can give full play to the advantages of each mode and achieve the purpose of really improving the scalability of system design.

3.2 Implementation of operational activities

3.2.1 Status Mode

The scenario

In the execution process of operational activities, it involves the change of activity state, the condition detection before the change and the operation treatment after the change. Correspondingly, we can easily think of state patterns.

Pattern analysis

In GoF’s classic Design Patterns: The Foundation of Reusable Object-oriented Software, state patterns allow an object to change its behavior as its internal state changes.

The function of the state mode is to separate the behavior of the state and invoke different functions corresponding to different states by maintaining the changes of the state. Their relationship can be described as: State determines behavior. Since the state is changed at run time, the behavior changes at run time as the state changes.

Typical code examples

/** * State mode * abstract state class ** /
interface State {
    // State corresponding processing
    void handle(a)
}
 
  
// The specific state is the present class
public  class ConcreteStateA implements  State {
    @Override
    public void handle(a) {}}public  class ConcreteStateB implements  State {
    @Override
    public void handle(a) {}}// Environment class Context, access entry
public class Context {
    // Holds an instance of an object of type State
    private State state;
 
    public void setState(State state) {
        this.state = state;
    }
      
    public void request(a) {
        // call state to processstate.handle(); }}public class Client {
    public static void main(String[] args){
        // Create state
        State state = new ConcreteStateB();
        // Create environment
        Context context = new Context();
        // Set the state to the environment
        context.setState(state);
        / / requestcontext.request(); }}Copy the code

practice

In the actual software project development, the scenario of few business states and simple state transfer can be realized by using state mode. However, if the business process involved in the state transition is complicated, using the state mode will introduce many state classes and methods, when the state logic changes, the code will become difficult to maintain, then using the state mode is not very suitable.

However, when the process states are various and the business logic involved in event verification and triggering execution action is complex, how to implement it?

Here we have to stop and think: using design patterns is just one way to solve a real problem, but design patterns are not a “one-size-fits-all” hammer and need to be clear about its strengths and weaknesses. In this case, the industry has a more common solution – finite state machine, through higher level encapsulation, to provide more convenient business applications.

3.2.2 Application of state patterns — Finite state machines

Finite-state Machine (FSM), abbreviated as State Machine in the industry. It is also composed of three parts: event, state and action. The relationship between the three parts is that the event triggers the transfer of state, and the transfer of state triggers the execution of subsequent actions. State machines can be hardcoded based on traditional state patterns, or they can be implemented by storing state and moving configuration through a database/file configuration or DSL (recommended).

Many open source statemachine frameworks have emerged in the industry, including spring-statemachine (officially provided by Spring), squirrel statemachine and ali’s cola-statemachine.

The practical application

In the actual project development, we chose a stateless and more lightweight solution based on the open source state machine realization idea for the development of our own business characteristics: business process states are many, but events trigger and state change actions are relatively simple. (The realization and selection of the state machine will be further analyzed in subsequent articles. If you are interested, you can visit the official website for more information).

Practice code

/** * State machine factory */
public class StatusMachineEngine {
    private StatusMachineEngine(a) {}private static final Map<OrderTypeEnum, String> STATUS_MACHINE_MAP = new HashMap();
 
    static {
        // SMS push status
        STATUS_MACHINE_MAP.put(ChannelTypeEnum.SMS, "smsStateMachine");
        //PUSH state
        STATUS_MACHINE_MAP.put(ChannelTypeEnum.PUSH, "pushStateMachine");
        / /...
    }
 
    public static String getMachineEngine(ChannelTypeEnum channelTypeEnum) {
        return STATUS_MACHINE_MAP.get(channelTypeEnum);
    }
 
   /** * Triggers state transition *@param channelTypeEnum
     * @paramStatus Current status *@paramEventType Trigger event *@paramContext Context parameter */
    public static void fire(ChannelTypeEnum channelTypeEnum, String status, EventType eventType, Context context) {
        StateMachine orderStateMachine = StateMachineFactory.get(STATUS_MACHINE_MAP.get(channelTypeEnum));
        // Push the state machine to flow
        orderStateMachine.fireEvent(status, eventType, context);
    }
 
/** * Initializes the SMS push activity state machine */
@Component
public class SmsStateMachine implements ApplicationListener<ContextRefreshedEvent> {
 
    @Autowired
    private  StatusAction smsStatusAction;
    @Autowired
    private  StatusCondition smsStatusCondition;
 
    // Based on the DSL build state configuration that triggers event transitions and subsequent actions
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { StateMachineBuilder<String, EventType, Context> builder = StateMachineBuilderFactory.create(); builder.externalTransition() .from(INIT) .to(NOT_START) .on(EventType.TIME_BEGIN) .when(smsStatusAction.checkNotifyCondition()) .perform(smsStatusAction.doNotifyAction()); builder.externalTransition() .from(NOT_START) .to(DATA_PREPARING) .on(EventType.CAL_DATA) .when(smsStatusCondition.doNotifyAction()) .perform(smsStatusAction.doNotifyAction()); builder.externalTransition() .from(DATA_PREPARING) .to(DATA_PREPARED) .on(EventType.PREPARED_DATA) .when(smsStatusCondition.doNotifyAction()) .perform(smsStatusAction.doNotifyAction()); . (omit the other state) builder. Build (StatusMachineEngine. GetMachineEngine ChannelTypeEnum. SMS ()); }/ / call end
   public class Client {
     public static void main(String[] args){
          // Build the active context
          Context context = newContext(...) ;// Trigger state flowStatusMachineEngine.fire(ChannelTypeEnum.SMS, INIT, EventType.SUBMIT, context); }}}Copy the code

The ApplicationListener interface is implemented by pre-defining the state transition process. When the application starts, the events, state transition conditions and the process triggering the operation are loaded into the state machine working memory, and the event trigger drives the state machine to automate the flow.

practice

In actual scenarios, it is not necessary to apply the design mode forcibly, but should fully combine the characteristics of the business, and at the same time, according to the advantages and disadvantages of the design mode, more appropriate selection or further expansion.

3.3 Automated operational activity approval

3.3.1 Integrated application of design pattern — workflow engine

The scenario

In order to do a good job in quality and risk control, activity creation needs to be added into the approval process to control the release and execution of operation activities. Meanwhile, different types of operation activities may involve different business fields and departments, and different approval and control personnel, so corresponding approval relationships need to be configured.

In this case:

  • A. The approval process is fully configured and easy to modify and add;

  • B. Business process nodes can be freely arranged and components are common;

  • C. Process data persistence, and approval process data need to be monitored.

In view of this requirement, the industry has a set of common business tools — workflow engine. Workflow engine obviously does not belong to a specific design pattern implementation, it is a component application covering a variety of design patterns.

Not only approval function, in fact, the previous automatic marketing process engine design also uses workflow engine to build process components:

State machine VS workflow engine

Workflow engines and state machines seem to have a lot in common in that they can complete business processes by defining process nodes, transition conditions, and actions that are triggered accordingly. If only from the complexity of applicable scenarios, state machines are more suitable for single-dimension business problems, which can clearly describe all possible states and events leading to transformation, and are more flexible and portable. On the other hand, workflow engine is more suitable for business process management and can improve the efficiency of the overall business process by solving the process automation problems with higher complexity of large CRM.

Among the workflow engines, Activiti and JBPM are well known. (The comparison between state machine and workflow engine, the specific introduction and selection of open source workflow engine, and how to develop and build a basic workflow engine component will also be further analyzed in subsequent articles. Due to the theme and length of this paper, it will not be introduced in detail.)

In actual development, we developed a simplified version of the Activiti workflow engine based on the open source workflow engine, simplifying many of the relevant configurations and leaving only the core process operations and data logging.

Workflow engine flowchart:

practice

Workflow engine is an application component that covers a variety of design patterns. It needs to be applied only in complex and changeable business scenarios, and it needs to be carefully evaluated based on business. Use the right solution in the right scenario, follow the principles of simplicity, suitability and evolution of system architecture design, and do not over-design.

Four,

Based on the business practice of automated marketing, this paper analyzes and introduces the concrete realization process of factory method mode, template method mode, strategy mode and state mode in project development. It also introduces state machines and workflow engines beyond simple patterns, which cover a variety of design pattern system components and share choices and considerations in the process.

In the face of the complex requirements, business needs to focus on system design reusability and extensibility, and can design principles and design patterns in system design implementation gives us direction guidance, but also need to carry on the reasonable choice according to actual business scenarios, the appropriate adaptations, constantly improve their own methodology.

Stay tuned for additional articles in the series, each with a detailed breakdown of the technical practices.

Author: Vivo Internet Server Team -Chen Wangrong