この記事は「もくもく会ブログリレー」 14 日目 の記事です。

この記事では、CQRS についてご紹介します。

話すこと

  • CRUD と CQRS の違い
  • CQRS のメリット / デメリット
  • イベントソーシング
  • クラウドと CQRS の相性が良い理由

話さないこと

  • DDD について

あるあるな話題

『 API のデータベース、何使う?』

という話は、Web アプリケーションを設計する上でよくある話題だと思います。

具体的には、こんなことを考えたことはないでしょうか。

安くてスケールするのは DynamoDB だけど、フィルタとか JOIN とか考えると RDS がいいな

こんな悩みの解決策になるかもしれない考え方をご紹介したいと思います。

CRUD

みなさん、CRUD はご存知でしょうか?

Web エンジニアの方なら、学習を始めた最初の頃から聴き馴染みのある言葉だったりするかなと思います。

Create, Read, Update, Delete の頭文字をとったもので、データ操作の 4 つの基本操作です。

例えば RESTful API の場合、下の画像のように HTTP メソッドを対応させて CRUD を表現するのが一般的です。

しかし、CRUD の中で R とそれ以外ではかなり特性が違います。

UI で想像していただければと思うのですが、Read は一覧画面や詳細画面であり、頻繁に呼び出されます。また、多くの場合リクエストごとに挙動が変わるわけではないので、キャッシュが有効です。フィルターや検索もよく使われます。

一方の Create, Update, Delete では、トランザクションが重要視されます。また、アプリケーションの振る舞いに関わる複雑なドメインロジックが要求されます。

プログラミングをされたことのある方はよくご存知とは思うのですが、このように全く特性の違うことをごちゃ混ぜに考えると、「パフォーマンスの低下」や「ロジックの複雑化」が発生しやすくなります。

CQRS

そこで、CQRS という考え方があります。簡単に言うと、CRUD の R を Query (取得系)、CUD を Command (更新系) と 2 分割する考え方です。

CQRS は、Command Query Responsibility Segregation の略で、日本語にすると コマンド・クエリ責務分離です。

AWS で CQRS を使って実装してみる

AWS で CQRS なアーキテクチャ設計をする場合、シンプルな例だと以下のような構成を考えることができます。

順を追って見ていきましょう。

まず、記事の作成(post/create)、記事のタイトル更新(post/update_title)、記事の公開(post/publish) という 3 つの API が順に叩かれたとします。これらは更新処理なので、Command です。API Gateway を通り、Lambda が起動されます。Lambda はデータを DynamoDB に書き込みます。

このとき DynamoDB に保存するデータの形式には、イベントソーシングパターンを使用します。イベントソーシングは CQRS とセットで使われることが多いですが、ここでは深く触れません。図にあるように、既存のデータを更新するのではなく、単に発火されたイベントをデータベースに積み上げていくという手法です。

DynamoDB にイベントが追加される度に、Insert イベントをトリガーしている Lambda が起動されます。Lambda は DynamoDB から受け取ったデータを RDS に書き込みます。

RDS に書き込むのは、DynamoDB に積まれたイベントから導出された「記事」の現在の状態です。今回の 3 つのイベントをもとにデータが書き込まれると、下の図のようになります。リレーショナルデータベースでよく見る、一般的なデータ保存形式です。

あとは Query ですが、これは単に RDS に保存されているデータを返すだけです。

これが、今回の構成になります。

CQRS のメリット

CQRS には、以下のようなメリットがあります。

・ 取得系と更新系のパフォーマンスを別々に最適化できる

・ ロジックが簡潔になる

・ 更新系の変更が、取得系に影響を与えない

例えば、更新系に AppSync を加えて、AppSync からもデータを DynamoDB に追加できるようにします。更新系と取得系は切り離されているので、この変更によって取得系の改修は必要ありません。

CQRS (というかイベントソーシング) のメリット

これはどちらかというとイベントソーシングのメリットになりますが、以下のような点があります。

・ 全てのイベントが保存されているので、デバッグや監査が簡単

・ 過去のイベントから現在の状態を導出できるため、取得系の DB の変更も可能

下の例では、取得系のデータベースとして RDS に加えて Neptune を追加しています。DynamoDB に保存されているイベントを順に適用していくだけで現在の状態を導出できるので、特定のデータベースサービスに縛られることがありません。

CQRS のデメリット

CQRS をアーキテクチャとして採用する場合には、いくつかの注意点が存在します。

・ データベースが分離されていて非同期になるので、データは結果整合性が前提です。

・ Query と Command を個別にマイクロサービス化するようなものなので、一般的なマイクロサービスと同じようなデメリットがあります。「構成要素が増える」「デプロイが増える」「トランザクション処理の複雑化」のような点です。

CQRS とクラウドは相性がいい

タイトルに「クラウド時代は」と書いている通り、CQRS とクラウドは相性が良いと考えています。

・ クラウドでは「イベントドリブン」が簡単に実装できる点

・ クラウドの「ユースケースに適したサービスを使うことで、コストやパフォーマンスを最適化できる」という点と相性がいい。

また、お気づきの方もいらっしゃるかと思いますが、 RDS のリードレプリカは、AWS が提供してくれている CQRS なソリューションの一つです。

この記事で伝えたいこと

アーキテクチャとして、CRUD ではなく、CQRS という選択肢もあります。

しかし、本当に伝えたいことは、アーキテクチャとして採用しなくても、更新系・取得系で切り分けて考えるというのはとても重要、ということです。

あとがき

この記事は、7/12 に行われた社内勉強会での発表内容をほぼそのまま書き起こしたものになります。

本当は、なぜ CRUD がアプリケーションの価値を下げるのかについて発表したかったのですが、そのためにはまず CQRS と DDD を説明する必要があり、 どう考えても発表時間の 7 分の中に収まりきらなかったので断念しました。

結果的に、DDD に触れずに CQRS について紹介するという内容になってしまい、CQRS の本当の良さは全然伝えられなかったです。

この記事に興味を持ってくださった方は、ぜひ DDD についても調べてみていただけると理解が深まるかと思います。


明日の記事は、こまきちさんの「AWSの生成AI活用事例集GenUを使い倒す」です!
GenU は使ったことがないので、どんな内容なのか楽しみです!