1. 程式人生 > >.Net輕量狀態機Stateless的簡單應用

.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;
        }
    }
Order

  對於列舉值而言,我這貌似沒太大含義,但是有些場景下可能會用的到,暫時先搞上

/// <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,
    }
OrderState

  對於買賣雙方,各自應有各自的頁面,但是為了操作簡便,將合併在一起,重心放在流程的變更上,利用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,望技術有成後能回來看見自己的腳步