TL;DR
- 異なる境界づけられたコンテキストの間は、メッセージ(ドメインイベント)を使って非同期にやり取りする。
- このときの整合性は結果整合性であり、整合性が取れるのはある程度時間が経ってからである。
内容
境界をまたぐ更新の難しさ
「注文が確定すると、それに対応する請求書が必ず作成される」という要件を考える。請求書の発行は「受注」ドメインではなく「請求」ドメインの仕事である。2 つは別々の境界づけられたコンテキストに分かれているので、境界を越えて相手のオブジェクトを直接操作できない。
素朴に考えると、請求コンテキストに公開 API を用意して、次のように処理することになる。
Ask billing context to create invoice
if successfully created:
create order is order-taking contextしかし、どちらかの更新が失敗した場合を考慮しなければならず、複雑になる。別々のシステム間で更新を同期させる方法として2 フェーズコミットがあるが、本書はそれが必要になることはないとする。現実の世界でも、サブシステムが 1 つの段階を終えるのを待ってから次の段階に移ることはない(スターバックスは2フェーズコミットを使わない)。
メッセージによる非同期の調整
代わりに、メッセージ(イベント)を使って非同期に調整する。まれにうまくいかないこともあるが、まれなエラーに対処するコストは、すべてを同期させるコストよりはるかに小さいことが多い。たとえば、請求書の作成をその場で要求せず、請求ドメインにメッセージを送って、残りの注文処理を続ける。
メッセージが失われた場合の対応には、次の 3 つがある。
- 何もしない。顧客は無料で商品を手に入れ、企業はそのコストを負担する。コストが小さいなら、これが適切な解決策になる。
- メッセージが失われたことを検知して再送する。受信側と送信側のデータを比較し、一致しなければエラーとする。
- 前のアクションを取り消す、またはエラーを修正する補償アクションを作る。注文をキャンセルして商品の返送を求めたり、返金したりする。
整合性が求められる場合は、2 か 3 を選ぶ必要がある。このとき整合性が取れるのは、ある程度時間が経ってからになる。これを結果整合性と呼ぶ。
結果整合性の例
商品の価格が変わったときに、まだ発送されていない注文をすべて更新するケースを考える。すぐに整合性を取る必要があるなら、商品レコードを変更するたびに、影響を受けるすべての注文も更新しなければならず、時間がかかる。
すぐに整合性を取る必要がなければ、「価格が変更された」(PriceChanged)ドメインイベントを作る。このイベントが「変更済みの価格で注文を更新する」(UpdateOrderWithChangePrice)コマンドのきっかけになる。このコマンドは、商品の価格が変わってからしばらくして処理される。
キーワード
自分の理解
補償アクションはイベントソーシングと関係があるのではないかと思っているが、まだ確かめていない。