金山石化網站建設襄陽seo優(yōu)化排名
一、狀態(tài)機
1. 定義
有限狀態(tài)機(Finite-state machine, FSM),簡稱狀態(tài)機,是表示有限個狀態(tài)以及在這些狀態(tài)之間的轉移和動作等行為的數(shù)學模型。
2. 組成要素
-
現(xiàn)態(tài)(src state):事務當前所處的狀態(tài)。
-
事件(event):事件就是執(zhí)行某個操作的觸發(fā)條件,當一個事件被滿足,將會觸發(fā)一個動作,或者執(zhí)行一次狀態(tài)的遷移。
-
動作(action):事件滿足后執(zhí)行的動作。動作執(zhí)行完畢后,可以遷移到新的狀態(tài),也可以仍舊保持原狀態(tài)。動作不是必需的,當事件滿足后,也可以不執(zhí)行任何動作,直接遷移到新狀態(tài)。
-
次態(tài)(dst state):事件滿足后要遷往的新狀態(tài)?!按螒B(tài)”是相對于“現(xiàn)態(tài)”而言的,“次態(tài)”一旦被激活,就轉變成新的“現(xiàn)態(tài)”了。
-
狀態(tài)流轉(transition):事物從現(xiàn)態(tài)轉為次態(tài)的整個過程。
3. 優(yōu)點
- 代碼抽象:將業(yè)務流程進行抽象和結構化,將復雜的狀態(tài)轉移圖,分割成相鄰狀態(tài)的最小單元。這樣相當于搭建樂高積木,在這套機制上可以組合成復雜的狀態(tài)轉移圖,同時隱藏了系統(tǒng)的復雜度。
- 簡化流程:業(yè)務rd只需要關注當前操作的業(yè)務邏輯(狀態(tài)流轉過程中的業(yè)務回調函數(shù)),極大的解耦了狀態(tài)和業(yè)務。
- 易擴展:在新增狀態(tài)或事件時,無需修改原有的狀態(tài)流轉邏輯,直接建立新的狀態(tài)轉移鏈路即可。
- 業(yè)務建模:通過最小粒度的相鄰狀態(tài)拼接,最終組成了業(yè)務的整體graph。
二、代碼
假設我們有要實現(xiàn)一個訂單下單功能,上圖是訂單狀態(tài)的流轉圖,方框為訂單的狀態(tài),箭頭旁的文字為事件。
1. database包
package databaseimport "fmt"// DB 模擬數(shù)據(jù)庫對象
type DB struct {
}// Transaction 模擬事務
func (db *DB) Transaction(fun func() error) error {fmt.Println("事務執(zhí)行開始。")err := fun()fmt.Println("事務執(zhí)行結束。")return err
}// Order 訂單
type Order struct {ID int64 // 主鍵IDState int // 狀態(tài)
}type OrderList []*Order// 查詢所有訂單
func ListAllOrder() (OrderList, error) {orderList := OrderList{&Order{1, 0},&Order{2, 1},&Order{2, 2},}return orderList, nil
}// UpdateOrderState 更新訂單狀態(tài)
func UpdateOrderState(curOrder *Order, srcState int, dstState int) error {if curOrder.State == srcState {curOrder.State = dstState}fmt.Printf("更新id為 %v 的訂單狀態(tài),從現(xiàn)態(tài)[%v]到次態(tài)[%v]\n", curOrder.ID, srcState, dstState)return nil
}
用來模擬數(shù)據(jù)庫的操作,如數(shù)據(jù)庫事務、查詢所有訂單、更新訂單狀態(tài)等。
2. fsm包
package fsmimport ("fmt""reflect""zuzhiang/database"
)// FSMState 狀態(tài)機的狀態(tài)類型
type FSMState int// FSMEvent 狀態(tài)機的事件類型
type FSMEvent string// FSMTransitionMap 狀態(tài)機的狀態(tài)轉移圖類型,現(xiàn)態(tài)和事件一旦確定,次態(tài)和動作就唯一確定
type FSMTransitionMap map[FSMState]map[FSMEvent]FSMDstStateAndAction// FSMTransitionFunc 狀態(tài)機的狀態(tài)轉移函數(shù)類型
type FSMTransitionFunc func(params map[string]interface{}, srcState FSMState, dstState FSMState) error// FSMDstStateAndAction 狀態(tài)機的次態(tài)和動作
type FSMDstStateAndAction struct {DstState FSMState // 次態(tài)Action FSMAction // 動作
}// FSMAction 狀態(tài)機的動作
type FSMAction interface {Before(bizParams map[string]interface{}) error // 狀態(tài)轉移前執(zhí)行Execute(bizParams map[string]interface{}, tx *database.DB) error // 狀態(tài)轉移中執(zhí)行After(bizParams map[string]interface{}) error // 狀態(tài)轉移后執(zhí)行
}// FSM 狀態(tài)機,元素均為不可導出
type FSM struct {transitionMap FSMTransitionMap // 狀態(tài)轉移圖transitionFunc FSMTransitionFunc // 狀態(tài)轉移函數(shù)
}// CreateNewFSM 創(chuàng)建一個新的狀態(tài)機
func CreateNewFSM(transitionFunc FSMTransitionFunc) *FSM {return &FSM{transitionMap: make(FSMTransitionMap),transitionFunc: transitionFunc,}
}// SetTransitionMap 設置狀態(tài)機的狀態(tài)轉移圖
func (fsm *FSM) SetTransitionMap(srcState FSMState, event FSMEvent, dstState FSMState, action FSMAction) {if int(srcState) < 0 || len(event) <= 0 || int(dstState) < 0 {panic("現(xiàn)態(tài)|事件|次態(tài)非法。")return}transitionMap := fsm.transitionMapif transitionMap == nil {transitionMap = make(FSMTransitionMap)}if _, ok := transitionMap[srcState]; !ok {transitionMap[srcState] = make(map[FSMEvent]FSMDstStateAndAction)}if _, ok := transitionMap[srcState][event]; !ok {dstStateAndAction := FSMDstStateAndAction{DstState: dstState,Action: action,}transitionMap[srcState][event] = dstStateAndAction} else {fmt.Printf("現(xiàn)態(tài)[%v]+事件[%v]+次態(tài)[%v]已定義過,請勿重復定義。\n", srcState, event, dstState)return}fsm.transitionMap = transitionMap
}// Push 狀態(tài)機的狀態(tài)遷移
func (fsm *FSM) Push(tx *database.DB, params map[string]interface{}, currentState FSMState, event FSMEvent) error {// 根據(jù)現(xiàn)態(tài)和事件從狀態(tài)轉移圖獲取次態(tài)和動作transitionMap := fsm.transitionMapevents, eventExist := transitionMap[currentState]if !eventExist {return fmt.Errorf("現(xiàn)態(tài)[%v]未配置遷移事件", currentState)}dstStateAndAction, ok := events[event]if !ok {return fmt.Errorf("現(xiàn)態(tài)[%v]+遷移事件[%v]未配置次態(tài)", currentState, event)}dstState := dstStateAndAction.DstStateaction := dstStateAndAction.Action// 執(zhí)行before方法if action != nil {fsmActionName := reflect.ValueOf(action).String()fmt.Printf("現(xiàn)態(tài)[%v]+遷移事件[%v]->次態(tài)[%v], [%v].before\n", currentState, event, dstState, fsmActionName)if err := action.Before(params); err != nil {return fmt.Errorf("現(xiàn)態(tài)[%v]+遷移事件[%v]->次態(tài)[%v]失敗, [%v].before, err: %v", currentState, event, dstState, fsmActionName, err)}}// 事務執(zhí)行execute方法和transitionFuncif tx == nil {tx = new(database.DB)}transactionErr := tx.Transaction(func() error {fsmActionName := reflect.ValueOf(action).String()fmt.Printf("現(xiàn)態(tài)[%v]+遷移事件[%v]->次態(tài)[%v], [%v].execute\n", currentState, event, dstState, fsmActionName)if action != nil {if err := action.Execute(params, tx); err != nil {return fmt.Errorf("狀態(tài)轉移執(zhí)行出錯:%v", err)}}fmt.Printf("現(xiàn)態(tài)[%v]+遷移事件[%v]->次態(tài)[%v], transitionFunc\n", currentState, event, dstState)if err := fsm.transitionFunc(params, currentState, dstState); err != nil {return fmt.Errorf("執(zhí)行狀態(tài)轉移函數(shù)出錯: %v", err)}return nil})if transactionErr != nil {return transactionErr}// 執(zhí)行after方法if action != nil {fsmActionName := reflect.ValueOf(action).String()fmt.Printf("現(xiàn)態(tài)[%v]+遷移事件[%v]->次態(tài)[%v], [%v].after\n", currentState, event, dstState, fsmActionName)if err := action.After(params); err != nil {return fmt.Errorf("現(xiàn)態(tài)[%v]+遷移事件[%v]->次態(tài)[%v]失敗, [%v].before, err: %v", currentState, event, dstState, fsmActionName, err)}}return nil
}
狀態(tài)機包含的元素有兩個:狀態(tài)轉移圖和狀態(tài)轉移函數(shù),為了防止包外直接調用,這兩個元素都設為了不可導出的。狀態(tài)轉移圖說明了狀態(tài)機的狀態(tài)流轉情況,狀態(tài)轉移函數(shù)定義了在狀態(tài)轉移的過程中需要做的事情,在創(chuàng)建狀態(tài)時指定,如更新數(shù)據(jù)庫實體(order)的狀態(tài)。
而狀態(tài)轉移圖又包含現(xiàn)態(tài)、事件、次態(tài)和動作,一旦現(xiàn)態(tài)和事件確定,那么狀態(tài)流轉的唯一次態(tài)和動作就隨之確定。
狀態(tài)機的動作又包含三個:Before、Execute和After。Before操作在是事務前執(zhí)行,由于此時沒有翻狀態(tài),所以該步可能會被重復執(zhí)行。Execute操作是和狀態(tài)轉移函數(shù)在同一事務中執(zhí)行的,同時成功或同時失敗。After操作是在事務后執(zhí)行,因為在執(zhí)行前狀態(tài)已經翻轉,所以最多會執(zhí)行一次,在業(yè)務上允許執(zhí)行失敗或未執(zhí)行。
狀態(tài)機的主要方法有兩個,SetTransitionMap方法用來設置狀態(tài)機的狀態(tài)轉移圖,Push方法用來根據(jù)現(xiàn)態(tài)和事件推動狀態(tài)機進行狀態(tài)翻轉。Push方法中會先執(zhí)行Before動作,再在同一事務中執(zhí)行Execute動作和狀態(tài)轉移函數(shù),最后執(zhí)行After動作。在執(zhí)行Push方法的時候會將params參數(shù)傳遞給狀態(tài)轉移函數(shù)。
3. order包
(1) order
package orderimport ("fmt""zuzhiang/database""zuzhiang/fsm"
)var (// 狀態(tài)StateOrderInit = fsm.FSMState(0) // 初始狀態(tài)StateOrderToBePaid = fsm.FSMState(1) // 待支付StateOrderToBeDelivered = fsm.FSMState(2) // 待發(fā)貨StateOrderCancel = fsm.FSMState(3) // 訂單取消StateOrderToBeReceived = fsm.FSMState(4) // 待收貨StateOrderDone = fsm.FSMState(5) // 訂單完成// 事件EventOrderPlace = fsm.FSMEvent("EventOrderPlace") // 下單EventOrderPay = fsm.FSMEvent("EventOrderPay") // 支付EventOrderPayTimeout = fsm.FSMEvent("EventOrderPayTimeout") // 支付超時EventOrderDeliver = fsm.FSMEvent("EventOrderDeliver") // 發(fā)貨EventOrderReceive = fsm.FSMEvent("EventOrderReceive") // 收貨
)var orderFSM *fsm.FSM// orderTransitionFunc 訂單狀態(tài)轉移函數(shù)
func orderTransitionFunc(params map[string]interface{}, srcState fsm.FSMState, dstState fsm.FSMState) error {// 從params中解析order參數(shù)key, ok := params["order"]if !ok {return fmt.Errorf("params[\"order\"]不存在。")}curOrder := key.(*database.Order)fmt.Printf("order.ID: %v, order.State: %v\n", curOrder.ID, curOrder.State)// 訂單狀態(tài)轉移if err := database.UpdateOrderState(curOrder, int(srcState), int(dstState)); err != nil {return err}return nil
}// Init 狀態(tài)機的狀態(tài)轉移圖初始化
func Init() {orderFSM = fsm.CreateNewFSM(orderTransitionFunc)orderFSM.SetTransitionMap(StateOrderInit, EventOrderPlace, StateOrderToBePaid, PlaceAction{}) // 初始化+下單 -> 待支付orderFSM.SetTransitionMap(StateOrderToBePaid, EventOrderPay, StateOrderToBeDelivered, PayAction{}) // 待支付+支付 -> 待發(fā)貨orderFSM.SetTransitionMap(StateOrderToBePaid, EventOrderPayTimeout, StateOrderCancel, nil) // 待支付+支付超時 -> 訂單取消orderFSM.SetTransitionMap(StateOrderToBeDelivered, EventOrderDeliver, StateOrderToBeReceived, DeliverAction{}) // 待發(fā)貨+發(fā)貨 -> 待收貨orderFSM.SetTransitionMap(StateOrderToBeReceived, EventOrderReceive, StateOrderDone, ReceiveAction{}) // 待收貨+收貨 -> 訂單完成
}// ExecOrderTask 執(zhí)行訂單任務,推動狀態(tài)轉移
func ExecOrderTask(params map[string]interface{}) error {// 從params中解析order參數(shù)key, ok := params["order"]if !ok {return fmt.Errorf("params[\"order\"]不存在。")}curOrder := key.(*database.Order)// 初始化+下單 -> 待支付if curOrder.State == int(StateOrderInit) {if err := orderFSM.Push(nil, params, StateOrderInit, EventOrderPlace); err != nil {return err}}// 待支付+支付 -> 待發(fā)貨if curOrder.State == int(StateOrderToBePaid) {if err := orderFSM.Push(nil, params, StateOrderToBePaid, EventOrderPay); err != nil {return err}}// 待支付+支付超時 -> 訂單取消if curOrder.State == int(StateOrderToBePaid) {if err := orderFSM.Push(nil, params, StateOrderToBePaid, EventOrderPayTimeout); err != nil {return err}}// 待發(fā)貨+發(fā)貨 -> 待收貨if curOrder.State == int(StateOrderToBeDelivered) {if err := orderFSM.Push(nil, params, StateOrderToBeDelivered, EventOrderDeliver); err != nil {return err}}// 待收貨+收貨 -> 訂單完成if curOrder.State == int(StateOrderToBeReceived) {if err := orderFSM.Push(nil, params, StateOrderToBeReceived, EventOrderReceive); err != nil {return err}}return nil
}
order包中做的事情主要有:
- 定義訂單狀態(tài)機的狀態(tài)和事件;
- 創(chuàng)建一個狀態(tài)機,并設置狀態(tài)轉移函數(shù)和狀態(tài)轉移圖;
- 執(zhí)行訂單任務,推動狀態(tài)轉移。
(2) order_action_place
package orderimport ("fmt""zuzhiang/database"
)type PlaceAction struct {
}// Before 事務前執(zhí)行,業(yè)務上允許多次操作
func (receiver PlaceAction) Before(bizParams map[string]interface{}) error {fmt.Println("執(zhí)行下單的Before方法。")return nil
}// Execute 事務中執(zhí)行,與狀態(tài)轉移在同一事務中
func (receiver PlaceAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {fmt.Println("執(zhí)行下單的Execute方法。")return nil
}// After 事務后執(zhí)行,業(yè)務上允許執(zhí)行失敗或未執(zhí)行
func (receiver PlaceAction) After(bizParams map[string]interface{}) error {fmt.Println("執(zhí)行下單的After方法。")return nil
}
(2) ~ (5)是訂單不同動作的聲明和實現(xiàn)。
(3) order_action_pay
package orderimport ("fmt""zuzhiang/database"
)type PayAction struct {
}// Before 事務前執(zhí)行,業(yè)務上允許多次操作
func (receiver PayAction) Before(bizParams map[string]interface{}) error {fmt.Println("執(zhí)行支付的Before方法。")return nil
}// Execute 事務中執(zhí)行,與狀態(tài)轉移在同一事務中
func (receiver PayAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {fmt.Println("執(zhí)行支付的Execute方法。")return nil
}// After 事務后執(zhí)行,業(yè)務上允許執(zhí)行失敗或未執(zhí)行
func (receiver PayAction) After(bizParams map[string]interface{}) error {fmt.Println("執(zhí)行支付的After方法。")return nil
}
(4) order_action_deliver
package orderimport ("fmt""zuzhiang/database"
)type DeliverAction struct {
}// Before 事務前執(zhí)行,業(yè)務上允許多次操作
func (receiver DeliverAction) Before(bizParams map[string]interface{}) error {fmt.Println("執(zhí)行發(fā)貨的Before方法。")return nil
}// Execute 事務中執(zhí)行,與狀態(tài)轉移在同一事務中
func (receiver DeliverAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {fmt.Println("執(zhí)行發(fā)貨的Execute方法。")return nil
}// After 事務后執(zhí)行,業(yè)務上允許執(zhí)行失敗或未執(zhí)行
func (receiver DeliverAction) After(bizParams map[string]interface{}) error {fmt.Println("執(zhí)行發(fā)貨的After方法。")return nil
}
(5) order_action_receive
package orderimport ("fmt""zuzhiang/database"
)type ReceiveAction struct {
}// Before 事務前執(zhí)行,業(yè)務上允許多次操作
func (receiver ReceiveAction) Before(bizParams map[string]interface{}) error {fmt.Println("執(zhí)行收貨的Before方法。")return nil
}// Execute 事務中執(zhí)行,與狀態(tài)轉移在同一事務中
func (receiver ReceiveAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {fmt.Println("執(zhí)行收貨的Execute方法。")return nil
}// After 事務后執(zhí)行,業(yè)務上允許執(zhí)行失敗或未執(zhí)行
func (receiver ReceiveAction) After(bizParams map[string]interface{}) error {fmt.Println("執(zhí)行收貨的After方法。")return nil
}
4. main包
package mainimport ("fmt""zuzhiang/database""zuzhiang/order"
)func main() {order.Init()orderList, dbErr := database.ListAllOrder()if dbErr != nil {return}for _, curOrder := range orderList {params := make(map[string]interface{})params["order"] = curOrderif err := order.ExecOrderTask(params); err != nil {fmt.Printf("執(zhí)行訂單任務出錯:%v\n", err)}fmt.Println("\n\n")}
}
最后在main包里先初始化一個訂單狀態(tài)機,查詢所有訂單,并使用狀態(tài)機執(zhí)行訂單任務,推動訂單狀態(tài)轉移。注意多個訂單可以用同一個狀態(tài)機來進行狀態(tài)的遷移。
三、個人總結
為什么要使用狀態(tài)機,我想主要是它可以對一個復雜的業(yè)務流程進行模塊化拆分,使得代碼更為易讀。并且擴展性更好,如果后續(xù)有新狀態(tài)加入,只需要在原來的基礎上進行擴展即可,甚至不需要了解整個業(yè)務流程。
其次,它將數(shù)據(jù)庫實體的狀態(tài)流轉進行了模范化,避免了不同的開發(fā)人員在寫更新數(shù)據(jù)庫實體狀態(tài)代碼時可能導致的問題。