TL;DR
内容
Modeling Workflows as Pipelines の一節である。
入力としてのコマンド
ワークフローの本当の入力はコマンドであり、単なるドメインオブジェクトではない。コマンドは、DTO からデシリアライズされた状態で渡される。
注文確定ワークフローでは、コマンドを PlaceOrder(注文を確定する)とする。このコマンドは、ワークフローがリクエストを処理するのに必要な情報をすべて含んでいなければならない。その中心はドメインオブジェクトの UnvalidatedOrder である。
type UnvalidatedOrder =
{ OrderId: string
CustomerInfo: UnvalidateCustomerInfo
ShippingAddress: UnvalidatedAddress }今回は記録や監査のために、誰がコマンドを作ったかとタイムスタンプというメタデータも追跡したい。そこで、次のように型を定義する。
type PlaceOrder =
{ OrderForm: UnvalidatedOrder
TimeStamp: DateTime
UserId: string }ジェネリクスによる共通構造の共有
各コマンドは、タイムスタンプやユーザ ID のような共通のフィールドを持つ。オブジェクト指向プログラミングなら、共通のフィールドを持つ基底クラスを定義し、各コマンドがそれを継承すれば解決できる。関数型プログラミングでは、ジェネリクスを使って解決する。
type Command<'data> = {
Data: 'data
TimeStamp: DateTime
UserId: string
}
type PlacedOrderCommand = Command<UnvalidatedOrder>複数のコマンドを 1 つの型にまとめる
境界づけられたコンテキストのすべてのコマンドが、同じ入力チャネル(メッセージキュー)で送られることもある。この場合、コマンドをシリアライズできる 1 つのデータ構造にまとめる必要がある。複数のコマンドから 1 つを選ぶので、選択型を使う。
type OrderTakingCommand =
| Place of PlaceOrder
| Change of ChangeOrder
| Cancel of CancelOrderこの選択型は DTO に変換され、入力チャネル上でシリアライズとデシリアライズが行われる。そのために、オニオンアーキテクチャのインフラストラクチャ層に、コマンドを振り分けるルーティング(ディスパッチ)の入力ステージを追加する。
キーワード
自分の理解
ワークフローの入力がドメインオブジェクトそのものではなく、コマンドだという点が大事だと思った。また、コマンドにメタデータを持たせるかどうかは仕様次第で、必須ではない。