Microservices分割大全
Microserviceの分割の仕方について語られているものを収集します。
microservices.ioのサイトに載っている分割パターンは4つ。ただし「自己完結型サービス」と「チームごとのサービス」は、直交していないので大きくは「ビジネスケイパビリティでの分割」と「サブドメインでの分割」の2つ。
ビジネスケイパビリティでの分割
現在の業務機能にしたがってサービスを分割する。
したがって、コンウェイの法則にしたがった分割とされる。
サブドメインでの分割
DDDのサブドメインに応じてサービスを分割する。
まずコアドメインの分析、Bounded Contextの認識などが入るので、現状の業務を再構成する可能性があるので、逆コンウェイの法則にしたがった分割とされる。
自己完結型サービス
OrderServiceは、リクエストのバリデーションだけ完了したら、レスポンスを返す。
あとは、非同期にサービスを呼び出してリクエストを処理する。
SagaやCQRSを使って実現する
チームごとのサービス
ただ、ビジネスケイパビリティでの分割」と「サブドメインでの分割」も違いは曖昧である。
その他、ベンダによる指針
Kloia
Entity Service
モノリスのリポジトリレイヤと同じ粒度でサービスを作る。
Capability Service
業務のユースケースを表現する粒度でサービスを作る。
Query Service
Entity Serviceは複雑なクエリを扱うには十分でない。複数のEntityをJOINしたりしたいため。
Microsoft
1マイクロサービスは、Bounded Contextをまたいではいけない。
集約をマイクロサービスの単位にする
ドメインサービスをマイクロサービスの単位にする。
ThoughtWorks
既存のコードを活用してサービスを切り出すのはNG。
IKEA効果が発動して、分割のスピードが遅くなる。
モノリスの機能を少しずつ新規に書き下ろしたマイクロサービスとして切り出し、モノリスの元の機能を確実に消す。
モノリスの機能を消すところまでやらないとエントロピーが増大し続けてしまう。
Volatility Based Decompisition
ビジネスケイパビリティによる分割の対案として提唱されている。
業務機能で分割すると、クライアントからの呼び出し回数が多くなるし、マイクロサービスの変更により多くのクライアントも変更しなくてはならなくなる。
そこでユースケースに相当するMircoservicesのレイヤを設けることで、変更の影響を小さくするという手法。前述のKloiaと似た構成になる。
ここまでまとめ
だいたいの方向性は…
DDDでシステムを分析し、集約ルートおよびサブドメインで分割する。少なくともBounded Contextをまたいではいけない。
新規の開発/事業ドメインでMicroserviceアーキテクチャを採用するな
が共通した見解である。
集約ルートおよびサブドメインの分割がプロジェクトごとに検討するケースバーケースの要素を多分に含むので、これでは指針とは言い難い (これで、うまくやれる現場は多くはないよね…)。
Service by Lifecycle
そもそもCRUD単位にサービスを作るのはアンチパターンとされる。
Entity Serviceアンチパターン
https://gyazo.com/76af80c334072d0c3591b64425731e65
ECサイトをアカウント、商品、カート、注文のサービスに分割した例が取り上げられる。なお、このようなサイトでDDDを考えたときに、同様に「アカウント」「商品」「カート」「注文」を集約ルートとすることが多いのではなかろうか。後述のとおり、それではEntity Serviceアンチパターンである。これが集約ルートで「サービス分割しましょうねー」では指針として弱いと考える理由である。
このような分割は、結局上流のシステムが複数のサービス(Entity Service)を必要とすることが多い。例えば、Online Shopping Serviceで「カートの中の商品の合計価格を計算する」を実装するためには、カートサービスにアクセスし、含まれる商品の価格を取得するために商品サービスにアクセスし、税金を計算するために、アカウントサービスにアクセスする必要がある。
このように上流のサービスがたくさんのEntityサービスに依存するようになると、システム全体の可用性が損なわれるし、デプロイも慎重にならざるを得なくなってしまう。
https://gyazo.com/da783ef188942c1df6296cffdbe70d6b
間にユースケース層に相当するCart Pricerを置いても、事態はさほど好転しない。(Volatility Based Decompositionでみたように、Entityサービスの変更がフロントエンドへ波及することは防ぐことができる)
https://gyazo.com/dc64f58e8f19154f64e9ee78762e12d1
まずやるべきは意味的結合度を下げること!
システムの変動性からみたときの結合度には、以下5つが存在する。 Operational(運用上の結合度)
Development(開発上の結合度)
Semantic(意味的結合度)
Functional(機能結合度)
Incidental(偶発的結合度)
このうち意味的結合とは以下のようなものである。
例えばEコマースにおけるSKUを考える。
SKUは、価格、コスト、棚番号、商品説明、有効期限などの属性をもつ。
SKUに「プライスポイント」という概念を新たに導入する。仕入原価が少しずつ異なる商品でも、一律の価格を設定するもので、値付け作業の負荷を下げると同時に、消費者の購入時の比較検討負荷を下げる目論見がある。
SKUにプライスポイント属性を追加すると、SKUを受け取るすべてのシステムでプライスポイントが設定されているものはそちらを参照するように変更しなければいけなくなる。
データではなく振る舞いにフォーカスする
「サービスが何を知っているか?」(そうするとすぐCRUDに戻ってしまう)ではなく、「そのサービスが何をしてくれるのか?」を考えるようにする。
例えば以下のような融資システム考える。
融資申請書を作る。
融資申請を金融機関に提出し、アナリストが審査する。
審査と並行して、申請者の信用調査をする。
審査OKだったら、公開して出資者を募る
出資者が決まれば出資者は3日以内に送金する。
融資案件を中心に業務が回っていくので、下のようなサービス構成を考えがちである。
https://gyazo.com/929653626f6fb2e85191e7ecde1c27aa
が、これは融資サービス(Loan Service)が典型的な、EntityServiceである。(融資サービスの停止はシステム全体の停止に繋がるし、融資サービスのデプロイはモノリス時代と同じくらい気をつけなければならない)
融資エンティティには、「融資ステータス」があって、業務プロセスの進行にしたがって、これが更新されていく。ここで、融資エンティティに「融資ステータス」をもたせることをやめて、各業務プロセスごとにサービスを分けることにしてみよう。
https://gyazo.com/1035b03343330975ef4b4492c9b01608
先行の業務プロセスから、後続の業務プロセスが必要とするデータ作り受け渡していく。融資という意味的結合度は、分解されそれぞれの業務プロセスに必要なデータのリレーになるため、あるプロセスの変更影響が全体に波及することもない。
この考え方はイミュータブルデータモデルのロングタームイベントパターンにピタッとはまる。課題は各サービスで共通して参照するリソースエンティティをどう扱うか? だが、ここまでくればSam NewmanのBuilding Microservicesに書いてある内容でなんとかなりそう。