DDDとかプログラミングとかアーキテクチャとか雑多に
Reactive Messaging Patterns読書会のなかで、「マイクロサービスとAkkaとGo」な面白い話題が出たので代表でまとめる試みエントリです。(結構、色々な話題に飛んでいるので難度高い。)
まとめ方としては、会話ログを転記して、最後にまとめる形をとっています。また、議論と私の考えが混ざらないように所感は分けておきます。
TL;DR
要素技術(どんな言語使うとか、どんなアーキテクチャにするとか)の前に、組織やプロダクトの性格を考えて戦略を決めましょう。 そして、その中で最適と思われる戦術をとれるような要素技術を採用しましょう。 Akka良いよ。
ログ(一部抜粋)
Slackからの引用のためテキストベースです。
事の始まりは、荒木さん(以下、 @applideveloper )の発言でした。 (この記事絡みですね。 集合知で各言語、ライブラリ、フレームワーク、DB、アーキテクチャの適材適所をみんなでまとめたら面白いんじゃないか? - Qiita )
Goもstructとinterfaceを使えば、仕様を表現できて、DDDでmicroserviceできるって聞いたのですが、GoでもDDDとかmicroservice向いてるんですかね?https://github.com/rogeralsing/gam/blob/dev/README.md
かとじゅんさん(以下、 @j5ik2o )
GoでもDDDはできるとは思います。
ただEventSourcingは言語機能でサポートしているものがないんじゃないかな。akkaならakka-persistence(-query)ですが、ほかの言語ならEventStoreをどうやって実装するかですかね。Kafkaを使うとしても似たようなことできますが、現実的に無限にイベントを永続化することはできないのでKVSにイベントを永続化する機構が必要ですね。これがあれば境界づけられたコンテキスト間でイベントを相互に伝搬させるイベントデータポンプを作ることが可能ですね。つまり、Go版のakka-persistence(-query)がほしくなるんじゃないかな。 あと、集約スナップショットを使って集約のリプレイをショートカットする仕組みとか考えるとKafkaだけでできないので作り込む必要がありますね。
GoでActorはできてうれしいとしてもそれは単一のサービスのプログラミングモデルの話であって、マイクロサービスを考えると永続化されたイベントをBC=サービスで自由に読めて連携できるというアーキテクチャにならないといけない。
私(以下、 @yoskhdia )
色々混ざっているような? 便乗で書いちゃうと、
・GoでDDD
オブジェクト指向な書き方ができるなら可能。(抽象データ型が作れるかどうか) この間、増田さんと話したときは、関数型言語でDDDができるかどうかについてDDD本の原文は might が使われていて、「多分ダメだけど可能性って意味では否定できないよね」というレベルなんだよ、と教えてもらいました。 (ただし、Haskellはmoduleがあるので抽象データ型が作れるからDDDできる)・GoでMicroservices
Microservicesはあくまでコンポーネント化をサービスとしてやる、という形でEventSourcingは必須要件ではないのでは? サービスはあくまで独立しており、サービス間のやり取りはあくまでもゆるやかなものに留まる認識でした。 (オーケストレーションな繋がりだと、HTTPによるコミュニケーションでもOK。つらいからコレオグラフィの方が良いと思うけど。) なので、サービス間のイベントを無限に永続化する必要は無いのかな、と。各サービスは自分の好きなデータの持ち方をしてOKで、それは従来のステートフルな永続化かもしれないし、EventStoreかもしれないですね。 必要なのは到達保証みたいなもので、それはKafkaでできそう。
https://github.com/Shopify/sarama
Microservicesに必要なのはCircuitBreakerとか、サービスの耐障害性をあげる仕組みなので、GoでMicroservicesができるかどうかで言えばできると思います。
https://github.com/go-kit/kit
ただまぁ、全体がActorモデルで統一されてる方が開発スピードあがりますし、Akkaだとそういった耐障害性とかが含まれてるメリットあると思うので、そういう選択をとるのもアリだなと思います。
Wantedlyの相川さんに聞くと
Akkaは分散システム(本家はdistributed applicationといってる)を作るためのフレームワークなんですね。マイクロサービスと言っても間違いではないですが、基本的には1つの言語体系の中で分散環境で動く物を作ろうってことなのかなと思います。Goに投げるとかにするときは別でHTTPリクエストとかが必要になるし、Goが動いているサーバー自体をどう管理するかの問題は残ったままですよね。 自分たちはGoとかというよりはKubernetesとかのインフラ領域でマイクロサービス化を担保して、一つ一つのサービスの中の言語はRailsでもGoでもErlangでもいい感じで考えています。
とのことで、Kubernetesを使う場合と、Akka Cluster Managerを使う場合だと、マイクロサービスにおいて何か違いがあるんですかね?https://speakerdeck.com/koudaiii/kubernetes-woshi-tutesabisuwojia-su-saseruqu-rizu-mi
マイクロサービスの実現形式にはいろいろなものがあるので相川さんの言っていることも正しいと思います。僕はAkkaというかEventSourcingに傾倒しているのでその点を強調した主張になっているというだけだと思います。DockerとKubernetesとクラウドである種のマイクロサービス(not EventSourcing)が実現できますが、EventSourcingをベースしたマイクロサービスの実現はできないというのが唯一の違いです。
マイクロサービスであることと、ESやSSであることは別で直交する概念ですね。
SS = State Soucring = CRUDベースのアプリケーション
KubernetesでEventSourcingをベースしたマイクロサービスの実現ができないと、何が実現できなくなるのでしょうか?どういう場合に使い分けたらいいのか
ちょっと表現が悪かったかも。KubernetesはDockerベースのオーケストレーションツールなので、その上で動作するアプリケーションがマイクロサービスかどうかは関係ありませんよね。 Kubernetes+RESTベースのマイクロサービスならGoでもPlay2でもできるでしょうね。 Kubernetes+EventSourcingなマイクロサービスは、GoでもPlay2でも実現できるけど自前で実装するのは大変だというぐらいかな。Scala+Akkaなら楽だというぐらい。
RESTベースのマイクロサービスとEventSorcingなマイクロサービスだと、どういう違いがあって、どういう使い分けがあるのでしょうか?
s/REST/State Sourcing/でした
ドメインイベントによって駆動されるマイクロサービスがESのほうですね。SSの方はイベントをソースにすることが現実的じゃないですね。https://speakerdeck.com/j5ik2o/zui-xin-dddakitekutiyatoakkadefalseshi-zhuang-hintonituite
24ページ
29ページデータストアに最後に確認された状態を持つのではなく、起こった出来事がすべて永続化されているのがESですね。 基本的にドメインイベントは不変で追記しかできないので、書き込みに強くてスケールしやすいのと、いつでも必要な時にサブシステムで利用することができますね。イベントデータポンプなどはそういうドメインイベントの利用例ですね。
Kubernetes+Event Horizonを使った場合と
Event Horizon is a CQRS/ES toolkit for Go.
https://github.com/looplab/eventhorizon
Scala/AkkaのCQRS/ESは、何か違いがあるのでしょうか?
ぱっと見た感じなのでなんともですが、コンセプト的にはこれでもESできるんじゃないですかね? Akkaとの差分でいえば、イベントはシリアライゼーションできるが、集約スナップショットがなさそう。 Event配信はRedis使うとリモートノードでできそうですね。Akkaではakka-remoteでP2Pでできるし、Distrusted PubSub(redisなど不要)でもできますね。 EventBus経由でPubSubするのはリアルタイム要件でそれ以外だとシリアライズされたイベントを直接読むことがありますが、Akkaならpersistence-queryを使うとできます。
あと、開発体制がどうかぐらいではないですかね?akkaはreactiveの旗頭のlightbendが付いてるので。Goでもそういうしっかりした体制で開発されているスタックがあれば問題ないと思います。
あーあと、ステートフルなActorインスタンスをノードを跨いで稼働させられないじゃないかな。akka-persistenceを使えばできます。そのIDのActorインスタンスをクラスターで唯一一つにしたいならcluster-shardingを使ったりしますが。まぁ、スケール性の高い分散システム志向なんで旧来のスケール戦略と方向性が違うので比較が難しいですが。 Akkaは、特定のノードが故障したとき、そのActorを別のノードで稼働させますが、今までのイベントを読み込んで状態を復元できます。結局これができないとステートフルなActorをつくれないですね。つまり、状態は全部DBにいっちゃう。Actorがキャッシュのかわりにならないとかデメリットはありますね。
釘屋さん(以下、 @jkugiya )
Scala/AkkaのCQRS/ESは、何か違いがあるのでしょうか?
ESとリアクティブ云々は別の話なので、リアクティブマニフェストに書かれているような性質をサポートするための道具をセットとして持っているかどうかというのは大きな違いではないですかね?
リアクティブマニフェストも評価軸にいれてみるとよいかもですね。 マイクロサービスという主語がでかい問題があるので、非リアクティブなマイクロサービスもあるだろうし。
マイクロサービスもDDDと同じで戦略と戦術のどっちの話か分けて考えるのが大事 (組織論がくっついてくるのも同じ)
自分たちはGoとかというよりはKubernetesとかのインフラ領域でマイクロサービス化を担保して、一つ一つのサービスの中の言語はRailsでもGoでもErlangでもいい感じで考えています。 とのことで、Kubernetesを使う場合と、Akka Cluster Managerを使う場合だと、マイクロサービスにおいて何か違いがあるんですかね?
これも同じことが言えそう。Rails / Goを中で使ってもいいけど、Akkaのツールキットは使えなくなるので、リアクティブのための道具は調達するか自分で作る必要がある。
そうですね。言語や既存のエコシステムでカバーできなければ、歯を食いしばって自前でやるってことになると、戦術的にはコスト的な問題ぐらいで、人によってはその方が楽とも苦ともいえちゃうんじゃないでしょうか?言語系って結局主観でなんとでも言い訳できるので甲乙つけがたい。 DDDのコンセプト的にも言語非依存だし。
増田さんが関数型はDDDに向かないかもというのも、間違いでも正しいわけでもないと思うんですよね。個人的には、Scalaは幸いオブジェクト指向が中心で使い分けができるので、できる方に入っちゃうと思いますが。
まとめ
話は大きく3つあったように思います。
- GoとDDDとMicroservices
- EventSourcingとStateSourcing
- Akkaとの比較
GoとDDDとMicroservices
まず、GoによるDDDとマイクロサービスは可能なのかどうか、という点については、可能であろうという結論に至ったように思います。 私もGoに詳しいわけではありませんが、下記記事を流し読んだ限り、GoでもDDDは可能なように思いました。 (DDDは可能かというと意味が広すぎるのですが、ここではユビキタス言語で定義された言葉を使って、データや振る舞いをコードと対になるように書くことができるかという程度に留めています。)
マイクロサービスも同様ですね。
EventSourcingとStateSourcing
そして、議論の中ではマイクロサービスという切り口に対して、StateSourcingとEventSourcingの比較が出てきました。 StateSourcingは起こった事象によって変化させた状態を保存するやり方、EventSourcingは起こった事象そのものを保存するやり方です。
に対して、GoでActorモデルを使える利点は、このライブラリではまだ単一のサービスのプログラミングモデルの話に留まっており、マイクロサービスを考えるとStateSourcingなやり方になりそうです。
StateSourcingを用いた場合、状態をDBに保存することになりますが、例えばサブシステムにある事象(処理)が起きたら連携するというケースがあるとき、それ専用の連携インタフェースを作る必要があります。
EventSourcingの場合、(ドメイン)イベントをイベントデータポンプなどを利用して送信することもできますし、イベント自体が記録されているので過去の好きな時点から再送するということも可能です。(また、ある仮説があったとして実験イベントを登録し、それらを再生することで未来予測のように使う、ということもできるようになるかもしれません。)
その他、EventSourcingとStateSourcingのメリット・デメリットはかとじゅんさんのスライドが参考になります。
※このスライド中のコードはあくまでイメージサンプルなので、下記のコードの方が参考になります。
Kubernetesなどを使ってインフラレベルでマイクロサービス化を支援し、(DDDでいう)公表された言語を使ってサービス間の対話を補助するという戦略や、Akkaで統一してEventSourcingなアーキテクチャを構築してBoundedContext(境界づけられたコンテキスト)などによって分散させるという戦略など、マイクロサービスと一口に言っても様々なやり方がありそうです。
マイクロサービスであることと、EventSourcingやStateSourcingであることは別で直交する概念と言えそうです。
Akkaとの比較
とAkkaを比較して、Akkaのメリットについても言及されました。
Akkaでは、Akka Persistenceを使うと(DDDでの)集約の単位でActorを作成しているとき、あるノードがダウンしてもイベントを再生することで簡単にノードをまたいでActorを再生することができます。(ライブラリ側で吸収されている。) また、Lightbend社が開発についており、個人開発のライブラリと比べて安心感があります。
Akkaはリアクティブマニフェストに則ったアーキテクチャも構築しやすく、リアクティブシステムを指向する場合の選択肢としても有力です。(AkkaHTTPなどミドルに近いところからAkkaスタックが用意されている。)
所感
話は色々とありましたが、最終的には「戦略と戦術」に収斂されたように思います。 私の中の戦略と戦術の定義も記載しておきます。
- 戦略
「メタの作戦」のことで、そもそもどの土俵で戦うのか、いかに自分の得意な領域に勝負を持ち込むのか - 戦術
決められた土俵の中でいかに勝つかの作戦
どんな技術であっても、組織やプロダクトに適していなければ最大の効果を発揮することは難しくなります。 そのため、まずはどの土俵で戦うのかを決めることが必要です。 今回の例でいくと、プロダクトはいくつかのサービスの集合体として、それぞれの自治で物事が進むようにしたい、というのであればマイクロサービスを選択することも一つの手です。(もっとも、小さなプロダクトの時点から適用すると複雑さの方が勝ってしまう点で戒められたりしますが。)
ここで面白いのが、戦略にも二段階あるように見える点です。 マイクロサービスもDDDと同じように、書籍などで語られる様々なトピックは戦略的なものと戦術的なものが混在しているように思います。 例えば、サービスの粒度を考えるとき、最初のきっかけ(基礎)は、DDDでいう境界づけられたコンテキストが一つの指針になるのではないでしょうか。
「そもそも何故マイクロサービスを選択するのか」と「マイクロサービスの戦略的設計」は、土俵をどこにするか、どういった方向へ進むかを担います。(この辺、人によって解釈が違うかもしれませんが...)
前述のようにマイクロサービスという主語は大きく、会社やチームの方針や習熟度によって最初の戦略的設計が変化してきます。(というより、組織とプロダクトの目指す所が相互的に関与しあってデザインが形作られていく。) どういった要素技術を使うのかは、これらが規定された後です。マイクロサービスとDockerの関係などは以下の記事も参考になります。
マイクロサービス化が進む背景について考えてみた · GitHub
その他、コレオグラフィが取り上げられるため、イベントを主として扱うEventSourcingやReactive Systemと混同しがちですが、マイクロサービスにおいては、RESTであってもメッセージ駆動であっても、どちらでも構いません。(その点では、EventSourcingとReactiveSystemも混同されがちかも。) 自分たちのプロダクトにとって、一番最速で目指す価値を達成できるアーキテクチャを採用したいですね。
そのためには、引き出しに多くの選択肢を持つことが大切です。(宣伝フェーズ)
Akkaを使ったReactive Systemについて、書籍「Reactive Messaging Patterns」読書会を開催中です。 ご興味のある方はご連絡ください。
謝辞
会話ログにて、Wantedlyの相川さんとギルドワークスの増田さんのお名前をそのまま転記しております。どちらもRMP読書会のメンバーではなく、本記事の見識がご本人の見識を代弁するものではありません。 はじめ削ることも考えたのですが、会話の流れがおかしくなってしまうため、会話ログのままとしております。 不都合などありましたら、ご連絡ください。 また、この場を借りてお礼申し上げます。貴重な知見の公開ありがとうございます。
0 件のコメント:
コメントを投稿