🎫

なぜ「Event Sourcing」を選択したのか 〜事実に基づくことの重要性

に公開

はじめに

なぜチームで Event Sourcing というアーキテクチャパターンを選択したのか、その背景と実際に導入してみての知見を共有させていただきます。以前イベントソーシング・CQRS勉強会のLTで発表した内容に少し補足説明を追加して記事としてまとめています。

対象読者

  • Event Sourcingに興味がある人
  • Event Sourcingで具体的にどんなメリデメがあるか知りたい人
  • 設計やモデリングが難しいと感じている人

結論

  • 「事実」に基づくことで、より壊れづらいプロダクトを実現しやすくなる
  • 現実に存在する「事実」駆動の開発とすることで、設計がしやすくなり共通認識を保持しやすくなる
  • 銀の弾丸ではない。使い方を誤ればうまく機能しないこともある

Event Sourcingとは?

Event Sourcing(イベントソーシング)は、システムの状態を管理する方法の一つです。従来のシステムでは、現在の「状態」(例:銀行口座の残高)だけを保存していましたが、Event Sourcingでは「何が起きたか」という全ての「事実(イベント)」を時系列で記録していきます。

例えば、銀行口座の例で考えてみましょう:

従来の方法:

  • 残高:100,000円(現在の状態のみ保存)

Event Sourcing:

  • 口座開設:100,000円入金
  • コーヒーを購入:300円引き落とし
  • 給料振込:250,000円入金
  • 家賃引き落とし:80,000円引き落とし

Event Sourcingでは、これらの「事実」を全て記録し、現在の状態(残高)はこれらの「事実」を順番に計算することで導き出します。

メリットとして以下の点が挙げられます。

  1. 全ての変更履歴が残るため、「なぜこの状態になったのか」が明確である
  2. システムの状態を任意の時点に戻して確認できる
  3. 新しい要件が出てきた時に、過去の事実を使って新機能を実装できる

デメリットとしては実装が複雑になる可能性がありますが、「事実」に基づいた堅牢なシステム設計が可能になります。

プロダクト特性と課題

ビットキーではスマートロックなどの非IoTの物理デバイスを扱っており、スマートフォンなどとBLE(Bluetooth Low Energy)通信することでデバイスを操作するユースケースが多くあります。

ただしBLE通信で実際のデバイスに影響する操作において、ソフトウェアでDBを扱うような感覚でトランザクションを張ることはできませんし、場合によってはBLEの通信が安定しない、途中でアプリキルしてしまった…などの操作はしたけど結果はわからない…といった状況となりえます。

例えば、スマートロックに対して施錠のコマンドをBLE通信で投げたけれど、結果を受け取る前にアプリキルしてしまったため、実際に施錠できたのか、できていないのか、確実な判断ができないことがある…といったことがありえます。

特に、スマートロックというプロダクトを扱っているため、デバイスの状況をただしく追従しきれていないと「入れるべきなのに入れない」や「入れないべきなのに入れちゃう」という事業リスクがつながりえてしまいます。

そのため、特定の操作が「成功したのか」「失敗したのか」「実施したけど結果はわからないのか」を適切に管理する必要があります。

スマートフォンでスマートロックを解錠する際の図
スマートフォンでスマートロックを解錠する際の図

NFCカードでスマートロックを解錠する際の図
NFCカードでスマートロックを解錠する際の図

NFCカードで解錠できるように登録する際の図
NFCカードで解錠できるように登録する際の図

NFCカードを登録する処理に失敗して解錠できるか/できないか確定できない場合の図
NFCカードを登録する処理に失敗して解錠できるか/できないか確定できない場合の図

なぜ State 管理では難しかったのか?

従来の State 管理アプローチでは、以下のような課題があると感じていました。

  1. 人によって認識の仕方が異なる
  2. 事業のフェーズやドメイン理解が深まることによって、あるべき定義が変わりやすい
  3. 状態の根拠が「ソースコード」になり、変更されやすい

例えば、スマートロックの「設置状況」を「処理開始」「処理中」「処理完了」といった状態で管理しようとした場合に…

  • 「処理中」という状態が実際には何を意味するのか不明確
  • 「設置状況」自体の定義があいまい
  • 処理状況がどんな状態を表現するかがソースコードに依存
  • 必要な処理(実装)が変わることで、ステータスと実際の状態が乖離する可能性

つまり「状態」は現実には存在しない「概念」であり、そのため移ろいやすく、壊れやすい性質を持っていると考えています。そのため「設置のためのBLE通信を実施した」…などの「事実」を確実に記録し、事実に基づいて現在の「状態」をより確実に再現できる「Event Sourcing」に着目しました。

Event Sourcing を導入してみて良かったこと

実際にEvent Sourcingを導入してみて良かったこととしては以下のようなことが挙げられます。

  1. 「事実が記録されていること」の安心感
  2. 「事実が記録されていること」による追加対応時の容易さ
  3. 新たな「概念」を生み出す必要がなく、「事実」の整理に注力しやすい
  4. 実在する「事実」に基づくため、ステークホルダー内で共通認識を取りやすい

「事実が記録されていること」の安心感

これは気持ちの部分が大きいですが「事実」を記録していることで、何かあった際の状況(不測の事態を)をより適切に把握することができる…という安心感がありました。「状態」を管理している場合、なぜその「状態」となっているのか根拠がソースコードとなってしまいがちです。ソースコードが変更する、その「状態」となりえる条件が変わったりして、確証をえづらい…といったこともありえます。しかしながら「事実」を記録することで、この懸念をかなり低減することができたと感じます。

「事実が記録されていること」による追加対応時の容易さ

「状態」で管理する場合、実装時に必要とされるものに絞って定義されることが多く、そのため後からこの定義を変更したり追加したりする必要があるシチュエーションも多くなりやすいと思います。「事実」を記録する場合、必要な「状態」を構成する「事実」を記録するため、認識しやすいように丸められて限定的に定義された「状態」より、「事実」の方がカバーできる範囲が広く、追加対応が必要となった際にも大きな修正なく対応できることが多かったように感じます。

新たな「概念」を生み出す必要がなく、「事実」の整理に注力しやすい

「状態」で管理する場合、どうしても現実世界には存在しないが管理上だからそれっぽいものを追加して対応する…といったことが多くありました。またこの営みはとても難しく、プロダクトを脆くしてしまう行為であるとも感じています。今までの経験上においても、物事を抽象的かつ具体的に幅広く考えその上で適切な「状態」を定義する…といったことがとても難しく、設計がなかなかうまく進まない要因であると感じています。(もちろんこれが面白い部分でもあるのですが)

一方で「Event Sourcing」の場合には、現実世界に存在する「事実」に基づくため考えやすく、また整理に注力することができ、結果として設計をスムーズに進めることができたと感じています。

実在する「事実」に基づくため、ステークホルダー内で共通認識を取りやすい

「状態」は現実世界にない「概念」となることが多く、そのためステークホルダー内で認識を合わせることが難しいと考えています。一方で「事実」は現実世界に存在していることであり認識が合いやすく「事実」の単位を元にして会話とすることでコミュニケーションをスムーズに進めることができたように感じます。

良くなかったこと / 改善しきれなかったこと

実際にEvent Sourcingを導入してみて、あまり良くなかったこと / 改善しきれなかったこととしては以下のようなことが挙げられます。

  1. 調査時は記録した「事実」よりもログを見た方が早いことが多い
  2. 実装ミスにより事実が正しく記録されないケースがある
  3. 事実から現在の状態を構築する処理は複雑化しやすい
  4. ドメイン知識/制約の集約方法はまだ試行錯誤中

調査時は記録した「事実」よりもログを見た方が早いこともある

当初は調査する際などは「事実」をみて確認して現在の状態を正しく把握することができるのでは?…と思っていましたが、「特定の事実が記録される」がおおよそ「特定のAPIが実装される」と同義になることが多いので、ログ見て状況確認することの方が早かったことが意外に多かったです。

※ もちろん状況によってはDBに記録されている「事実」をもとに調査することで調査しやすくなることもあります。

実装ミスにより事実が正しく記録されないケースがある

これは当然ですが、そもそも実装がミスしている(実装に不備がある)と記録されるべき事実が記録されません…。そのため記録されている「事実」を確認しても、「あれ?おかしいな」となって、実は実装に不備があった…みたいなこともありました…。

なので「事実」が記録されているから安心!…といっても、実装不備があったら元も子もないので注意は必要です。

事実から現在の状態を構築する処理は複雑化しやすい

まだEvent Sourcingに慣れていないから…かもしれませんが、「事実」から「状態」を再現する処理はどうしても複雑化しやすい印象を持っています。特に管理する「事実」が多くなればなるほど「状態」x「事実」の組み合わせも増えロジックが複雑化しやすい印象を持っています。

ドメイン知識 / 制約の集約方法はまだ試行錯誤中

Event Sourcingを使いつつ、どこにドメイン知識を集約させるべきか…は試行錯誤中です…。

Eventを生成する部分とEventからStateを再現する部分などで求められるルールをどこでどう管理するのが良いかは、まだ確証を持てる段階ではないです。

その他感じたこと

  1. CQRS やデータストアを CQ で分離しなくても活用できた
  2. 事実から状態を構築する処理のパフォーマンスについて今の所、大きな問題はなし
  3. 関数型プログラミングとの相性が良さそう
  4. 設計のプロセスを簡易化できる可能性あり

まとめ

Event Sourcing の導入により、状態管理の課題に対する一定の解決策を見出すことができました。
特に「事実」に基づいた設計ができることで、より堅牢なシステム設計が可能になりました。

一方で、実装の複雑さやドメイン知識の集約方法など、まだ改善の余地がある部分も明確になってきています。
これらの課題に対しては、今後も継続的に改善を進めていく予定です。

Bitkey Developers

Discussion