一厢蒜、業(yè)務(wù)背景
營銷自動化平臺支持多種不同類型運營活動策略(比如:短信推送策略运嗜、微信圖文推送策略、App Push推送策略)逞泄,每種活動類型都有各自不同的執(zhí)行流程和活動狀態(tài)。比如短信活動的活動執(zhí)行流程如下:
[圖片上傳失敗...(image-611f9b-1649642767478)]
(圖1-1:短信活動狀態(tài)轉(zhuǎn)移)
整個短信活動經(jīng)歷了 未開始 → 數(shù)據(jù)準備中 → 數(shù)據(jù)已就緒 → 活動推送中→ 活動結(jié)束 多個狀態(tài)變更流程静檬。不僅如此炭懊, 我們發(fā)現(xiàn)在活動業(yè)務(wù)邏輯處理過程中,都有以下類似的特點:
- 每增加一種新的活動業(yè)務(wù)類型拂檩,就要新增相應的活動狀態(tài)以及處理狀態(tài)變更的邏輯侮腹;
- 當一個類型的活動業(yè)務(wù)流程有修改時,可能需要對原先的狀態(tài)轉(zhuǎn)移過程進行變更;
- 當每個業(yè)務(wù)都各自編寫自己的狀態(tài)轉(zhuǎn)移的業(yè)務(wù)代碼時稻励,核心業(yè)務(wù)邏輯和控制邏輯耦合性會非常強父阻,擴展性差,成本也很高望抽。
針對系統(tǒng)狀態(tài)的流轉(zhuǎn)管理加矛,計算機領(lǐng)域有一套標準的理論方案模型——有限狀態(tài)機。
二煤篙、理解狀態(tài)機
2.1 狀態(tài)機定義
有限狀態(tài)機(Finite-State Machine , 縮寫:FSM)斟览,簡稱狀態(tài)機。是表示有限個狀態(tài)以及這些狀態(tài)之間的轉(zhuǎn)移和觸發(fā)動作的模型辑奈。
- 狀態(tài)是描述系統(tǒng)對象在某個時刻所處的狀況苛茂。
- 轉(zhuǎn)移指示狀態(tài)變更,一般是通過外部事件為條件觸發(fā)狀態(tài)的轉(zhuǎn)移鸠窗。
- 動作是對給定狀態(tài)下要進行的操作妓羊。
簡而言之,狀態(tài)機是由事件稍计、狀態(tài)躁绸、動作三大部分組成。三者的關(guān)系是:事件觸發(fā)狀態(tài)的轉(zhuǎn)移,狀態(tài)的轉(zhuǎn)移觸發(fā)后續(xù)動作的執(zhí)行净刮。其中動作不是必須的剥哑,也可以只進行狀態(tài)轉(zhuǎn)移,不進行任何操作庭瑰。
[圖片上傳失敗...(image-c4f395-1649642767478)]
(圖2-1:狀態(tài)機組成)
所以將上述【圖1-1:短信活動狀態(tài)轉(zhuǎn)移 】使用狀態(tài)機模型來描述就是:
[圖片上傳失敗...(image-e41438-1649642767478)]
(圖2-2:短信活動狀態(tài)機)
狀態(tài)機本質(zhì)上是對系統(tǒng)的一種數(shù)學建模星持,將問題解決方案系統(tǒng)化表達出來抢埋。下面我們來看下在實際開發(fā)中有哪些實現(xiàn)狀態(tài)機的方式 弹灭。
2.2 狀態(tài)機的實現(xiàn)方式
2.2.1 基于條件判斷的實現(xiàn)
這是最直接的一種實現(xiàn)方式,所謂條件判斷就是通過使用 if-else 或 switch-case 分支判斷進行硬編碼實現(xiàn)揪垄。對于前面短信活動穷吮,基于條件判斷方式的代碼實例如下:
/**
* 短信活動狀態(tài)枚舉
*/
public enum ActivityState {
NOT_START(0), //活動未開始
DATA_PREPARING(1), //數(shù)據(jù)準備中
DATA_PREPARED(2), //數(shù)據(jù)已就緒
DATA_PUSHING(3), //活動推送中
FINISHED(4); //活動結(jié)束
}
/**
* 短信活動狀態(tài)機
*/
public class ActivityStateMachine {
//活動狀態(tài)
private ActivityState currentState;
public ActivityStateMachine() {
this.currentState = ActivityState.NOT_START;
}
/**
* 活動時間開始
*/
public void begin() {
if (currentState.equals(ActivityState.NOT_START)) {
this.currentState = ActivityState.DATA_PREPARING;
//發(fā)送通知給運營人員
notice();
}
// do nothing or throw exception ...
}
/**
* 數(shù)據(jù)計算完成
*/
public void finishCalData() {
if (currentState.equals(ActivityState.DATA_PREPARING)) {
this.currentState = ActivityState.DATA_PREPARED;
//發(fā)送通知給運營人員
notice();
}
// do nothing or throw exception ...
}
/**
* 活動推送開始
*/
public void beginPushData() {
//省略
}
/**
* 數(shù)據(jù)推送完成
*/
public void finishPushData() {
//省略
}
}
通過條件分支判斷來控制狀態(tài)的轉(zhuǎn)移和動作的觸發(fā),上述的 if 判斷條件也可以換成 switch 語句饥努,以當前狀態(tài)為分支來控制該狀態(tài)下可以執(zhí)行的操作捡鱼。
適用場景
適用于業(yè)務(wù)狀態(tài)個數(shù)少或者狀態(tài)間跳轉(zhuǎn)邏輯比較簡單的場景。
缺陷
當觸發(fā)事件和業(yè)務(wù)狀態(tài)之間對應關(guān)系不是簡單的一對一時酷愧,就需要嵌套多個條件分支判斷驾诈,分支邏輯會變得異常復雜;當狀態(tài)流程有變更時溶浴,也需要改動分支邏輯乍迄,不符合開閉原則,代碼可讀性和擴展性非常差士败。
2.2.2 基于狀態(tài)模式的實現(xiàn)
了解設(shè)計模式的童鞋闯两,很容易就可以把狀態(tài)機和狀態(tài)模式這兩個概念聯(lián)系起來,狀態(tài)模式其實可以作為狀態(tài)機的一種實現(xiàn)方式谅将。主要實現(xiàn)思路是通過狀態(tài)模式將不同狀態(tài)的行為進行分離漾狼,根據(jù)狀態(tài)變量的變化,來調(diào)用不同狀態(tài)下對應的不同方法饥臂。代碼示例如下:
/**
* 活動狀態(tài)接口
*/
interface IActivityState {
ActivityState getName();
//觸發(fā)事件
void begin();
void finishCalData();
void beginPushData();
void finishPushData();
}
/**
* 具體狀態(tài)類—活動未開始狀態(tài)
*/
public class ActivityNotStartState implements IActivityState {
private ActivityStateMachine stateMachine;
public ActivityNotStartState(ActivityStateMachine stateMachine) {
this.stateMachine = stateMachine;
}
@Override
public ActivityState getName() {
return ActivityState.NOT_START;
}
@Override
public void begin() {
stateMachine.setCurrentState(new ActivityDataPreparingState(stateMachine));
//發(fā)送通知
notice();
}
@Override
public void finishCalData() {
// do nothing or throw exception ...
}
@Override
public void beginPushData() {
// do nothing or throw exception ...
}
@Override
public void finishPushData() {
// do nothing or throw exception ...
}
}
/**
* 具體狀態(tài)類—數(shù)據(jù)準備中狀態(tài)
*/
public class ActivityDataPreparingState implements IActivityState {
private ActivityStateMachine stateMachine;
public ActivityNotStartState(ActivityStateMachine stateMachine) {
this.stateMachine = stateMachine;
}
@Override
public ActivityState getName() {
return ActivityState.DATA_PREPARING;
}
@Override
public void begin() {
// do nothing or throw exception ...
}
public void finishCalData() {
stateMachine.setCurrentState(new ActivityDataPreparedState(stateMachine));
//TODO:發(fā)送通知
}
@Override
public void beginPushData() {
// do nothing or throw exception ...
}
@Override
public void finishPushData() {
// do nothing or throw exception ...
}
}
...(篇幅原因逊躁,省略其他具體活動類)
/**
* 狀態(tài)機
*/
public class ActivityStateMachine {
private IActivityState currentState;
public ActivityStateMachine(IActivityState currentState) {
this.currentState = new ActivityNotStartState(this);
}
public void setCurrentState(IActivityState currentState) {
this.currentState = currentState;
}
public void begin() {
currentState.begin();
}
public void finishCalData() {
currentState.finishCalData();
}
public void beginPushData() {
currentState.beginPushData();
}
public void finishPushData() {
currentState.finishCalData();
}
}
狀態(tài)模式定義了狀態(tài)-行為的對應關(guān)系, 并將各自狀態(tài)的行為封裝在對應的狀態(tài)類中。我們只需要擴展或者修改具體狀態(tài)類就可以實現(xiàn)對應流程狀態(tài)的需求隅熙。
適用場景
適用于業(yè)務(wù)狀態(tài)不多且狀態(tài)轉(zhuǎn)移簡單的場景稽煤,相比于前面的if/switch條件分支法,當業(yè)務(wù)狀態(tài)流程新增或修改時猛们,影響粒度更小念脯,范圍可控,擴展性更強弯淘。
缺陷
同樣難以應對業(yè)務(wù)流程狀態(tài)轉(zhuǎn)移復雜的場景绿店,此場景下使用狀態(tài)模式會引入非常多的狀態(tài)類和方法,當狀態(tài)邏輯有變更時,代碼也會變得難以維護假勿。
可以看到借嗽,雖然以上兩種方式都可以實現(xiàn)狀態(tài)機的觸發(fā)、轉(zhuǎn)移转培、動作流程恶导,但是復用性都很低。如果想要構(gòu)建一個可以滿足絕大部分業(yè)務(wù)場景的抽象狀態(tài)機組件浸须,是無法滿足的惨寿。
2.2.3 基于DSL的實現(xiàn)
2.2.3.1 DSL 介紹
DSL 全稱是 Domain-Specific Languages,指的是針對某一特定領(lǐng)域删窒,具有受限表達性的一種計算機程序設(shè)計語言裂垦。不同于通用的編程語言,DSL只用在某些特定的領(lǐng)域肌索,聚焦于解決該領(lǐng)域系統(tǒng)的某塊問題蕉拢。DSL通常分為 內(nèi)部 DSL ( Internal DSLs ),外部 DSL ( external DSLs ) 诚亚。
- 內(nèi)部DSL :基于系統(tǒng)的宿主語言晕换,由宿主語言進行編寫和處理的 DSL,比如:基于 Java 的 內(nèi)部 DSL 站宗、基于 C++ 的內(nèi)部 DSL 闸准、基于 Javascript 的 內(nèi)部 DSL 。
- 外部DSL :不同于系統(tǒng)宿主語言份乒,由自定義語言或者其他編程語言編寫并處理的 DSL恕汇,有獨立的解析器。比如:正則表達式或辖、XML瘾英、SQL、HTML 等颂暇。
(有關(guān)DSL的更多內(nèi)容可以了解:Martin Fowler《Domain Specific Languages》)缺谴。
2.2.3.2 DSL 的選型和狀態(tài)機實現(xiàn)
使用DSL作為開發(fā)工具,可以用更加清晰和更具表達性的形式來描述系統(tǒng)的行為耳鸯。DSL 也是目前實現(xiàn)狀態(tài)機比較推薦的方式湿蛔,可以根據(jù)自身的需要選用內(nèi)部 DSL 或者外部DSL 來實現(xiàn)。
- 內(nèi)部 DSL :業(yè)務(wù)系統(tǒng)如果只希望通過代碼直接進行狀態(tài)機的配置县爬,那么可以選擇使用內(nèi)部 DSL阳啥,特點是簡單直接,不需要依賴額外的解析器和組件财喳。
Java 內(nèi)部 DSL 一般是利用 Builder Pattern 和 Fluent Interface 方式(Builder 模式和流式接口)察迟,實現(xiàn)示例:
StateMachineBuilder builder = new StateMachineBuilder();
builder.sourceState(States.STATE1)
.targetState(States.STATE2)
.event(Events.EVENT1)
.action(action1());
- 外部 DSL :可以利用外部存儲和通用腳本語言的解析能力斩狱,實現(xiàn)運行時動態(tài)配置、支持可視化配置和跨語言應用場景扎瓶。
外部 DSL 本質(zhì)上就是將狀態(tài)轉(zhuǎn)移過程用其他外部語言進行描述所踊,比如使用 XML 的方式:
<state id= "STATE1">
<transition event="EVENT1" target="STATE2">
<action method="action1()"/>
</transition>
</state>
<state id= "STATE2">
</state>
外部 DSL 一般放在配置文件或者數(shù)據(jù)庫等外部存儲中,經(jīng)過對應的文本解析器概荷,就可以將外部 DSL 的配置解析成類似內(nèi)部 DSL 的模型秕岛,進行流程處理;同時由于外部存儲的獨立性和持久性误证,可以很方便地支持運行時動態(tài)變更和可視化配置继薛。
Java開源的狀態(tài)機框架基本上都是基于DSL的實現(xiàn)方式。
三雷厂、開源狀態(tài)機框架
我們分別使用三種開源狀態(tài)機框架來完成短信活動狀態(tài)流轉(zhuǎn)過程惋增。
3.1 Spring Statemachine
enum ActivityState {
NOT_START(0),
DATA_PREPARING(1),
DATA_PREPARED(2),
DATA_PUSHING(3),
FINISHED(4);
private int state;
private ActivityState(int state) {
this.state = state;
}
}
enum ActEvent {
ACT_BEGIN, FINISH_DATA_CAL,FINISH_DATA_PREPARE,FINISH_DATA_PUSHING
}
@Configuration
@EnableStateMachine
public class StatemachineConfigurer extends EnumStateMachineConfigurerAdapter<ActivityState, ActEvent> {
@Override
public void configure(StateMachineStateConfigurer<ActivityState, ActEvent> states)
throws Exception {
states
.withStates()
.initial(ActivityState.NOT_START)
.states(EnumSet.allOf(ActivityState.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<ActivityState, ActEvent> transitions)
throws Exception {
transitions
.withExternal()
.source(ActivityState.NOT_START).target(ActivityState.DATA_PREPARING)
.event(ActEvent.ACT_BEGIN).action(notice())
.and()
.withExternal()
.source(ActivityState.DATA_PREPARING).target(ActivityState.DATA_PREPARED)
.event(ActEvent.FINISH_DATA_CAL).action(notice())
.and()
.withExternal()
.source(ActivityState.DATA_PREPARED).target(ActivityState.DATA_PUSHING)
.event(ActEvent.FINISH_DATA_PREPARE).action(notice())
.and()
.withExternal()
.source(ActivityState.DATA_PUSHING).target(ActivityState.FINISHED)
.event(ActEvent.FINISH_DATA_PUSHING).action(notice())
.and() ;
}
@Override
public void configure(StateMachineConfigurationConfigurer<ActivityState, ActEvent> config)
throws Exception {
config.withConfiguration()
.machineId("ActivityStateMachine");
}
public Action<ActivityState, ActEvent> notice() {
return context -> System.out.println("【變更前狀態(tài)】:"+context.getSource().getId()+";【變更后狀態(tài)】:"+context.getTarget().getId());
}
//測試類
class DemoApplicationTests {
@Autowired
private StateMachine<ActivityState, ActEvent> stateMachine;
@Test
void contextLoads() {
stateMachine.start();
stateMachine.sendEvent(ActEvent.ACT_BEGIN);
stateMachine.sendEvent(ActEvent.FINISH_DATA_CAL);
stateMachine.sendEvent(ActEvent.FINISH_DATA_PREPARE);
stateMachine.sendEvent(ActEvent.FINISH_DATA_PUSHING);
stateMachine.stop();
}
}
通過重寫配置模板類的三個configure方法,通過流式Api形式完成狀態(tài)初始化改鲫、狀態(tài)轉(zhuǎn)移的流程以及狀態(tài)機的聲明,實現(xiàn)Java內(nèi)部DSL的狀態(tài)機林束。外部使用狀態(tài)機通過sendEvent事件觸發(fā)像棘,推動狀態(tài)機的自動流轉(zhuǎn)。
優(yōu)勢
- Spring Statemachine 是 Spring 官方的產(chǎn)品壶冒,具有強大生態(tài)社區(qū)缕题。
- 功能十分完備,除了支持基本的狀態(tài)機配置外胖腾,還具備可嵌套的子狀態(tài)機烟零、基于zk的分布式狀態(tài)機和外部存儲持久化等豐富的功能特性。
缺陷
- Spring Statemachine 在每個 statemachine 實例內(nèi)部保存了當前狀態(tài)機上下文相關(guān)的屬性咸作,也就是說是有狀態(tài)的(這一點從觸發(fā)狀態(tài)機流轉(zhuǎn)只需事件作為參數(shù)也可以看出來)锨阿,所以使用單例模式的狀態(tài)機實例不是線程安全的。要保證線程安全性只能每次通過工廠模式創(chuàng)建一個新的狀態(tài)機實例记罚,這種方式在高并發(fā)場景下墅诡,會影響系統(tǒng)整體性能。
- 代碼層次結(jié)構(gòu)稍顯復雜桐智,二次開發(fā)改造成本大末早,一般場景下也并不需要使用如此多的功能,使用時觀感上顯得比較沉重说庭。
3.2 Squirrel Foundation
public class SmsStatemachineSample {
// 1. 狀態(tài)定義
enum ActivityState {
NOT_START(0),
DATA_PREPARING(1),
DATA_PREPARED(2),
DATA_PUSHING(3),
FINISHED(4);
private int state;
private ActivityState(int state) {
this.state = state;
}
}
// 2. 事件定義
enum ActEvent {
ACT_BEGIN, FINISH_DATA_CAL,FINISH_DATA_PREPARE,FINISH_DATA_PUSHING
}
// 3. 狀態(tài)機上下文
class StatemachineContext {
}
@StateMachineParameters(stateType=ActivityState.class, eventType=ActEvent.class, contextType=StatemachineContext.class)
static class SmsStatemachine extends AbstractUntypedStateMachine {
protected void notice(ActivityState from, ActivityState to, ActEvent event, StatemachineContext context) {
System.out.println("【變更前狀態(tài)】:"+from+";【變更后狀態(tài)】:"+to);
}
}
public static void main(String[] args) {
// 4. 構(gòu)建狀態(tài)轉(zhuǎn)移
UntypedStateMachineBuilder builder = StateMachineBuilderFactory.create(SmsStatemachine.class);
builder.externalTransition().from(ActivityState.NOT_START).to(ActivityState.DATA_PREPARING).on(ActEvent.ACT_BEGIN).callMethod("notice");
builder.externalTransition().from(ActivityState.DATA_PREPARING).to(ActivityState.DATA_PREPARED).on(ActEvent.FINISH_DATA_CAL).callMethod("notice");
builder.externalTransition().from(ActivityState.DATA_PREPARED).to(ActivityState.DATA_PUSHING).on(ActEvent.FINISH_DATA_PREPARE).callMethod("notice");
builder.externalTransition().from(ActivityState.DATA_PUSHING).to(ActivityState.FINISHED).on(ActEvent.FINISH_DATA_PUSHING).callMethod("notice");
// 5. 觸發(fā)狀態(tài)機流轉(zhuǎn)
UntypedStateMachine fsm = builder.newStateMachine(ActivityState.NOT_START);
fsm.fire(ActEvent.ACT_BEGIN, null);
fsm.fire(ActEvent.FINISH_DATA_CAL, null);
fsm.fire(ActEvent.FINISH_DATA_PREPARE, null);
fsm.fire(ActEvent.FINISH_DATA_PUSHING, null);
}
}
squirrel-foundation 是一款輕量級的狀態(tài)機庫然磷,設(shè)計目標是為企業(yè)使用提供輕量級、高度靈活刊驴、可擴展姿搜、易于使用、類型安全和可編程的狀態(tài)機實現(xiàn)。
優(yōu)勢
- 和目標理念一致痪欲,與 Spring Statemachine 相比悦穿,不依賴于spring框架,設(shè)計實現(xiàn)方面更加輕量业踢,雖然也是有狀態(tài)的設(shè)計栗柒,但是創(chuàng)建狀態(tài)機實例開銷較小,功能上也更加簡潔知举,相對比較適合二次開發(fā)瞬沦。
- 對應的文檔和測試用例也比較豐富,開發(fā)者上手容易雇锡。
缺陷
- 過于強調(diào)“約定優(yōu)于配置”的理念逛钻,不少默認性的處理,比如狀態(tài)轉(zhuǎn)移后動作是通過方法名來調(diào)用锰提,不利于操作管理曙痘。
- 社區(qū)活躍度不高。
3.3 Cola Statemachine
/**
* 狀態(tài)機工廠類
*/
public class StatusMachineEngine {
private StatusMachineEngine() {
}
private static final Map<OrderTypeEnum, String> STATUS_MACHINE_MAP = new HashMap();
static {
//短信推送狀態(tài)
STATUS_MACHINE_MAP.put(ChannelTypeEnum.SMS, "smsStateMachine");
//PUSH推送狀態(tài)
STATUS_MACHINE_MAP.put(ChannelTypeEnum.PUSH, "pushStateMachine");
//......
}
public static String getMachineEngine(ChannelTypeEnum channelTypeEnum) {
return STATUS_MACHINE_MAP.get(channelTypeEnum);
}
/**
* 觸發(fā)狀態(tài)轉(zhuǎn)移
* @param channelTypeEnum
* @param status 當前狀態(tài)
* @param eventType 觸發(fā)事件
* @param context 上下文參數(shù)
*/
public static void fire(ChannelTypeEnum channelTypeEnum, String status, EventType eventType, Context context) {
StateMachine orderStateMachine = StateMachineFactory.get(STATUS_MACHINE_MAP.get(channelTypeEnum));
//推動狀態(tài)機進行流轉(zhuǎn)立肘,具體介紹本期先省略
orderStateMachine.fireEvent(status, eventType, context);
}
/**
* 短信推送活動狀態(tài)機初始化
*/
@Component
public class SmsStateMachine implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private StatusAction smsStatusAction;
@Autowired
private StatusCondition smsStatusCondition;
//基于DSL構(gòu)建狀態(tài)配置边坤,觸發(fā)事件轉(zhuǎn)移和后續(xù)的動作
@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());
...(省略其他狀態(tài))
builder.build(StatusMachineEngine.getMachineEngine(ChannelTypeEnum.SMS));
}
//調(diào)用端
public class Client {
public static void main(String[] args){
//構(gòu)建活動上下文
Context context = new Context(...);
// 觸發(fā)狀態(tài)流轉(zhuǎn)
StatusMachineEngine.fire(ChannelTypeEnum.SMS, INIT, EventType.SUBMIT, context);
}
}
}
Cola Statemachine 是阿里COLA開源框架里面的一款狀態(tài)機框架,和前面兩者最大的不同就是:無狀態(tài)的設(shè)計——觸發(fā)狀態(tài)機流轉(zhuǎn)時需要把當前狀態(tài)作為入?yún)⒘履辏瑺顟B(tài)機實例中不需要保留當前狀態(tài)上下文消息茧痒,只有一個狀態(tài)機實例,也就直接保證了線程安全性和高性能融蹂。
優(yōu)勢
輕量級無狀態(tài)旺订,安全,性能高超燃。
設(shè)計簡潔区拳,方便擴展。
社區(qū)活躍度較高淋纲。
缺陷
- 不支持嵌套劳闹、并行等高級功能。
3.4 小結(jié)
三種開源狀態(tài)機框架對比如下:
[圖片上傳失敗...(image-a658c9-1649642767478)]
希望直接利用開源狀態(tài)機能力的系統(tǒng)洽瞬,可以根據(jù)自身業(yè)務(wù)的需求和流程復雜度本涕,進行合適的選型。
四伙窃、營銷自動化業(yè)務(wù)案例實踐
4.1 設(shè)計選型
vivo營銷自動化的業(yè)務(wù)特點是:
- 運營活動類型多菩颖,業(yè)務(wù)流量大,流程相對簡單为障,性能要求高晦闰。
- 流程變更頻繁放祟,經(jīng)常需要新增業(yè)務(wù)狀態(tài),需要支持快速新增配置和變更呻右。
- 在狀態(tài)觸發(fā)后會有多種不同的業(yè)務(wù)操作跪妥,比如狀態(tài)變更后的消息提醒,狀態(tài)完結(jié)后的業(yè)務(wù)處理等声滥,需要支持異步操作和方便擴展眉撵。
針對以上業(yè)務(wù)特點,在實際項目開發(fā)中落塑,我們是基于開源狀態(tài)的實現(xiàn)方案——基于內(nèi)部DSL的方式進行開發(fā)纽疟。同時汲取了以上開源框架的特點,選用了無狀態(tài)高性能憾赁、功能簡潔污朽、支持動作異步執(zhí)行的輕量設(shè)計。
[圖片上傳失敗...(image-d9ba39-1649642767478)]
- 無狀態(tài)高性能:保證高性能龙考,采用無狀態(tài)的狀態(tài)機設(shè)計蟆肆,只需要一個狀態(tài)機實例就可以進行運轉(zhuǎn)。
- 功能簡潔:最小設(shè)計原則洲愤,只保留核心的設(shè)計颓芭,比如事件觸發(fā),狀態(tài)的基本流轉(zhuǎn)柬赐,后續(xù)的操作和上下文參數(shù)處理。
- 動作異步執(zhí)行:針對異步業(yè)務(wù)流程官紫,采用線程池或者消息隊列的方式進行異步解耦肛宋。
4.2 核心流程
- 沿用開源狀態(tài)機的內(nèi)部DSL流式接口設(shè)計,在應用啟動時掃描狀態(tài)機定義束世;
- 創(chuàng)建異步處理線程池支持業(yè)務(wù)的后置動作酝陈;
- 解析狀態(tài)機的DSL配置,初始化狀態(tài)機實例毁涉;
- 構(gòu)建執(zhí)行上下文沉帮,存放各個狀態(tài)機的實例和其他執(zhí)行過程信息;
- 狀態(tài)機觸發(fā)時贫堰,根據(jù)觸發(fā)條件和當前狀態(tài)穆壕,自動匹配轉(zhuǎn)移過程,推動狀態(tài)機流轉(zhuǎn)其屏;
- 執(zhí)行后置同步/異步處理操作喇勋。
[圖片上傳失敗...(image-c2d2a-1649642767478)]
(圖4-1:核心流程設(shè)計)
4.3 實踐思考
1)狀態(tài)機配置可視化,結(jié)合外部DSL的方式(比如JSON的方式偎行,存儲到數(shù)據(jù)庫中)川背,支持更快速的配置贰拿。
2)目前只支持狀態(tài)的簡單流轉(zhuǎn),在流轉(zhuǎn)過程加入流轉(zhuǎn)接口擴展點熄云,應對未來可能出現(xiàn)的復雜場景膨更。
五、總結(jié)
狀態(tài)機是由事件缴允、狀態(tài)荚守、動作三大部分組成。三者的關(guān)系是:事件觸發(fā)狀態(tài)的轉(zhuǎn)移癌椿,狀態(tài)的轉(zhuǎn)移觸發(fā)后續(xù)動作的執(zhí)行健蕊。利用狀態(tài)機進行系統(tǒng)狀態(tài)管理,可以提升業(yè)務(wù)擴展性和內(nèi)聚性踢俄。狀態(tài)機可以使用條件分支判斷缩功、狀態(tài)模式和基于DSL來實現(xiàn),其中更具表達性的DSL也是很多開源狀態(tài)機的實現(xiàn)方式都办〉招浚可以基于開源狀態(tài)機的特點和自身項目需求進行合適的選型,也可以基于前面的方案自定義狀態(tài)機組件琳钉。
本篇是《營銷自動化技術(shù)解密》系列專題文章的第三篇势木,系列文章回顧:
后面我們將繼續(xù)帶來系列專題文章的其他內(nèi)容,每一篇文章都會對里面的技術(shù)實踐進行詳盡解析歌懒,敬請期待啦桌。
作者:vivo互聯(lián)網(wǎng)服務(wù)器團隊-Chen Wangrong