読者です 読者をやめる 読者になる 読者になる

DDDのドメインイベントについて勉強

DomainEvent

ドメインエキスパートが「…した『とき』に、こうなる。」とか。
何かの状態変更をトリガーにして、別の処理をしたい場合にドメインイベントを使う。
イベントオブジェクトがイベントの情報を運んでくれる。

source data & processing data

Domain Event

ドメインイベントはイミュータブルな「source data」とミュータブルな「processing data」で構成される。
source dataってのは「イベントが発生した」という部分の情報で、これは一旦作成されたら変更されることはないよね。
processing dataは「システムがこのイベントをどうしたか」って情報ね。分けてもいいかもね。

Aggregate & DomainEvent

DDDでは、1つのユースケース(トランザクション)で触ってイイ集約は1個だけ。なんだよね。
だけど、DomainEventは同じトランザクションで触っていいみたい。

結局CRUDなアプリだと、複数の集約を1つのトランザクションで更新することで整合性を保っていたけど。
DDDは、このDomainEventに整合性を運ばせてるのねというのが今の僕の印象。結果整合性。

Implementations

みんなはどんな実装をしてるのかな?

テストのためにイベントを溜める派

A better domain events pattern | Jimmy Bogard's Blog

イベントをリアルタイムに発火するとテストしづらいから、まずは溜めておく。
そしたら溜まってることをテストすればいい。で、commit前にfireするんだ。

ふむ。テストの観点はあると良さげね。溜めとくのもいいけど、テスト用のサブスクライバを作ってもいい気がする。
データストアへのcommitの後にメッセージングインフラでメッセージ投げたいかなぁと思ってるので、どうするのがいいかなー?と思いつつ次行こう。

テストはサブスクライバでやる派

Domain Events – Salvation

この人は、テスト用のサブスクライバを使えばいいよって派だな。僕もこっちのが好きかな。
でもイベントハンドラトランザクション管理するのは嫌だな。

DomainEventsクラスのstaticメソッドでイベントを発行する。サブスクライバはスレッドローカルで保持。か。
なるほどなるほど。

ふむ。メッセージングインフラでメッセージ投げたいってのも、それのトランザクション管理すればいっか。
DBへのコミットが終わった後にメッセージキューのコミットをする感じにできればいいかなー。と思いつつ次。

予期せぬエラー時にもイベント発行するんかな?

例えば競合とかでトランザクションロールバックするときには、「失敗をあらわすイベント」を発行するのかな?と思ったけど。
あれだな。それはドメインの興味の外っぽいな。単純にアプリ側の興味でアラートメールでも飛ばせばいいか。

ドメインイベントに含まれる情報

ドメイン駆動設計のリファレンス本 | GuildWorks Blog

また、ドメインイベントは、通常「イベントが発生したタイムスタンプ」「イベントに関連するエンティティの識別子」を含みます。また、それ以外にも「イベントがシステムに投入されたタイムスタンプ」「イベントをシステムに投入した人物の識別子」も含むこともあり、これらのプロパティのサブセットが、例えば分散システムにおけるドメインイベントの識別子に使われる、といったことにも触れています。

なるほど。

ドメインイベントの名前

ドメインの状態が変更したことをきっかけに発火するので、過去形にするのが自然ね。
OrderCommittedとか、SprintClosedとか。

イベントを返そう派

Don't publish Domain Events, return them! - Jayway

ふーむ。最初の「イベントを溜める派」とちょっと似てるかな。テスト視点というよりは、コントロール視点っぽい。
どんなイベントが発行されたか、と、イベントの発行タイミングをアプリケーションサービス側がコントロールしたいってことかな。

どうだろう?僕は、そのコントロールをしたくないからドメインイベントとして発行すると思っているので、このやり方は好きじゃないな。
僕の前提は、これはしない→「同じスレッドでイベントを受けとって別の処理」、なのでその前提がこの人のコンテキストと違うのかも。

non-blocking

Using Domain Events

CustomerChangedPasswordイベントを受け取ってメールをカスタマーに送る、という処理なんだけど。

It's worth noting that your handlers will be called on the same thread as the events were thrown. So these actions will be blocking by default. In a lot of cases (such as the example above) it would be best to put a message on a bus/broker for work to be done elsewhere, or at least make the call asyncronous to negate the blocking.

うんうん。そうよね。僕もそう思う。で、bus/brokerをどう実装すればいいかなー。

あと、この人はサブスクライバが同じスコープで動作するのを避けたければ
「イベントを溜める派」のを参考にすると良いよ。って書いてる。

「イベントを溜める派」は、ドメインの処理中にイベントのハンドリング処理が実行されるのを避ける意味もあるのね。
なる。

EventBus実装のサンプル

ASP.NET Boilerplate - Web Application Framework | EventBus & Domain Events

ん。そういえば、JavaでEventBus的なライブラリありそうだな。探してみよっと。

guava!

Google's Guava Libraries で EventBus - sugarlife's blog

EventBusExplained - guava-libraries - Guava's event bus utility, explained. - Guava: Google Core Libraries for Java 1.6+ - Google Project Hosting

そうか。guavaさんか。

からのCamel!

Apache Camel: Guava EventBus

あぁ、そうですか。ありますか。

Think about implementation

イメージでは、何かイベントが発生した場合に、そのイベントによってしたい処理って複数あるときもあると思うんよね。
だから例えば顧客名の変更をしたときを考えてみると。

  1. CustomerNameChanged イベント発生。イベントオブジェクトをDBに保存する。メッセージキューに積む。まだどっちもコミットされてない。
  2. メインのユースケース処理が終わるときにDBをコミット。メッセージキューもコミット。
  3. そのメッセージをCamelで受け取る。
  4. 案1: そのメッセージに対する複数のサブスクライバをCamelに登録(できるかどうか知らんけど)しておいて、それぞれが処理を始める。
  5. 案2: そのメッセージを受け取って、複数のイベントを発行する。それぞれのイベントを受け取ってそれぞれの処理が始まる。

案2の方がいいかな。イベント一つに対して、そのイベントを処理する人が1人だけいる感じになるので、プロセスを追いやすそう。

うむ。今日はこんなところか。面白かった。