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

2021年11月30日火曜日

Kaleidox状態機械/エンティティの状態遷移

前回は状態機械を持ったエンティティをデータベースに格納、管理してみました。

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

モデル

々回、前回とエンティティsalesorderを定義し状態機械の設定も行いました。

今回もこの定義をそのまま使います。

* 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
  }]
},{
  name=canceled
  transition=[{
    to=FINAL
  }]
},{
  name=suspended
  transition=[{
    guard=resume
    to=HISTORY
  }]
}]
statemachine=[{
  name="running"
  state=[{
    name=applying
    transition=[{
      to=confirming
    }]
  },{
    name=confirming
    transition=[{
      guard=confirm
      to=confirmed
    },{
      guard=reject
      to=rejected
    }]
  },{
    name=confirmed
    transition=[{
      to=delivering
    }]
  },{
    name=rejected
    transition=[{
      to=FINAL
    }]
  },{
   name=delivering
    transition=[{
      guard=delivered
      to=delivered
    }]
  },{
    name=delivered
    transition=[{
      to=FINAL
    }]
  }]
  transition=[{
    guard=cancel
    to=canceled
  },{
    guard=suspend
    to=suspended
  }]
}]

イベント

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

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

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

エンティティ

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

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

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

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

状態機械

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

実行

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

準備

まず、前回記事までのおさらいです。

前回の記事でデータベース上に状態機械を持ったsalesorderオブジェクトを作成しました。手順を再現すると以下になります。

entity-create-collection関数でエンティティを格納するコレクションを作成します。コレクションのバックエンドはデータベースのテーブルになります。

kaleidox> entity-create-collection 'salesorder
true

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

状態機械statusの状態はconfirmingになっています。

kaleidox> entity-create 'salesorder price=100
id:qXj7DyqEdu7rosSNdQ9R7;price:100;status:confirming
kaleidox> :show:pretty
┏━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━┓
┃Name  │Value                 ┃
┣━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━┫
┃id    │qXj7DyqEdu7rosSNdQ9R7 ┃
┃price │100                   ┃
┃status│confirming            ┃
┗━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━┛

salesorderエンティティのデータベースでの格納状況を確認するためにstore-get関数でデータベースから直接データを取得してみます。

kaleidox> store-get 'salesorder "qXj7DyqEdu7rosSNdQ9R7"
ID:qXj7DyqEdu7rosSNdQ9R7;PRICE:100;STATUS:501
kaleidox> :show:pretty
┏━━━━━━┯━━━━━━━━━━━━━━━━━━━━━┓
┃Name  │Value                ┃
┣━━━━━━┿━━━━━━━━━━━━━━━━━━━━━┫
┃ID    │qXj7DyqEdu7rosSNdQ9R7┃
┃PRICE │100                  ┃
┃STATUS│201                  ┃
┗━━━━━━┷━━━━━━━━━━━━━━━━━━━━━┛

STATUSカラムには、状態confirmingに対応する数値201が格納されていることが確認できました。

エベントの送出

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

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

状態遷移の確認

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

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

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

salesorderエンティティのデータベースでの格納状況を確認するためにstore-get関数でデータベースから直接データを取得してみます。

kaleidox> store-get 'salesorder "qXj7DyqEdu7rosSNdQ9R7"
ID:qXj7DyqEdu7rosSNdQ9R7;PRICE:100;STATUS:501
kaleidox> :show:pretty
┏━━━━━━┯━━━━━━━━━━━━━━━━━━━━━┓
┃Name  │Value                ┃
┣━━━━━━┿━━━━━━━━━━━━━━━━━━━━━┫
┃ID    │qXj7DyqEdu7rosSNdQ9R7┃
┃PRICE │100                  ┃
┃STATUS│501                  ┃
┗━━━━━━┷━━━━━━━━━━━━━━━━━━━━━┛

STATUSカラムには、状態deliveringに対応する数値501が格納されていることが確認できました。

まとめ

今回はデータベース上に永続化されているエンティティに対してイベント送出によって状態遷移を起こし、その状態遷移がデータベース上に反映されていることを確認することができました。

永続オブジェクトであるエンティティの状態機械は、永続オブジェクトの読込みと書き戻しの処理が伴うので、プログラムで実装するのは少し手間がかかります。この手間をモデル駆動開発によって削減でできることが分かりました。

次回は状態機械のアクションについてみていきます。

諸元

Kaleidox
0.3.4

2021年10月31日日曜日

Kaleidox状態機械/エンティティ・ストア

今回より、エンティティの状態機械の操作について見ていきます。

今回は、エンティティをデータベースに保存、管理する方法について説明します。

モデル

前回、エンティティsalesorderを定義し、状態機械の設定も行いました。

今回はこの定義をそのまま使います。

* 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
  }]
},{
  name=canceled
  transition=[{
    to=FINAL
  }]
},{
  name=suspended
  transition=[{
    guard=resume
    to=HISTORY
  }]
}]
statemachine=[{
  name="running"
  state=[{
    name=applying
    transition=[{
      to=confirming
    }]
  },{
    name=confirming
    transition=[{
      guard=confirm
      to=confirmed
    },{
      guard=reject
      to=rejected
    }]
  },{
    name=confirmed
    transition=[{
      to=delivering
    }]
  },{
    name=rejected
    transition=[{
      to=FINAL
    }]
  },{
   name=delivering
    transition=[{
      guard=delivered
      to=delivered
    }]
  },{
    name=delivered
    transition=[{
      to=FINAL
    }]
  }]
  transition=[{
    guard=cancel
    to=canceled
  },{
    guard=suspend
    to=suspended
  }]
}]

イベント

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

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

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

エンティティ

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

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

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

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

状態機械

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

実行

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

エンティティのテーブル作成

まずエンティティを格納するデータベースのテーブルを作成します。

最初はデータベースのテーブルは作成されていません。

このため以下のようにstore-select関数の実行はエラーとなります。

kaleidox> store-select 'salesorder
Error[org.h2.jdbc.JdbcSQLSyntaxErrorException: テーブル "SALESORDER" が見つかりません[NL]Table "SALESORDER" not found; SQL statement:[NL]SELECT * FROM salesorder WHERE 1 = 1 LIMIT 10 [42102-199]]

entity-create-collection関数でエンティティを格納するデータベースの作成を行うことができます。

デフォルトでは以下の設定が行われているため、H2のメモリデータベースを使用することができます。

db.default.driver="org.h2.Driver"
db.default.url="jdbc:h2:mem:"

以下のようにentity-create-collection関数を実行します。

kaleidox> entity-create-collection 'salesorder
true

store-select関数を用いてデータベースにエンティティsalesorderを格納するためのテーブルsalesorderが作成されていることを確認します。

kaleidox> store-select 'salesorder
Table[0x0]

実行の結果、テーブルは作成されておりデータが0件であることが確認できました。

entity-select関数を使ってエンティティsalesorderの一覧としても確認してみます。

kaleidox> entity-select 'salesorder
Table[3x0]

こちらも0件のンティティ・コレクションが作成されていることが確認できました。

エンティティの作成

データベースのテーブルが作成できたので、次はエンティティの作成を行います。

エンティティの作成はentity-create関数で行うことができます。第1引数にエンティティのクラス名、第2引数にプロパティをレコード形式で指定します。

kaleidox> entity-create 'salesorder price=100
id:51QK2CqGdgVLTqddOFdn6n;price:100;status:confirming
kaleidox> :show:pretty
┏━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━┓
┃Name  │Value                 ┃
┣━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━┫
┃id    │51QK2CqGdgVLTqddOFdn6n┃
┃price │100                   ┃
┃status│confirming            ┃
┗━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━┛

entity-create関数実行の結果、オブジェクトID「51QK2CqGdgVLTqddOFdn6n」のエンティティが作成されました。

状態機械statusは初期状態の「confirm」になっています。

エンティティの取得

entity-get関数を用いて、作成したエンティティの取得を行います。

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

先程、作成したエンティティを取得することができました。

エンティティの検索

次はエンティティの検索を行ってみます。

まず、データベースのテーブルを直接検索するstore-select関数で検索を行います。検索条件を指定していないので全件検索になります。

以下のようにテーブルの内容を取得することができました。状態機械statusはデータベース上では数値102として格納されています。

kaleidox> store-select 'salesorder
Table[3x1]
kaleidox> :show:pretty
┏━━━━━━━━━━━━━━━━━━━━━━┯━━━━━┯━━━━━━┓
┃ID                    │PRICE│STATUS┃
┣━━━━━━━━━━━━━━━━━━━━━━┿━━━━━┿━━━━━━┫
┃51QK2CqGdgVLTqddOFdn6n│100  │102   ┃
┗━━━━━━━━━━━━━━━━━━━━━━┷━━━━━┷━━━━━━┛

次はentity-select関数を用いて、エンティティsalesorderの一覧としても確認してみます。

以下のようにエンティティの内容を取得することができました。状態機械statusはconfirmingとして表示されています。

kaleidox> entity-select 'salesorder
Table[3x1]
kaleidox> :show:pretty
┏━━━━━━━━━━━━━━━━━━━━━━┯━━━━━┯━━━━━━━━━━┓
┃id                    │price│status    ┃
┣━━━━━━━━━━━━━━━━━━━━━━┿━━━━━┿━━━━━━━━━━┫
┃51QK2CqGdgVLTqddOFdn6n│100  │confirming┃
┗━━━━━━━━━━━━━━━━━━━━━━┷━━━━━┷━━━━━━━━━━┛

entity-select関数は、エンティティに対する検索を行い検索結果をテーブル形式で取得します。

entity-query関数は、エンティティに対する検索を行い、検索結果をエンティティのシーケンスとして取得します。

こちらもエンティティの内容を取得することができました。

kaleidox> entity-query 'salesorder
[id:51QK2CqGdgVLTqddOFdn6n;price:100;status:confirming]
kaleidox> .head
id:51QK2CqGdgVLTqddOFdn6n;price:100;status:confirming
kaleidox> :show:pretty
┏━━━━━━┯━━━━━━━━━━━━━━━━━━━━━━┓
┃Name  │Value                 ┃
┣━━━━━━┿━━━━━━━━━━━━━━━━━━━━━━┫
┃id    │51QK2CqGdgVLTqddOFdn6n┃
┃price │100                   ┃
┃status│confirming            ┃
┗━━━━━━┷━━━━━━━━━━━━━━━━━━━━━━┛

まとめ

今回は状態機械を持っているエンティティをデータベースに格納して管理する方法について説明しました。

次回はイベントに対するエンティティの振る舞いについて検証します。

諸元

Kaleidox
0.3.3

2021年9月30日木曜日

Kaleidox状態機械/エンティティ

前回はリソースに対するイベントに対して反応する状態機械を定義し、Kaleidox上で動作させました。

今回はこの機能を拡張してエンティティ・オブジェクトの状態機械を定義してみます。

モデル

今回のモデルはエンティティsalesorderを定義し、そのプロパティとして状態機械statusを定義しています。

状態機械statusは前回定義したpurchaseと同じものです。

この設定を行ったエンティティの定義は以下になります。

* 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 |
*** statemachines
**** status
state=[{
  name=INIT
  transition=[{
    to=running
  }]
},{
  name=canceled
  transition=[{
    to=FINAL
  }]
},{
  name=suspended
  transition=[{
    guard=resume
    to=HISTORY
  }]
}]
statemachine=[{
  name="running"
  state=[{
    name=applying
    transition=[{
      to=confirming
    }]
  },{
    name=confirming
    transition=[{
      guard=confirm
      to=confirmed
    },{
      guard=reject
      to=rejected
    }]
  },{
    name=confirmed
    transition=[{
      to=delivering
    }]
  },{
    name=rejected
    transition=[{
      to=FINAL
    }]
  },{
   name=delivering
    transition=[{
      guard=delivered
      to=delivered
    }]
  },{
    name=delivered
    transition=[{
      to=FINAL
    }]
  }]
  transition=[{
    guard=cancel
    to=canceled
  },{
    guard=suspend
    to=suspended
  }]
}]

イベント

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

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

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

エンティティ

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

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

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

状態機械statusは、前回作成した状態機械purchaseのstate部分を使用しています。

前回の定義にある以下の部分は:

  name="purchase"
  kind=resource

それぞれ以下のようになっています。

  • 状態機械名は定義していません。
  • kindはエンティティのプロパティなのでresourceに設定されます。

状態機械

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

実行

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

まず最初にentity-create関数でエンティティを生成します。第1引数にエンティティ名、第2引数にエンティティ作成時のパラメタを指定します。今回はパラメタがないのでnilを指定しています。

生成したエンティティのIDは「3mRehDtuMYmTO0gpzCcaDa」、エンティティの状態機械statusの状態はconfirmingになっています。

kaleidox> entity-create 'salesorder nil
Entity[3mRehDtuMYmTO0gpzCcaDa;status:confirming]
kaleidox> setq o
Entity[3mRehDtuMYmTO0gpzCcaDa;status:confirming]

ここでevent-issue関数でconfirmイベントを送出します。

前回と同様に状態機械はevent-issue関数で発行されたイベントには無反応です。

kaleidox> event-issue 'confirm
Event[confirm]
kaleidox> o
Entity[4WVhMPHFzEJQCqUFpSxN3r;status:confirming]

リソースに関連付けられた状態機械へのイベント送出にはevent-call関数を用います。第1引数にイベント名、第2引数にイベント送信先のリソースID「12340」を指定しました。

この場合は、先ほど作成した状態機械には変化がありません。

kaleidox> event-call 'confirm "12340"
Event[confirm]
kaleidox> o
Entity[4WVhMPHFzEJQCqUFpSxN3r;status:confirming]

次に第1引数にイベント名、第2引数にイベント送信先のリソースID「3mRehDtuMYmTO0gpzCcaDa」を指定しました。リソースID「3mRehDtuMYmTO0gpzCcaDa」は先程作成した状態機械に関連付けられたものです。

今回は、エンティティの状態機械の状態が無事confirmingからdeliveringに変わりました。

kaleidox> event-call 'confirm "3mRehDtuMYmTO0gpzCcaDa"
Event[confirm]
kaleidox> o
Entity[4WVhMPHFzEJQCqUFpSxN3r;status:delivering]

まとめ

今回は状態機械を持ったエンティティを作成し、エンティティのID指定で状態機械を駆動させてみました。

オブジェクト・モデルの動的モデルはオブジェクトに包含された状態機械によって実現するのがセオリーですが、一般的なオブジェクト指向言語では状態機械はサポートされていないので、実装はそれなりの手間が必要でした。

この距離を埋める解決策はモデル駆動開発です。定義したモデルを直接実行することができれば、この問題が発生することはありません。その一実例としてKaleidoxでのエンティティ&状態機械のモデル定義と実行の様子をご紹介しました。

次回は状態機械にアクションを登録して、エンティティの振る舞いを定義してみます。

諸元

Kaleidox
0.3.2

2021年8月31日火曜日

Kaleidox:状態機械/リソース

前回は状態機械のヒストリー機能を用いて保留機能をモデル定義し、Kaleidox上で動作させました。

ここまで取り扱ってきた状態機械は、ローバルなスコープとなっていて、発生した該当イベントの全てに反応するものでした。

システム上に状態機械が1つしかない応用の場合はこれでよいですが、システムのリソースごとに状態機械を持ちたい場合には、ターゲットのリソースに対応付けられた状態機械のみ反応するための仕掛けが必要です。

今回はリソースに対応した状態機械を定義してKaleidox上で動作させてみます。

モデル

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

  • 状態機械purchaseの種別をresourceにする

具体的には以下の設定を入れます。

statemachine={
  name="purchase"
  kind=resource ← この設定

この設定を行うことで、定義した状態機械はリソースに関連付けられます。

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

* event
event=[{
    name="confirm"
  },{
    name="reject"
  },{
    name="delivered"
  },{
    name="cancel"
  },{
    name="suspend"
  },{
    name="resume"
}]
* statemachine
statemachine={
  name="purchase"
  kind=resource
  state=[{
    name=INIT
    transition=[{
      to=running
    }]
  },{
    name=canceled
    transition=[{
      to=FINAL
    }]
  },{
    name=suspended
    transition=[{
      guard=resume
      to=HISTORY
    }]
  }]
  statemachine=[{
    name="running"
    state=[{
      name=applying
      transition=[{
        to=confirming
      }]
    },{
      name=confirming
      transition=[{
        guard=confirm
        to=confirmed
      },{
        guard=reject
        to=rejected
      }]
    },{
      name=confirmed
      transition=[{
        to=delivering
      }]
    },{
      name=rejected
      transition=[{
        to=FINAL
      }]
    },{
     name=delivering
      transition=[{
        guard=delivered
        to=delivered
      }]
    },{
      name=delivered
      transition=[{
        to=FINAL
      }]
    }]
    transition=[{
      guard=cancel
      to=canceled
    },{
      guard=suspend
      to=suspended
    }]
  }]
}

イベント

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

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

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

状態機械

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

実行

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

最初にstatemachine-new関数で状態機械を作成します。第1引数には状態機械名を指定しますが、第2引数にリソースを特定するIDとして「12345」を指定しています。

kaleidox> setq sm (statemachine-new 'purchase "12345")
StateMachine[purchase.running.confirming]

状態機械purchaseが作成され、confirming状態になりました。

ここでevent-issue関数でconfirmイベントを送出します。

前回までの状態機械はconfirmイベントに反応して状態がdeliveringに変わりましたが、今回の状態機械は無反応です。

kaleidox> event-issue 'confirm
Event[confirm]
kaleidox> sm
StateMachine[purchase.running.confirming]

リソースに関連付けられた状態機械へのイベント送出にはevent-call関数を用います。第1引数にイベント名、第2引数にイベント送信先のリソースID「12340」を指定しました。

この場合は、先ほど作成した状態機械には変化がありません。

kaleidox> event-call 'confirm "12340"
Event[confirm]
kaleidox> sm
StateMachine[purchase.running.confirming]

次に第1引数にイベント名、第2引数にイベント送信先のリソースID「12345」を指定しました。

リソースID「12345」は先程作成した状態機械に関連付けられたものです。

今回の場合は、状態機械の状態が無事confirmingからdeliveringに変わりました。

kaleidox> event-call 'confirm "12345"
Event[confirm]
kaleidox> sm
StateMachine[purchase.running.delivering]

まとめ

今回はリソースに紐付いた状態機械を作成し、リソースIDを使って駆動する状態機械を選択を行ってみました。

次回はエンティティに状態機械に組み込む予定です。

諸元

Kaleidox
0.3.1

2021年7月31日土曜日

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

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

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

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

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

モデル

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

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

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

* event
event=[{
    name="confirm"
  },{
    name="reject"
  },{
    name="delivered"
  },{
    name="cancel"
  },{
    name="suspend"
  },{
    name="resume"
}]
* statemachine
statemachine={
  name="purchase"
  state=[{
    name=INIT
    transition=[{
      to=running
    }]
  },{
    name=canceled
    transition=[{
      to=FINAL
    }]
  },{
    name=suspended
    transition=[{
      guard=resume
      to=HISTORY
    }]
  }]
  statemachine=[{
    name="running"
    state=[{
      name=applying
      transition=[{
        to=confirming
      }]
    },{
      name=confirming
      transition=[{
        guard=confirm
        to=confirmed
      },{
        guard=reject
        to=rejected
      }]
    },{
      name=confirmed
      transition=[{
        to=delivering
      }]
    },{
      name=rejected
      transition=[{
        to=FINAL
      }]
    },{
     name=delivering
      transition=[{
        guard=delivered
        to=delivered
      }]
    },{
      name=delivered
      transition=[{
        to=FINAL
      }]
    }]
    transition=[{
      guard=cancel
      to=canceled
    },{
      guard=suspend
      to=suspended
    }]
  }]
}

イベント

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

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

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

状態機械

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

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

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

  state=[{
...
  },{
    name=suspended
    transition=[{
      guard=resume
      to=HISTORY
    }]
  }]

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

    transition=[{
...
    },{
      guard=suspend
      to=suspended
    }]

実行

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

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

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

正常系

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

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

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

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

kaleidox> statemachine-new 'purchase
StateMachine[purchase.running.confirming]
kaleidox> setq sm
StateMachine[purchase.running.confirming]

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

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

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

kaleidox> event-issue 'confirm
Event[confirm]
kaleidox> sm
StateMachine[purchase:delivering]

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

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

kaleidox> event-issue 'delivered
Event[delivered]
kaleidox> sm
StateMachine[purchase:FINAL]

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

reject

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

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

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

kaleidox> statemachine-new 'purchase
StateMachine[purchase.running.confirming]
kaleidox> setq sm
StateMachine[purchase.running.confirming]
kaleidox> event-issue 'reject
Event[reject]
kaleidox> sm
StateMachine[purchase:FINAL]

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

suspendとresume

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

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

kaleidox> statemachine-new 'purchase
StateMachine[purchase.running.confirming]
kaleidox> setq sm
StateMachine[purchase.running.confirming]

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

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

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

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

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

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

kaleidox> statemachine-new 'purchase
StateMachine[purchase.running.confirming]
kaleidox> setq sm
StateMachine[purchase.running.confirming]
kaleidox> event-issue 'confirm
Event[confirm]
kaleidox> sm
StateMachine[purchase.running.delivering]

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

kaleidox> event-issue 'suspend
Event[suspend]
kaleidox> sm
StateMachine[purchase.suspended]

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

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

まとめ

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

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

諸元

Kaleidox
0.3.1

2021年6月30日水曜日

Kaleidox/状態機械 : サブステート

前回は状態機械のモデル定義を行い、そのままKaleidoxで動作させることができることを紹介しました。

前回の状態機械はステートがフラットに並んでいる単純なものでしたが、実際のアプリケーションを作成するにはやや不便なところがあります。

というのは、実際のアプリケーションではキャンセルや保留といった状態遷移が必須となってきますが、フラットなステートの遷移では表現が煩雑になったり、実現が困難になったりという問題があるからです。

この問題を解決する仕組みの一つがサブステートです。

今回はこのサブステートをつかってキャンセル機能を実現します。

モデル

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

  • Cancelイベントを追加
  • 基幹部をサブステートとして、サブステート内の任意のステートでキャンセル可能にした

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

* event
event=[{
    name="confirm"
  },{
    name="reject"
  },{
    name="delivered"
  },{
    name="cancel"
}]
* statemachine
statemachine={
  name="purchase"
  state=[{
    name=init
    transition=[{
      to=running
    }]
  },{
    name=canceled
    transition=[{
      to=FINAL
    }]
  }]
  statemachine=[{
    name="running"
    state=[{
      name=applying
      transition=[{
        to=confirming
      }]
    },{
      name=confirming
      transition=[{
        guard=confirm
        to=confirmed
      },{
        guard=reject
        to=rejected
      }]
    },{
      name=confirmed
      transition=[{
        to=delivering
      }]
    },{
      name=rejected
      transition=[{
        to=FINAL
      }]
    },{
     name=delivering
      transition=[{
        guard=delivered
        to=delivered
      }]
    },{
      name=delivered
      transition=[{
        to=FINAL
      }]
    }]
    transition=[{
      guard=cancel
      to=canceled
    }]
  }]
}

イベント

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

confirm
確認OK
reject
確認却下
delivered
配送済み
cancel
キャンセル

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

状態機械

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

サブステートrunningを作成し、applying, confirming, confirmed, rejected, delivering, deliveredの各ステートをサブステートrunningに移しました。

また、新たにキャンセル状態を示すステートcanceledを作成し、サブステートrunningの任意のステートからの状態遷移ができるようにしました。

実行

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

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

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

正常系

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

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

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

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

kaleidox> statemachine-new 'purchase
StateMachine[purchase.running.confirming]
kaleidox> setq sm
StateMachine[purchase.running.confirming]

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

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

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

kaleidox> event-issue 'confirm
Event[confirm]
kaleidox> sm
StateMachine[purchase:delivering]

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

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

kaleidox> event-issue 'delivered
Event[delivered]
kaleidox> sm
StateMachine[purchase:FINAL]

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

reject

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

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

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

kaleidox> statemachine-new 'purchase
StateMachine[purchase.running.confirming]
kaleidox> setq sm
StateMachine[purchase.running.confirming]
kaleidox> event-issue 'reject
Event[reject]
kaleidox> sm
StateMachine[purchase:FINAL]

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

cancel

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

まずステートconfirmingでcancelイベントを出してみます。以下に示すとおりFINAL状態になりました。状態機械がcancelイベントを受信した結果、ステートcanceledに遷移し、ステートcanceledにはガードを設定していないのでFINAL状態に遷移しています。

kaleidox> statemachine-new 'purchase
StateMachine[purchase.running.confirming]
kaleidox> setq sm
StateMachine[purchase.running.confirming]
kaleidox> event-issue 'cancel
Event[cancel]
kaleidox> sm
StateMachine[purchase:FINAL]

次にステートdeliveringでcancelイベントを出してみます。以下に示すとおりこちらもFINAL状態になりました。

kaleidox> statemachine-new 'purchase
StateMachine[purchase.running.confirming]
kaleidox> setq sm
StateMachine[purchase.running.confirming]
kaleidox> event-issue 'confirm
Event[confirm]
kaleidox> sm
StateMachine[purchase.running.delivering]
kaleidox> event-issue 'cancel
Event[cancel]
kaleidox> sm
StateMachine[purchase:FINAL]

いずれの場合もサブステートrunninigに設定した以下の遷移によって、サブステート内の状態からcanceled状態に遷移することができました。

    transition=[{
      guard=cancel
      to=canceled
    }]

まとめ

サブステートをもった状態機械モデルでキャンセル機能をモデル定義し、Kaleidox上で動作させてみました。

キャンセル機能は実際のアプリケーションでも頻出の機能ですが、実装するのは案外煩雑なので、サブステートを用いたシンプルなモデル定義のみでそのまま動作するのは大きなメリットだと思います。

サブステートを使った別の頻出機能として保留機能があります。次回は状態機械モデルとKaleidoxで保留機能を実現する予定です。

諸元

Kaleidox
0.3.0