提供一个基于 Go 泛型的有限状态机(Finite State Machine, FSM)实现,用于在业务流程中管理状态流转,并通过钩子(hook)在事件触发与状态切换前后执行自定义逻辑。
出于性能考虑,避免过早优化,该 FSM 为无锁实现。在并发环境下使用时,需要自行加锁。 考虑到每个
Machine都是一个独立的状态机,一般在独立事务中调用,如果多个并发访问同时对同一实例进行操作,可以考虑交由数据库事务兜底,不必对Machine进行加锁。仅对Factory的访问加锁即可。
State[S, I] 表示,S 为状态 ID(需 comparable),I 为实例类型(业务上下文)Event[E, I] 表示,E 为事件 ID(需 comparable)Ttrans[E, S] 描述 FromState + Event -> ToStateNewFactory 构建 FSM schema,并用于创建 / 加载 MachineTransition 执行状态流转该包提供两个默认无操作监听器:
NoopStateListener[I]:默认实现 State 的 OnEnter / OnExitNoopEventListener[I]:默认实现 Event 的 BeforeEvent / OnEvent / AfterEvent用途:
由于 Factory 和 Machine 仅通过泛型约束了 id 和 instance 的类型;
对于事件和状态的类型,仅要求为满足对应泛型约束的接口,并没有使用泛型约束为同一类型 ,所以可以使用 示例二 的实现方式,将每种状态和事件都实现为独立类型,省去在回调方法里对状态和事件进行判断。
作为对比,当前的实现仅约束状态和事件的类型为接口:
State[S, I]和Event[E, I]如果约束状态和事件分别必须为单一类型,对应的类型声明应为
S State[SK, I]和E Event[EK, I]此时 State 的类型不仅要求为 State 接口类型,而且必须是同一个类型 S; Event 同理。
但这里仅提供这样一种灵活性,以备特殊情况下可以兼容外部实现的类型;但出于可读性的考虑,还是建议使用第一种实现方式,将 State 和 Event 分别实现为单一类型,代码量少,可读性好。
分别为 state 和 event 定义一个类型,在回调中通过 switch 决定逻辑。
package main
import (
"context"
"errors"
"fmt"
"cnb.cool/letmelife/open/golf/workflow/fsm"
)
type OrderState string
type OrderEvent string
const (
StateCreated OrderState = "created"
StatePaid OrderState = "paid"
StateCanceled OrderState = "canceled"
)
const (
EventPay OrderEvent = "pay"
EventCancel OrderEvent = "cancel"
)
type Order struct {
ID int64
}
type orderState struct {
fsm.NoopStateListener[*Order]
id OrderState
}
func (s orderState) ID() OrderState { return s.id }
func (s orderState) OnEnter(ctx context.Context, o *Order) error {
switch s.id {
case StatePaid:
fmt.Printf("order=%d enter paid\n", o.ID)
case StateCanceled:
fmt.Printf("order=%d enter canceled\n", o.ID)
}
return nil
}
type orderEvent struct {
fsm.NoopEventListener[*Order]
id OrderEvent
}
func (e orderEvent) ID() OrderEvent { return e.id }
func (e orderEvent) BeforeEvent(ctx context.Context, o *Order) error {
switch e.id {
case EventCancel:
if o.ID <= 0 {
return errors.New("invalid order id")
}
}
return nil
}
func (e orderEvent) OnEvent(ctx context.Context, o *Order) error {
switch e.id {
case EventPay:
fmt.Printf("pay order=%d\n", o.ID)
case EventCancel:
fmt.Printf("cancel order=%d\n", o.ID)
}
return nil
}
func main() {
// 仅作示例,实际应用中需要使用上层传入的真实 context,避免使用默认的 background context
ctx := context.Background()
created := orderState{id: StateCreated}
paid := orderState{id: StatePaid}
canceled := orderState{id: StateCanceled}
pay := orderEvent{id: EventPay}
cancel := orderEvent{id: EventCancel}
factory, err := fsm.NewFactory[OrderState, OrderEvent, *Order](
StateCreated,
[]fsm.State[OrderState, *Order]{created, paid, canceled},
[]fsm.Event[OrderEvent, *Order]{pay, cancel},
[]fsm.Ttrans[OrderEvent, OrderState]{
{Event: EventPay, FromState: StateCreated, ToState: StatePaid},
{Event: EventCancel, FromState: StateCreated, ToState: StateCanceled},
{Event: EventCancel, FromState: StatePaid, ToState: StateCanceled},
},
nil,
)
if err != nil {
panic(err)
}
m, err := factory.Create(&Order{ID: 1})
if err != nil {
panic(err)
}
_ = m.Transition(ctx, EventPay)
err = m.Transition(ctx, EventPay)
if errors.Is(err, fsm.ErrInvalidTransition) {
fmt.Printf("invalid transition: state=%s event=%s\n", m.Current(), EventPay)
}
}
state / event 分别由多个类型实现。
package main
import (
"context"
"errors"
"fmt"
"cnb.cool/letmelife/open/golf/workflow/fsm"
)
type OrderState string
type OrderEvent string
const (
StateCreated OrderState = "created"
StatePaid OrderState = "paid"
StateCanceled OrderState = "canceled"
)
const (
EventPay OrderEvent = "pay"
EventCancel OrderEvent = "cancel"
)
type Order struct {
ID int64
}
type createdState struct{ fsm.NoopStateListener[*Order] }
func (createdState) ID() OrderState { return StateCreated }
type paidState struct{ fsm.NoopStateListener[*Order] }
func (paidState) ID() OrderState { return StatePaid }
type canceledState struct{ fsm.NoopStateListener[*Order] }
func (canceledState) ID() OrderState { return StateCanceled }
type payEvent struct{ fsm.NoopEventListener[*Order] }
func (payEvent) ID() OrderEvent { return EventPay }
func (payEvent) OnEvent(ctx context.Context, o *Order) error {
fmt.Printf("pay order=%d\n", o.ID)
return nil
}
type cancelEvent struct{ fsm.NoopEventListener[*Order] }
func (cancelEvent) ID() OrderEvent { return EventCancel }
func (cancelEvent) BeforeEvent(ctx context.Context, o *Order) error {
if o.ID <= 0 {
return errors.New("invalid order id")
}
return nil
}
func main() {
// 仅作示例,实际应用中需要使用上层传入的真实 context,避免使用默认的 background context
ctx := context.Background()
factory, err := fsm.NewFactory[OrderState, OrderEvent, *Order](
StateCreated,
[]fsm.State[OrderState, *Order]{createdState{}, paidState{}, canceledState{}},
[]fsm.Event[OrderEvent, *Order]{payEvent{}, cancelEvent{}},
[]fsm.Ttrans[OrderEvent, OrderState]{
{Event: EventPay, FromState: StateCreated, ToState: StatePaid},
{Event: EventCancel, FromState: StateCreated, ToState: StateCanceled},
{Event: EventCancel, FromState: StatePaid, ToState: StateCanceled},
},
nil,
)
if err != nil {
panic(err)
}
m, err := factory.Create(&Order{ID: 1})
if err != nil {
panic(err)
}
_ = m.Transition(ctx, EventPay)
err = m.Transition(ctx, EventPay)
if errors.Is(err, fsm.ErrInvalidTransition) {
fmt.Printf("invalid transition: state=%s event=%s\n", m.Current(), EventPay)
}
}
一次 Transition(ctx, event) 的执行顺序如下:
Event.BeforeEventState.OnExit(当前状态)Event.OnEventState.OnEnter(目标状态)StateSaver.Save(如果配置了 persister)Event.AfterEvent状态更新语义:
BeforeEvent / OnExit / OnEvent / OnEnter / Save 任一阶段失败:状态保持不变(仍为 FromState)AfterEvent 失败:状态已切换到 ToState,但会返回错误通过 StatePersister[I, S] 注入 Save/Load 逻辑:
Machine.Transition 在 OnEnter 成功后调用 Save(ctx, instance, nextState)Factory.Load(ctx, instance) 通过 Load 获取实例当前状态并构建 Machinetype memPersister struct {
store map[int64]OrderState
}
func (p *memPersister) Save(ctx context.Context, o *Order, s OrderState) error {
if p.store == nil {
p.store = make(map[int64]OrderState)
}
p.store[o.ID] = s
return nil
}
func (p *memPersister) Load(ctx context.Context, o *Order) (OrderState, error) {
return p.store[o.ID], nil
}
该包提供了一组可用于 errors.Is 判断的 sentinel error:
ErrInvalidTransition:当前状态下不允许该事件ErrInvalidInitState / ErrInvalidState / ErrInvalidEvent:schema 构建或加载时校验失败ErrDuplicateState / ErrDuplicateEvent / ErrDuplicateTransition:schema 定义冲突ErrEventsNotCovered:定义的事件未被 transitions 覆盖(每个 Event 至少出现一次)ErrHookFailed:钩子执行失败ErrStateLoadFailed / ErrStateSaveFailed:状态加载 / 保存失败ErrStateLoaderNotAvailable:未提供 loader,但调用了 Factory.LoadErrDeprecated:schema 已被废弃(见下)错误通常会携带额外上下文信息(如 state、event、hook、instance),便于定位问题。
当 schema 发生变更且希望禁用旧状态机时,可调用 Factory.Deprecate():
Factory.Create / Factory.Load 返回 ErrDeprecatedFactory 创建的 Machine.Transition 也会返回 ErrDeprecated一般用于状态机定义发生变更时,用于废弃旧的状态机 factory,并构建新 factory 代替。