聊聊Cola-StateMachine輕量級狀態機的實現
背景
在分析Seata的saga模式實現時,實在是被其復雜的 json 狀態語言定義文件勸退,我是有點沒想明白為啥要用這么來實現狀態機;盲猜可能是基于可視化的狀態機設計器來定制化流程,更方便快捷且上手快吧,畢竟可以通過UI直接操作,設計狀態流轉圖,但我暫時不太能get到。對于Saga模式的實現,之前的博文中已經闡述了基于狀態機模式實現Saga,是比較常見且合適的做法,因此了解了下Java中的狀態機實現方案,以后有相關的業務場景也可以直接上手使用狀態機。
Cola-StateMachine
Cola-StateMachine組件是一種輕量級的、無狀態的、基于注解的狀態機實現,可以方便地管理訂單等業務對象的狀態轉換。COLA框架的狀態機使用了連貫接口(Fluent Interfaces)來定義狀態和事件,以及對應的動作和檢查。COLA框架的狀態機是COLA 4.0應用架構的一部分,旨在控制復雜度,提高開發效率。開發背景可見實現一個狀態機引擎,教你看清DSL的本質。
基礎模型
在Cola-StateMachine組件中有如下的抽象概念模型:
1.State:狀態
2.Event:事件,狀態由事件觸發,引起變化
3.Transition:流轉,表示從一個狀態到另一個狀態
4.External Transition:外部流轉,兩個不同狀態之間的流轉
5.Internal Transition:內部流轉,同一個狀態之間的流轉
6.Condition:條件,表示是否允許到達某個狀態
7.Action:動作,到達某個狀態之后,可以做什么
8.StateMachine:狀態機
Cola-StateMachine鏈路圖
業務應用示例
基于訂單業務的場景,做一個簡單的demo。
關閉訂單的簡單流程圖
關閉訂單簡單的狀態流轉圖
添加依賴
<dependency>
<groupId>com.alibaba.cola</groupId>
<artifactId>cola-component-statemachine</artifactId>
<version>4.3.1</version>
</dependency>
定義一個訂單的實體類、訂單狀態的枚舉值、訂單事件的枚舉值
@Data
@Builder
public class Order {
public OrderStatusEnum orderStatusEnum;
public Integer orderId;
public String orderName;
}
public enum OrderStatusEnum {
INIT("0", "待付款"),
WAITING_FOR_DELIVERY("1", "待發貨"),
HAVE_BEEN_DELIVERY("2", "已發貨"),
CLOSE("3", "已取消");
private final String code;
private final String info;
OrderStatusEnum(String code, String info)
{
this.code = code;
this.info = info;
}
public String getCode()
{
return code;
}
public String getInfo()
{
return info;
}
}
public enum OrderEvent {
/**
* 用戶關閉
*/
USER_CLOSE("0", "用戶取消"),
/**
* 管理員關閉
*/
ADMIN_CLOSE("1", "后臺取消"),
/**
* 超時關閉
*/
OVERTIME_CLOSE("2", "超時取消"),
/**
* 檢查錯誤關閉
*/
CHECK_ERROR_CLOSE("3", "上級審核取消"),
/**
* 用戶付費
*/
USER_PAY("4", "用戶支付");
/**
* 密碼
*/
private final String code;
/**
* 信息
*/
private final String info;
/**
* 訂單事件
*
* @param code 密碼
* @param info 信息
*/
OrderEvent(String code, String info) {
this.code = code;
this.info = info;
}
/**
* 獲取代碼
*
* @return {@link String}
*/
public String getCode() {
return code;
}
/**
* 獲取信息
*
* @return {@link String}
*/
public String getInfo() {
return info;
}
}
在容器啟動的時候注冊一個訂單狀態變更的工廠
@Component
public class StateMachineBuilderConfig {
@Autowired
UserCloseAction userCloseAction;
@Bean("orderOperaMachine")
public StateMachine orderOperaMachine() {
String ORDER_OPERA = "order_opera";
StateMachineBuilder<OrderStatusEnum, OrderEvent, Order> builder = StateMachineBuilderFactory.create();
//訂單從初始化狀態-待發貨-狀態-轉到-關閉訂單狀態--用戶關閉
builder.externalTransitions()
.fromAmong(OrderStatusEnum.INIT, OrderStatusEnum.WAITING_FOR_DELIVERY)
.to(OrderStatusEnum.CLOSE)
.on(OrderEvent.USER_CLOSE)
.when(checkCondition())
.perform(userCloseAction);
//訂單從-初始化狀態-已發貨-待發貨--轉到-關閉訂單狀態--后臺操作人員關閉
builder.externalTransitions()
.fromAmong(OrderStatusEnum.INIT, OrderStatusEnum.HAVE_BEEN_DELIVERY, OrderStatusEnum.WAITING_FOR_DELIVERY)
.to(OrderStatusEnum.CLOSE)
.on(OrderEvent.ADMIN_CLOSE)
.when(checkCondition())
.perform(doAction());
//訂單從等待發貨狀態-轉為-訂單關閉狀態-超時關閉
builder.externalTransition()
.from(OrderStatusEnum.WAITING_FOR_DELIVERY)
.to(OrderStatusEnum.CLOSE)
.on(OrderEvent.OVERTIME_CLOSE)
.when(checkCondition())
.perform(doAction());
//訂單從待發貨狀態--轉為-訂單關閉狀態-上級審批不通過關閉
builder.externalTransition()
.from(OrderStatusEnum.WAITING_FOR_DELIVERY)
.to(OrderStatusEnum.CLOSE)
.on(OrderEvent.CHECK_ERROR_CLOSE)
.when(checkCondition())
.perform(doAction());
//訂單從初始化狀態--轉為待發貨狀態--用戶支付完畢動
builder.externalTransition()
.from(OrderStatusEnum.INIT)
.to(OrderStatusEnum.WAITING_FOR_DELIVERY)
.on(OrderEvent.USER_PAY)
.when(checkCondition())
.perform(doAction());
StateMachine orderOperaMachine = builder.build(ORDER_OPERA);
//打印uml圖
String plantUML = orderOperaMachine.generatePlantUML();
System.out.println(plantUML);
return orderOperaMachine;
}
private Condition<Order> checkCondition() {
return (ctx) -> {
return true;
};
}
private Action<OrderStatusEnum, OrderEvent, Order> doAction() {
return (from, to, event, ctx) -> {
System.out.println(ctx.getOrderName() + " 正在操作 " + ctx.getOrderId() + " from:" + from + " to:" + to + " on:" + event);
};
}
}
在定義一個特殊的,只是舉個例子,可以通過集成的方式集成實現一個用戶關單的具體操作
@Component
public class UserCloseAction implements Action<OrderStatusEnum, OrderEvent, Order> {
@Override
public void execute(OrderStatusEnum from, OrderStatusEnum to, OrderEvent event, Order context) {
System.out.println("用戶關閉流程開始走了");
System.out.println("從這個狀態-【" + from.getInfo() + "】-轉為+【" + to.getInfo() + "】 的狀態");
System.out.println("上下文信息:" + context.toString());
System.out.println("中間執行的一些操作.......");
System.out.println("用戶關閉流程完畢了");
}
}
定義一個 controller 的操作接口
@RestController
public class OrderOperaController {
@Autowired
@Qualifier("orderOperaMachine")
StateMachine<OrderStatusEnum, OrderEvent, Order> orderOperaMachine;
/**
* 場景1-用戶關閉訂單
*
* @return {@link Boolean}
*/
@RequestMapping("userclose")
public Boolean userCloseOrder() {
//把訂單狀態改為關閉
String machineId = orderOperaMachine.getMachineId();
System.out.println(machineId);
Order order = Order.builder().orderId(1).orderName("用戶").orderStatusEnum(OrderStatusEnum.INIT).build();
OrderStatusEnum orderStatusEnum = orderOperaMachine.fireEvent(OrderStatusEnum.INIT,OrderEvent.USER_CLOSE, order);
System.out.println(orderStatusEnum.toString());
return true;
}
/**
* 場景2-管理員關閉訂單
*
* @return {@link Boolean}
*/
@RequestMapping("adminClose")
public Boolean adminCloseOrder() {
//把訂單狀態改為關閉
Order order = Order.builder().orderId(1).orderName("后臺操作人員").orderStatusEnum(OrderStatusEnum.HAVE_BEEN_DELIVERY).build();
OrderStatusEnum orderStatusEnum = orderOperaMachine.fireEvent(OrderStatusEnum.HAVE_BEEN_DELIVERY, OrderEvent.ADMIN_CLOSE, order);
System.out.println(orderStatusEnum.toString());
return true;
}
/**
* 場景3-超時關閉訂單
*
* @return {@link Boolean}
*/
@RequestMapping("overTimeclose")
public Boolean overTimeCloseOrder() {
//把訂單狀態改為關閉
Order order = Order.builder().orderId(1).orderName("超時了關閉訂單")
.orderStatusEnum(OrderStatusEnum.WAITING_FOR_DELIVERY).build();
//OrderStatusEnum orderStatusEnum = orderOperaMachine.fireEvent(OrderStatusEnum.CLOSE, OrderEvent.OVERTIME_CLOSE, order);
OrderStatusEnum orderStatusEnum = orderOperaMachine.fireEvent(OrderStatusEnum.WAITING_FOR_DELIVERY, OrderEvent.OVERTIME_CLOSE, order);
System.out.println(orderStatusEnum.toString());
return true;
}
/**
* 場景4-檢查錯誤關閉訂單
*
* @return {@link Boolean}
*/
@RequestMapping("checkErrorClose")
public Boolean checkErrorCloseOrder() {
//把訂單狀態改為關閉
Order order = Order.builder().orderId(1).orderName("上級檢查錯誤").orderStatusEnum(OrderStatusEnum.WAITING_FOR_DELIVERY).build();
OrderStatusEnum orderStatusEnum = orderOperaMachine.fireEvent(OrderStatusEnum.WAITING_FOR_DELIVERY, OrderEvent.CHECK_ERROR_CLOSE, order);
System.out.println(orderStatusEnum.toString());
return true;
}
}
啟動程序
安裝UML
隨便新建一個uml文件,然后將啟動程序的控制臺輸出內容復制到uml中
最后運行下