モノリスをマイクロサービスに分割する方法

何をいつデカップリングするか

モノリシックシステムが大きくなりすぎて扱えなくなると、多くの企業はそれをマイクロサービスアーキテクチャスタイルに分割することに目を向けます。これは価値のある取り組みですが、容易ではありません。これをうまく行うには、単純なサービスから始める必要がありますが、その後、ビジネスにとって重要で頻繁に変更される垂直方向の機能に基づいたサービスを展開する必要があります。これらのサービスは最初は大きく、残りのモノリスに依存しない方が好ましいです。移行の各ステップが、全体的なアーキテクチャへのアトミックな改善を表していることを確認する必要があります。

2018年4月24日


Photo of Zhamak Dehghani

Zhamakは、エンタープライズにおける分散システムアーキテクチャとデジタルプラットフォーム戦略に重点を置いた、Thoughtworksの主任テクノロジーコンサルタントです。彼女はThoughtworksテクノロジーアドバイザリーボードのメンバーであり、Thoughtworksテクノロジーレーダーの作成に貢献しています。


モノリシックシステムをマイクロサービスのエコシステムに移行することは、壮大な旅です。この旅に乗り出す人々は、運用規模の拡大、変化のペースの加速、変化の高コストからの脱却といった願望を持っています。彼らはチーム数を増やしつつ、互いに並行して独立して価値を提供できるようにしたいと考えています。彼らはビジネスのコア機能を迅速に実験し、より迅速に価値を提供したいと考えています。また、既存のモノリシックシステムへの変更に伴う高コストから脱却したいと考えています。

いつどの機能をデカップリングし、どのように増分的に移行するかを決定することは、モノリスをマイクロサービスのエコシステムに分解する際のアーキテクチャ上の課題の一部です。この文書では、デリバリーチーム(開発者、アーキテクト、技術マネージャー)がこの分解に関する決定を旅の途中で下せるように導くことができるいくつかのテクニックを共有します。

テクニックを明確にするために、マルチティアのオンライン小売アプリケーションを使用します。このアプリケーションは、ユーザーインターフェース、ビジネスロジック、データレイヤーを密に結合しています。この例を選択した理由は、そのアーキテクチャが多くの企業が運用しているモノリシックアプリケーションの特徴を持っており、そのテクノロジースタックは、完全な書き直しと置き換えではなく、分解を正当化するのに十分近代的であるためです。

マイクロサービスエコシステムの目的地

開始する前に、マイクロサービスエコシステムについての共通の理解を持つことが重要です。マイクロサービスエコシステムとは、それぞれビジネス機能をカプセル化したサービスのプラットフォームです。ビジネス機能とは、ビジネスが特定のドメインで目的と責任を果たすために実行する業務を表します。各マイクロサービスは、開発者がセルフサービス方式で発見して使用できるAPIを公開します。マイクロサービスは独立したライフサイクルを持っています。開発者は、各マイクロサービスを独立して構築、テスト、リリースできます。マイクロサービスエコシステムは、自律的な長期間にわたるチームの組織構造を強制します。各チームは1つまたは複数のサービスを担当します。一般的な認識やマイクロサービスの「マイクロ」とは逆に、各サービスのサイズはほとんど問題ではなく、組織の運用成熟度に応じて異なる場合があります。Martin Fowler の言葉を借りれば、「マイクロサービスはラベルであり、説明ではない」ということです。

図1:サービスはビジネス機能をカプセル化し、セルフサービスAPIを通じてデータと機能を公開します

道のりガイド

ガイドに進む前に、既存のシステムをマイクロサービスに分解することには全体的なコストが高く、多くの反復が必要になることを知っておくことが重要です。開発者とアーキテクトは、既存のモノリスの分解が正しい道かどうか、そしてマイクロサービス自体が正しい目的地かどうかを綿密に評価する必要があります。それを明確にしてから、ガイドを見ていきましょう。

シンプルで比較的疎結合な機能でウォームアップ

マイクロサービスの道を歩み始めるには、最低限の運用準備が必要です。オンデマンドでデプロイメント環境にアクセスし、実行可能なサービスを独立して構築、テスト、デプロイするための新しい種類の継続的デリバリーパイプラインを構築し、分散アーキテクチャを保護、デバッグ、監視する機能が必要です。グリーンフィールドサービスを構築する場合でも、既存のシステムを分解する場合でも、運用準備の成熟度が必要です。この運用準備の詳細については、Martin Fowlerの記事Microservices prerequisitesを参照してください。朗報は、Martinの記事以来、マイクロサービスアーキテクチャを運用するためのテクノロジーが急速に進化していることです。これには、高速で信頼性が高く安全なマイクロサービスネットワークを実行するための専用のインフラストラクチャレイヤーであるサービスメッシュ、より高度なデプロイメントインフラストラクチャの抽象化を提供するコンテナオーケストレーションシステム、そしてマイクロサービスをコンテナとして構築、テスト、デプロイするためのGoCDなどの継続的デリバリーシステムの進化が含まれます。

私の提案は、開発者と運用チームが、分解または新規に構築する最初の2つのサービスで、基盤となるインフラストラクチャ、継続的デリバリーパイプライン、API管理システムを構築することです。モノリスから比較的疎結合されている機能から始めます。それらは、現在モノリスを使用している多くのクライアント側のアプリケーションへの変更を必要とせず、データストアを必要としない可能性があります。デリバリーチームがその時点で最適化しているのは、デリバリーアプローチの検証、チームメンバーのスキルアップ、独立してデプロイ可能なセキュアなサービスを提供するために必要な最小限のインフラストラクチャの構築です。セルフサービスAPIを公開します。例として、オンライン小売アプリケーションでは、最初のサービスは、モノリスがエンドユーザーを認証するために呼び出すことができる「エンドユーザー認証」サービスであり、2番目のサービスは、「顧客プロファイル」サービス、新しいクライアントアプリケーションに顧客のより良いビューを提供するファサードサービスにすることができます。

まず、シンプルなエッジサービスのデカップリングをお勧めします。次に、モノリシックシステムに深く組み込まれた機能をデカップリングする異なるアプローチを取ります。旅の初めは、デリバリーチームにとって最大のリスクはマイクロサービスを適切に運用できないことであるため、最初にエッジサービスを使用することをお勧めします。必要な運用前提条件を実践するためにエッジサービスを使用することをお勧めします。それを解決したら、モノリスの分割という主要な問題に対処できます。

図2:運用準備を構築するための変更範囲の小さい単純な機能でウォームアップする

モノリスへの依存関係を最小限に抑える

基本原則として、デリバリーチームは、新しく形成されたマイクロサービスのモノリスへの依存関係を最小限に抑える必要があります。マイクロサービスの大きな利点の1つは、高速で独立したリリースサイクルを持つことです。モノリスへの依存関係(データ、ロジック、API)があると、サービスがモノリスのリリースサイクルに結び付けられ、この利点が妨げられます。モノリスから離れる主な動機は、モノリスにロックされている機能の高コストと変化の遅さであることが多いため、モノリスへの依存関係を削除することで、これらのコア機能をデカップリングする方向に徐々に移行する必要があります。チームがこのガイドラインに従って機能を独自のサービスに構築すると、代わりにモノリスからサービスへの逆方向の依存関係が見つかります。これは、新しいサービスの変化のペースを遅くしないため、望ましい依存関係の方向です。

オンライン小売システムでは、「購入」と「プロモーション」がコア機能であるとします。「購入」は、チェックアウトプロセス中に「プロモーション」を使用して、購入しているアイテムに応じて、顧客が適格な最良のプロモーションを提供します。これらの2つの機能のどちらを次にデカップリングするかを決定する必要がある場合、「プロモーション」を最初にデカップリングしてから「購入」をデカップリングすることをお勧めします。この順序では、モノリスへの依存関係を削減できます。この順序では、「購入」は最初に新しい「プロモーション」マイクロサービスへの依存関係とともにモノリスにロックされたままになります。

次のガイドラインは、開発者がサービスをデカップリングする順序を決定するための他の方法を提供します。つまり、常にモノリスへの依存関係を回避できるとは限りません。新しいサービスがモノリスへのコールバックで終わる場合、モノリスから新しいAPIを公開し、アンチ腐敗レイヤーを新しいサービスで使用して、モノリスの概念が漏れないようにすることをお勧めします。モノリスの内部実装が異なる場合でも、明確に定義されたドメインの概念と構造を反映したAPIを定義することを心がけてください。この残念なケースでは、デリバリーチームはモノリスの変更、新しいサービスのテストとリリース、モノリスのリリースとの結合のコストと困難を負担することになります。

図3:モノリスへの依存関係を必要としないサービスを最初にデカップリングし、モノリスへの変更を最小限に抑える

密結合機能の早期分割

この時点で、デリバリーチームはマイクロサービスの構築に慣れており、厄介な問題に取り組む準備ができていると仮定します。しかし、モノリスへの依存関係なしに次にデカップリングできる機能に制限されていることに気づく場合があります。その根本原因は、多くの場合、モノリス内の機能がリーキーであり、ドメイン概念として明確に定義されておらず、モノリスの多くの機能がそれに依存していることです。進歩するためには、開発者はスティッキーな機能を特定し、それを明確に定義されたドメイン概念に分解し、次に具体化して個別のサービスにする必要があります。

例として、Webベースのモノリスでは、「(Web)セッション」という概念が、最も一般的な結合要因の1つです。オンライン小売の例では、セッションは多くの場合、配送や支払い設定などのさまざまなドメイン境界にわたるユーザー設定から、最近閲覧したページ、クリックした商品、ウィッシュリストなどのユーザーの意図やインタラクションまで、多くの属性のバケットです。デカップリング、デコンストラクション、そして現在の「セッション」の概念の具体化に取り組まない限り、将来の多くの機能をデカップリングしようとすると、リーキーなセッション概念を通じてモノリスと絡み合うため、苦労することになります。また、モノリスの外側に「セッション」サービスを作成することもお勧めしません。これは、現在モノリスのプロセス内で存在する同様の密な結合を、プロセス外でネットワーク全体に悪化させるだけだからです。

開発者は、スティッキーな機能から一度に1つのサービスずつ、マイクロサービスを段階的に抽出できます。例として、最初に「顧客のウィッシュリスト」をリファクタリングして新しいサービスに抽出し、次に「顧客の支払い設定」を別のマイクロサービスにリファクタリングするなど、繰り返します。

図4:最も結合している概念を特定し、デカップリング、デコンストラクション、そして具体的なドメインサービスに具体化します

Structure101などの依存関係と構造コード分析ツールを使用して、モノリスで最も結合し、制約のある要因機能を特定します。

垂直方向にデカップリングし、データを早期にリリースする

モノリスから機能をデカップリングする主な推進力は、それらを独立してリリースできるようにすることです。この第一原則は、開発者がデカップリングの実行方法に関する意思決定を行う際に、すべての意思決定を導くべきです。モノリシックシステムは、多くの場合、密接に統合されたレイヤー、または一緒にリリースする必要があり、壊れやすい相互依存関係を持つ複数のシステムで構成されます。例として、オンライン小売システムでは、1つまたは複数の顧客向けオンラインショッピングアプリケーション、状態を保持するための集中統合データストアを持つ多くのビジネス機能を実装するバックエンドシステムで構成されるモノリスがあります。

多くのデカップリングの試みは、最新のUI向けの開発者フレンドリーなAPIを提供するために、ユーザー向けコンポーネントと少数のファサードサービスを抽出することから始まります。一方、データは1つのスキーマとストレージシステムにロックされたままです。このアプローチは、UIの変更頻度を高めるなどの迅速な成果をもたらしますが、コア機能に関しては、デリバリーチームは最も遅い部分であるモノリスとそのモノリシックなデータストアと同じ速度でしか動けません。簡単に言うと、データをデカップリングしなければ、アーキテクチャはマイクロサービスではありません。すべてのデータを同じデータストアに保持することは、マイクロサービスの分散データ管理という特性に反しています。

戦略としては、機能を垂直に移動し、コア機能とそのデータをデカップリングし、すべてのフロントエンドアプリケーションを新しいAPIにリダイレクトすることです。

複数のアプリケーションが中央で共有されるデータへの書き込みと読み込みを行うことが、サービスとともにデータをデカップリングする上で最大の障壁となっています。デリバリーチームは、すべてのデータの読み取り/書き込みを同時にリダイレクトおよび移行できるかどうかによって、環境に適したデータマイグレーション戦略を取り入れる必要があります。Stripeの4段階のデータマイグレーション戦略は、データベースを介して統合するアプリケーションを段階的に移行する必要がある多くの環境に適用できます。ただし、変更中のすべてのシステムは継続して実行する必要があります。

図5:機能とそのデータを、新しいインターフェースを公開するマイクロサービスにデカップリングし、コンシューマーを変更して新しいAPIにリダイレクトする

ファサードのみをデカップリングする、バックエンドサービスのみをデカップリングする、そしてデータをデカップリングしないというアンチパターンを避けてください。

ビジネスにとって重要で頻繁に変更されるものをデカップリングする

モノリスから機能をデカップリングすることは困難です。私はニール・フォードが、精密な臓器手術のアナロジーを使っているのを聞きました。オンライン小売アプリケーションでは、機能を抽出するには、機能のデータ、ロジック、ユーザー向けコンポーネントを注意深く抽出し、新しいサービスにリダイレクトする必要があります。これは容易ではない作業量であるため、開発者はデカップリングのコストと得られるメリット(高速化やスケールアップなど)を継続的に評価する必要があります。たとえば、デリバリーチームの目的が、モノリスにロックされた既存機能の変更を高速化することである場合、最も変更されている機能を特定して抽出する必要があります。継続的に変更されており、開発者から多くの関心を集め、価値を迅速に提供することを最も阻害しているコードの部分をデカップリングします。デリバリーチームは、コードコミットのパターンを分析して、歴史的に最も変更されたものを調べ、それを製品ロードマップとポートフォリオに重ね合わせることで、近い将来に注目を集める可能性のある最も望ましい機能を理解することができます。ビジネスおよびプロダクトマネージャーと話し合って、ビジネスにとって本当に重要な差別化された機能を理解する必要があります。

たとえば、オンライン小売システムでは、「顧客パーソナライゼーション」は、顧客に最高のエクスペリエンスを提供するために多くの実験が行われている機能であり、デカップリングに適した候補です。これは、ビジネス、顧客エクスペリエンスにとって非常に重要な機能であり、頻繁に変更されます。

図6:最も重要な機能を特定してデカップリングする:ビジネスと顧客にとって最大の価値を生み出し、定期的に変更される機能

ソーシャルコード分析ツール(CodeSceneなど)を使用して、最も活発なコンポーネントを見つけます。ビルドシステムがすべてのコミットでコードに触れたり、自動生成したりする場合、ノイズからシグナルをフィルタリングするようにしてください。頻繁に変更されるコードを製品ロードマップの今後の変更と重ね合わせて、交点を特定してデカップリングします。

コードではなく機能をデカップリングする

開発者が既存システムからサービスを抽出したい場合、コードを抽出するか、機能を書き直すかの2つの方法があります。

多くの場合、デフォルトで、サービスの抽出またはモノリスの分解は、既存の実装を現状のまま再利用して、別のサービスに抽出することとして考えられています。部分的には、私たちが設計および作成したコードに対する認知バイアスがあるためです。構築の労力は、プロセスがどれほど困難で、結果がどれほど不完全であっても、私たちに愛着を抱かせます。これは実際にはIKEA効果として知られています。残念ながら、このバイアスはモノリスの分解の取り組みを妨げます。これにより、開発者、そしてより重要なことに技術マネージャーは、コードの抽出と再利用の高コストと低価値を無視するようになります。

あるいは、デリバリーチームは機能を書き直し、古いコードを廃止するという選択肢もあります。書き直しは、ビジネス機能を再検討し、ビジネスと話し合ってレガシープロセスを簡素化し、時間をかけてシステムに組み込まれた古い仮定と制約に挑戦する機会を提供します。また、テクノロジーの刷新の機会も提供し、その特定のサービスに最適なプログラミング言語とテクノロジースタックを使用して新しいサービスを実装します。

たとえば、小売システムでは、「価格設定とプロモーション」機能は、知的にも複雑なコードです。これにより、価格設定とプロモーションルールの動的な構成と適用が可能になり、顧客行動、ロイヤルティ、製品バンドルなど、さまざまなパラメーターに基づいて割引やオファーを提供します。

この機能は、おそらく再利用と抽出の優れた候補です。対照的に、「顧客プロファイル」は単純なCRUD機能であり、シリアライゼーション、ストレージと構成の処理のための定型コードで構成されているため、書き直しと廃止の優れた候補です。

私の経験では、分解シナリオの大部分において、チームは機能を新しいサービスとして書き直し、古いコードを廃止する方が良いでしょう。これは、以下のような理由による再利用の高コストと低価値を考慮したものです。

  • 実行時のアプリケーション構成へのアクセス、データストアへのアクセス、キャッシングなど、環境依存性に対処する定型コードが大量にあり、古いフレームワークで構築されています。この定型コードの大部分は書き直す必要があります。マイクロサービスをホストするための新しいインフラストラクチャは、数十年前のアプリケーションランタイムとは大きく異なり、非常に異なる種類の定型コードが必要です。
  • 既存の機能が明確なドメイン概念に基づいて構築されていない可能性が非常に高いです。その結果、新しいドメインモデルを反映していないデータ構造の転送または格納が行われ、大規模な再構築が必要になります。
  • 長年にわたって多くの変更を経てきた長寿命のレガシーコードは、コードの毒性レベルが高く、再利用の価値が低い可能性があります。

機能が関連していて、明確なドメイン概念と整合性があり、高い知的財産権がある場合を除き、コードの書き直しと廃止を強くお勧めします。

図7:毒性の低い高価値コードの再利用と抽出、毒性の高い低価値コードの書き直しと廃止

CheckStyleなどのコード毒性分析ツールを使用して、書き直しと再利用に関する意思決定を行います。

まずマクロ、それからマイクロ

レガシーモノリス内のドメイン境界を見つけることは、芸術と科学の両方です。一般則として、ドメイン駆動設計手法を適用して、マイクロサービスの境界を定義するバウンデッドコンテキストを見つけることは、良い出発点です。認めますが、あまりにも頻繁に、巨大なモノリスから非常に小さなサービス、つまり既存の正規化されたデータビューによってインスパイアされ、駆動される設計を持つ非常に小さなサービスへの過剰修正を見ています。サービス境界を特定するためのこのアプローチは、ほとんどの場合、CRUDリソースの多数の貧血性サービスのカンブリア爆発につながります。マイクロサービスアーキテクチャに慣れていない多くのユーザーにとって、これは、サービスの独立したリリースと実行のテストに最終的に失敗する、高い摩擦環境を作り出します。これは、デバッグが困難な分散システム、トランザクション境界を超えて破損しているため一貫性を維持することが困難な分散システム、組織の運用成熟度にとって複雑すぎるシステムを作成します。マイクロサービスがどれほど「マイクロ」であるべきかについては、いくつかのヒューリスティック(チームの規模、サービスの書き直し時間、カプセル化する必要のある動作量など)がありますが、私のアドバイスは、そのサイズは、デリバリーチームと運用チームが独立してリリース、監視、運用できるサービスの数によって異なります。論理的なドメイン概念を中心としたより大きなサービスから始め、チームが運用準備が整ったときにサービスを複数のサービスに分割します。

たとえば、小売システムのデカップリングの過程で、開発者は「ショッピングバッグ」の内容と「ショッピングバッグ」を購入する機能(つまり「チェックアウト」)の両方をカプセル化する1つのサービス「購入」から始める場合があります。より小さなチームを形成し、より多くのサービスをリリースする能力が高まると、「ショッピングバッグ」と「チェックアウト」を別のサービスにデカップリングできます。

図8:豊富なドメイン概念を中心としたマクロサービスをデカップリングし、準備ができたら、サービスをより小さなドメイン概念に分解する

リチャードソン成熟度モデルL3とハイパーリンクを使用して、呼び出し元に影響を与えることなくサービスの将来のデカップリングを可能にします(つまり、呼び出し元はチェックアウト方法を発見し、事前に知る必要はありません)。

アトミックな進化ステップで移行する

レガシーモノリスを美しく設計されたマイクロサービスにデカップリングすることで、空中に消えるという考えは、ある種の神話であり、望ましくないものです。経験豊富なエンジニアであれば、楽観的な完全完了計画で開始され、最適な状況でも適切な時点で放棄されたレガシー移行と近代化の試みに関する話を共有できます。そのような取り組みの長期計画は、マクロ条件が変化するため放棄されます。プログラムが資金を使い果たす、組織が焦点を別のものに変える、またはそれをサポートするリーダーシップが離れるなどです。そのため、チームがモノリスからマイクロサービスへの移行にどのようにアプローチするかに、この現実を設計する必要があります。私はこのアプローチを「アーキテクチャ進化の原子ステップでの移行」と呼びます。移行の各ステップは、アーキテクチャを目標状態に近づける必要があります。進化の各単位は小さなステップまたは大きな飛躍になる可能性がありますが、原子であり、完了するか、元に戻るかです。これは、全体的なアーキテクチャとサービスのデカップリングを改善するために反復的かつ増分的なアプローチを採用しているため、特に重要です。すべての増分は、アーキテクチャの目標に関して、より良い場所に私たちを残さなければなりません。進化アーキテクチャのフィットネス関数の比喩を使用すると、移行の原子ステップごとに、アーキテクチャのフィットネス関数は、アーキテクチャの目標により近い値を生成する必要があります。

例を用いて説明しましょう。マイクロサービスアーキテクチャの目標が、価値を提供するためのシステム全体の変更を開発者がより迅速に行えるようにすることだとします。チームは、OAuth 2.0プロトコルに基づいて、エンドユーザー認証を独立したサービスに切り離すことを決定します。このサービスは、既存の(旧アーキテクチャ)クライアントアプリケーションがエンドユーザーを認証する方法と、新しいアーキテクチャのマイクロサービスがエンドユーザーを検証する方法の両方を置き換えることを目的としています。「Authサービス導入」という進化の段階としましょう。この新しいサービスを導入する1つの方法は、まず以下の手順に従うことです。

(1) OAuth 2.0プロトコルを実装したAuthサービスを構築する。

(2) モノリスバックエンドに、リクエストを処理するエンドユーザーを認証するためにAuthサービスを呼び出す新しい認証パスを追加する。

ここでチームが作業を中断し、他のサービスや機能の構築に転向した場合、アーキテクチャ全体はエントロピーが増加した状態になります。この状態では、新しいOAuth 2.0ベースパスと古いクライアントのパスワード/セッションベースパスの2つのユーザー認証方法が存在します。この時点で、チームは変更を迅速に行うという全体的な目標からさらに遠ざかっています。モノリスコードの新しい開発者は、2つのコードパスに対処する必要があり、コードの理解における認知負荷の増加、変更とテストのプロセスの遅延につながります。

代わりに、チームは進化の原子単位に以下の手順を含めることができます。

(3) 古いクライアントのパスワード/セッションベースの認証をOAuth 2.0パスに置き換える。

(4) モノリスから古い認証コードパスを削除する。

この時点で、チームは目標アーキテクチャに近づいたと言えるでしょう。

図9:各ステップの後、全体的なアーキテクチャがその目標に向かって改善されるアーキテクチャ進化の原子ステップで、マイクロサービスに向かってアーキテクチャを進化させる。ただし、中間的なコード変更によって、その適合性目標から遠ざかる可能性があります。

モノリス分解の原子単位には、以下が含まれます。

  • 新しいサービスを切り離す。
  • すべてのコンシューマーを新しいサービスにリダイレクトする。
  • モノリスから古いコードパスを削除する。

アンチパターン:新しいサービスを切り離し、新しいコンシューマーに使用し、古いサービスを削除しない。

私は多くの場合、チームがモノリスから機能の移行を終え、古いコードパスを削除せずに新しい機能が構築された時点で勝利を宣言しているのを目にします(上記のアンチパターン)。その主な理由は、(a) 新しい機能を導入することによる短期的な利益に焦点を当てていること、(b) 新しい機能の構築のための競合する優先順位がある中で、古い実装を削除するために必要な労力の総量が多いことです。正しいことを行うためには、原子ステップをできるだけ小さくすることに努める必要があります。

このアプローチで移行することで、旅をより短い行程に分割できます。安全に停止し、復活し、この長い旅を生き延び、モノリスを打ち倒すことができます。


重要な改訂

2018年4月24日: 初版公開