こんにちは、MLBお兄さんこと松村です。
ついに MLB が開幕しました。今年はどのようなシーズンとなるでしょうか。
今回は Azure Cosmos DB for NoSQL のデザインパターン解説記事の第6弾です。
前回は「ドキュメントのバージョン管理 (Document versioning) パターン」を解説しました。
今回は「イベントソーシング (Event sourcing) パターン」について解説をします。
このデザインパターンについては、Cosmos DB の GitHub リポジトリやブログで紹介されていますので、詳しく読みたい方はこちらもご覧ください。
イベントソーシングパターンは、サーバーレスアーキテクチャーに関連してよく聞くデザインパターンです。
まずイベントソーシングパターンは、データに関するイベントをすべて追跡可能な状態で記録する方法です。
データに関するイベントとして、CRUD のうちの以下を指します。
- 登録 (create)
- 更新 (update)
- 削除 (delete)
また、イベントの記録と同時に、そのイベントの対象となるデータ自体も一緒に記録します。
こうすることで、以下のようなメリットがあります。前回の Document versioning パターンにも近いものがあります。
- データの変化を時系列で追跡できるため、監査証跡に用いられる
- システムがクラッシュして、当該データが記録されていない場合でも、イベントデータを再送することができる(やり直せる)
- 大量のデータを効率的に処理することができ、スケーラビリティを保つことができる
- テストのためにイベントを複製することができる
サンプルコードと照らし合わせながら、理解していきましょう。
サンプルはデザインパターンのリポジトリで公開されています。リポジトリをクローンして、実際に動かしてみましょう。
サンプルコードでは、 EC サイトのショッピングカートに関するイベントを記録するようになっています。
イベントは4種類定義されています。
string[] actions = new string[] { "cart_created", // カートの作成 "product_added", // カートへ商品追加 "product_deleted", // カートから商品削除 "cart_purchased" // カート内の商品購入 };
イベントごとに「いつ・誰が・どこへ・どのようなデータ」を更新操作したのか、というのを随時記録していきます。
イベントごとに記録されるデータは以下のような形になっています。
カートの作成
{ "id": "a6e8d56b-4ffb-4a01-a6c1-105955ff70fc", "CartId": "d5e3f139-fafe-4acd-91cf-0fbab0e12584", "SessionId": "36b8ed87-0a5e-4b80-aa81-926bbed564ec", "UserId": 339, "EventType": "cart_created", "Product": null, "QuantityChange": null, "ProductsInCart": null, "EventTimestamp": "2024/03/27 14:27:13", ... }
カートへの商品追加
{ "id": "107ad5b5-eb9d-4263-b7ed-cab34542f979", "CartId": "d5e3f139-fafe-4acd-91cf-0fbab0e12584", "SessionId": "36b8ed87-0a5e-4b80-aa81-926bbed564ec", "UserId": 339, "EventType": "product_added", "Product": "Product 1", "QuantityChange": 0, "ProductsInCart": [ { "ProductName": "Product 1", "Quantity": 0 } ], "EventTimestamp": "2024/03/27 14:27:13", ... }
カートからの商品削除
{ "id": "07cf958e-b08b-485b-a204-e7ab91b42ca0", "CartId": "d5e3f139-fafe-4acd-91cf-0fbab0e12584", "SessionId": "36b8ed87-0a5e-4b80-aa81-926bbed564ec", "UserId": 339, "EventType": "product_deleted", "Product": "Product 2", "QuantityChange": -1, "ProductsInCart": [ { "ProductName": "Product 1", "Quantity": 0 } ], "EventTimestamp": "2024/03/27 14:27:13", ... }
カート内の商品購入
{ "id": "edd4eda9-fdd1-428a-8e49-84839cbd1f54", "CartId": "d5e3f139-fafe-4acd-91cf-0fbab0e12584", "SessionId": "36b8ed87-0a5e-4b80-aa81-926bbed564ec", "UserId": 339, "EventType": "cart_purchased", "Product": null, "QuantityChange": null, "ProductsInCart": null, "EventTimestamp": "2024/03/27 14:27:13", ... }
いずれもデータとしては独立している(前後のイベントを意識しない)ため、そのデータを再送信することも可能です。
このように、いつ・誰が・どこへ・どのようなデータを更新操作したかというイベントデータを記録しておくことで、時系列でデータを検索するクエリを発行することが可能となります。
SELECT * FROM c WHERE c.CartId = 'd5e3f139-fafe-4acd-91cf-0fbab0e12584' ORDER BY c.EventTimestamp ASC
[ { "id": "a6e8d56b-4ffb-4a01-a6c1-105955ff70fc", "CartId": "d5e3f139-fafe-4acd-91cf-0fbab0e12584", "SessionId": "36b8ed87-0a5e-4b80-aa81-926bbed564ec", "UserId": 339, "EventType": "cart_created", "Product": null, "QuantityChange": null, "ProductsInCart": null, "EventTimestamp": "2024/03/27 14:27:13", "_rid": "DkMRAIy4LC0BAAAAAAAAAA==", "_self": "dbs/DkMRAA==/colls/DkMRAIy4LC0=/docs/DkMRAIy4LC0BAAAAAAAAAA==/", "_etag": "\"0100a85d-0000-2300-0000-66042cc40000\"", "_attachments": "attachments/", "_ts": 1711549636 }, { "id": "107ad5b5-eb9d-4263-b7ed-cab34542f979", "CartId": "d5e3f139-fafe-4acd-91cf-0fbab0e12584", "SessionId": "36b8ed87-0a5e-4b80-aa81-926bbed564ec", "UserId": 339, "EventType": "product_added", "Product": "Product 1", "QuantityChange": 0, "ProductsInCart": [ { "ProductName": "Product 1", "Quantity": 0 } ], "EventTimestamp": "2024/03/27 14:27:13", "_rid": "DkMRAIy4LC0CAAAAAAAAAA==", "_self": "dbs/DkMRAA==/colls/DkMRAIy4LC0=/docs/DkMRAIy4LC0CAAAAAAAAAA==/", "_etag": "\"0100a95d-0000-2300-0000-66042cc40000\"", "_attachments": "attachments/", "_ts": 1711549636 }, { "id": "55a47e16-c5eb-455e-9a4d-59c612222f2d", "CartId": "d5e3f139-fafe-4acd-91cf-0fbab0e12584", "SessionId": "36b8ed87-0a5e-4b80-aa81-926bbed564ec", "UserId": 339, "EventType": "product_added", "Product": "Product 2", "QuantityChange": 3, "ProductsInCart": [ { "ProductName": "Product 1", "Quantity": 0 }, { "ProductName": "Product 2", "Quantity": 3 } ], "EventTimestamp": "2024/03/27 14:27:13", "_rid": "DkMRAIy4LC0DAAAAAAAAAA==", "_self": "dbs/DkMRAA==/colls/DkMRAIy4LC0=/docs/DkMRAIy4LC0DAAAAAAAAAA==/", "_etag": "\"0100aa5d-0000-2300-0000-66042cc40000\"", "_attachments": "attachments/", "_ts": 1711549636 }, { "id": "07cf958e-b08b-485b-a204-e7ab91b42ca0", "CartId": "d5e3f139-fafe-4acd-91cf-0fbab0e12584", "SessionId": "36b8ed87-0a5e-4b80-aa81-926bbed564ec", "UserId": 339, "EventType": "product_deleted", "Product": "Product 2", "QuantityChange": -1, "ProductsInCart": [ { "ProductName": "Product 1", "Quantity": 0 } ], "EventTimestamp": "2024/03/27 14:27:13", "_rid": "DkMRAIy4LC0EAAAAAAAAAA==", "_self": "dbs/DkMRAA==/colls/DkMRAIy4LC0=/docs/DkMRAIy4LC0EAAAAAAAAAA==/", "_etag": "\"0100ab5d-0000-2300-0000-66042cc40000\"", "_attachments": "attachments/", "_ts": 1711549636 }, { "id": "edd4eda9-fdd1-428a-8e49-84839cbd1f54", "CartId": "d5e3f139-fafe-4acd-91cf-0fbab0e12584", "SessionId": "36b8ed87-0a5e-4b80-aa81-926bbed564ec", "UserId": 339, "EventType": "cart_purchased", "Product": null, "QuantityChange": null, "ProductsInCart": null, "EventTimestamp": "2024/03/27 14:27:13", "_rid": "DkMRAIy4LC0FAAAAAAAAAA==", "_self": "dbs/DkMRAA==/colls/DkMRAIy4LC0=/docs/DkMRAIy4LC0FAAAAAAAAAA==/", "_etag": "\"0100ac5d-0000-2300-0000-66042cc40000\"", "_attachments": "attachments/", "_ts": 1711549636 } ]
イベントソーシングパターンは、スケーラブルで回復力のあるアプリケーションを構築するために有効な実装方法です。
実践できるようにポイントを抑えておきたいです。
サービス一覧 www.alterbooth.com cloudpointer.tech www.alterbooth.com