- 開発
READ MORE
原著者の許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。
参考: The Read Model · Nick Chamberlain
イベントソーシング(Event Sourcing)は一定のメリットを得られる優れた技法ですが、1つ大きな制約があります。現在のステートの概念を簡単に得られないため、たとえば出荷可能数が10個以下の全製品が欲しいという問い合わせの回答を得るのが簡単ではありません。
ID=1
のProduct
としてイベントのProduct-1
ストリームを読み出し、それを用いてこの製品の現在のステートを再構成して、その製品の出荷可能数が10個より少ないかどうかという回答を得ることも一応可能ですが、すべての製品について回答を得るには、すべてのProduct-*
ストリームを列挙し、全製品の保存済みドメインイベントをすべて処理する必要があります。この操作には膨大な時間とコストがかかるでしょう。
普段の業務で目にする次のユースケースはいずれも困難が少し増すでしょう。
他にいくらでも考えられます。
どうしてこうなってしまうのでしょうか。
その理由は、エンティティ(Entity)や集約(Aggregate)がイベントソーシングされると、オブジェクトのリポジトリへの問い合わせ方法が1つに限定されてしまうためです。その方法とはすなわちfind_by_id
です。
他の場所(他のエンティティなど)で得たこのid
が、その場所への参照(またはUIからの参照、APIからの参照、リクエストからの参照)を持つことは理解できるので、次のように書けます。
id = params[:id]
product = ProductRepository.find_by_id(id)
このリポジトリは、自分が読み取るべきイベントのストリーム(Product-1
など)を認識し、それらのイベントがProduct
のインスタンスに適用されることで1つの製品の現在のステートが再構成されます。以上。
では上述のユースケースをすべて解決する方法があるとしたらそれは何だかおわかりでしょうか?それがRead Modelです。
e-コマースアプリの製品リストを顧客に表示し、AddToBasket
やProduct
などのコマンドをイベントソーシングしたいのであれば、Product
のRead Modelが1つ必要になります。このRead Modelは個別の要件次第で、ElasticsearchやSQLなどどんなデータベースにも配置できます。
あるRead Modelを手順に沿って構成するにはどうしたらよいでしょうか。
例を1つご紹介します。
ProductRegistered
イベントは、ProductList
というActiveRecordベースのRead Modelへの新しい要素の追加を発生させます。
ProductList.create!(
id: event.data[:product_id],
name: event.data[:name],
price: BigDecimal.new(event.data[:price]),
)
ProductPriceChanged
イベントは、リスト上の価格の更新を発生させます。
ProductList.
find_by!(id: event.data[:product_id]).
update_attributes!(
price: BigDecimal.new(event.data[:price]),
)
他にいくらでも考えられます。
そして価格の高い製品上位10種を表示したい場合は、このProductList
というRead Modelに基づいてアプリの読み出し側で行えばよいのです。
ProductList.order("price DESC").limit(10)
アプリの書き込み側では、イベントソーシングされているProduct
クラスの書き込みで変更をトラッキングしつつビジネスルールを保護します。書き込み側は、ProductRegistered
やProductPriceChanged
を公開する責務を持ちます。
イベントハンドラやRead Modelやイベントソーシングについてもっと詳しく知りたい方は、私たちの近刊「Domain-Driven Rails ebook」をぜひ手にお取りください。