3.2 运营活动执行
3.2.1 状态模式
具体场景
在运营活动的执行过程中,会涉及活动状态的变更,以及变更前的条件检测和变更后的操作处理。与之相对应地,我们很容易就会想到状态模式。
模式分析
在 GoF 经典的《设计模式:可复用面向对象软件的基础》中:状态模式允许一个对象在其内部状态改变的时候改变其行为。
状态模式的作用就是分离状态的行为,通过维护状态的变化,来调用不同状态对应的不同功能。它们的关系可以描述为:状态决定行为。由于状态是在运行期被改变的,因此行为也会在运行期随着状态的改变而改变。
典型代码示例
/** * 状态模式 * 抽象状态类 * */ interface State { //状态对应的处理 void handle() } //具体状态关现类 public class ConcreteStateA implements State { @Override public void handle() { } } public class ConcreteStateB implements State { @Override public void handle() { } } //环境类Context,访问入口 public class Context { //持有一个State类型的对象实例 private State state; public void setState(State state) { this.state = state; } public void request() { //转调state来处理 state.handle(); } } public class Client { public static void main(String[] args){ //创建状态 State state = new ConcreteStateB(); //创建环境 Context context = new Context(); //将状态设置到环境中 context.setState(state); //请求 context.request(); } }
实践总结
在实际软件项目开发中,业务状态不多且状态转移简单的场景, 可使用状态模式来实现;但如果是涉及的业务流程状态转移繁杂时,使用状态模式会引入非常多的状态类和方法,当状态逻辑有变更时,代码也会变得难以维护,此时使用状态模式并不十分适合。
而当流程状态繁多,事件校验和触发执行动作包含的业务逻辑比较复杂时,如何去实现呢?
这里我们必须停下来思考:使用设计模式只是解决实际问题的一种手段,但设计模式不是一把“万能的”锤子,需要清楚地了解到它的优势和不足。而这种问题场景下,业界已经有一个更通用的方案——有限状态机,通过更高层的封装,提供给业务更便捷的应用。
3.2.2 状态模式的应用——有限状态机
有限状态机(Finite-State Machine , 缩写:FSM),业界简称状态机。它亦是由事件、状态、动作 三大部分组成,三者的关系是:事件触发状态的转移,状态的转移触发后续动作的执行。状态机可以基于传统的状态模式硬编码来实现,也可以通过数据库/文件配置或者DSL的方式来保存状态及转移配置来实现(推荐)。
业界中也已涌现出了不少开源状态机的框架,比较常用的有Spring-statemachine(Spring官方提供) 、squirrel statemachine和阿里开源的cola-statemachine。
实际应用
在实际项目开发中,我们针对自身业务的特点:业务流程状态多,但是事件触发和状态变更动作相对简单,故而选择了无状态、更加轻量级的解决方案——基于开源的状态机实现思想进行开发。(关于状态机的实现和使用选型会在后续的文章中做进一步的分析,感兴趣的童鞋可以访问官网先做了解)。
实践代码
/** * 状态机工厂类 */ public class StatusMachineEngine { private StatusMachineEngine() { } private static final Map<OrderTypeEnum, String> STATUS_MACHINE_MAP = new HashMap(); static { //短信推送状态 STATUS_MACHINE_MAP.put(ChannelTypeEnum.SMS, "smsStateMachine"); //PUSH推送状态 STATUS_MACHINE_MAP.put(ChannelTypeEnum.PUSH, "pushStateMachine"); //...... } public static String getMachineEngine(ChannelTypeEnum channelTypeEnum) { return STATUS_MACHINE_MAP.get(channelTypeEnum); } /** * 触发状态转移 * @param channelTypeEnum * @param status 当前状态 * @param eventType 触发事件 * @param context 上下文参数 */ public static void fire(ChannelTypeEnum channelTypeEnum, String status, EventType eventType, Context context) { StateMachine orderStateMachine = StateMachineFactory.get(STATUS_MACHINE_MAP.get(channelTypeEnum)); //推动状态机进行流转,具体介绍本期先省略 orderStateMachine.fireEvent(status, eventType, context); } /** * 短信推送活动状态机初始化 */ @Component public class SmsStateMachine implements ApplicationListener<ContextRefreshedEvent> { @Autowired private StatusAction smsStatusAction; @Autowired private StatusCondition smsStatusCondition; //基于DSL构建状态配置,触发事件转移和后续的动作 @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()); ...(省略其他状态) builder.build(StatusMachineEngine.getMachineEngine(ChannelTypeEnum.SMS)); } //调用端 public class Client { public static void main(String[] args){ //构建活动上下文 Context context = new Context(...); // 触发状态流转 StatusMachineEngine.fire(ChannelTypeEnum.SMS, INIT, EventType.SUBMIT, context); } } }
通过预定义状态转换流程的方式,实现ApplicationListener接口,在应用启动时将事件、状态转移条件和触发操作的流程加载到状态机工作内存中,由事件触发驱动状态机进行自动流转。
实践总结
实际场景中,不必强行套用设计模式,而是应当充分结合业务的特点,同时针对设计模式的优劣势,进行更加合适的选型或者进一步扩展。
3.3 自动化运营活动审批
3.3.1 设计模式的综合应用——工作流引擎
具体场景
为了做好品质和风险管控,活动创建需要加入审批环节,把控运营活动的发布执行,同时对于不同类型的运营活动,可能涉及的业务领域和部门各不相同,审批管控人员也不一样,需要配置相对应的审批关系。
此时需要做到:
a.审批流程全配置化,易修改和添加;
b.业务流程节点可自由编排,组件公用化;
c.流程数据持久化,审批过程数据需要进行操作监控。
针对这方面的需求,业界有一套通用的业务工具——工作流引擎。工作流引擎显然并不属于具体某一种设计模式的实现,它是涵盖了多种设计模式的组件应用。
不仅仅是审批功能,其实前面自动化营销流程引擎设计也同样是使用工作流引擎搭建流程组件:
状态机 VS 工作流引擎
工作流引擎和状态机似乎存在非常多的相似之处,都可以通过定义流程的节点、转移条件和相应触发的操作来完成业务流程。如果只从适用场景的复杂性上看,状态机更适用于单维度的业务问题,能够清晰地描绘出所有可能的状态以及导致转换的事件,更加灵活轻便;而工作流引擎则更适合业务流程管理,解决如大型CRM复杂度更高的流程自动化问题,可以改善整体业务流程的效率。
在业界的工作流引擎中,比较著名的有Activiti和JBPM等。(关于状态机和工作流引擎的对比、开源工作流引擎的具体介绍和选型,以及如何自行开发构建一款基本的工作流引擎组件,同样是会在后续的文章中做进一步分析,本文由于主题和篇幅的原因暂不做详细介绍。)
在实际开发过程中,我们是基于开源的Activiti工作流引擎自研了一套简易版的工作流引擎,精简了许多相关的配置,只留下了核心流程操作和数据记录。
工作流引擎流程图:
实践总结
工作流引擎是涵盖了多种设计模式的应用组件,只有在复杂多变的业务场景中才需要应用,需要结合业务进行仔细评估。在合适的场景使用合适的解决方案,遵循系统架构设计的简单、合适、可演化原则,不过度设计。
四、总结
本文基于自动化营销的业务实践,分析介绍了工厂方法模式、模板方法模式、策略模式以及状态模式这四种模式在项目开发中的具体实现过程。也在单纯的模式之外介绍了状态机和工作流引擎这些涵盖了多种设计模式系统组件,并分享了过程中的选择和思考。
面对业务复杂多变的需求,需要时刻关注系统设计的复用性和可扩展性,而设计原则和设计模式可以在系统设计实现时给予我们方向性的指导,同时更需要根据实际业务场景进行合理的选择,合适的变通,不断完善自己的方法论。
后续我们将带来系列专题文章的其他内容,每一篇文章都会对里面的技术实践进行详尽解析,敬请期待。