はじめに
CQRSで書き込み側と読み込み側を分けた際に、読み込み側を最新に保つ方法について考察してみます。
参考サイト
- The unfinished guide to Event-Sourcing, CQRS and DDD [Unreleased, RAW] + notes about Uber
- Choosing an architecture
- Event Sourcing - Projections
- Some thoughts on using CQRS without Event Sourcing
- Tackling Complexity in CQRS
考察の元とするCQRSの図
Tackling Complexity in CQRSから引用
下図のProjection Enginesの部分についての考察です。
Pub/Subパターンで最新に保つ
Projection EnginesがPub/Sub機能を持つなにがしかを購読しといて、発行されたら読み込み側に投影します。
書き込み側にPub/Sub機能があればそれを使用しますし、
RDBMSのようにPub/Sub機能がなければGoogle Cloud Pub/Sub、Amazon SQS、Azure Service BusのようなPub/Subサービスを利用、もしくは自前で用意する必要があります。
EventStore用のDBであるEventStoreにはPub/Sub機能がついているのでお勧めです。
書き込み側をイベントベースで実装している場合は、読み込み側に必要なイベントだけを購読して投影することができます。
書き込み側を状態ベース(State Based Projections)で実装している場合は、読み込み側に必要な集約だけを購読して投影することができます。
Pub/Subパターンだけだとメッセージが発行されなかった場合、読み込み側が最新にならないので別の方法でも監視する必要があります。
Pollingで最新に保つ
Projection Enginesが書き込み側を定期的にチェックして、必要な情報が更新されていたら読み込み側に投影します。
更新されているか否かの目印になる情報は
- 書き込み側をイベントベースで実装している場合は、イベント毎にインクリメントされるEventNumber(EventStoreだと)
- 書き込み側を状態ベース(State Based Projections)で実装している場合は、SQL Serverで言うところのrowversionやその行の更新日時列
が使えるかと思います。
Pollingだけだと書き込み側の情報量にもよりますが、読み込み側への反映が遅いかもしれません。
二系統用意することで最新に保つ
読み込み側を主、副の二系統用意します。
主が動作中に、副を破棄して
- 書き込み側をイベントベースで実装している場合は、全てのイベントをリプレイすることで最新の状態にします
- 書き込み側を状態ベース(State Based Projections)で実装している場合は、必要な集約の情報を取得して最新の状態にします
主と副を切り替えます。
これを繰り返すことで読み込み側を最新に保てそうです。
可用性を高めるために二系統用意していても、いざというときに動作するかは不安なので常日頃から使うような運用にすれば安心できる気がします。
まとめ
二系統用意する話は無理やりな気はしてます。
他に有効な投影方法があればご教示ください。
CQRSで書き込み側と読み込み側を分割するメリットデメリットでいつも悩みます。
(結局はアプリの規模感次第だとは思いますけど。書き込みと読み込みが同一でもCQRS自体は出来ますし)
メリット
- 読み込み側が別にDBじゃなくても良い(ファイルとかBLOBに書き出して常に上書きするとか)
- 読み込み側のスケーラビティが向上する。
- マイクロサービスへの第一歩
デメリット
- Projection Enginesという投影するための仕組みを用意する必要がある
- 結果整合性を許容しなければならない