目录
- 一、状态机
- 二、代码
- 1. database包
- 2. fsm包
- 3. order包
- 4. main包
- 三、个人总结
一、状态机
1. 定义
有限状态机(Finite-state machine, FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
2. 组成要素
- 现态(src state):事务当前所处的状态。
- 事件(event):事件就是执行某个操作的触发条件,当一个事件被满足,将会触发一个动作,或者执行一次状态的迁移。
- 动作(action):事件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当事件满足后,也可以不执行任何动作,直接迁移到新状态。
- 次态(dst state):事件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
- 状态流转(transition):事物从现态转为次态的整个过程。
3. 优点
- 代码抽象:将业务流程进行抽象和结构化,将复杂的状态转移图,分割成相邻状态的最小单元。这样相当于搭建乐高积木,在这套机制上可以组合成复杂的状态转移图,同时隐藏了系统的复杂度。
- 简化流程:业务rd只需要关注当前操作的业务逻辑(状态流转过程中的业务回调函数),极大的解耦了状态和业务。
- 易扩展:在新增状态或事件时,无需修改原有的状态流转逻辑,直接建立新的状态转移链路即可。
- 业务建模:通过最小粒度的相邻状态拼接,最终组成了业务的整体graph。
二、代码
假设我们有要实现一个订单下单功能,上图是订单状态的流转图,方框为订单的状态,箭头旁的文字为事件。
1. database包
package database | |
import "fmt" | |
// DB 模拟数据库对象 | |
type DB struct { | |
} | |
// Transaction 模拟事务 | |
func (db *DB) Transaction(fun func() error) error { | |
fmt.Println("事务执行开始。") | |
err := fun() | |
fmt.Println("事务执行结束。") | |
return err | |
} | |
// Order 订单 | |
type Order struct { | |
ID int64 // 主键ID | |
State int // 状态 | |
} | |
type OrderList []*Order | |
// 查询所有订单 | |
func ListAllOrder() (OrderList, error) { | |
orderList := OrderList{ | |
&Order{1, 0}, | |
&Order{2, 1}, | |
&Order{2, 2}, | |
} | |
return orderList, nil | |
} | |
// UpdateOrderState 更新订单状态 | |
func UpdateOrderState(curOrder *Order, srcState int, dstState int) error { | |
if curOrder.State == srcState { | |
curOrder.State = dstState | |
} | |
fmt.Printf("更新id为 %v 的订单状态,从现态[%v]到次态[%v]\n", curOrder.ID, srcState, dstState) | |
return nil | |
} |
用来模拟数据库的操作,如数据库事务、查询所有订单、更新订单状态等。
2. fsm包
package fsm | |
import ( | |
"fmt" | |
"reflect" | |
"zuzhiang/database" | |
) | |
// FSMState 状态机的状态类型 | |
type FSMState int | |
// FSMEvent 状态机的事件类型 | |
type FSMEvent string | |
// FSMTransitionMap 状态机的状态转移图类型,现态和事件一旦确定,次态和动作就唯一确定 | |
type FSMTransitionMap map[FSMState]map[FSMEvent]FSMDstStateAndAction | |
// FSMTransitionFunc 状态机的状态转移函数类型 | |
type FSMTransitionFunc func(params map[string]interface{}, srcState FSMState, dstState FSMState) error | |
// FSMDstStateAndAction 状态机的次态和动作 | |
type FSMDstStateAndAction struct { | |
DstState FSMState // 次态 | |
Action FSMAction // 动作 | |
} | |
// FSMAction 状态机的动作 | |
type FSMAction interface { | |
Before(bizParams map[string]interface{}) error // 状态转移前执行 | |
Execute(bizParams map[string]interface{}, tx *database.DB) error // 状态转移中执行 | |
After(bizParams map[string]interface{}) error // 状态转移后执行 | |
} | |
// FSM 状态机,元素均为不可导出 | |
type FSM struct { | |
transitionMap FSMTransitionMap // 状态转移图 | |
transitionFunc FSMTransitionFunc // 状态转移函数 | |
} | |
// CreateNewFSM 创建一个新的状态机 | |
func CreateNewFSM(transitionFunc FSMTransitionFunc) *FSM { | |
return &FSM{ | |
transitionMap: make(FSMTransitionMap), | |
transitionFunc: transitionFunc, | |
} | |
} | |
// SetTransitionMap 设置状态机的状态转移图 | |
func (fsm *FSM) SetTransitionMap(srcState FSMState, event FSMEvent, dstState FSMState, action FSMAction) { | |
if int(srcState) < 0 || len(event) <= 0 || int(dstState) < 0 { | |
panic("现态|事件|次态非法。") | |
return | |
} | |
transitionMap := fsm.transitionMap | |
if 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("现态[%v]+事件[%v]+次态[%v]已定义过,请勿重复定义。\n", srcState, event, dstState) | |
return | |
} | |
fsm.transitionMap = transitionMap | |
} | |
// Push 状态机的状态迁移 | |
func (fsm *FSM) Push(tx *database.DB, params map[string]interface{}, currentState FSMState, event FSMEvent) error { | |
// 根据现态和事件从状态转移图获取次态和动作 | |
transitionMap := fsm.transitionMap | |
events, eventExist := transitionMap[currentState] | |
if !eventExist { | |
return fmt.Errorf("现态[%v]未配置迁移事件", currentState) | |
} | |
dstStateAndAction, ok := events[event] | |
if !ok { | |
return fmt.Errorf("现态[%v]+迁移事件[%v]未配置次态", currentState, event) | |
} | |
dstState := dstStateAndAction.DstState | |
action := dstStateAndAction.Action | |
// 执行before方法 | |
if action != nil { | |
fsmActionName := reflect.ValueOf(action).String() | |
fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], [%v].before\n", currentState, event, dstState, fsmActionName) | |
if err := action.Before(params); err != nil { | |
return fmt.Errorf("现态[%v]+迁移事件[%v]->次态[%v]失败, [%v].before, err: %v", currentState, event, dstState, fsmActionName, err) | |
} | |
} | |
// 事务执行execute方法和transitionFunc | |
if tx == nil { | |
tx = new(database.DB) | |
} | |
transactionErr := tx.Transaction(func() error { | |
fsmActionName := reflect.ValueOf(action).String() | |
fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], [%v].execute\n", currentState, event, dstState, fsmActionName) | |
if action != nil { | |
if err := action.Execute(params, tx); err != nil { | |
return fmt.Errorf("状态转移执行出错:%v", err) | |
} | |
} | |
fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], transitionFunc\n", currentState, event, dstState) | |
if err := fsm.transitionFunc(params, currentState, dstState); err != nil { | |
return fmt.Errorf("执行状态转移函数出错: %v", err) | |
} | |
return nil | |
}) | |
if transactionErr != nil { | |
return transactionErr | |
} | |
// 执行after方法 | |
if action != nil { | |
fsmActionName := reflect.ValueOf(action).String() | |
fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], [%v].after\n", currentState, event, dstState, fsmActionName) | |
if err := action.After(params); err != nil { | |
return fmt.Errorf("现态[%v]+迁移事件[%v]->次态[%v]失败, [%v].before, err: %v", currentState, event, dstState, fsmActionName, err) | |
} | |
} | |
return nil | |
} |
状态机包含的元素有两个:状态转移图和状态转移函数,为了防止包外直接调用,这两个元素都设为了不可导出的。状态转移图说明了状态机的状态流转情况,状态转移函数定义了在状态转移的过程中需要做的事情,在创建状态时指定,如更新数据库实体(order)的状态。
而状态转移图又包含现态、事件、次态和动作,一旦现态和事件确定,那么状态流转的唯一次态和动作就随之确定。
状态机的动作又包含三个:Before、Execute和After。Before操作在是事务前执行,由于此时没有翻状态,所以该步可能会被重复执行。Execute操作是和状态转移函数在同一事务中执行的,同时成功或同时失败。After操作是在事务后执行,因为在执行前状态已经翻转,所以最多会执行一次,在业务上允许执行失败或未执行。
状态机的主要方法有两个,SetTransitionMap方法用来设置状态机的状态转移图,Push方法用来根据现态和事件推动状态机进行状态翻转。Push方法中会先执行Before动作,再在同一事务中执行Execute动作和状态转移函数,最后执行After动作。在执行Push方法的时候会将params参数传递给状态转移函数。
3. order包
(1) order
package order | |
import ( | |
"fmt" | |
"zuzhiang/database" | |
"zuzhiang/fsm" | |
) | |
var ( | |
// 状态 | |
StateOrderInit = fsm.FSMState(0) // 初始状态 | |
StateOrderToBePaid = fsm.FSMState(1) // 待支付 | |
StateOrderToBeDelivered = fsm.FSMState(2) // 待发货 | |
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") // 发货 | |
EventOrderReceive = fsm.FSMEvent("EventOrderReceive") // 收货 | |
) | |
var orderFSM *fsm.FSM | |
// orderTransitionFunc 订单状态转移函数 | |
func orderTransitionFunc(params map[string]interface{}, srcState fsm.FSMState, dstState fsm.FSMState) error { | |
// 从params中解析order参数 | |
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) | |
// 订单状态转移 | |
if err := database.UpdateOrderState(curOrder, int(srcState), int(dstState)); err != nil { | |
return err | |
} | |
return nil | |
} | |
// Init 状态机的状态转移图初始化 | |
func Init() { | |
orderFSM = fsm.CreateNewFSM(orderTransitionFunc) | |
orderFSM.SetTransitionMap(StateOrderInit, EventOrderPlace, StateOrderToBePaid, PlaceAction{}) // 初始化+下单 -> 待支付 | |
orderFSM.SetTransitionMap(StateOrderToBePaid, EventOrderPay, StateOrderToBeDelivered, PayAction{}) // 待支付+支付 -> 待发货 | |
orderFSM.SetTransitionMap(StateOrderToBePaid, EventOrderPayTimeout, StateOrderCancel, nil) // 待支付+支付超时 -> 订单取消 | |
orderFSM.SetTransitionMap(StateOrderToBeDelivered, EventOrderDeliver, StateOrderToBeReceived, DeliverAction{}) // 待发货+发货 -> 待收货 | |
orderFSM.SetTransitionMap(StateOrderToBeReceived, EventOrderReceive, StateOrderDone, ReceiveAction{}) // 待收货+收货 -> 订单完成 | |
} | |
// ExecOrderTask 执行订单任务,推动状态转移 | |
func ExecOrderTask(params map[string]interface{}) error { | |
// 从params中解析order参数 | |
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 | |
} | |
} | |
// 待支付+支付 -> 待发货 | |
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 | |
} | |
} | |
// 待发货+发货 -> 待收货 | |
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包中做的事情主要有:
- 定义订单状态机的状态和事件;
- 创建一个状态机,并设置状态转移函数和状态转移图;
- 执行订单任务,推动状态转移。
(2) order_action_place
package order | |
import ( | |
"fmt" | |
"zuzhiang/database" | |
) | |
type PlaceAction struct { | |
} | |
// Before 事务前执行,业务上允许多次操作 | |
func (receiver PlaceAction) Before(bizParams map[string]interface{}) error { | |
fmt.Println("执行下单的Before方法。") | |
return nil | |
} | |
// Execute 事务中执行,与状态转移在同一事务中 | |
func (receiver PlaceAction) Execute(bizParams map[string]interface{}, tx *database.DB) error { | |
fmt.Println("执行下单的Execute方法。") | |
return nil | |
} | |
// After 事务后执行,业务上允许执行失败或未执行 | |
func (receiver PlaceAction) After(bizParams map[string]interface{}) error { | |
fmt.Println("执行下单的After方法。") | |
return nil | |
} |
(2) ~ (5)是订单不同动作的声明和实现。
(3) order_action_pay
package order | |
import ( | |
"fmt" | |
"zuzhiang/database" | |
) | |
type PayAction struct { | |
} | |
// Before 事务前执行,业务上允许多次操作 | |
func (receiver PayAction) Before(bizParams map[string]interface{}) error { | |
fmt.Println("执行支付的Before方法。") | |
return nil | |
} | |
// Execute 事务中执行,与状态转移在同一事务中 | |
func (receiver PayAction) Execute(bizParams map[string]interface{}, tx *database.DB) error { | |
fmt.Println("执行支付的Execute方法。") | |
return nil | |
} | |
// After 事务后执行,业务上允许执行失败或未执行 | |
func (receiver PayAction) After(bizParams map[string]interface{}) error { | |
fmt.Println("执行支付的After方法。") | |
return nil | |
} |
(4) order_action_deliver
package order | |
import ( | |
"fmt" | |
"zuzhiang/database" | |
) | |
type DeliverAction struct { | |
} | |
// Before 事务前执行,业务上允许多次操作 | |
func (receiver DeliverAction) Before(bizParams map[string]interface{}) error { | |
fmt.Println("执行发货的Before方法。") | |
return nil | |
} | |
// Execute 事务中执行,与状态转移在同一事务中 | |
func (receiver DeliverAction) Execute(bizParams map[string]interface{}, tx *database.DB) error { | |
fmt.Println("执行发货的Execute方法。") | |
return nil | |
} | |
// After 事务后执行,业务上允许执行失败或未执行 | |
func (receiver DeliverAction) After(bizParams map[string]interface{}) error { | |
fmt.Println("执行发货的After方法。") | |
return nil | |
} |
(5) order_action_receive
package order | |
import ( | |
"fmt" | |
"zuzhiang/database" | |
) | |
type ReceiveAction struct { | |
} | |
// Before 事务前执行,业务上允许多次操作 | |
func (receiver ReceiveAction) Before(bizParams map[string]interface{}) error { | |
fmt.Println("执行收货的Before方法。") | |
return nil | |
} | |
// Execute 事务中执行,与状态转移在同一事务中 | |
func (receiver ReceiveAction) Execute(bizParams map[string]interface{}, tx *database.DB) error { | |
fmt.Println("执行收货的Execute方法。") | |
return nil | |
} | |
// After 事务后执行,业务上允许执行失败或未执行 | |
func (receiver ReceiveAction) After(bizParams map[string]interface{}) error { | |
fmt.Println("执行收货的After方法。") | |
return nil | |
} |
4. main包
package main | |
import ( | |
"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"] = curOrder | |
if err := order.ExecOrderTask(params); err != nil { | |
fmt.Printf("执行订单任务出错:%v\n", err) | |
} | |
fmt.Println("\n\n") | |
} | |
} |
最后在main包里先初始化一个订单状态机,查询所有订单,并使用状态机执行订单任务,推动订单状态转移。注意多个订单可以用同一个状态机来进行状态的迁移。
三、个人总结
为什么要使用状态机,我想主要是它可以对一个复杂的业务流程进行模块化拆分,使得代码更为易读。并且扩展性更好,如果后续有新状态加入,只需要在原来的基础上进行扩展即可,甚至不需要了解整个业务流程。
其次,它将数据库实体的状态流转进行了模范化,避免了不同的开发人员在写更新数据库实体状态代码时可能导致的问题。