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

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

2021年4月30日金曜日

Kaleidox/Service

前回はソフトウェア開発の中軸モデル要素であるコンポーネントのメタモデルについて検討しました。

今回はコンポーネントの派生モデル要素であるサービスについて取り上げます。

サービスは外部に対して公開されたコンポーネントという位置付けなので、外部から直接実行できるコンポーネントということができます。

Kaleidoxでサービスモデルを直接実行することができるので、サービスモデルを使ってコンポーネントの特性を見ていきたい思います。

今回はサービスのAPIを定義して、Kaleidox上で実行してみます。

サービスモデル

足し算と引き算を行うサービスcalcを以下のように定義しました。この定義に沿って、サービスモデル(コンポーネントモデル)の定義について説明します。

* Service
** calc
*** Operation
**** plus
***** In
| 名前 | 型  | 多重度 |
|------+-----+--------|
| arg1 | int |      1 |
| arg2 | int |      1 |
***** Out
| 型  | 多重度 |
|-----+--------|
| int |      1 |
***** Method
+ arg1 arg2
**** minus
***** In
| 名前 | 型  | 多重度 |
|------+-----+--------|
| arg1 | int |      1 |
| arg2 | int |      1 |
***** Out
| 型  | 多重度 |
|-----+--------|
| int |      1 |
***** Method
****** js
arg1 - arg2

全体構造

サービスモデルの全体構造は以下のようになっています。

* Service
** サービス名
*** Operation
**** オペレーション名
***** In
***** Out
***** Method

Service節でサービスモデルの定義を行います。

Service節の直下にはサービス名を節名としたサービスの定義が並びます。

サービス名節の直下にはOperation節があります。

Operation節の直下にはオペレーション名を節名としたオペレーションの定義が並びます。

オペレーションの定義は次節で説明します。

拡張予定

前回、コンポーネントの部品として検討した部品の中でAPIに該当する部品をサポートしています。

以下の部品は現在は未実装です。

  • SPI
  • Bus
  • StateMachine
  • Entity
  • Configuration
  • Rule
  • Log
  • Metrics
  • Audit trails

これらのモデル要素はkaleidox上で順次実現していく予定です。

オペレーション

plusオペレーションの定義は以下になります。

*** Operation
**** plus
***** In
| 名前 | 型  | 多重度 |
|------+-----+--------|
| arg1 | int |      1 |
| arg2 | int |      1 |
***** Out
| 型  | 多重度 |
|-----+--------|
| int |      1 |
***** Method
+ arg1 arg2

オペレーションの定義は以下の3つです。

In
入力定義
Out
出力定義
Method
Method定義

In節には入力情報を表形式で記述します。表では入力パラメタの名前、型、多重度を定義します。

Out節には出力情報を表敬式で記述します。表では出力パラメタの型、多重度を記述します。

Method節にはオペレーションの実装であるメソッドの定義を行います。

メソッド

Method節では、オペレーションの実装であるメソッドの定義を行います。

モデルが仕様のみで実装まで定義しない場合はMethod節を省略することができます。

ただし、Method節を省略した場合はKaleidoxでの直接実行はできません。手動で作成したプログラムを、SimpleModelerのプログラム自動生成と組み合わせて使用する形になります。

Kaleidox

オペレーションの実装をKaleidoxスクリプトで定義する場合は、Method節に直接Kaleidoxスクリプトを記述します。

サービスモデルのplusオペレーションの下には、以下のMethod節を定義しています。

***** Method
+ arg1 arg2

calcサービスのplusオペレーションが呼び出されると、Kalidoxスクリプト「+ arg1 arg2」が実行され、その結果が返却されます。

JavaScript

Method節の下にjs節が配置されている場合は、js節内をMethod実現のためのJavaScriptスクリプトとして使用します。

***** Method
****** js
arg1 - arg2

calcサービスのminusオペレーションが呼び出されると、JavaScriptスクリプト「arg1 - arg2」が実行され、その結果が返却されます。

Java ScriptEngine

Method節の下には、JavaScript以外にもJava ScriptEngineでサポートしているスクリプト言語を使用することができます。この場合は、Java ScriptEngineの認識する言語名を節名で指定します。

拡張予定

メソッドの実現方法として、外部のJava/Scalaプログラムを利用可能にする予定です。以下の形式が候補となっています。

  • JavaBeans (JAR)
  • Servlet (WAR)
  • OSGi

実行

それでは、Kaleidoxでサービスモデルを直接実行してみます。

前述のサービスモデルをinit.kldに格納してkaleidoxが認識できるようにします。

コマンド

まず、コマンドラインで実行してみます。

$ klaidox calc.plus 1 2
3

サービス名とオペレーション名を「.」で繋いだ文字列でオペレーション名を指定します。calcサービスのplusオペレーションなので「calc.plus」となります。

引数には「1」と「2」を指定した結果、計算結果の3が返されました。

REPL

次はKaleidoxのREPLで実行してみます。

REPLのプロンプトから「calc.plus 1 2」と実行すると、計算結果の3が返されました。

kaleidox> calc.plus 1 2
3

トレース

calc.plusの実行時のコールツリーは、REPLコマンド「:trace」で表示することができます。先程の「calc.plus 1 2」の場合は以下になります。

kaleidox> :trace
INVOKE(302)[Script] (calc.plus 1 2) => 3
  INVOKE(104)[Function(Operation):service-script-kaleidox(calc.plus)] (calc.plus 1 2) => 3
    INVOKE(60)[Lambda:calc.plus] (1 2) => 3
      INVOKE(53)[Script] (+ arg1 arg2) => 3
        INVOKE(23)[Function(Eval):+] (+ arg1 arg2) => 3
          INVOKE(15)[Variable:arg1] arg1 => 1
          INVOKE(1)[Variable:arg2] arg2 => 2

Web REST

KaleidoxをWebで実行するためのコンテナとしてpbjを開発しました。

  • https://github.com/asami/pbj

pbjを実行するとWebサーバーが立ち上がります。ポート番号は8080です。

$ pbj

curlコマンドでpbjサーバーを呼び出します。

パス名はサービス名「calc」、オペレーション名「plus」をつなげた「/calc/plus」です。引数にはarg1、arg2にそれぞれ「1」、「2」を指定します。

$ curl "http://localhost:8080/calc/plus?arg1=1&arg2=2"
{"status":200,"data":3}

実行の結果、JSONの形式で実行結果が返されました。プロパティdataに計算結果の「3」が入っています。

jqコマンドを併用してJSONを整形すると、以下のようになります。

$ curl "http://localhost:8080/calc/plus?arg1=1&arg2=2" | jq .
{
  "status": 200,
  "data": 3
}

トレース

Webサーバ上で実行したときのコールツリーは「_trace」パラメタを「true」にすると取得することができます。

$ curl "http://localhost:8080/calc/plus?arg1=1&arg2=2&_trace=true"  | jq
{
  "status": 200,
  "data": 3,
  "trace": {
    "kind": "root",
    "trace": [
      {
        "kind": "invoke",
        "timestamp": "1619340084576",
        "label": "Script",
        "enter": "(calc.plus :arg1 1 :arg2 2)",
        "leave": "3",
        "lap": 8,
        "trace": [
          {
            "kind": "invoke",
            "timestamp": "1619340084577",
            "label": "Function(Operation):service-script-kaleidox(calc.plus)",
            "enter": "(calc.plus :arg1 1 :arg2 2)",
            "leave": "3",
            "lap": 6,
            "trace": [
              {
                "kind": "invoke",
                "timestamp": "1619340084578",
                "label": "Lambda:calc.plus",
                "enter": "(1 2)",
                "leave": "3",
                "lap": 5,
                "trace": [
                  {
                    "kind": "invoke",
                    "timestamp": "1619340084581",
                    "label": "Script",
                    "enter": "(+ arg1 arg2)",
                    "leave": "3",
                    "lap": 2,
                    "trace": [
                      {
                        "kind": "invoke",
                        "timestamp": "1619340084582",
                        "label": "Function(Eval):+",
                        "enter": "(+ arg1 arg2)",
                        "leave": "3",
                        "lap": 1,
                        "trace": [
                          {
                            "kind": "invoke",
                            "timestamp": "1619340084582",
                            "label": "Variable:arg1",
                            "enter": "arg1",
                            "leave": "1",
                            "lap": 0
                          },
                          {
                            "kind": "invoke",
                            "timestamp": "1619340084582",
                            "label": "Variable:arg2",
                            "enter": "arg2",
                            "leave": "2",
                            "lap": 1
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
}

まとめ

サービスのモデルを作成し、Kaleidoxで直接実行してみました。今回はAPIのみでしたが、コンポーネントの回に検討したBusなどのメカニズムとそのモデルについても順次実現していく予定です。

同じモデルがコマンドライン、REPL、Webのそれぞれでそのまま実行できることを確認できました。モデルを直接実行できれば、プログラミングの工数削減、仕様と実装のズレの低減といった大きなメリットがあります。

また、Kaleidoxのようなモデル実行プラットフォームが提供するアプリケーションフレームワーク機能により、ドメインモデルではスコープ外の非機能要件についても自動的に利用可能になるメリットもあります。

今回はKaleidoxが提供する機能であるトレース機能を紹介しました。実運用では、本番環境でしか発生しない障害などが発生することが多々ありますが、トレース機能を用いると効率的に障害調査を行うことができることを期待できます。

諸元

Kaleidox
0.1.16
Pbj
0.0.2

2021年3月31日水曜日

SimpleModeling/Component

SimpleModelingでは、コンポーネントをソフトウェア開発を行う上での中軸となるモデル要素と捉えています。今回はコンポーネントのメタモデルを検討します。

コンポーネントはオブジェクトやサービス、システムと同様にメタモデル上は分類子(classifier)を親としており分類子の構造を引き継いでいます。コンポーネントのメタモデルの検討を行うのはコンポーネントのメタモデルを軸に、上方向ではサービスやシステム、下方向ではオブジェクトのメタモデルを考える上でのベースラインにする狙いもあります。

Componentの構造

SimpleModelingではComponentの構造を以下の図に示すメタモデルをベースにメタモデルの検討を行っています。

以下でそれぞれのモデル要素についてみていきます。

API

本案では以下の3つのAPIを定義しました。

  • Service API
  • Management API
  • Resource API

APIではOperation群を定義します。

Operationの実装はプログラム言語のメソッドが基本で、必要に応じてRESTを含むRPCとして実装します。

Service API

コンポーネント利用者向けのAPIです。

Webなどフロントエンド・アプリケーションやその延長線上にある他コンポーネントからの利用を想定しています。

Management API

コンポーネントの提供する機能やコンポーネントが管理するデータの運用管理をするためのAPIです。

管理コンソールやその延長線上にある他コンポーネントからの利用を想定しています。

Resource API

リソース操作用のAPIです。

リソース操作用のAPIは個々のOperationの責務は前述のService API、Management APIに分類されるため分類上の重複がありますが、モデルからの自動生成の対象なので図では独立した形で記述しています。

SPI

SPI(Service Provider Interface)はComponentに対してサービスを影響するコンポーネントやオブジェクトを接続するためのインタフェースです。

たとえば、ECシステムで外部の決済サービスを利用する場合、決済サービスを呼び出しすためのドライバを作成しSPIを通じてコンポーネントと接続します。

SPIを経由して接続する外付けのプログラムでComponentの機能を拡張していくことができます。このため、SPIはComponentの拡張点(Extension Point)ということができます。

Bus

クラウド・アプリケーションを開発する上で重要な構成要素がBusです。クラウド・アプリケーションはクラウド内で発生する各種イベントに即応するためイベント駆動型の振舞いが極めて重要です。単純にAPIが提供するOperationを呼び出すだけの処理ではまかないきれなくなっているわけです。

このクラウドワイドなイベント駆動処理の基盤となるのがBusです。

SimpleModelingではBusを第一級の構成要素としてメタモデル上での扱いを明確にし、SimpleModelerやKaleidoxといったツールでもBusの使用を前提とした機能セットを提供する予定です。

Busには以下の示す特性があります。

  • イベント駆動
  • 透過性
  • 疎結合
  • 非同期
  • スケーラビリティ
  • 可用性(availablity)

これらの特性はクラウド・アプリケーションを構築する上で重要な役割を担います。

Busも用途に応じていくつかの種類が考えられます。図では以下のBusを示しています。

  • Service Bus
  • Inter-Component Bus
  • Async Bus
  • Sync Bus

それぞれ見ていきましょう。

Service Bus

ネットワークワイドでコンポーネント間の同報通知を行うバスです。

Apache KafkaやAWS SNS、AWS Kinesis、AWS EventBridgeといったサービスの利用を想定しています。

基本のBusとなります。Service Busを使うことでComponent内のObject間通信もインターネットワイドなサービス間の通信も透過的に行うことができるので、理想的にはService Busのみを使用するのが望ましいといえます。

ただ、現実のアプリケーションではさまざまなニーズが存在するので、それらのニーズをすくい取るためにメタモデルでは用途に応じたBusを定義しています。

Inter-Component Bus

同一プロセス内でComponentを接続するバスです。

通常のOperationに対してBusを使う上での問題点として以下のものがあります。

  • 非同期になってしまう。
  • データベース・トランザクションの制御から外れてしまう。

非同期の問題点はエラー情報をその場ではクライアントに伝えることができなくなることです。このため大掛かりなエラー処理の仕掛けを作る必要が出てくるのでアプリケーション開発の複雑度が一気に増加します。

いうまでもなくデータベース・トランザクションから外れてしまう問題もエラー処理の複雑度が大きく増加します。

もちろんBusの提供するイベント駆動、透過性、疎結合の特性は魅力的です。

そこで同一プロセス内で動くComponentやObject間の協業では、本来同期型&トランザクションは可能なはずなので、イベント駆動、透過性、疎結合の特性を持ちつつ、同期型&トランザクションのサポートを行うBusとしてInter-Component Busを用意しました。

Async Bus と Sync Bus

Component内部で使用するBusです。このメタモデルでは以下の2つに分けています。

  • Async Bus
  • Sync Bus

いずれもComponent内に閉じていることにより効率的な実行を想定しています。

Async Busはシグナルの同報処理を非同期で行うバスです。Service Busと同じ特性を持ちます。

Sync Busははシグナルの同報処理を同期で行うバスです。Inter-Component Busと同じ特性を持ちます。

Async BusとSync Busは、プラットフォームによってはそれぞれService Bus、Inter-Component Busを直接使用する方式でよいかもしれません。

Object

Component内には用途ごとに各種オブジェクトが存在することになります。

図では以下のObjectを図示しています。

  • Service API, Management API, Resource APIを司るObject
  • SPIを司るObject
  • Bus(Async Bus, Sync Bus)に接続したObject
  • Entity
  • 図の中央にあり各Objectを中継しているObject

StateMachine

Objectのメタモデルを定義する上で重要と考えているのがState Machineです。本来オブジェクト指向の動的モデルはState Machineを使ってモデル化するのが筋ですが、既存のプログラミング言語に該当する機能がないこともあり、一般のエンタープライズやWebアプリケーションでは現状はほとんど利用されることがないと思います。

しかし、イベント駆動が重要な位置を占めるクラウド・アプリケーションではStateMachineが動的モデルの要のモデルです。このためSimpleModeingではStateMachineをメタモデルで定義し、SimpleModelerやKaleidoxといったツールで直接使用できるようにする予定です。

Entity

データベースに格納して管理するPersistent ObjectをSimpleModelingではEntityと呼びます。

SimpleModelingではEntityはデータベース内にあるため直接操作はせず、一旦メモリ内のObjectに転記し、転記したObjectで状態を更新した上ででデータベース上のEntityに書き戻すと、という操作モデルを取ります。

メモリ内のObjectに転記する場合に、必ずしもデータベース上のEntityのクローンである必要はなく、アプリケーションのモデルに応じたメモリ内のObjectに必要な情報を転記することになります。

EntityはStateMachineを持つことができます。実装的には、Entityをメモリ上に展開したObjectにStateMachineを定義し、そのStateMachineが動作するという形になります。

設定

Componentの振舞いをComponentの想定した範囲で変えるための変化点(Variation Point)のメカニズムとして以下の2つを用意しました。

  • Configuration
  • Rule

Configuration

ロケールやタイムゾーンといったアプリケーションの国際化情報やタイムアウト時間など、Componentの基本的な振舞いに対するパラメタ設定を行います。。

格納場所は配備時にファイルを添付したり、外部のリポジトリに格納といった手段が考えられます。

Rule

アプリケーションがモデル化したRuleを、アプリケーションのユースケースに合わせて設定します。

消費税率といったパラメタ的なものから、エキスパート型AIの推論ルールのようなものまでを想定しています。

記録

運用上の様々な要件からコンポーネントの動作記録を取っておく必要があります。

  • Log
  • Metrics
  • Audit trail

Log

動作ログです。

アプリケーションの各種メトリックスの計算、デバッグ情報、性能改善の基礎データ、課金情報として用いることを想定しています。

上記の用途に必要な情報を採取できるよう、ログとして採取する情報についても分析・設計が必要です。

Audit trail

セキュリティの監査記録です。セキュリティ監査のための基本情報としてユーザーがComponentをどのように使ったか、どのリソースを参照・更新したかの記録を取ります。

Logよりも厳密な情報を採取する必要があるので、モデルを分けています。

Metrics

コンポーネントの振る舞いをデータ化したメトリックス情報です。オペレーションの呼び出し回数やキャッシュのヒット率といった情報を記述します。

性能改善の基礎データや課金情報として用いることを想定しています。

まとめ

今回はSimpleModelingで重要視しているモデル要素であるComponentについてメタモデルを検討しました。

次回はこのメタモデルをベースに、ComponentをKaleidoxで扱う方法について考えてみます。

2021年2月28日日曜日

Kaleidox/データ型

アプリケーション開発をモデル駆動で行う時に、見過ごしがちですが案外重要なのがデータ型の扱いです。

データ型の標準化

一般的にプログラミング言語では数値や文字列を中心に必要最小限のデータ型を定義し、アプリケーションで必要なデータ型はクラスライブラリで定義して使用するという建付けになっています。

UMLを使用したモデリングも事情は同じで、アプリケーションで必要なデータ型を定義して使用することになります。プログラミング言語と違ってクラスライブラリ的な共通モジュールはあまり提供されていないので、データ型の問題はより深刻といえます。

まだモデル駆動開発という観点からは、モデリング側のデータ型とプログラミング言語側のデータ型の連携も問題となります。

このためアプリケーション開発を始めるにあたっては、アプリケーション開発者側で以下の定義が必要になります。

  • モデリングでのデータ型
  • プログラミング言語でのデータ型
  • モデリングのデータ型とプログラミング言語のデータ型のマッピング

エンタープライズアプリケーションは最初に小さなWebシステムを作るだけであっても、次々と追加のアプリケーションを開発し、それらを連携させて動作させることになりがちです。このような場合、アプリケーションごとに独自のデータ型を定義指定使っていては連携に支障が出てくる可能性が高いでしょう。

リテラル問題

データ型の標準化を行う際には、データ型の文字列表現であるリテラルの標準化も重要な問題です。

モデリング、プログラミング、データ入出力の各フェーズでデータの表現形式が異なるのは非効率ですし、トラブルの元になります。

移入、移出するデータフォーマットの統一という観点からもリテラルの標準化、共通化が重要です。

国際化

アプリケーション開発で、分かっていても開発期間や工数の関係から後回しになりがちなのが国際化(I18N, L10N)です。

直近では使わないかもしれないけれど、海外展開する時には必要になる、という優先度の見えにくい機能なのでアプリケーションを普通に開発すると自然と国際化対応できているという形が理想的です。

ここで鍵になるのがデータ型です。時間や文字列などで国際化を意識したデータ型を用いることで、特別な意識なしに開発したアプリケーションを国際化対応にすることが可能になります。

SimpleModelingでの解決策

データ型の問題に対しては、上流のモデリングからプログラミングまで共通のデータ型を共通化するのが有力な作戦です。

本Blogで進めているSimpleModelingでは以下のアプローチで対応しています。

  • Action言語Kaleidoxで、アプリケーション向けのデータ型を定義する
  • データ型はできるだけ文字列リテラルで記述可能にする
  • SimpleModelerでのモデリングでKaleidoxで定義したデータ型を使えるようにする
  • 移入、移出するデータフォーマットもデータ型のリテラルを扱えるようにする

実行例

データ型をリテラルで指定する例です。

準備

準備として以下のinit.kldを用意します。

schema区画でスキーマpersonを定義しています。

data-store区画でデータベースのテーブルpersonに移入するデータを定義しています。

* env

db.default.driver="org.h2.Driver"
db.default.url="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false"

* schema

** person

| 特性 | 名前          | 型       | 多重度 | ラベル  |
|------+---------------+----------+--------+---------|
| 属性 | id            | int      | 1      | User ID |
| 属性 | name          | string   | 1      | 名前    |
| 属性 | city          | string   | ?      | 市      |
| 属性 | age           | int      | ?      | 年齢    |
| 属性 | registered_at | datetime | ?      | 登録日  |

* data-store

** person

100,Taro,Yokohama,28,2021-01-10T00:00:00
200,Hanako,Kawasaki,24,2021-02-20T00:00:00

データ定義の際に「2021-01-10T00:00:00」という形で日時をリテラルで記述しています。これはローカル日時を記述するデータ型localdatetime型のデータとなります。

データベース移入時には、実行環境のタイムゾーンを補完したdatetime型のデータに自動的にマッピングされます。

使用例

上記のinit.kldに初期化の結果、インメモリデータベースのpersonテーブルにデータが2件移入されています。

store-select関数で取得すると以下になります。

kaleidox> store-select 'person
Table[5x2]
kaleidox> :show
Table[5x2]
┏━━━┯━━━━━━┯━━━━━━━━┯━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃id │name  │city    │age│registered_at            ┃
┣━━━┿━━━━━━┿━━━━━━━━┿━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃100│Taro  │Yokohama│35 │2021-01-10T00:00:00+09:00┃
┃200│Hanako│Kawasaki│24 │2021-02-20T00:00:00+09:00┃
┗━━━┷━━━━━━┷━━━━━━━━┷━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━┛

このテーブルに対して30代の会員の検索を行います。

「30代」を30以上、39以下と考えるとinterval型のリテラル「30~39」で記述することができます。

このリテラルを検索条件に指定すると、目的通りの結果が出力されました。

kaleidox> store-select 'person age=30~39
Table[5x1]
kaleidox> :show
Table[5x1]
┏━━━┯━━━━┯━━━━━━━━┯━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃id │name│city    │age│registered_at            ┃
┣━━━┿━━━━┿━━━━━━━━┿━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃100│Taro│Yokohama│35 │2021-01-10T00:00:00+09:00┃
┗━━━┷━━━━┷━━━━━━━━┷━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━┛

次に2021年2月1日以降の入会者の検索はを行います。

「2021-02-01~」はlocaldatetimeinterval型として解釈されます。

データベース検索時にはタイムゾーンの補完が必要ですが、これはKaleidoxの実行コンテキストによって補完されます。

kaleidox> store-select 'regestered_at=2021-02-01~
Table[5x1]
kaleidox> :show
Table[5x1]
┏━━━┯━━━━━━┯━━━━━━━━┯━━━┯━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃id │name  │city    │age│registered_at            ┃
┣━━━┿━━━━━━┿━━━━━━━━┿━━━┿━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃200│Hanako│Kawasaki│24 │2021-02-20T00:00:00+09:00┃
┗━━━┷━━━━━━┷━━━━━━━━┷━━━┷━━━━━━━━━━━━━━━━━━━━━━━━━┛

このリテラルを検索条件に指定すると、目的通りの結果が出力されました。

RESTサーバーでの指定

将来KaleidoxをRESTサーバー化した場合は以下のような指定方法になる想定です。

curl https://example.com/person?age=30~39

リテラル「30~39」という表現でデータ型intervalのデータを、モデリング段階の表記と同じ表記で記述できています。

SimpleModelerとの関係

init.kldのschema区画でデータのスキーマを定義していました。このスキーマでデータ型を指定していますが、このデータ型はSimpleModelerと共通のものになります。

SimpleModelerでのモデル定義からKaleidoxでのスキーマ定義へ、データ型がシームレスに引き継がれます。

まとめ

モデル駆動開発の重要な要素技術であるデータ型に対して、モデリングからプログラミング、データ記述までデータ型、リテラルを共通化するアプローチについて説明しました。

Kaleidoxを中心にSimpleModelerと共通のデータ型、リテラルを使用することでモデリングからプログラミング、運用までシームレスにデータ型を連携させることができます。

標準で共通化できるデータ型を十分に用意できれば、非常に強力なアプローチになると思います。アプリケーション開発に必要なデータ型については必要に応じて拡張していく予定です。

諸元

  • Kaleidox : 0.1.14

データ型一覧

現時点でKaleidoxで定義しているデータ型一覧です。

データ型説明
booleanBool値
shortShort型整数
intInt型整数
longLong型整数
floatFloat型浮動小数点数
doubleDouble型浮動小数点数
integer整数
decimal10進固定小数点数
rational有理数
complex複素数
rangeレンジ
intervalインターバル
string文字列
binaryバイナリ
i18nstring国際化文字列
i18ntemplate国際化テンプレート
regex正規表現
clobCLOB
blobBLOB
slipスリップ(伝票)
schemaスキーマ
queryクエリ
recordレコード
tableテーブル
vectorベクター
matrixマトリックス
dataframeデータフレーム
lxsvLXSV
urlURL
urnURN
uriURI
expression
scriptスクリプト
xmlXML
htmlHTML
xpathXPath
xslXSL
pugPUG
jsonJSON
datetime日時(タイムゾーン付き)
localdatetimeローカル日時
localdateローカル日
localtimeローカル時
montyday月日
datetimeinterval日時インターバル
localdatetimeintervalローカル日時インターバル
duration経過時間
periodピリオド
money金額
percentパーセント
unit単位

2021年1月31日日曜日

SimpleModeling 2021

Modegramming Styleブログではモデリングとプログラミングの一体化を指向したModegrammingというコンセプトを提唱しており、その実現のための技術体系としてSimpleModelingを整備しています。

整備活動

2019年5月に以下の記事でSimpleModelingによるモデル駆動開発の活動をリブートしました。

整備活動の内容はプロダクトの開発とメタモデルの整備に分けられます。

プロダクト

SimpleModelingでは以下の4つのプロダクトの開発を進めています。いずれもオープンソースです。

また商用製品としてボクがCTOをやっているEverforth社でクラウドプラットフォームであるPrefer Cloud Platform(以下PCP)を開発しています。

Smartdox

SmartDoxは以前開発していたSmartDocの後継となる汎用の文書処理系です。

SmartDocはXMLをメタ言語としていましたが、SmartDoxは独自のプレインテキスト(MarkDownとEmacs orgから派生)をメタ言語としています。

SimpleModelingではLiterate Modeilingのアプローチを取っているので、モデルの中の自然言語記述部分の取扱いを重要視しています。

この部分の中核技術となるのがSmartDoxというわけです。

後述するSimpleModelerやKaleidoxはSmartDoxをメタ言語として使用し、この上に固有のメタモデルを構築しています。

SimpleModeler

SimpleModelerはSimpleModelのモデルからプログラムなどの成果物を生成するモデルコンパイラです。

SmartDoxをメタ言語として記述し、SimpleModelメタモデルのインスタンスとして記述したモデル部の情報から各種成果物を生成します。

以前開発していたXMLスキーマコンパイラである Relaxer によるプログラムの自動生成が極めて有効だったことを受けて、プログラムの自動生成のターゲットをオブジェクトモデルに広げるために新規に開発したのがSimpleModelerです。

SimpleModelerは基本機能は動作済みで後述のPCPの開発にも適用しています。

現在、後述するようにSimpleModelingのメタモデルの刷新を行っています。SimpleModelerは、この新メタモデルへの対応を行う予定です。

Kaleidox

Kaleidoxはオブジェクトモデリングとプログラミングをシームレスに連携させることを目的とするアクション言語です。

Kaleidoxの開発を進めながら以下の記事を書いてきました。

Kaleidoxはかなりよい感じで開発が進んでおり、プログラム開発のテスト環境としてボクが日常的に使用するツールになっています。

今年は、SimpleModeler統合を行うことで、モデル駆動開発のアクション言語として本格的に活用できるようにする予定です。

Arcadia

クラウド・アプリケーションでは、サーバーサイドのRESTサービスを中心に、Web、スマートフォン(iOS, Android)、デスクトップの3種類のUIフロントエンドの構成になります。

この中でWebフロントエンドのためのWebフレームワークとして開発中なのがArcadiaです。

モデルコンパイラが扱うオブジェクトモデルとWebアプリケーションの間はインピーダンスミスマッチが大きく、その間を埋めるミドルウェアの存在が重要になってきます。

Webアプリケーション独特の振る舞いや特性を吸収して、モデル駆動開発したアプリケーションロジックとWebでの実装をスムースに連携させることを目的としています。

Webアプリケーションも、大きくWebサーバー上での実行を中心としてWebページの遷移で振る舞いを構成する伝統的な方式(以下Webページ方式)とJavaScriptフレームワークを中心にGUIアプリケーション的な振る舞いを行う方式(以下JavaScriptフレームワーク方式)の2方式に分けることができ、現実的には両者の式の折衷案の方式(以下Web/JavaScript折衷方式)が使用されるケースが多いでしょう。

ArcaidaではWebページ方式をサポートするとともに、JavaScriptフレームワーク方式で必要になるJavaScriptベースのWebフレームワークの実行コンテナとしての機能を提供し、合わせてWeb/JavaScript折衷方式に対応していくというアプローチを取っています。

SimpleModelerから生成されるコードに対して、HTMLページのデザインを行えばアプリケーションが完成するエコシステムの構築を目指しています。

Arcaidaは基本部は開発済みです。Kaleidox、SimpleModelerを組み込んでモデル駆動対応を達成できた後に公開する予定です。

Prefer Cloud Platform

Prefer Cloud Platform(PCP)はEverforth社から商用のクラウド・プラットフォームとしてリリースされています。

PCPはAPIベースのクラウド・プラットフォームとして各種機能を提供するとともに、運用環境なども提供しており、多くのプロダクトで活用して頂いています。

PCPは開発当初からモデル駆動開発を実現する上で、クラウドプラットフォームの存在が重要となる、という認識のもと開発を進めてきており、モデル駆動開発とのシームレスな連携を行うための仕掛けを内包しています。

SimpleModeingは、対象となるプラットフォームに依存しないニュートラルな技術体系を指向していますが、高機能のサービスを提供しているプラットフォームの能力を引き出すことも重要機能としています。

両方向からのアプローチにより、SimpleModelingではPCPの提供する高度な各種機能を活用したアプリケーション開発も可能になる予定です。

SimpleModeingのオープンソースプロダクトのみのモデル駆動開発でも十分に有効ですが、PCPを併用すると運用やセキュリティも含めたより高度なクラウド・アプリケーションの開発・運用が可能になるという形を目指しています。

メタモデル

SimpleModelingでは以下の書籍で解説したモデルをベースとしています。

これらのモデルはボクがwakhokでモデリングを教えていた時に教科書としても使用できるように執筆したものです。教育用に使えると同時に、モデル駆動開発のメタモデルとしての利用も目的の一つにしています。

ただし、このメタモデルを設計した2008年当時とは状況が大きく変わっています。

クラウド・プラットフォームがシステム開発の日用品として普及したことで、クラウド・アプリケーションを簡単に構築できるようになりました。この効果を考える補助線として、大規模エンタープライズシステムのミドルウェア群が超低価格(数億円→数千円)で利用できるようになったと考えるとよいと思います。

これらのミドルウェアを活用すると、高度なアプリケーションが簡単に構築できるわけですが、逆にミドルウェアを使いこなすスキルがいと宝の持ち腐れになってしまいます。

またモデリング段階でもミドルウェアの活用を前提としたモデリングが必要になります。

このような状況に対応するために、SimpleModelのメタモデルを再設計することにしました。

最新のアプリケーション開発事情を踏まえた検討を行うこととして、まずそのスコープについて考えました。

この後、一連の以下の記事で検討を進めています。

一通り切り口の整理ができたので、SimpleModelerに取り込む準備をしているところです。

まとめ

SmartDox, SimpleModeler, Kaleidox, Arcadiaとモデル駆動開発を構成する各種ツールの開発を粛々と進めてきましたが、それなりに動くところまで来ることができました。

当面は、従前通りツールの開発状況の紹介や、メタモデルの検討を続ける予定ですが、今年の後半ぐらいに具体的な応用につながる内容の記事を書くことできればと考えています。