ふと思いついたので書いた。
IDDD 本では Observer Pattern を使った基盤をドメインコードが利用しているが、これに似た機能を JavaScript の Generator/AsyncGenerator で再現してドメインイベントをより独立にする試み。
サンプルコードはこちらから。
IDDD 本では Observer Pattern を使っていた。ドメインコードを呼び出す前に、アプリケーション内であらかじめ subscribe しておく。呼ばれたドメインコードは、内でドメインイベントを DomainEventPublisher
で publish する。subscriber はそれを元に、別のアプリケーション操作を実行したりミドルウェアにメッセージを送信したりする。
翻って、JavaScript には Generator や AsyncGenerator がある。処理を停止させたり復帰させたりでき、その時に値をやり取りもできる。無限列挙したり非同期処理をしたりするのがよくある使い方と思われる。また、この機能により yield
で途中経過を得ることもできる。(ジェネレータ自体はほかの言語にもあるが、return がなかったりする。その時は列挙という側面が強くなる。)
1 | function* fibonacci() { |
1 | async function* longProcess(initial) { |
return
は従来通りドメインコードのメソッドの戻り値とし、yield
を途中経過や列挙でなく、ドメインイベントを表す、という表現が可能ではないかと考えた。
この利点は、基盤的なツールへの関心を一つ減らせることがある。ドメインがプログラミング上のみで必要になる外部コードに依存するよりも良いと考えている。副次的に、TypeScript の場合、Generator<DomeinEvent,TReturn>
と(勝手に)表現でき、ドメインイベントを外部に型付けすることができる(例: AsyncGenerator<NewUserCreated | UserActivated, User>
)。
1 | public static *createNew() { |
アプリケーションサービスではジェネレータの停止や復帰は必要のない側面なのでそれを無視(バイパス)して呼び出したい。こういう場所では yield*
が使える。
サンプルコードでは表現できていないが、 Decorator のような仕組みを利用して、ドメインイベント (yield
したもの)をアプリケーションサービスの代わりに何かのインフラにハンドルさせる。(Decorator の型をうまいこと設定できると良いのだが…)
こうすることで、ドメインイベントに関心を持たないコントローラやイベントリスナ層からは普通のメソッドとしてアプリケーションサービスを呼べる。
1 | public async *createUser(firstName: string, lastName: string) { |
1 | const user = await this.#userService.createUser(firstName, lastName); |
これらの処理の基盤となる部分はコードを見ていただくとして、大体このようになっている。
Decorate でやりたいところは Proxy で実現している。途中で出てくる eventReceiver
が Generator を「消費」してドメインイベントを MessageQueue に流す。(Decorator でうまいこと利用元で EventHandled 前を隠したいが難しい)
1 | <T extends {}>(a: T): EventHandled<T> => new Proxy(a, { |
1 | type EventHandled<T> = { |
Observer Pattern と比べ、この表現方法では(基盤以外は)関心が各々より閉じていると思う。トランザクションが失敗したらイベントは無かったことにする、というケースも同様にできるはず。言語によってはインフラ層で型タイプやトレイとでおもむろにインフラをドメインコードに追加することもできるのだろう。