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で扱う方法について考えてみます。