2021年7月31日土曜日

Kaleidox/状態機械:ヒストリー

前回は状態機械のサブステートを用いてキャンセル機能を実現しました。

サブステートに並ぶ状態機械の重要な機能にヒストリーがあります。

ヒストリーは状態間の遷移時に、遷移元状態の履歴を保持しておく機能で、状態遷移後に元の状態に戻る遷移を行うことができます。

ヒストリーを用いることで、保留/再開機能を記述することができます。今回はこのヒストリーを使ってKaleidoxの状態機械で保留/再開機能を実現します。

モデル

前回のモデルに対して以下の拡張を行っています。

  • suspendイベント、resumeイベントを追加
  • 保留状態を示すsuspendedステートを追加
  • サブステートrunningの任意のステートでsuspendイベントにより保留可能にした

この拡張設定を行った状態機械の定義は以下になります。

  1. * event  
  2. event=[{  
  3.     name="confirm"  
  4.   },{  
  5.     name="reject"  
  6.   },{  
  7.     name="delivered"  
  8.   },{  
  9.     name="cancel"  
  10.   },{  
  11.     name="suspend"  
  12.   },{  
  13.     name="resume"  
  14. }]  
  15. * statemachine  
  16. statemachine={  
  17.   name="purchase"  
  18.   state=[{  
  19.     name=INIT  
  20.     transition=[{  
  21.       to=running  
  22.     }]  
  23.   },{  
  24.     name=canceled  
  25.     transition=[{  
  26.       to=FINAL  
  27.     }]  
  28.   },{  
  29.     name=suspended  
  30.     transition=[{  
  31.       guard=resume  
  32.       to=HISTORY  
  33.     }]  
  34.   }]  
  35.   statemachine=[{  
  36.     name="running"  
  37.     state=[{  
  38.       name=applying  
  39.       transition=[{  
  40.         to=confirming  
  41.       }]  
  42.     },{  
  43.       name=confirming  
  44.       transition=[{  
  45.         guard=confirm  
  46.         to=confirmed  
  47.       },{  
  48.         guard=reject  
  49.         to=rejected  
  50.       }]  
  51.     },{  
  52.       name=confirmed  
  53.       transition=[{  
  54.         to=delivering  
  55.       }]  
  56.     },{  
  57.       name=rejected  
  58.       transition=[{  
  59.         to=FINAL  
  60.       }]  
  61.     },{  
  62.      name=delivering  
  63.       transition=[{  
  64.         guard=delivered  
  65.         to=delivered  
  66.       }]  
  67.     },{  
  68.       name=delivered  
  69.       transition=[{  
  70.         to=FINAL  
  71.       }]  
  72.     }]  
  73.     transition=[{  
  74.       guard=cancel  
  75.       to=canceled  
  76.     },{  
  77.       guard=suspend  
  78.       to=suspended  
  79.     }]  
  80.   }]  
  81. }  

イベント

以下の6つのイベントを定義しています。

confirm
確認OK
reject
確認却下
delivered
配送済み
cancel
キャンセル
suspend
保留
resume
再開

前回のものからはsuspendイベントとresumeイベントを追加しています。

状態機械

定義したモデルを状態機械図で記述すると以下になります。

前回、サブステートrunningを作成し、applying, confirming, confirmed, rejected, delivering, deliveredの各ステートを定義しています。

以下の定義により新たに保留状態を示すステートsuspendedを作成し、サブステートrunningの任意のステートからの状態遷移ができるようにしました。ステートsuspendedからの遷移先はHISTORYになっているので、履歴情報にある直近のステートに復帰します。

  1. state=[{  
  2. .  
  3. },{  
  4.   name=suspended  
  5.   transition=[{  
  6.     guard=resume  
  7.     to=HISTORY  
  8.   }]  
  9. }]  

サブステートrunningからは以下の遷移設定によって、任意のステートでsuspendイベントが発生するとステートsuspendedに遷移します。

  1. transition=[{  
  2.   
  3. },{  
  4.   guard=suspend  
  5.   to=suspended  
  6. }]  

実行

それでは実行してみます。

まず正常系とrejectイベントの2つのルートを確認します。これは前回と同じ動きになります。

この後に、今回のテーマであるサブステートを利用したsuspendイベント、resumeイベントの振る舞いを確認します。

正常系

最初に正常系の動きです。前回の正常系と同じ動きになります。

まずstatemachine-new関数で状態機械を生成します。生成する状態機械のモデル名はpurchaseです。

生成した状態機械をsetq関数で変数smに束縛します。

状態機械の名前はpurchaseです。状態機械の状態はconfirmingになっています。

  1. kaleidox> statemachine-new 'purchase  
  2. StateMachine[purchase.running.confirming]  
  3. kaleidox> setq sm  
  4. StateMachine[purchase.running.confirming]  

状態機械purchaseを作成すると、すぐにサブステートrunning内のステートconfirmingに遷移します。

次にevent-issue関数でconfirmイベントを発行します。

confirmイベント発行の結果、状態機械purchaseの状態はdeliveringになりました。引き続きサブステートrunning内です。

  1. kaleidox> event-issue 'confirm  
  2. Event[confirm]  
  3. kaleidox> sm  
  4. StateMachine[purchase:delivering]  

次にevent-issue関数でconfirmイベントを発行します。

confirmイベント発行の結果、状態機械purchaseの状態はdeliveringになりました。引き続きサブステートrunning内です。

  1. kaleidox> event-issue 'delivered  
  2. Event[delivered]  
  3. kaleidox> sm  
  4. StateMachine[purchase:FINAL]  

これで状態機械の状態遷移は終了です。

reject

続けてrejectイベントのルートも確認しておきます。

statemachine-new関数で状態機械purchaseを生成した直後はconfirming状態になっています。

ここでrejectイベントを発行するとモデルの定義どおり最終状態FINALに遷移しました。

  1. kaleidox> statemachine-new 'purchase  
  2. StateMachine[purchase.running.confirming]  
  3. kaleidox> setq sm  
  4. StateMachine[purchase.running.confirming]  
  5. kaleidox> event-issue 'reject  
  6. Event[reject]  
  7. kaleidox> sm  
  8. StateMachine[purchase:FINAL]  

状態機械purchaseはrejectイベントを受信することで状態rejectedに遷移しますが、状態rejectedから最終状態FINALの間にガードがないので最終状態FINALに自動遷移しています。

suspendとresume

次はsuspendイベントです。サブステートrunning内の任意のステートでsuspendが可能になります。

状態機械purchaseを作った直後、状態機械はステートconfirmingになっています。

  1. kaleidox> statemachine-new 'purchase  
  2. StateMachine[purchase.running.confirming]  
  3. kaleidox> setq sm  
  4. StateMachine[purchase.running.confirming]  

ステートconfirmingでsuspendイベントを出してみます。以下に示すとおりsuspended状態になりました。

  1. kaleidox> event-issue 'suspend  
  2. Event[cancel]  
  3. kaleidox> sm  
  4. StateMachine[purchase.suspended]  

ここでresumeイベントを出すと、suspended前の状態であるステートconfirmingに戻りました。

  1. kaleidox> event-issue 'resume  
  2. Event[resume]  
  3. kaleidox> sm  
  4. StateMachine[purchase.running.confirming]  

次には、ステートdeliveringでresume/suspendの動きを確かめます。

状態機械purchaseを作った後にconfirmイベントを出し、delivering状態にします。

  1. kaleidox> statemachine-new 'purchase  
  2. StateMachine[purchase.running.confirming]  
  3. kaleidox> setq sm  
  4. StateMachine[purchase.running.confirming]  
  5. kaleidox> event-issue 'confirm  
  6. Event[confirm]  
  7. kaleidox> sm  
  8. StateMachine[purchase.running.delivering]  

ステートdeliveringでsuspendイベントを出してみます。以下に示すとおりsuspended状態になりました。

  1. kaleidox> event-issue 'suspend  
  2. Event[suspend]  
  3. kaleidox> sm  
  4. StateMachine[purchase.suspended]  

ここでresumeイベントを出すと、suspended前の状態であるステートdeliveringに戻りました。ステートsuspendに遷移する前のステートを覚えていて、そのステートに復帰することが確認できました。

  1. kaleidox> event-issue 'resume  
  2. Event[resume]  
  3. kaleidox> sm  
  4. StateMachine[purchase.running.delivering]  

まとめ

今回はスブステートをもった状態機械モデルで保留機能をモデル定義し、Kaleidox上で動作させてみました。

前回実現したキャンセル機能と今回の保留機能は実用アプリケーションでは頻出の機能ですが、スクラッチで実装するのはなかなか大変だと思います。これらの機能がモデル定義のみで使用できることは、モデル駆動開発の大きなメリットといえます。

諸元

Kaleidox
0.3.1