🐈

Event Sourcing 完全に理解した

2022/08/22に公開

はじめに

  • Event Sourcing を何となく理解するための記事です。
  • CRUD を用いた State Sourcing と比較しながら説明していきます。

State Sourcing vs Event Sourcing

まず、State Sourcing と Event Soucing がそれぞれどのようなものかを挙げていきます。

State Sourcing

  • 状態を中心に考える設計手法
  • 現在の状態を永続化するのが一般的
  • CRUD(Create / Read / Update / Delete) を使って状態を変化させることが多い

Event Sourcing

  • アプリケーションの状態に対するすべての変化を一連のイベントで表現する設計手法
    • e.g.) 銀行の入出金履歴
  • イベントとは、過去に起こった出来事である
  • イベントは動詞の過去形で表す
    • e.g.) ItemAdded, Ordered, OrderCanceled, Paid
  • 任意の時点の状態はイベントをたどることで復元可能
  • イベントは不変であり、イベントの追加だけで状態を表現する
    • 削除 -> 「削除された出来事」をイベントして追加
    • 更新 ->「変更された出来事」をイベントとして追加

Event Sourcing の意義

State Sourcing と Event Sourcing の違いが分かったところで、Event Sourcing の意義を考えていきます。

要求に対して柔軟に対応できる

Event Sourcing では現在の状態だけでなく、ある時点の状態やそこに至るまでの過程を観測できます。
具体的にECサイトでの注文を例にして考えていきましょう。

太郎さんは本Aを注文しようとしました。注文前に水が切れていたことを思い出し、水をカートに追加しました。その後、何気なく他の本を調べていたら本Bが目に留まったので、本Bもカートに追加しました。カートを再度確認したところで、水は2本に増やし、やっぱり本Bはやめておこうとカートから削除しました。

State Sourcing の場合

CRUD を使った State Sourcing で表すとこうなります。
state_sourcing_cart

Event Sourcing の場合

Event Sourcing で表すとこうなります。
event_sourcing_cart

比較

一番右の図を見ると、State Sourcing は最終的にカートに入っている商品の一覧が分かるのに対し、Event Sourcing はそれに加えてカートの変遷が分かります。
関心の対象が結果のみである場合、State Sourcing で表す情報だけで事足ります。
ただ、例えば Event Sourcing では

  • 本Aと本Bが同じジャンルで似たような本ならば、レビューや価格等を比較して本Aのほうが優れていると判断した?
  • 本Aと本Bが全く異なるジャンルならば、金銭的な理由で購入をやめたのかもしれない。
    ただ、興味は持っていそうなので、おすすめリストに表示させたら次回以降購入してくれそう

といった推測ができるわけです。
このようにイベントのリストを分析の材料として使うことができます。

イベントのリストからは、「どうなっているのか」という現在の状態が分かるだけでなく、「どうなっていたか」、「どのように変わっていったか」という過去の状態や過程もわかり、データの扱い方の幅も広がります。それだけ要求に合わせてデータを活用できるというわけですね。

業務モデルの表現が直感的になる

デリバリーサービスを例にして State Sourcing, Event Sourcing それぞれで表現した図を見ていきます。

State Sourcing の場合

state_sourcing_delivery

Event Sourcing の場合

event_sourcing_delivery

比較

State Sourcing の方は「作成」と「更新」という操作の結果状態が変化しています。
CRUDを用いる場合、「作成」、「参照」、「更新」、「削除」という操作をすることでしか状態を変化させることができませんので、表現として乏しくなります。
一方、Event Sourcing の方はイベントによって状態を導出しています。
「注文した品物ができた」というイベントが発生したので、注文ステータスを「準備中」から「配達中」に変更するというのは直感的で、業務をそのまま表現していますよね。
このように、何か状態を変化させる上での意図や背景をモデルに落とし込むのに Event Sourcing は使えるわけです。

スケールしやすさ

システムを構築するにあたり、サービス間でのデータ連携・やり取りは自ずと発生します。
特にマイクロサービスアーキテクチャを採用している場合は、それ自身でシステムが成り立つわけではなく、何かしらの連携を前提としているかと思います。
ここでまたECサイトの注文を例に State Sourcing と Event Sourcing で設計した場合のアーキテクチャを比較してみましょう。(あくまで一例であり、必ずこの通りになるわけではありません)

State Sourcing の例

データ連携_state_sourcing

Event Sourcing の例

データ連携_event_sourcing

比較

State Sourcing の場合、更新・削除を検知しなくてはなりません。
検知の方法は主に以下2つの手法が考えられます。

  • 注文サービスから注文データを必要とするサービスに送信する
  • 注文データを必要とするサービスがデータストアをポーリングする

しかしながら、いずれの方法も注文サービスとそのデータストアに大きく依存し、負荷をかけることになります。そのため、注文データを必要とする新たなサービスが増えるにつれて注文サービスへの依存と負荷が高まり、どこかで限界が来るでしょう。
しかし、Event Sourcing の場合は追加のみであること、イベントが不変であることから他サービスへの通知やコピーが容易です。
通知が容易なのは、例えばどの商品が何時に注文 / キャンセルされたかという中身まで気にする必要はなく、イベントが追加されたかどうかを気にするだけで良いからですね。
Event Sourcing では、サービスの中間に Message Broker というものを用意して、イベントをキューイングできます。そのため、サービス間を疎結合に保つことができます。

ただし、ここで注意点があります。結果整合性を受け入れる必要があります。イベントが発生してから各サービスに伝搬するまでにタイムラグがあるので、数百ミリ秒~1秒程度の遅延を許容することが求められます。

Event Sourcing × CQRS

ECサイトの注文履歴では、ユーザーごとの注文情報と商品のリストが確認できますよね。
例えば、Aさんの注文番号「00001」の商品Aと商品Bのカテゴリと価格を知りたい場合はどのような処理になるでしょうか?
下図のような依存関係はスケールしないことをお伝えしました。
order_history_not_scaling

そこで Event Sourcing を用いるのですが、各サービスから集めたイベントはどのように扱えば良いでしょうか?
order_history_event_sourcing

ここで CQRS の出番です。
CQRS とは、コマンドクエリ責務分離原則(Command and Query Responsibility Segregation)のことで、簡単に言うと Command(Write) と Query(Read) のモデルを分離するパターンのことです。
注文履歴サービスに Query 用のデータストアを作り、注文履歴サービスが扱いやすいようなモデルにしてしまえば良いのです。
order_history_event_sourcing_cqrs

おわりに

Event Sourcing を使う意義について、State Soucing と比較しながら書きました。
何となく Event Sourcing が有用だということはわかっていただけたかと思いますが、「じゃあどうやって実践するの」という点が深堀できていないので、別の記事で書きたいと思います。

Discussion