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

2021年5月31日月曜日

Kaleidox/状態機械

オブジェクト指向分析設計(OOAD)によるソフトウェア開発を進めるための鍵となるのが状態機械です。

Javaなどのオブジェクト指向言語では状態機械は直接サポートはされておらず、状態機械を実現するためのクラスライブラリもよいものがありません。ワークフローエンジンといったものはありますが、仕掛けが大きすぎるので状態機械を実装するために手軽に使用できる感じではないでしょう。

このため、モデリング段階で状態機械を定義しても、プログラミング段階では手組みになってしまうので、モデリングすることの価値がそれほど高くないという状況になっています。また、状態機械をプログラミングで実装するのは案外大変です。

このこともあってか、開発現場では状態機械はほとんど使われていない状況だと思います。

OOADは静的構造モデル(クラス図)と動的モデル(状態機械)が両輪で、その2つのモデルと要求仕様をシナリオ分析であるユースケースモデルで連携させるという仕掛けになっています。

両輪の一つである動的モデルが使用されていないので、当然静的構造モデルと動的モデル、要求仕様を連携させるユースケースモデルも実力を発揮できない状況にあるといえます。

結果として、OOADではなくクラス図を用いたデータ中心設計になってしまっているのが現状ではないでしょうか。

クラウドアプリケーションでは、今後ますますイベント駆動型の応用が重要になってくると予想されますが、この核となるモデルが状態機械であり、状態機械がうまく活用されていない現状はかなり危機的な状況ではないかと思います。

SimpleModelingではこの問題に対処するため、モデルコンパイラSimpleModelerとアクション言語Kaleidoxで状態機械をサポートします。モデリング段階で状態機械モデルを定義することで、状態機械のプログラミングをせずに、アプリケーションでそのまま状態機械を利用できることになります。

今回はKaleidoxで状態機械を使用してみます。

モデル

Kaleidoxで状態機械を動かしてみましょう。

まず状態機械に使用するイベントを定義した後、状態機械のモデルを定義します。

商品販売を単純化したモデルにしてみました。本来は、注文毎に状態機械を持つことになりますが、ここでは簡単化のためグローバルな状態機械をグローバルなイベントで駆動することにします。

* event
event=[{
    name="confirm"
  },{
    name="reject"
  },{
    name="delivered"
}]
* statemachine
statemachine={
  name="purchase"
  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
    }]
  }]
}

イベント

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

confirm
確認OK
reject
確認却下
delivered
配送済み

状態機械

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

実行

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

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

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

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

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

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

confirmイベント発行の結果、状態機械purchaseの状態はdeliveringになりました。

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

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

confirmイベント発行の結果、状態機械purchaseの状態はdeliveringになりました。

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

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

状態機械の遷移の履歴をhistoryメソッドで取得することができます。

kaleidox> sm.history
((Event[system.control.init] . State[applying]) (Event[system.control.go-ahead] . State[confirming]) (Event[confirm] . State[confirmed]) (Event[system.control.go-ahead] . State[delivering]) (Event[delivered] . State[delivered]) (Event[system.control.go-ahead] . State[final]))

historyメソッドで取得した結果を元に状態遷移の経緯をまとめると以下になります。

イベント遷移先
system.control.initapplying
system.control.go-aheadconfirming
confirmconfirmed
system.control.go-aheaddelivering
delivereddelivered
system.control.go-aheadfinal

まず、初期状態から自動的に先頭の状態であるapplyingに遷移します。イベントは初期化を示す疑似イベントであるsystem.control.initです。

状態applyingと状態confirming間にはガードがないので状態間を自動的に遷移します。イベントは自動遷移を示す疑似イベントであるsystem.control.go-aheadです。状態機械モデルですが、アクティビティモデル的な振舞いになります。

ここでconfirming状態となります。

次にevent-issue関数でconfirmイベントを発行すると、confirming状態からconfirmed状態に遷移します。

状態confirmedと状態delivering間にはガードがないので状態間を自動的に遷移します。

ここでdelivering状態となります。

次にevent-issue関数でdeliveredイベントを発行すると、delivering状態からdelivered状態に遷移します。

状態deliveredと最終状態final間にはガードがないので状態間を自動的に遷移し、状態機械purchaseは終了します。

reject

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

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

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

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

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

まとめ

Kaleidoxで状態機械モデルの実行を行うことができるようになりました。

今回は最もシンプルなモデルですが、このレベルのモデルでもプログラミングレスで使用できるとかなり便利だと思います。

状態機械モデルはもっと複雑な記述が可能で、より広範囲な応用に適用することができます。次回以降、状態機械モデルの色々な使い方について見ていく予定です。

諸元

Kaleidox
0.2.1