これは「コードを書いていて困ったときに、suinがチャットで質問に答えたり相談に乗るsuinのプログラミング相談室(仮)」で頂いた質問と僕の回答の要約です。
質問
イベントソーシングで楽観ロックを実現するにはどうしたら良いでしょうか? 同時に変更がなされたとき、画面に変更が保存できなかった旨を表示したいです。なぜそうしたいかというと、他の人がせっかく作ったデータを知らずのうちに古いデータで上書きするようなことを避けたいからです。
例えば、本文が「X」の集約ルート(Aggregate Root)をAさんが「Y」に、Bさんが「Z」に変更する操作を同時に行ったとします。このとき、Aさんの変更は受付られますが、Bさんには保存する前にAさんが行った変更を確認してもらえるようにしたいです。
suinの回答
イベントソーシングで作られたシステムということは、集約ルートにストリームバージョンがついています。ストリームバージョンは集約ルートに対して操作(Command)があるたびに1ずつ加算されていきます。もし、あらゆる操作において楽観ロックを実現したいのであれば、ストリームバージョンを手がかりにするのも一つの方法です。
ストリームバージョンを活用できないケースも考えられます。ストリームバージョンは集約ルートに対する操作全てで加算されるので、ざっくりとした楽観ロックを手軽に実装するには向いていますが、特定の操作に対してのみ楽観ロックを行いたい場合は、ストリームバージョンでは荒すぎます。
例に、ブログの記事集約ルートを考えてみます。記事にはタイトル、本文、ページビュー数の状態があり、操作はタイトルを変更する、本文を変更する、ページビュー数を+1するがあるとします。
- 記事集約ルート
- 状態
- タイトル : String
- 本文 : String
- ページビュー数 : Int
- 操作
- タイトルを変更する
- 本文を変更する
- ページビュー数を+1する
- 状態
このとき、タイトルや本文では楽観ロックをしたく、ページビュー数を+1する操作は楽観ロックの範囲外にしたい。その場合は、ストリームバージョンでは次のシナリオで意図しない楽観ロックがかかってしまいます。
- ストリームバージョン1の記事について、AさんとBさんが同時に操作しようとしています。
- サイト訪問者のAさんが記事を閲覧する。つまり、記事のページビュー数を+1する。Aさんの操作は受け付けられ、記事のストリームバージョンは2になります。
- 同時にBさんが記事のタイトルを変更する。Bさんの変更はストリームバージョン1に対して行おうとしたので、警告を受け変更は拒絶されます。
ストリームバージョンに頼るとかえって意味のない警告を執筆者に伝えてしまい、ユーザ体験としてはよくありません。執筆者が関心を持っているのは、集約のバージョンがあがったことではなく、共同執筆者の間で相手の創作を意図せず上書きしていないかどうかです。
こうしたケースでは、ストリームバージョンとは別にバージョンをドメインモデルに持つべきでしょう。例えば、記事であれば「版」といった属性を持つようにします。記事のコンテンツに関連する属性が変更されるたびに付随して初版、第二版、第三版と言った具合にバージョンが加算される属性です。
- 記事集約ルート
- 状態
- タイトル : String
- 本文 : String
- ページビュー数 : Int
- 版: Int
- 操作
- タイトルを変更する … この操作は版を+1する
- 本文を変更する … この操作は版を+1する
- ページビュー数を+1する … この操作は版に影響しない
- 状態