どすこい!
昨年ぶりです!最近よくお相撲さんを見る@sitotkfmです!
またエンジニアブログを書かせて頂くことになりました!
今回のトピックですが、最初は前回同様ストリーミングアルゴリズムでも発表しようかと思ったんですが、まああんまり受けが良くなかったので今回はパスで。。。(同期に「お前Advent Calender書いてたのかよ」と言われました。。。)
それとあの後業務で本当にSparkをつかうことになりまして、まあ、えっと、本当に大変ですね。。。
さて今回のエンジニアブログではCQRSについて説明したいと思います。
CQRSが何故必要なのか
まずドメイン駆動設計(Domain-Driven Degien)という、ビジネスの概念の抽象化であるドメインの要求を最優先として設計を行うソフトウェアの設計思想があります。
ここではドメイン駆動設計について説明しだすと主題から外れかねないのと私がブログの締め切りまで間に合わないのでの細かい説明は省きます。ドメイン駆動設計には様々な概念がありますが、その中にレイヤ化アーキテクチャという概念があります。レイヤ化アーキテクチャとはシステムをユーザインターフェイス層、アプリケーション層、ドメイン層、インフラストラクチャ層に分けてドメインを表すオブジェクトとその他の役割を行うオブジェクトを明確に分けることを目的としています。このレイヤ化システムアーキテクチャというのは様々なシステムで取り入れられているかと思いますが、ここで問題になるのはそのレイヤをどう重ねているかいうことです。
一番シンプルな方法はユーザインターフェイス層、アプリケーション層、ドメイン層、インフラストラクチャ層を一層ずつそのまま重ね合わせる方法だと思いますが、CQRSの文献にこの重ね合わせたアーキテクチャ(文献中ではstereotypical architectureと書かれてますので以後ステレオタイプアーキテクチャとします)の長所と短所を指摘しています。長所はシンプルで直感的な事、短所はドメインを正確に表すのが難しいあるいはドメインの表現力が乏しいということです。これはどういうことでしょうか?見て行きましょう。
ドメインで何らかの変更が起きたとき、ドメインイベントが発生します。
このステレオタイプアーキテクチャではデータストアのアクセスはドメインイベントをBeanのようなgetter/setter付きのオブジェクトに詰め込んでCRUDで行われると考えられます。
例えばブログの記事だと「記事名」、「ライター名」、「文章」等ブログ記事を表す情報を対応するフィールドを持つ「記事オブジェクト」があるとします。記事を作成するときは記事オブジェクトに記事の情報を詰め込んでインフラストラクチャ層に渡し、記事を取得するときはインフラストラクチャ層から取得した記事オブジェクトから必要な情報を取り出せば良いわけです。
このぐらい単純ならさほど問題なくむしろシンプルに見えます。
ではドメインが複雑化した場合どうでしょうか。
多くのブログサービスには下書きという機能があります。書いている途中の記事を公開することなく保存する機能ですね。
これをステレオタイプアーキテクチャで表す場合どうなるでしょうか。
おそらく記事オブジェクトの中にin_draftの様なBool型のフィールドを新たに設けて、下書きかそうじゃないかをこのフィールドで見極めるようになるかと思います。
ドメインイベントは動詞で終わる一文で表す事が出来ます。この下書きの例では「ブログ記事を下書きにする」ですね。対して記事オブジェクトでは「下書き状態の記事」のように名詞で表すことができます。つまりステレオタイプアーキテクチャではドメインイベントを「ブログ記事を下書きにする」を「下書き状態の記事に更新する」と置換する必要があります。
これが先の文献で指摘しているドメインの表現力の乏しさとなります。
もっと詳らかにすると「ブログの記事を下書き状態とするフィールドを真にして記事に更新する」と表すことができます。ドメインがもっと複雑になり状態を表すフィールドが増えると、「Anemic Domain Model」というドメインに関係ないオブジェクトすら扱う必要が生じるアンチパターンに陥ってしまうと先の文献では指摘しています。
CQRSについて
ここでCQRSの登場です。
CQRSは「Command Query Responsibility Segregation」の略です。
ここでいうCommandは「状態を変更するメソッド」でQueryは「状態を変更するメソッド」を表します。つまりWritetとReadですね。
では「Responsibility」は何でしょうか。日本語だと責務と訳される事が多いようです。
実は「Command Query Segregation」という手法も存在します。
CQSはCommandとQueryをメソッドで切り分けるのに対し、CQRSはCQSの拡張でCommandとQueryをメソッドではなくオブジェクトで切り分け、そしてQueryをドメイン層の外に出します。
CQRSではもう一つ大きな特徴があります。それは一貫性を結果整合性(Eventual Consistency)として許容する点です。
これによってドメインイベントを即時にデータストアに反映させるのではなく、非同期でQueryが取得し易いように非正規化したデータ構造に変換することが可能になります。こうしてCommandはQueryのデータ構造を気にする必要がなくなり、先述の記事オブジェクトのようなオブジェクトに変換してデータストアに渡す必要がなくなります。またQueryの参照性能向上も期待できます。
CQRSと共に語られることが多い概念としてEvent Sourcingというものがあります。参考URL
これは端的に言えばアプリケーションの状態をイベントの列を持つ事で管理する手法です。例えば「ブログを公開したが公開した写真が盗用ということでネット上で炎上したので急いでブログを削除した」という香ばしい事案が生じたとします。このときのドメインイベントは「ブログ記事を公開する」「ブログ記事を削除する」の二つです。一般的なデータストアだとブログ記事にあたるエントリを作成し、そのエントリを削除するという処理が行われます(もしくは削除フラグを立てる)。Event Sourcingではブログを作成したエントリの後にブログ記事を削除したというエントリが追加されます。つまりEvent Sourcingではエントリの削除を実際にするわけではなく、削除というイベントを追記する事で削除と見なします。
データの復元するはイベントの列を遡ることで可能になります。(これをローリングスナップショットと呼びます。)
Event Sourcingの利点としてドメインイベントをデータストアに(ほぼ)そのまま渡す事ができるということです。
CQRSとEvent Sourcingを踏まえた上でどのようなアーキテクチャになるか見てみましょう。
Command HandlerはCommandを渡すだけのレイヤーです。
Domain Layerはドメインの処理、ドメインイベントの生成を行うレイヤです。前述の通りここにQueryは含まれません。
Event StorageはEventを保持するデータストアでEvent Handlerは非同期でEvent Storeのデータを非正規化してData Strorageに保存します。データを読み込む場合はQuery HandlerのからQueryつかってData Sotrageのデータを取得します。
ここまで読んでいて私は何か似ているなと思いました。
(゚o゚;) ハッ
データ解析システムってCQRSに似てるじゃん!
…...はい、皆さんが思っても思わなくても話を続けます。
ではデータ解析のシステムをCQRSに無理矢理当てはめてみましょう!
データ解析システムをCQRSに当てはめてみよう
User Interface → 各サービス
ユーザが触るのはサービスですね。
Domain Event → 各サービスのログ
これはまさにそのまんまですね。ログはユーザの振る舞いを表します。
Event Storage → Flume&HDFS
例えばFlumeでは一つのチャネルから複数のSinkを指定する事が可能です。このSinkでデータを流す先を決める事が出来ますが、そのうち一つにHDFS Sinkを使えば分散ファイルシステムであるHDFSにTimestampやHeaderの値に基づいてパスを変えてファイルに追記することができます。ログはドメインの振る舞いを表しそれをファイルに追記して行くHDFS Sinkは乱暴にいうとこれも一種のEvent Sourcingと言えるんじゃないかと。
実際にシステムに落とし込むときにこのEvent Storageをどうするかっていうのが問題になりそうな気がします。
データストアですとその名の通り、Event Storeというデータベースもあります。またDatomicも良さそうかなとは思いますが、これを使えば良いっていうものは出てないように思えます。
それとイベントハンドラに渡す事を考えて、データ解析の例で言うFlumeに相当する部分をPub/Subの様なメッセージングシステムを置いておくことも有り得そうですね。Apache KafkaやRabbitMQ等が思いつきますが、Redisの作者の@antirezさんの開発しているdisqueも気になりますね。(productionですぐ使える事はないでしょうが)
Event Handler →各種解析(Hadoop, Hive, Spark, Onix, etc.)
ここはHadoopやHiveでもBigQueryでもRedShiftでも良いんですが、前回紹介したSpark Streamingとしましょう。
Spark StreamingはFlumeから直接イベントを受け取って解析を行う事が可能で解析結果を非正規化してData Storageに保存します。
ここで何らかの理由でSpark Streamingが止まったりして再解析が必要になったとき、HDFSに保存しているログからSparkを使って復元することも可能です。解析にもよりますがSparkのデータ構造であるRDDのスキーマをストリーム処理とバッチ処理で共通にしておけばこのようにローリングスナップショット的なことも出来るっちゃできます。
Data Storage → HBase
解析データは弊社の場合はHBaseに入れる事が多いです。
ではHBaseで非正規化されたデータ構造をもたせるにはどうすればいいか。
そこらへんについて詳しく書いたHBaseの入門書が弊社のエンジニアが出していた気がしますが、広告のレギュレーションが問題になっている昨今わざわざ渦中につっこんでいく必要も無いという事でここでリンクを貼るのはやめておきます。そもそも宣伝した所で私の懐には一銭も入りません!
あとはData Storageからデータを取得するQuery Handlerのレイヤーを設ければCQRSとほぼ見なせますね。
まとめ
みなさん、いかがでしょうか。
我ながら無理矢理な感じは否めませんが、CQRSの理解の手助けになっていただければ幸いです。
もちろんCQRSは結果整合性を容認する必要があったりコンポーネントが多くシンプルとは言い辛いのでアプリケーションを選ぶアーキテクチャではあるかと思いますが、ドメインが複雑になり得るアプリケーションには有効かなとは思います。あとはEvent Storageの部分をどうソフトウェアに落とし込むのかでブレイクスルーがあれば普及するんじゃないかと。
データを一方通行に流すアーキテクチャという点でFacebookのFluxにも類似しているのかなと思います。
ではみなさん、ごっつぁんでした!
参考文献・URL
CQRS和訳
Event Store
Datomic
Apache Kafka
RabbitMQ
Disque
↧
データ解析のシステムからCQRSについて学ぼう
↧