2021年12月31日金曜日

Kaleidox状態機械/エンティティの状態遷移によるアクション

前回は状態機械を持ったエンティティを状態遷移させてみました。

今回は状態機械にアクションを設定し、状態遷移に伴うアクションの動作を確認します。

モデル

モデルは基本的には前回のものと同じですが、各アクションにアクションが実行されたことを示す文字列をコンソールに表示するようにしました。

アクションにはKaleidox言語を設定することができます。そこで、各アクションではKaledioxのprint関数を呼び出すようになっています。

* event
event=[{
    name="confirm"
  },{
    name="reject"
  },{
    name="delivered"
  },{
    name="cancel"
  },{
    name="suspend"
  },{
    name="resume"
}]
* entity
** salesorder
*** features
table=salesorder
*** attributes
| Name | Type  | Multiplicity |
|------+-------+--------------|
| id   | token |            1 |
| price| int   |            1 |
*** statemachines
**** status
state=[{
  name=INIT
  transition=[{
    to=running
    effect="println \"transition from INIT to running\""
  }]
  entry="println \"entry INIT\""
  exit="println \"exit INIT\""
  do.entry="println \"do.entry INIT\""
  do.exit="println \"do.exit INIT\""
},{
  name=canceled
  transition=[{
    to=FINAL
    effect="println \"transition from canceled to FINAL\""
  }]
  entry="println \"entry canceled\""
  exit="println \"exit canceled\""
  do.entry="println \"do.entry canceled\""
  do.exit="println \"do.exit canceled\""
},{
  name=suspended
  transition=[{
    guard=resume
    to=HISTORY
    effect="println \"transition from suspended to HISTORY\""
  }]
  entry="println \"entry suspended\""
  exit="println \"exit suspended\""
  do.entry="println \"do.entry suspended\""
  do.exit="println \"do.exit suspended\""
}]
statemachine=[{
  name="running"
  state=[{
    name=INIT
    transition=[{
      to=applying
      effect="println \"transition from running.INIT to running.applying\""
    }]
    entry="println \"entry running.INIT\""
    exit="println \"exit running.INIT\""
    do.entry="println \"do.entry running.INIT\""
    do.exit="println \"do.exit running.INIT\""
  },
  {
    name=applying
    transition=[{
      to=confirming,
      effect="println \"transition from running.applying to running.confirming\""
    }]
    entry="println \"entry running.applying\""
    exit="println \"exit running.applying\""
    do.entry="println \"do.entry running.applying\""
    do.exit="println \"do.exit running.applying\""
  },{
    name=confirming
    transition=[{
      guard=confirm
      to=confirmed
      effect="println \"transition from running.confirming to running.confirmed\""
    },{
      guard=reject
      to=rejected
      effect="println \"transition from running.confirming to running.rejected\""
    }]
    entry="println \"entry running.confirming\""
    exit="println \"exit running.confirming\""
    do.entry="println \"do.entry running.confirming\""
    do.exit="println \"do.exit running.confirming\""
  },{
    name=confirmed
    transition=[{
      to=delivering
      effect="println \"transition from running.confirmed to running.delivering\""
    }]
    entry="println \"entry running.confirmed\""
    exit="println \"exit running.confirmed\""
    do.entry="println \"do.entry running.confirmed\""
    do.exit="println \"do.exit running.confirmed\""
  },{
    name=rejected
    transition=[{
      to=FINAL
      effect="println \"transition from running.rejected to FINAL\""
    }]
    entry="println \"entry running.rejected\""
    exit="println \"exit running.rejected\""
    do.entry="println \"do.entry running.rejected\""
    do.exit="println \"do.exit running.rejected\""
  },{
   name=delivering
    transition=[{
      guard=delivered
      to=delivered
      effect="println \"transition from running.delivering to running.delivered\""
    }]
    entry="println \"entry running.delivering\""
    exit="println \"exit running.delivering\""
    do.entry="println \"do.entry running.delivering\""
    do.exit="println \"do.exit running.delivering\""
  },{
    name=delivered
    transition=[{
      to=FINAL
      effect="println \"transition from running.delivered to FINAL\""
    }]
    entry="println \"entry running.delivered\""
    exit="println \"exit running.delivered\""
    do.entry="println \"do.entry running.delivered\""
    do.exit="println \"do.exit running.delivered\""
  }]
  transition=[{
    guard=cancel
    to=canceled
    effect="println \"transition from running to canceled\""
  },{
    guard=suspend
    to=suspended
    effect="println \"transition from running to suspended\""
  }]
}]

イベント

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

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

前回からの変更点はありません。

エンティティ

エンティティの定義も前回から変更点はありません。

entity節の下にエンティティ「salesorder」を定義しています。

エンティティ「salesorder」の下にfeatures節で特性、attributes節で属性、statemachines節で状態機械を定義しています。

statemachines節の下に状態機械「status」を定義しています。

状態機械

定義したモデルの状態機械図は前回と同じ以下となります。

アクション

状態機械の以下の場所にアクションを定義しました。

  • 状態のentry, exit, do.entry, do.exit
  • 遷移のeffect

アクションはKaleidoxスクリプトでアクションの設定位置をコンソールに表示するものです。

実行

それでは実行してみましょう。

準備

まず、entity-create-collection関数を使ってエンティティを格納する入れ物であるコレクションの作成(内部的にはDBテーブルの作成)を行います。

kaleidox> entity-create-collection 'salesorder
true

この処理ではエンティティの状態遷移は起こらないのでコンソールへの表示は行われません。

エンティティの作成

次にentity-create関数でエンティティを作成します。

kaleidox> entity-create 'salesorder price=100
entry INIT
do.entry INIT
do.exit INIT
exit INIT
transition from INIT to running
entry running.INIT
do.entry running.INIT
do.exit running.INIT
exit running.INIT
transition from running.INIT to running.applying
entry running.applying
do.entry running.applying
do.exit running.applying
exit running.applying
transition from running.applying to running.confirming
entry running.confirming
do.entry running.confirming
id:salesorder-67Yh9FdYry41hzuS9WDEtF;price:100;status:confirming

アクションの設定を行う前だと以下のようになっていたところですが、多数のアクションが実行されたことが分かります。この差分がアクションの動作ということになります。

kaleidox> entity-create 'salesorder price=100
id:salesorder-67Yh9FdYry41hzuS9WDEtF;price:100;status:confirming

それぞれ詳しく見ていきます。

オブジェクトが作成されると初期状態INITに入ります。初期状態INITへの進入時に呼ばれるentryアクションとdo.entryアクションが実行されました。

entry INIT
do.entry INIT

初期状態INITから最初の状態に自動的に移るので初期状態INITからの退出時に呼ばれるdo.exitアクションとexitアクションが実行されました。

do.exit INIT
exit INIT

次に初期状態INITから状態runningへの実際の遷移が行われます。

transition from INIT to running

状態runningは状態の入れ子になっているので状態機械running内で新たな状態遷移が始まります。このため状態機械runningの初期状態に入り最初の状態applyingに遷移します。状態機械runningの初期状態INITへの進入処理としてentry, do.entry、退出処理としてdo.exit, exitアクションが実行され、初期状態INITから最初の状態applyingに遷移する中でeffectアクションが実行されます。

entry running.INIT
do.entry running.INIT
do.exit running.INIT
exit running.INIT
transition from running.INIT to running.applying

状態applyingに入った後は自動的に状態confirmingに移ります。このため状態applyingへの進入処理、退出処理の各アクションが実行され、状態applyingから状態confirmingへの遷移アクションが実行されます。

entry running.applying
do.entry running.applying
do.exit running.applying
exit running.applying
transition from running.applying to running.confirming

最後に状態confirmingへの進入時のアクションとしてentry, do.entryアクションが実行され状態confirmingに落ち着きました。

entry running.confirming
do.entry running.confirming

オブジェクトの状態を確認するとconfirmingとなっています。

kaleidox> :show:pretty
┏━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃Name  │Value                            ┃
┣━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃id    │salesorder-67Yh9FdYry41hzuS9WDEtF┃
┃price │100                              ┃
┃status│confirming                       ┃
┗━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

エベントの送出

event-call関数でCALLイベントconfirmをsalesorderエンティティ「67Yh9FdYry41hzuS9WDEtF」に対して送出します。

kaleidox> event-call :entity 'salesorder 'confirm "67Yh9FdYry41hzuS9WDEtF"
do.exit running.confirming
exit running.confirming
transition from running.confirming to running.confirmed
entry running.confirmed
do.entry running.confirmed
do.exit running.confirmed
exit running.confirmed
transition from running.confirmed to running.delivering
entry running.delivering
do.entry running.delivering
Event[confirm]

アクションの設定を行う前だと以下のようになっていたところです。この差分がアクションの動作ということになります。

kaleidox> event-call :entity 'salesorder 'confirm "67Yh9FdYry41hzuS9WDEtF"
Event[confirm]

それぞれ詳しく見ていきます。

まず状態confirmingから状態confirmedへの遷移が起こります。このため状態confirmingからの退出アクションdo.exit, exitが実行されます。続けて状態confirmingから状態confirmedへの遷移アクションが実行されます。

do.exit running.confirming
exit running.confirming
transition from running.confirming to running.confirmed

状態confirmedから状態deliveringへの遷移は無条件なので自動で遷移が起こります。

状態confirmedへの進入アクションentry, do.entryに続いて退出アクションdo.exit, exitが実行されます。続いて状態confirmedから状態deliveringへの遷移アクションが実行されます。

entry running.confirmed
do.entry running.confirmed
do.exit running.confirmed
exit running.confirmed
transition from running.confirmed to running.delivering

最後に状態deliveringへ進入するので状態deliveringのentry, do.entryアクションが実行されます。

entry running.delivering
do.entry running.delivering

ここで状態遷移は終わり状態deliveringに落ち着きました。

状態遷移の確認

confirmイベントを受信したsalesorderエンティティの状態をentity-get関数で確認します。

状態機械statusが状態confirmingから状態deliveringに遷移していることを確認できました。

kaleidox> entity-get 'salesorder "67Yh9FdYry41hzuS9WDEtF"
id:salesorder-67Yh9FdYry41hzuS9WDEtF;price:100;status:delivering
kaleidox> :show:pretty
┏━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃Name  │Value                            ┃
┣━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃id    │salesorder-67Yh9FdYry41hzuS9WDEtF┃
┃price │100                              ┃
┃status│delivering                       ┃
┗━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

まとめ

今回は状態機械のアクションの動作について確認しました。

状態遷移に伴って、設定したアクションが動作するのでイベント駆動の処理を自然に記述できます。

通常プログラミング言語で状態機械を実装するとそれなりのコード量になり、状態機械モデルとの関係も不明確になりがちですが、モデルそのものにアクションを設定することによりこのような問題が解消されます。

状態機械モデルを直接実行できることはモデル駆動開発の大きなアドバンテージになると思います。

諸元

Kaleidox
0.3.4