CQRSとEventSourcingの基本

## はじめに
エアークローゼットのエンジニアアインです。
この記事はエアークローゼット Advent Calendar 2023 6日目の記事になります。

https://qiita.com/advent-calendar/2023/air-closet

## CQRS
​
CQS (Command Query Separation) 設計パターンは、メソッドを 2 つの主要なグループに分割することを目的としています。
​
1. Command (コマンド): オブジェクトのデータを変更して何も返さないか、メタデータのみを返すグループです。
​
2. Query (クエリ): 情報を返しますが、オブジェクトのデータは変更しないグループです。
​
それに基づいて、実際のメソッドは、クエリとコマンドの両方の役割を担うことはありません。Javascript の典型的な例を見てみましょう:
​
- `push`メソッドはstackに新たな要素を追加する`コマンド`です。
- `top`メソッドはstackの一番上の要素の値を取得する`クエリ`です。
- `pop`メソッドはstackの最上位から要素を削除して、削除されたばかりの要素の情報を返すCQS原則に違反するメソッドです。
​
したがって、CQSは「読み取り」と「書き込み」という2つの操作を明確に分割する。クエリを平行に実行すれば重要な問題が発生することが全くなくて、一方でコマンドを平行に実行すればデータが不整合になる可能性が全然あると思ってます。だからこそ、「読み取り」と「書き込み」という2つの操作を明確に分割することは実際に非常に意味があります。
​
実際にはHTTPを活用してCQRS実施することは全然できます。
​
- `コマンド`に対しては「POST」メソッドで。
- `クエリ`に対しては「GET」メソッドで。
​
![268427522-a4ced43b-d6b2-4d85-8386-144d8bc2850e.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135669/390c6537-6a19-caad-f2bd-03a1e43cc84c.png)

​
さらに進んで、1つのDBから2つの別個のDB（「読み取り用DB」と「書き込み用DB」）に分割します。
​
- 「読み取り用DB」は読み取り速度のために非正規化する可能性が全然あります。
- 「書き込み用DB」は正規化が必要です。
​
ただし、これら2つのDB間のデータの「一貫性」を確保するメカニズムが必要です。
​
![268427970-6fc501b4-7a13-4f9c-80c6-8248b6d4ded6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135669/14dfafa9-1dbc-64f7-3e1b-21aed673413c.png)
​
上の画像を見るとイベント駆動でDB間のデータの「一貫性」が確かに担保できる。
​
## CQRS活用してAPIを開発する
​
CQRS活用してAPIを開発する時にroutesを`POST`と`GET`分けるだけではなく、コマンドが**何もデータを返さないこと**も確保が必要そうです。データを返せば **meta-data**の程度まで返します。
​
もう一つの問題は「クエリを実行しないとコマンドがちゃんと実行されたかどうかを全然わかりません」。
​
"Third APIs" (例: Events API)利用して`Web socket`または`HTTP Streaming`で`push notifications`実行してイベント経由でコマンドがちゃんと実行されたかどうかを分かれると思ってます。
​
`Graph`世界には3つ概念があり
​
- Query
- Mutation
- Subscription
​
これら3つの概念は以下のCQRSの概念それぞれに対応する
​
- Query
- Command
- Event API
​
だからこそ `GraphQL`はCQRS実施するツールと認められます。
​
## CQRS, DDDとEventSourcing
​
CQRSはよく「DDD」と「EventSourcing」を一緒に使われてるんで、本質は違うんですけど、お互いにうまく補完し合っています。
​
CQRSベースのアプリケーションのコマンド API に送信されるコマンドは、DDDの意味で**集約のコマンドとして解釈することもできます**。
​
その後、集約は1つ以上のドメインイベントを順番に生成して、これらのイベントは、イベントソーシングを使用してイベントストアに保存し、後で集約を再生するために使用できます。
​
さらに、ドメインイベントはアプリケーションの読み取りページにも転送され、そこで (事前計算された) ビューが更新される、この目的のために、いわゆるプロジェクションが使用されます。これは、技術ドメインのイベントがどのビューに対してどのような関連性を持っているかを判断し、CRUD ステートメントを使用して影響を受けるビューをそれに応じて調整します。

## CAPルール

CAPルールとは

- **C**onsistency: 一貫性
- **A**vailability: 可用性
- **P**artition Tolerance: パーティション耐性

分散システムは、書き込み操作がクライアントに返される前に、すべてのデータベースのノードに反映されることで、一貫性を確保しています。これにより、システム内のすべてのノードが唯一のレスポンスを返します。

可用性は、システムがすべての書き込みおよび読み取りリクエストに対応でき、いかなる時点でも待機時間が発生せず、システムからのリクエストを拒否しない能力を表します。

Partition Toleranceはノードの障害やノード間の接続が失われた場合でも、システムが動作し続けることを保証します。

現実的には、分散システムは通常、3つの特性のうち2つしか確保できないことがあります。つまり、CA（一貫性と可用性）、CP（一貫性とパーティション耐性）、またはAP（可用性とパーティション耐性）のいずれかです。これはCAP定理として知られており、Eric Brewerが提唱し、分散システムの構築と展開において重要な側面となっています。

一貫性の欠如は最も危険と見なされています。

## Why CQRS?

これに関連する典型的な理由の一つは、それがDDDやEvent-Sourcingなどの他の概念と相互補完的であることです。

CQRSが読み取りと書き込みを明確に分離することは、マイクロサービスアーキテクチャに多くの利点をもたらすこともあります。

また、DDDやイベントソーシングとの良好な結びつきは、技術的なコードとビジネスコードの分離をより明確で容易にします。

CQRSはもちろん色んなメリットがあるんですが、デメリットも全然ありだと思ってます。それは実施するの大変さなんですが、現在、NestJS（typescript、nodejs）などの多くのフレームワークがCQRSを強力にサポートしています。

## Event-Sourcingとは

Event-Sourcingは通常、DDDとCQRSで使用されるデータ保存メカニズムです。これらの概念は互いに独立していますが、完璧に補完し合います。

データの保存は通常、CRUD操作を使用して行われます。ここでは、データベースに対する4つの主要な操作があります（CREATE、READ、UPDATE、DELETE）。その中で、UPDATEとDELETEの2つの操作は、それらが **destructive operation** - 破壊的な操作に関連しているため、残りの2つの操作とは異なるカテゴリに属しています。

DELETEはもちろん名前通りデータを削除します。
UPDATEでは、古いデータが**削除され**、新しいデータで**置き換えれます**。

そのため、Event-Sourcingは**UPDATEとDELETEを捨て去り、代わりにCREATEとREADだけを使用します**。

Event-Sourcingでは、すべての変更は新しいエントリとしてリストに追加され、これらのエントリは永遠に変更されることなく削除されることもありません。

そのため、Event-Sourcingのデータベースは通常**Event-Store**と呼ばれ、その後のイベントは再読み込みおよび集約され、データを再現します（現在の時点であるか、または過去のある時点であるかもしれません）。

## Event-Sourcingのデメリット

イベントはEvent-Storeに**追加される**ため、Event-Storeはどんどん大きくなり、その結果、データを再現するための集約プロセスが遅くなる可能性があります。

あまりにも多くのイベントレコードが発生する問題は、スナップショットによって解決できます。

## CRUD vs Event-Sourcing

CRUDには生データを保存する際の高い性能など、独自の利点も存在します。特別なロジックが必要ありません。

しかし、CRUDの明らかな欠点の一つは、トランザクションが互いに**上書きされる**可能性があるデータの競合状態です。一方、Event-Sourcingでは、このデータ競合の状態が少なくなります。その理由は、イベントエントリはデータを**デルタ形式**でのみ保存するためです。

## Event-Sourcingにはどのデータベースを使用する ?

Event-Sourcingは**INSERT**と**SELECT**のみを必要とし、JOINやCTE（common table expression）などの複雑なクエリは不要です。そのため、Event-Sourcing向けのデータベースに対する要件もかなり低いです。

イベントをBLOBまたはJSON形式で保存することは多くの利点をもたらします。そのため、NoSQL DB（MongoDB）は、スキーマやデータの組織を必要としないため、JSON形式でデータを保存する場合には優れた選択肢です。

それに加えて、Apache KafkaもEvent-Storeの選択肢となりますが、ただし、自分の状況についてよく理解している場合にのみApache Kafkaを使用するべきです。

## DDDとEvent-Souring

DDDには**domain events**という概念もあります。この概念とEvent-Sourcingの**event**概念は、いずれもシステムのビジネスロジックに関連するイベントを記述しています。

> domain-eventsは通常、コマンドの結果であるため、これらのdomain-eventsはevent-storeに保存されます。

## まとめ

CQRSは分散システムに対する興味深いアプローチであり、これによりDDDやEvent-sourcingの利点を活用できます。CQRSの導入は従来のクライアントサーバーアーキテクチャよりも複雑ですが、その代わりにアプリケーションは将来の拡張性が高まります

Event-Sourcingはデータの保存に対して多くの利点を持っていますが、標準のフォームが必要なデータや重複や一意性に注意が必要なデータに対しては、Event-Sourcingが良い選択肢ではないことがあります。

そのため、Event-SourcingはCRUDとは適合せず、その逆もまた然りです。ただし、状況によってはお互いを補完できることもあります。したがって、どのツールを選択するかは、直面している状況によって決定してください。

CQRSとEvent-Sourcingの記事をお読みいただき、ありがとうございます。次の記事でお会いしましょう。

また、エアークローゼットはエンジニア採用活動も行っておりますので、興味のある方はぜひご覧ください！

https://corp.air-closet.com/recruiting/developers/

https://www.wantedly.com/companies/airCloset/projects