はじめに
ビジネスロジックを実装していると、状態の管理はいつもついて回ります。
この辺のgolangでの実装について、bykof/statefulがいい感じだったので紹介します。
bykof/statefulの使い方
例として、以下のようなとある注文システムの状態遷移図を実現してみます
1. 状態を定義する
まずは状態遷移図の○部分、状態の定義を stateful.DefaultState
を使って行います。
import "github.com/bykof/stateful"
var (
BEGIN = stateful.DefaultState("BEGIN")
InCart = stateful.DefaultState("InCart")
Ordered = stateful.DefaultState("Ordered")
Canceled = BEGIN
Shipped = BEGIN
)
そして State() state stateful.State
, SetState(state stateful.State) error
のinterface実装を行います。
type OrderState struct {
state stateful.State
deposit int
}
func NewOrderState(deposit int) OrderState {
return OrderState{
state: BEGIN,
deposit: deposit,
}
}
func (s *OrderState) State() stateful.State {
return s.state
}
//this will be called after calling a transition function
func (s *OrderState) SetState(state stateful.State) error {
s.state = state
return nil
}
この実装がstateを持つobjectになります。
2. 状態遷移を定義する
状態遷移は以下のように、 stateful.StateMachine
の AddTransition
を利用します。
statefulパッケージではstate machineに1で定義したobjectを渡して使うため、登録する関数はそのobjectのメソッドで定義するのが扱いやすそうです。
stateMachine := stateful.StateMachine{
StatefulObject: object,
}
//Add transition
stateMachine.AddTransition(
object.Order,//状態遷移時に実行する関数
stateful.States{BEGIN, InCart},//状態遷移元
stateful.States{Ordered},//状態遷移先
)
状態遷移時に実行する関数では、 stateful.TransitionArguments
というinterfaceが引数として渡されます。これは、状態遷移時を走らせる時に呼ぶ Run
関数で渡す、任意の構造体となります。なので関数内でcastして使います。
errなしの場合は次に遷移させる状態を返します。
func (s OrderState) Order(transitionArguments stateful.TransitionArguments) (stateful.State, error) {
//transition argument is in Run parameter
p, ok := transitionArguments.(*Product)
if !ok {
return nil, errors.New("Invalid argument")
}
if s.deposit < p.Fee {
return nil, errors.New("The deposit is not enough")
}
s.deposit -= p.Fee
return Ordered, nil//遷移後の状態を返す
}
3. 状態遷移の定義を確認する。
1, 2で状態遷移が作成できました。これらが正しく作成できているかは、 github.com/bykof/stateful/statefulGraph
を使って確認ができます。
//check graph
stateMachineGraph := statefulGraph.StateMachineGraph{StateMachine: machine}//machineは2で作ったもの
_ = stateMachineGraph.DrawGraph()
こんな感じに出力されます。PlantUMLで使用したい場合は、 DrawGraphWithName
を利用ください
digraph {
BEGIN->InCart[ label="SelectProduct" ];
BEGIN->Ordered[ label="Order" ];
InCart->Ordered[ label="Order" ];
Ordered->BEGIN[ label="Cancel" ];
Ordered->BEGIN[ label="ShopProblem" ];
Ordered->BEGIN[ label="Ship" ];
BEGIN;
InCart;
Ordered;
}
また、AddTransitionは同じ状態からの遷移に対して複数設定可能なので、使い勝手が良さそうです。
stateMachine.AddTransition(
object.Cancel,
stateful.States{Ordered},
stateful.States{Canceled},
)
stateMachine.AddTransition(
object.ShopProblem,
stateful.States{Ordered},
stateful.States{Canceled},
)
4. 状態遷移を回す
あとは2で作ったstate machineに対して Run
を実行するだけです。
状態遷移の識別は AddTransaction
で指定した関数で行われます。
err = machine.Run(order.Order, &product1)
if err != nil {
fmt.Printf("Error")
return
}
sample code
本家のsampleはこちら
この記事に記載したコードはこちらに置いてあります。