2
2

More than 3 years have passed since last update.

bykof/statefulを使って、Golangで状態遷移(State machine)を扱う

Last updated at Posted at 2020-05-24

はじめに

ビジネスロジックを実装していると、状態の管理はいつもついて回ります。
この辺のgolangでの実装について、bykof/statefulがいい感じだったので紹介します。

bykof/statefulの使い方

例として、以下のようなとある注文システムの状態遷移図を実現してみます

image.png

1. 状態を定義する

まずは状態遷移図の○部分、状態の定義を stateful.DefaultState を使って行います。

statemachine/definition.go
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実装を行います。

statemachine/order_state.go
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.StateMachineAddTransition を利用します。
statefulパッケージではstate machineに1で定義したobjectを渡して使うため、登録する関数はそのobjectのメソッドで定義するのが扱いやすそうです。

statemachine/state_machine.go
        stateMachine := stateful.StateMachine{
                StatefulObject: object,
        }
        //Add transition
        stateMachine.AddTransition(
                object.Order,//状態遷移時に実行する関数
                stateful.States{BEGIN, InCart},//状態遷移元
                stateful.States{Ordered},//状態遷移先
        )

状態遷移時に実行する関数では、 stateful.TransitionArguments というinterfaceが引数として渡されます。これは、状態遷移時を走らせる時に呼ぶ Run関数で渡す、任意の構造体となります。なので関数内でcastして使います。

errなしの場合は次に遷移させる状態を返します。

statemachine/state_machine.go
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 を使って確認ができます。

statemachine/state_machine.go
        //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/state_machine.go
        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 で指定した関数で行われます。

main.go
        err = machine.Run(order.Order, &product1)
        if err != nil {
                fmt.Printf("Error")
                return
        }

sample code

本家のsampleはこちら

この記事に記載したコードはこちらに置いてあります。

参考

Programming business processes in Golang

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2