【WIP】Modular Monolith(モジュラーモノリス)についてまとめる

モジュラーモノリスについて

目次

モジュラーモノリスとは

  • モノリスからマイクロサービスへでは単一プロセスのモノリスのサブセットと紹介
  • 独立して作業が可能なモジュールで構成され、デプロイ時に結合
  • モノリスアプリケーション内で、ドメインモデル等を単位としてモジュールに分解し、モノリスのように1つのデプロイパイプラインだけを持ちつつも、マイクロサービスのようにシステムのモジュール化・独立性を両立
    • マイクロサービスアーキテクチャの場合OrderサービスやPaymentサービス等が独立したAPIとして分割されることでモジュール性・境界を定義している
    • 同じことをコードベース内でモジュールとして切り出し、他モジュールとのやりとりは公開用I/Fを用い、各モジュール間の境界を明確にすることで実現する

ShopifyのDeconstructing the Monolith: Designing Software that Maximizes Developer Productivityより

We’d have to maintain multiple different test & deployment pipelines and take on infrastructural overhead for each service while not always having access to the data we need when we need it. Since each service is deployed independently, communicating between services means crossing the network, which adds latency and decreases reliability with every call. Additionally, large refactors across multiple services can be tedious, requiring changes across all dependent services and coordinating deploys.

複数の異なるテストとデプロイメントのパイプラインを維持し、各サービスのためにインフラのオーバーヘッドを負担しなければならない一方で、必要なときに必要なデータに常にアクセスできるとは限りません。また、各サービスは独立して配置されているため、サービス間の通信はネットワークを介することになり、通信のたびに遅延が発生し、信頼性が低下します。さらに、複数のサービスにまたがる大規模なリファクタリングには、依存するすべてのサービスを変更し、デプロイを調整する必要があり、面倒な作業となります。

We wanted a solution that increased modularity without increasing the number of deployment units, allowing us to get the advantages of both monoliths and microservices without so many of the downsides.

私たちは、デプロイメントユニットの数を増やさずにモジュール性を高め、モノリスとマイクロサービスの両方のメリットを、多くのデメリットなしに得られるソリューションを求めていました。

メリット

多くの組織にとってモジュラーモノリスは優れた選択肢

  • モノリスとマイクロサービスのメリットを得つつも、モノリスやマイクロサービスのデメリットを削減可能
    • モジュールの境界が明確であれば、独立して作業可能
      • 開発組織のスケール
        • マイクロサービスアーキテクチャの特徴、採用する理由・メリットの一つの、独立デプロイ可能性とか自律性・責任をチームに持たせる話
        • モジュラーモノリスは、公開I/Fによるリクエストを要求する事でモジュール毎の独立性を担保し、それらモジュール(コードベースの一部)の所有権をチームに渡すことで上記を実現する
    • 分散システムであるマイクロサービスアーキテクチャの課題や複雑性、コスト面でのデメリットの緩和・回避
      • ネットワークとかパフォーマンスとかマイクロサービス間の認証認可とか監視とかCross-cutting concernとかトランザクション管理とか
      • 各チーム・サービスでデプロイパイプラインの構築・運用を行う必要がなくなる
  • 将来的なマイクロサービス移行容易性
    • モジュール毎にマイクロサービス化を進める事が可能
    • マイクロサービスでは、サービス境界を間違えると単一のモノリスを持つよりも辛くなるため「マイクロサービスの境界線をどのように定義するのか」は重要
      • スタートアップや開発初期段階でマイクロサービスが推奨されない理由の一つ
        • まだどの単位で切るのが有効かわかっていない
    • モジュラーモノリスの場合は、境界の決定に失敗・変化があった場合でもマイクロサービスと比較して低コストで修正が可能

デメリット・課題

モノリスであるが故に残るデメリット・課題がある

  • デプロイ・デリバリー衝突
    • ある程度の規模の開発組織でモジュラーモノリスを採用する場合、デプロイに何らかルール・工夫は必要そう
    • 各チーム・サービスでデプロイパイプラインの構築・運用を行う必要がなくなるのはコスト的にはプラス
  • マイクロサービスと比較し、境界を適切に維持し続けるコストがかかる
    • ネットワークを介して通信するマイクロサービスと違い、境界違反が容易
    • 境界違反をどう検知するかの検討はマイクロサービス以上に必要
  • e2eテストの実行時間の増大
  • データベース共有によりDB周りのモジュール性が失われ、マイクロサービス移行時に辛い思いする可能性
    • モジュラーモノリスでデータベースを分割、ドメイン結合を減らす事で緩和・準備は出来る
      • 各モジュールでDBを分割、他モジュールが管理するDBには専用のサービス、I/F経由でアクセス
      • 注文情報全体を共有せずに、配送指示という専用のドメインモデルを用いる

採用例・実装例

DB共有orモジュール単位でDB持つのか、モジュール間のやりとりどうするかなど色々パターンがある
ShopifyはDBが1つでモジュール間でのやりとりもネットワークを介してないっぽいが、Alp社ではDB独立も視野に入れつつ、コンテキスト間の通信はgRPCで行ってる

ShopifyのRailsアプリケーションの例

  • ビジネスドメイン・ドメインモデルでのモジュール化
    • 一つのアプリケーションがbilling, checkoutsなどのビジネスドメイン・ドメインモデルで切られた複数のモジュールで構成される
  • モジュール間の境界・依存関係の明確か
    • billing→checkoutsのアクセスは、controllerのメソッドなど公開I/F経由でのみ行う
    • マイクロサービスでは独立したシステム・APIとして独立してるのと、実質同じような事がモノリス内で実現
  • モジュール境界の違反検知
  • 参考

Alp社のScalaでの例

  • コンテキスト毎のsbtプロジェクト
  • デプロイ単位の集約とコンテキスト間通信を行うためのモノリスアダプタ
  • コンテキスト間通信はgRPC

Full Modular Monolith application with Domain-Driven Design approach

  • ドメイン駆動設計アプローチによる.NETのモジュラーモノリスアプリケーション
  • TODO: ちゃんと見れてないので後で更新

必要・向いているケース

  • スタートアップ・開発初期段階
    • 将来的なマイクロサービス移行を見込んでモジュラーモノリスを初期から採用するとか
      • モノリスよりはモジュール分割のコストがかかる
    • いきなりマイクロサービスにしてサービス境界を見誤る、変化した場合のダメージは大きい
    • モジュラーモノリスであれば同一コードベース内で完結するため、まだ修正コストは低い
  • シンプルなモノリスでは厳しい。マイクロサービスにはしたくない
    • 10~15人ほどのチーム内で、ドメインモデル単位で細かくマイクロサービスを作る必要性は薄いが、ただのモノリスにはしたくない・ある程度のモジュール性は何らかで持たせたい場合とか
    • shopifyの記事で述べられていたようにデプロイパイプラインを増やす必要はなく、各モジュール・チームに自律性等を与えたいだけならモジュラーモノリスでも良いかも
    • パフォーマンスに関してもネットワーク通信がなくなる分、モジュラーモノリスの方が有利

不必要・向いていないケース

  • 開発組織のスケールがモジュラーモノリスでは達成できない場合?
    • モノリスではあるので、あまりにも開発者が多い場合には流石に厳しくなる。50人とかは流石に怪しい
      • モジュラーモノリスのシステムを複数持つ構成はありな気がする
        • UberのDOMAのようにドメイン単位でシステムを切って、それらのシステムをモジュラーモノリスを構成する
        • イメージ(サービスの切り方とか配分は適当)
          • 開発者50人でマイクロサービス自体は3つ。各マイクロサービスをモジュラーモノリスで構成する
            • payment: 20人
            • order: 15人
            • delivery: 15人
        • マイクロサービスの数を出来るだけ少なくし、複雑性に対処するコストを下げて、可能な限りモノリス、モジュラーモノリスで開発してる期間を長くする
        • マイクロサービスのサイズ
  • 技術選定の自由を各チーム・マイクロサービスに与えたい場合
    • モノリスのため言語やFW等には縛られる
    • citadel architectureのようにその部分だけ独立させる所から始めるべきではある

そもそもマイクロサービスアーキテクチャ

  • 「マイクロサービスはアプリケーションを個別にデプロイできる疎結合のサービスコレクションとして構成するものだ」
    • chris richardson. Microservices Patterns.
  • 「ビジネスドメインに基づいてモデル化され、独立してデプロイ可能なマイクロサービス同士がネットワークを介して連携することによってなりたるアーキテクチャ」
  • 「マイクロサービスはカプセル化されたビジネス機能を一つ以上のネットワークエンドポイントを介して公開する。それらはネットワークを介して相互に通信しあい、分散システムを形成する。マイクロサービスはデータの保存や検索をカプセル化して、適切に定義されたインターフェイスを介してデータを公開する。データベースはサービス境界の内側に隠される」
    • monolith to microservices sam newman

キーワード

  • 独立デプロイ可能性
    • あるマイクロサービスに対する変更を他のサービスに影響なく本番環境へデプロイできること
      • 単にそれが行える以上に、実際にそのように管理されていることが重要
    • そのためにはサービス同士が疎結合である必要がある
      • データベースの共有がマイクロサービスで推奨されないのは、疎結合や独立デプロイが難しくなるため
  • ビジネスドメインに基づいたモデル化
    • ある機能を実装するのに、二つのサービスに変更を加えて、デプロイを行う必要があるなら、単一のサービス(モノリスなど)内で同じ変更を加えるよりも、多くの作業が必要になる
      • 特に開発初期フェーズで、モノリスの方が開発スピードが速くなる理由の一つ
    • マイクロサービス化する単位を誤れば余分なコストを払うことになる
  • 相互のネットワーク通信を介してシステムを構成

メリット

ただAPI・システムを分割すればこれらの利点が享受できるわけではなく、分散システムの複雑さを解決し、メリットを引き出すように設計・実装しなければいけない

  • デプロイ独立性、それに伴う開発組織の自立性・スケール
    • 各マイクロサービスの開発・デプロイを並行して行えるため、開発組織をスケールできる
  • 開発者が自分の担当しているマイクロサービス 、ビジネスドメインを理解すれば良い
    • 理解容易性
  • 技術選定の自由
    • 言語、フレームワーク、デプロイメントプラットフォーム、データベース、etc
  • 柔軟性

デメリット・課題

  • 複雑さ・コストの増大
    • マイクロサービスはアプリケーションを細かく分解することで、マイクロサービス単位で見た時の複雑さを軽減するために設計されている
    • しかしマイクロサービスアーキテクチャ自体がデプロイやメンテナンスに複雑さをもたらす
      • トレーシングとか監視とかトランザクションとかサービスディスカバリとかレポーティングとか
      • モノリスならトレーシングとか別に必要ない・必要性は薄くなる
        • その分コスト増加
  • デプロイのコスト
    • 各マイクロサービスごとにデプロイパイプラインを持つ
  • 分散のコスト
    • マイクロサービスは複数のコンテナに独立してデプロイする必要がある
    • 一つ一つのサービスのサイズは小さくなるが、その分のデプロイパイプライン構築、運用やサービスのオーケストレーションやメンテナンスにコストがかかる
  • パフォーマンス、レイテンシー
    • ネットワーク通信にかかるコストやらネットワーク起因の障害発生率増加とか
    • ネットワークを介した通信が増えれば増えるほど分散システムに関連した問題にぶつかる可能性が高くなる
  • トランザクション、データ整合性
    • モノリスでは単純だがマイクロサービスではより困難になる
    • 結果整合性やサーガパターンなど、より複雑な実装をして担保する必要がある
      • ex) EC
        • 商品購入時は実際の決済は走らない
        • 決済完了メールの受信、注文履歴への表示が行われる
        • その後決済が走り、失敗した場合に、決済失敗メールや注文履歴への反映が行われる
        • 同期的には処理しておらず、非同期でイベント経由でやりとり
  • 適切な分割単位を見つける難しさと失敗した時の影響
    • 過剰に分割・不適切な単位で分割する事で、逆に変更に弱くなり、可用性も下がる可能性
      • ex) 機能追加のために、多くのマイクロサービスをまたいで修正が必要になる
      • ex) 一時的結合の数が増えれば、その分レイテンシー悪化や障害発生する表面積が増える

Monoliths are the futureより
↑記事に対するアンサー的な他記事

Monoliths are the future because the problem people are trying to solve with microservices doesn’t really line up with reality. Just to be honest - and I’ve done this before, gone from microservices to monoliths and back again. Both directions.

Most people say,

“Look, we lost all of our discipline in the monolith. We just started creating classes, this person went and bought the Gang of Four book, came back and started doing design patterns and then QUIT, so half our codebase is doing this thing over here…”

So now it’s a nightmare. Now the codebase is so bad, and you say,

“You know what we should do? We should break it up. We’re gonna break it up and somehow find the engineering discipline we never had in the first place.”

And then what they end up doing is creating 50 deployables, but it’s really a distributed monolith. So it’s actually the same thing, but instead of function calls and class instantiation, they’re initiating things and throwing it over a network and hoping that it comes back. And since they can’t reliably make it come back, they introduce things like Prometheus, OpenTracing, all of this stuff. I’m like, “What are you doing?!”

Now you went from writing bad code to building bad infrastructure that you deploy the bad code on top of. There are reasons that you do a microservice. So, to me a microservice makes sense in the context of…

You’re a bank, and you have this big monolith that does everything. Then mobile comes out. You wanna do mobile banking, but it requires a different set of APIs. You don’t have to add that to the monolith. You can go create a new application that handles most of the mobile concerns, and then connect back to the existing infrastructure to do its work. That makes sense to me. But this idea of “microservices are a best practice” - it seems to be unpopular with most people’s initiatives.

マイクロサービスで解決しようとしている問題が現実と一致していないため、モノリスが未来である。正直に言うと、私は以前、マイクロサービスからモノリスになり、また戻ってきたことがあります。両方向です。
多くの人はこう言います。
"モノリスでは規律を失ってしまった。クラスを作り始めたばかりなのに、この人がGang of Fourの本を買ってきて、戻ってきてデザインパターンをやり始めたのに、辞めてしまったから、コードベースの半分はここでこんなことをやっているんだ......」と。
だから、今は悪夢です。コードベースがひどくなって、あなたはこう言いました。
"「どうすればいいかわかりますか?壊してしまおう。解体して、元々持っていなかった技術的な規律をなんとか見つけよう」と。
そして結局、50個のデプロイメント可能な製品を作ることになるのですが、それは実際には分散型モノリスです。つまり、同じことをしているのですが、関数呼び出しやクラスのインスタンス化の代わりに、何かを開始し、それをネットワークに投げて、戻ってくることを期待しているのです。そして、確実に戻ってくることができないので、PrometheusやOpenTracingなどのものを導入しています。私は「何をしているんだ!」と思いました。

悪いコードを書くことから、悪いインフラを構築して、その上に悪いコードをデプロイすることになったのです。マイクロサービスを導入するには理由があります。私にとってマイクロサービスが意味を持つのは、次のような場合だと思います。

あなたは銀行で、すべてをこなす大きなモノリスを持っています。そこにモバイルが登場します。モバイル・バンキングをやりたいが、それには別のAPIが必要だとします。しかし、モノリスに追加する必要はありません。モバイル関連のほとんどを処理する新しいアプリケーションを作成し、既存のインフラに接続してその作業を行えばいいのです。私にはそれが理にかなっていると思います。しかし、「マイクロサービスはベストプラクティスである」というこの考えは、ほとんどの人の取り組みでは不評のようですね。

マイクロサービスを選択する理由

  • チームの自律性を高める
    • Amazonの2枚のピザルール
    • Spotifyモデル
    • マイクロサービス以外の選択肢もある
      • 自律性、責任をどうチームに持たせるか
      • コードベースの一部の所有権を異なるチームに渡すことで実現できるかも
        • モジュラーモノリスはこの形
      • コードベースの一部を特定の開発者に渡すことも同じようなもの
        • 〇〇の分野は〇〇が詳しいから責任者にする
  • 市場投入までの時間を減らす
    • 個別のマイクロサービスが独立して変更をデプロイできることでリリースが迅速になる
    • 開発頻度や開発者の数によってはマイクロサービスにする必要はない・しても変わらないケースはある
      • 50人が10個のマイクロサービスを扱っている場合と、5人が10個のマイクロサービスを扱うのでは話が違う
  • 負荷への費用対効果が高いスケーリング
    • 特定プロセスだけを独立してスケーリングできる
    • 読み取りと書き込みが多いサービスでは、読み取りと書き込みを分離することでメリットが得られるかもしれ無い
      • 読み取りはメモリキャッシュしたいのでメモリを大量に欲しいかも
  • 堅牢性
    • 特定サービスが死んでもシステム全体をダウンさせる必要はなくなる
    • 単にシステムを分割して、ネットワーク通信をさせることで堅牢性が得られるわけじゃない
    • とりあえず複数のシステム・サービスに分割して、同期通信するだけではむしろ障害の表面積を増やすだけ
      • 一時的結合
        • 分散環境での同期呼び出し
          • 倉庫API → 注文API → 顧客API
          • これら三つのサービスは一時的に結合している
          • 障害発生したら波及するし、パフォーマンス的にも影響する
    • 一時的結合を避けるために非同期通信やイベント使ったり、サーキットブレイカー入れたり
  • 開発組織のスケール
    • デリバリー衝突を減らし、自律性を各チームに持たせる事で、開発スピードを落とさないまま開発組織のスケールができる
      • マイクロサービスで自動的に得られるわけではない
        • 所有権をどのように分離するかを失敗したら各チームで調整が必要なマイクロサービスアーキテクチャが出来上がる
      • モジュラーモノリスでもこの利点は得られる
        • とはいえデプロイ時に結合は発生するためその部分で調整が発生する
        • なので規模によっては怪しいケースが増えると想像出来る
  • 新技術の採用
    • マイクロサービス単位で技術選定ができる
    • 標準言語の選定など、完全な自由にしない方が良いケースもあるのでそれらを踏まえて考える必要はある

マイクロサービスを採用すべきでない時

  • 不明瞭なドメイン
    • サービス境界を間違えると単一のモノリスを持つよりも辛くなる
    • 開発者数と比較して、過剰に多いマイクロサービス数を持つことも辛みが強い
    • ドメインを把握できていない、分割することによるメリットが少ないならマイクロサービスを採用すべき理由は少ない
  • スタートアップ
    • まず最初はモノリスで作って、うまくいったらマイクロサービスにする事を基本的に考える方がいい
    • せめて境界線がはっきりしてる部分だけをマイクロサービスなどにすべき
  • 顧客の環境にインストールして使われるソフトウェア
    • 運用コストを顧客に払わせる
  • 最もな理由が無いとき
    • マイクロサービスによって何を達成しようとしているか明確に無い場合
    • 複雑性などに対処するコストやレイテンシーなどとトレードオフ
    • 複雑性を許容してまでマイクロサービスのメリットを得たい切実な理由が無いのであればマイクロサービスはやめておいた方がいい
      • システムをスケーリングしたいだけならマイクロサービスでなくてもできるのでは?
        • マイクロサービス・アーキテクチャの利点の1つは、個々のコンポーネントのスケーリングが簡単なこと
        • だがアプリケーションの個々のコンポーネントをスケールアップ、スケールアウトする必要が本当にあるのか?
      • システムのダウンタイムを削減する唯一の方法がマイクロサービスなわけでは無い
      • チーム内でドメインモデル単位でモジュール性を持たしたいだけならマイクロサービスはコストが高めな選択
        • チームの自律性に関しても同様
    • モノリスでもマイクロサービスでも究極的にはどちらもで開発はできる
    • 特に理由が無いならコストが低いモノリスにすべき

とりあえずまとめ

  • トレードオフ考慮して、マイクロサービスアーキテクチャにする事がトータルでメリットがあるならマイクロサービスアーキテクチャにすればいい
  • マイクロサービスアーキテクチャにしなくて済むならマイクロサービスにしない方がいい
    • KISSとYAGNI

uberのtech blogより

In other words, organizations adopt microservices for an operational benefit at the expense of performance. Organizations also must take on the cost to maintain the infrastructure necessary to support microservices. In many situations, it turns out, this trade-off makes a lot of sense, but it is also a strong argument against a premature adoption of a microservice architecture.

つまり、組織はパフォーマンスを犠牲にして、運用上のメリットのためにマイクロサービスを採用しているのです。また、マイクロサービスをサポートするために必要なインフラを維持するためのコストも負担しなければなりません。多くの状況では、このトレードオフは非常に理にかなっていることがわかりますが、マイクロサービス・アーキテクチャを早急に採用することを否定する強い論拠にもなります。

そもそもモノリス

デメリット・課題

  • 同じリポジトリ、システム、コードベースで作業する人が増えれば増えるほど、お互いの邪魔になる
    • デリバリー衝突
      • 誰が何を所有してるのか?誰が意思決定をするのか?
      • 所有権の境界線が曖昧・混乱してることによる課題
        • ここでの話とはそれるが、誰がメンテするのかわからない共有ライブラリの存在を思い出した。。。
      • マイクロサービスはシステムの境界をネットワーク越しで独立させることで所有権を定義してる
  • 初期段階、チームメンバーが少ない段階であえてマイクロサービスの複雑性を受け入れる必要はない。が時間が経過するにつれて以下のような事象が発生しがち
    • システムが機能追加、サービスの成長に応じて複雑化する
      • 技術的負債
        • システムの複雑化によりソースコードの変更を恐れ、リリース頻度が低下してしまったり、
        • 密結合なシステムが故にトラブルの影響が広範囲に広がってしまったり
      • 一部だけスケーリングすることができない
      • 一部だけに適した技術選定ができない
        • DHH氏はそこだけ分離させて、他はモノリスでいいじゃんって主張
      • 開発チーム、メンバーの増加に伴う、開発速度の低下
  • システムや開発組織のスケールに適切に対処し切れなかった故の技術負債
    • モノリスが悪いというわけではない
    • とはいえモノリスでは開発組織のスケールに対応するのが難しいフェーズは存在するので、そこでモジュラーモノリスやマイクロサービス

モノリスの種類

  • 単一プロセスのモノリス
    • 一般的に想像されるモノリス
    • 全てのコードが単一のプロセスとしてデプロイされているシステム
  • モジュラーモノリス
    • 上述
  • 分散モノリス
    • 複数のサービスで構成されているものの、何らかの理由でシステム全体を一緒にデプロイする必要があるシステム
    • 分散システム、マイクロサービスの欠点を持ち、単一プロセスモノリスの欠点も持つ。またどちらの利点も十分には持たない
    • サービス境界の定義や情報隠蔽やビジネスドメインに基づいたモデル化に失敗した環境で発生しやすい
      • サービスの境界を超えて変更が波及し、その結果局所的には問題なさそうな変更がシステムの他の部分を壊すような、密結合となったアーキテクチャが生み出される
      • 情報隠蔽
        • 頻繁に変更されるコードと、そうでないコードを分離する
        • モジュールの境界を安定したものにし、モジュールの実装で頻繁に変更されるであろう部分を隠蔽する
        • モジュールの互換性が維持されている限り、内部の変更を安全に行える
  • サードパーティ性のブラックボックスシステム

参考