.Net輕量狀態機Stateless的簡單應用
對於大部分系統中流程的變更,是十分正常的事情,小到一個狀態的切換,大到整個系統都是圍繞業務流再走,複雜點的有工作流引擎,簡單點的幾個if/else收工,但是往往有那種,心有餘而力不足的,比簡單複雜,比複雜簡單,最近,對業務流程的變更這一塊一直再琢磨,沒有找到一些讓我豁然開朗的資料,本次只能是講講我的設計過程,作為反面教材去對比的,同時借鑑staleless去簡化一下日常中的設計。
一、常用的流程變更
採用狀態控制資料操作,通過操作變更狀態,通過switch、if/else去區分狀態,然後依據狀態做出不同的指示,從一個大家十分熟悉,但又及其簡單的例子著手,思路更加清晰。
對於訂單的狀態,一開始用幾種描述的方式固定下來,程式碼實現中,有人喜歡列舉,有人喜歡字串,這都無所謂了,羅馬路條條。只是這個流程是相對固定的,與一些業務系統中,十幾個轉折或是幾十個流程節點那樣,那種貌似大多使用的都是工作流吧,存在變更,還要容易變更。
先定義個簡單的訂單類及用到的列舉值,這些資訊應該是很熟悉且常見的。
public class Order { public Order(long orderId, string orderName, string orderNo, double price) { OrderId = orderId; OrderName = orderName; OrderNo = orderNo; Price = price; CreateDate = DateTime.Now; InitOrderState(); } /// <summary> /// 訂單Id /// </summary> public long OrderId { get; set; } /// <summary> /// 訂單名稱 /// </summary> public string OrderName { get; set; } /// <summary> /// 訂單編號 /// </summary> public string OrderNo { get; set; } /// <summary> /// 建立日期 /// </summary> public DateTime CreateDate { get; set; } /// <summary> /// 訂單價格 /// </summary> public double Price { get; set; } /// <summary> /// 訂單狀態 /// </summary> public OrderState OrderState { get; private set; } /// <summary> /// 初始訂單狀態 /// </summary> public void InitOrderState() { OrderState = OrderState.OrderCreated; } /// <summary> /// 設定訂單狀態 /// </summary> /// <param name="orderState"></param> public void SetOrderState(OrderState orderState) { OrderState = orderState; } }
對於列舉值而言,我這貌似沒太大含義,但是有些場景下可能會用的到,暫時先搞上
/// <summary> /// 訂單狀態 /// </summary> public enum OrderState { /// <summary> /// 無效 /// </summary> [Description("無效")] OrderInvalided = 0, /// <summary> /// 已建立 /// </summary> [Description("已建立")] OrderCreated = 3, /// <summary> /// 待支付 /// </summary> [Description("待支付")] OrderPendingPay = 6, /// <summary> /// 待配送 /// </summary> [Description("待配送")] OrderPendingSend = 9, /// <summary> /// 待收貨 /// </summary> [Description("待收貨")] OrderPendingSign = 12, /// <summary> /// 待退款 /// </summary> [Description("待退款")] OrderPendingRefund = 15, /// <summary> /// 已完成 /// </summary> [Description("已完成")] OrderCompleted = 18, }
對於買賣雙方,各自應有各自的頁面,但是為了操作簡便,將合併在一起,重心放在流程的變更上,利用Razor語法快速突進,直接將後臺資料在頁面上展示出來。
對於各條資料,有單獨的狀態,按照之前狀態圖中的那種方式,依據每一個狀態前後的變更操作,設計相應的方法,比如待配送階段的訂單,完成發貨配送後,那麼相應的訂單變成待收貨,此時需要一個完成發貨的一個方法,如下,簡單方式配置下,先對當前要操作的訂單做個狀態判定,防止某些操作,其次更改訂單狀態,後直接跳回訂單頁,方便看到資料狀態的變更。
public IActionResult CompleteSend(long id) { var order = orderList.Where(o => o.OrderId == id).First(); if (order.OrderState != OrderState.OrderPendingSend) { throw new Exception("狀態錯誤"); } order.SetOrderState(OrderState.OrderPendingSign);//待簽收 return RedirectToAction("Index"); }
按照一系列預先規劃的操作,挨個編寫,最終直接執行後可以看下變更效果。
這種方式下,就是簡單,思路清晰,操作起來順手,通過人肉編排完成任務,但是一旦流程節點增多,甚至出現了交叉變更的情形,程式碼中簡單情形的if/else已經不能滿足時,就有點頭痛了。
二、採用Stateless簡化流程
在採用stateless簡化一下上面這個流程設計,也作為對stateless的一次掌握,看下簡化後的流程設計能否帶來什麼意想不到的事情。stateless採用行為觸發方式推動流程進行,比如說a狀態要變到b狀態,要經過一個行為,不管是買家點選,賣家點選,定時任務,其它行為觸發後的聯動觸發等,都是以行為進行驅動的,
因此,按照剛開始那張圖中設計好的一些狀態變更操作,封裝成行為觸發的列舉。
/// <summary> /// 針對訂單的操作 /// </summary> public enum OrderTrigger { /// <summary> /// 跳轉 /// </summary> Jump, /// <summary> /// 取消 /// </summary> Cancel, /// <summary> /// 支付 /// </summary> Payment, /// <summary> /// 配送 /// </summary> Send, /// <summary> /// 簽訂 /// </summary> Sign, /// <summary> /// 退款 /// </summary> Refund, }
按照預先的設計並利用stateless提供的相關方法完成流程的配置,採用集中式的配置方式,並且這部分配置工作,可以再度升級到使用資料庫儲存,而採用資料庫去動態配置。
//流程配置 orderStateMachine.Configure(OrderState.OrderCreated) .Permit(OrderTrigger.Jump, OrderState.OrderPendingPay) .Permit(OrderTrigger.Cancel, OrderState.OrderInvalided); orderStateMachine.Configure(OrderState.OrderPendingPay) .Permit(OrderTrigger.Payment, OrderState.OrderPendingSend) .Permit(OrderTrigger.Cancel, OrderState.OrderInvalided); orderStateMachine.Configure(OrderState.OrderPendingSend) .Permit(OrderTrigger.Send, OrderState.OrderPendingSign) .Permit(OrderTrigger.Cancel, OrderState.OrderPendingRefund); orderStateMachine.Configure(OrderState.OrderPendingSign) .Permit(OrderTrigger.Sign, OrderState.OrderCompleted); orderStateMachine.Configure(OrderState.OrderPendingRefund) .Permit(OrderTrigger.Refund, OrderState.OrderInvalided);
對於原有控制器部分,只需考慮一個事情,便是,訂單狀態的改變,把對訂單的所有行為,對外抽象成一個動作,更新訂單狀態,而通過引數,來區分不同的行為
public IActionResult UpdateOrderState(long id, OrderTrigger orderTrigger) { var order = orderList.Where(o => o.OrderId == id).First(); var orderManager = new OrderManager(order); orderManager.TriggerOrderState(orderTrigger); return RedirectToAction("Index"); }
對於頁面部分,改動量不是很大,只在於更新訂單狀態所用到的方法及引數的變更,按照這個設計思路,同樣可以得到如下的變更效果。
並且,如果存在一些,行為配置錯誤,而在配置過程中沒有處理,將直接報錯提示,這方便了在一些複雜場景下,純粹依靠人肉把控狀態的變更方式。同時,拿到stateless的副產物狀態圖,到viz.js或是把viz.js整合到系統中,可以看到具體的配置效果。
至此,對於使用狀態機去簡化常用方式的設計完成了,至於深入的更復雜的方式去研究,比如配置到資料庫中,頁面上配置,充分發揮這個輕量狀態機的能力,還得花個時間去琢磨。
倉庫地址:https://gitee.com/530521314/Partner.TreasureChest/tree/master/StateMachine/src
2019-10-31,望技術有成後能回來看見自己的腳步