BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル 分解とインクリメンタルな変更でモノリスをマイクロサービスに移行する

分解とインクリメンタルな変更でモノリスをマイクロサービスに移行する

キーポイント

  • Microservices migrations are not a trivial change. You have to understand if it will really solve your problem otherwise you might create a tangled entity that might kill you.
  • There are different types of monoliths and  some of them might work and be enough for business needs. Monoliths are not an enemy that should be killed.
  • Microservices is about independent deployability. There are some decomposition and incremental changes patterns that can help you to evaluate and migrate to a microservices architecture.
  • When you start to work with microservices, you will realize there are some really complex challenges as consequence. So microservices shouldn't be a default choice. You've got to think carefully about whether or they're right for you.

原文(投稿日:2021/02/09)へのリンク

この記事は、Leandro Guimarães氏がキャプチャし、Samがレビューした、QCon LondonでのSamのプレゼンテーションのトランスクリプトに基づいています。

 

QCon Londonで、モノリス分解パターンとマイクロサービスへのアクセス方法について話しました。私はそれらを厄介なクラゲと比較するのが好きです。なぜなら、それらは私たちを刺し、私たちを殺すかもしれないこれらのもつれた実体のようなものだからです。これは、平均的なエンタープライズマイクロサービスの移行で多くの共通点があります。

多くの組織が、ある種のデジタルトランスフォーメーションを遂げています。現在のデジタルトランスフォーメーションの表面をスクラッチすると、マイクロサービスが見つかります。現在、空港のラウンジには、Deloitte、DXC、Accentureなど、デジタルトランスフォーメーションを販売する主要なITコンサルタントの広告が掲載されているため、デジタルトランスフォーメーションは重大事です。マイクロサービスは大流行しています。

ただし、マイクロサービスについて話すときは、マイクロサービスの実装に使用するテクノロジーではなく、結果に焦点を合わせます。マイクロサービスアーキテクチャを選択する理由はたくさんありますが、私が繰り返し取り上げているのは、独立したデプロイ容易性の特性です。機能の一部があり、それはシステムの動作に加えたい変更です。私たちは、その変更をできるだけ早く解決したいと考えています。

図1: マイクロサービスのアプローチを示す簡単な図

これらのマイクロサービスアーキテクチャをモノリスと比較してください。私たちは、変更を加えることができない単一の侵入不可能なかたまりというビジョンをモノリスに持っています。モノリスは私たちの存在の中で最悪のもの、私たちの重荷と見なされるようになりました。私はそれはひどく不公平だと思います。この2、3年の「モノリス」という用語は、以前使用していた「レガシー」という用語に取って代わりました。これは根本的に問題です。人々はモノリスをレガシーと見なし始めているので、削除されるべきものになりました。それは非常に不適切だと思います。

モノリスの種類

モノリスには複数の形状とサイズがあります。モノリシックアプリケーションについて話すとき、私は主にデプロイメントの単位としてモノリスについて話します。単一のプロセスにパッケージ化されたすべてのコードである古典的なモノリスについて考えることができます。おそらくそれはTomcatのWARファイルやおそらくPHPベースのアプリケーションですが、すべてのコードはデータベースと通信する単一のデプロイ可能なユニットに一緒にパッケージ化されています。

この種類のモノリスは、単純な分散システムと見なすことができます。分散システムとは、非ローカルネットワークを介して相互に通信する複数のコンピュータで構成されるシステムです。この状況では、すべてのコードが1つのプロセスにパッケージ化され、重要なことに、すべてのデータが別のマシンで実行される1つの巨大なデータベースに存在します。すべてのデータを1つのデータベースに保存することは、将来的に多くの問題を引き起こすでしょう。

図2: モジュラーモノリス

モジュラーモノリスと呼ばれるこのシングルプロセスモノリスのバリエーションも検討できます。このモジュラーモノリスは、数十年経った今でも私たちを引きつけている構造化プログラミングに関する最先端のアイデア (1970年代初頭から!) を使用しています。図2に示すように、単一プロセスのモノリシックアプリケーションをモジュールに分割しました。モジュールの境界を正しく設定すれば、各モジュールで個別に機能します。ただし、デプロイメントのプロセスは本質的に静的にリンクされたアプローチのままです。デプロイメントを行うには、すべてのモジュールをリンクしなければなりません。多数のGEMファイルで構成されるRubyアプリケーション、NuGetパッケージ、またはMavenを介してアセンブルされたJARファイルについて考えてみてください。

まだモノリシックデプロイメントですが、モジュラーモノリスにはいくつかの重要な利点があります。コードをこれらのモジュールに分割すると、ある程度の独立した作業が可能になります。これにより、さまざまなチームが協力して、システムのさまざまな側面にアプローチして対処することが容易になります。これは非常に過小評価されているオプションだと思います。これに伴う問題は、人々はモジュールの境界を定義するのが苦手な傾向があるということです。さらに言えば、モジュールの境界を定義するのは得意ですが、それらの境界を維持するための規律を守るのは得意ではありません。残念ながら、構造化プログラミングまたはモジュール化の賢明な概念は、「巨大な泥だんご」の問題に陥る傾向があります。

私が協力している多くの組織は、マイクロサービスアーキテクチャよりもモジュラーモノリスを使用したほうがよいでしょう。過去3年間で、クライアントの半数に「マイクロサービスはあなた方には向いていません」と話しました。クライアントの何人かは私にさえ耳を傾けました。彼らの多くにとっての目的には、モジュール境界を定義する良い方法があることで十分です。彼らははるかに単純な分散システムとある程度の独立した自律的な機能を手に入れます。

図3: モジュラーモノリスのバリエーション

モジュラーモノリスにはバリエーションがあります。図3のものは少し奇妙に見えますが、特にスタートアップに対しては、マイクロサービスを延期したほうがよいと私がよく考えるものです。図3では、モジュラーモノリスを取り上げ、それをバックアップする単一のモノリシックデータベースを分解して、各モジュールのデータを個別に保存および管理しています。

これは奇妙に見えますが、これは最終的にはヘッジアーキテクチャです。モノリシックアーキテクチャを分解する上で最も難しいことの1つは、データ層であるという認識です。これらのモジュールにリンクされた個別のデータベースになると思われるものを事前に考え出すことで、後で個別のマイクロサービスに簡単に移行できるようになります。モジュール Cで作業している場合、モジュール Cに関連付けられたデータの完全な所有権と制御権があります。モジュール Cを分割したサービスにするときの移行時に楽になります。

私がまだThoughtWorksで働いていたときの私の古い同僚のPeter Gillard-Moss氏は、最初にこのパターンを見せてくれました。彼は私たちが取り組んでいる内部システムのためにそれを思いつきました。彼は言いました。「これでうまくいくと思います。サービスをやりたいかどうかわからないので、モノリスにすべきかもしれません」

私は言いました「ではやってみて、何が起こるか見てみましょう」昨年、約6年ぶりにPeter氏と話をしましたが、ThoughtWorksはまだアーキテクチャを変更していません。それはまだかなりハッピーに動いています。それらにはさまざまなモジュールで作業するさまざまな人々がいて、そのレベルでもデータを分離することは彼らに大きな利益をもたらします。

図4: 分散モノリス (不吉な響きのキュー)

今、私たちは最悪のモノリス、分散モノリスに到達しました。今、アプリケーションコードは、相互に通信する別々のプロセスで実行されています。何らかの理由により、ロックステップリリースではシステム全体を1つのユニットとしてデプロイする必要があります。多くの場合、これはサービス境界が間違っているために発生します。さまざまなレイヤにビジネスロジックを塗りつぶしています。結合と凝集に関する教訓に従わず、請求ロジックがサービススタック全体の15の異なる場所にあります。何かを成し遂げるためには、複数のチーム間で変更を調整する必要があります。組織内の多くの横断的な変更は、多くの場合、組織の境界またはサービス境界のいずれかが間違った場所にあることを示しています。

分散モノリスの問題は、本質的に分散システムであり、関連するすべての設計、実行時間、および運用上の課題がありますが、モノリスが要求する調整アクティビティがまだあることです。自分のものをライブでデプロイしたいのですが、それはできません。私はあなたの変更をするまで待たなければなりません、しかしあなたは他の誰かを待っているのであなたは変更をすることができません。さて、私たちは同意します。「さて、7月5日に、私たちは全員ライブになります。みんな準備はいいですか? スリー、ツー、ワン、そしてデプロイします」もちろん、それはいつもすべてうまくいきます。この種のシステムで問題はありません。

組織にフルタイムのリリース調整マネージャまたはそれらの方針に沿った仕事がある場合、分散したモノリスが存在する可能性があります。分散システムのロックステップデプロイメントを調整するのは楽しいことではありません。最終的には、変更コストがはるかに高くなります。デプロイメントのスコープははるかに大きくなります。より悪くなっていきます。おそらくリリースアクティビティだけでなく、一般的なデプロイメントアクティビティにも、固有の調整アクティビティもあります。

リーン生産方式をざっと調べただけでも、ハンドオフを減らすことがスループットを最適化するための鍵であることがわかります。他の誰かが私のために何かをするのを待つことは無駄を生み出します。それは私たちのスループットにボトルネックを生み出します。ソフトウェアをより迅速に出荷するには、ハンドオフを減らし、調整を減らすことが重要です。残念ながら、分散モノリスは、その調整を強制する環境を作り出す傾向があります。

時には、私たちの問題はサービス境界がどこにあるかではありません。時には、それは私たちがソフトウェア開発を行う方法から純粋に始めることができます。一部の人々は、リリーストレインを根本的に誤解しています。リリーストレインは常に、野心的な活動ではなく、是正的なリリース手法であると考えられていました。組織が継続的デリバリに移行するのを支援するために、リリーストレインのようなものを選びます。リリーストレインの概念は、定期的に、おそらく4週間ごとに、準備ができているすべてのソフトウェアがドアから出て行くというものです。ソフトウェアの準備ができていない場合は、次のリリーストレインにぶつかります。多くの組織にとって、これは一歩前進です。リリーストレインの間隔を短くし、最終的には完全に取り除くことになっています。ただし、あまりにも多くの組織がリリーストレインを採用し、先に進むことはありません。

多数のチームがすべて同じリリーストレインに向けて作業する場合、リリーストレインはすべてのソフトウェアの出荷の準備ができると出発します。そして、突然多くのサービスが一度にデプロイされます。これが本当の問題です。リリーストレインを演習するとき、重要なことの1つは、少なくとも、これらのリリーストレインを分解してチームのリリーストレインにすることです。自分のトレインが駅を出発するときに、それぞれのチームがスケジュールを設定できるようにします。最終的には、これらのトレインを取り除く必要があります。これらは、継続的デリバリーに向けた一歩に過ぎないと考えられます。

残念ながら、アジャイルのマーケティングにおけるいくつかの優れた取り組みにより、ソフトウェアをデリバリーするための究極の方法としてリリーストレインが成文化されました。多くの企業組織にぶら下がっている積層SAFeダイアグラムには、「リリーストレイン」というフレーズが明記されているため、彼らがそれを行ったことはわかっています。これは良くありません。SAFeやその他の問題に関係なく、リリーストレインは補習のようなものです。それは自転車の補助輪です。継続的デリバリーに向けて前進することになっています。問題は、これらのリリーストレインを長く使い続けると、すべてのサービスを一緒にデプロイすることに慣れるため、アーキテクチャが分散モノリスになってしまうことです。そのことに注意してください。一晩では起こらないかもしれません。独立したデプロイメントをサポートできるアーキテクチャから始めるかもしれませんが、リリーストレインを長く使い続けると、アーキテクチャはそれらのリリースプラクティスを中心に融合し始めます。

最終的に、分散モノリスは、分散システムのすべての複雑さと、単一のデプロイメントユニットの欠点を備えているため、問題があります。それを超えて、より良い仕事の仕方に移行したいと思うはずです。分散モノリスはトリッキーなものであり、それに対処する方法についてはたくさんのアドバイスがあります。時には、正しい答えはそれを単一プロセスのモノリスにマージして戻すことです。しかし、今日分散モノリスがある場合、最善の方法は、なぜそれがあるのかを解明し、新しいサービスを追加する前に、アーキテクチャの一部を独立してデプロイできるようにする動きを始めることです。その組み合わせに新しいサービスを追加すると、私たちの世界がはるかに困難になる可能性があります。

モノリスをマイクロサービスアーキテクチャに移行する方法

私たちは、独立したデプロイ容易性という特性のために、マイクロサービスアーキテクチャを使用します。他は何も変更せずに、サービスの変更をプロダクション環境にデプロイできるようにしたいと考えています。これがマイクロサービスの黄金律です。プレゼンテーションや記事では、それは本当に簡単に思えます。実生活では、特にほとんどの人がスクラッチから始めないことを考えると、実現するのははるかに困難です。大多数は、システムが大きすぎると感じており、それを小さなピースに分割したいと考えています。彼らはどこから始めればいいのだろうと思っています。

ドメイン駆動設計 (DDD) には、サービス境界を見つけるのに役立ついくつかの優れた方法があります。マイクロサービスの移行を検討している組織と協力しているときは、既存のモノリシックアプリケーションアーキテクチャでDDDモデリングの演習を行うことから始めることがよくあります。これは、モノリス内で何が起こっているかを把握し、ビジネスドメインの観点から機能単位を定めるために行います。

モノリスは巨大な箱のように見えるかもしれませんが、DDDを適用してモノリスに論理モデルを投影すると、内部が注文管理、PDFレンダリング、クライアント通知などで編成されていることがわかります。コードはおそらくこれらの概念に基づいて編成されていませんが、ユーザまたはビジネスドメインモデルの観点からは、これらの概念はコードに存在します。ここでは説明しませんが、DDDで「境界づけられたコンテキスト (Bounded Context)」と呼ばれることが多いビジネスドメイン境界は、分割の単位になります。

図5: モノリスの分割の単位と依存関係を見つける

最初に行うことは、私たちがどこから始めればよいか、何が優先であり、そして機能の単位は何かを尋ねることです。図5の最初のモノリスには、注文管理、請求、および通知があります。DDDモデリングの演習により、これらがどのように関連しているかがわかります。うまくいけば、これらの異なる機能間の依存関係の有向非巡回グラフを思い付くでしょう。(依存関係の循環グラフを得るには、さらに作業を行う必要があります)。このモノリスでは、顧客に通知を送信する機能に依存する多くのものが確認できます。それはドメインのコアの部分のようです。

チェックポイント: マイクロサービスは問題に対する正しい解決策でしょうか?

最初に何を抽出すべきかのような、質問を始めることができます。このレンズを通して純粋にそれを見ることができます。通知が多くのもので使用されていることがわかるかもしれません。マイクロサービスの方が優れている場合は、システムの多くの部分で使用されているものを抽出すると、より多くのものが改善されます。多分そこから始めるべきです。しかし、これらのインバウンド依存関係をすべて見てください。非常に多くのものが通知を求めているため、これを解きほぐして、既存のモノリシックアーキテクチャから取り除くことは困難です。モノリシックシステムでの請求や注文管理などの概念は、より自己完結しているようです。それらは分解しやすいものになるでしょう。そして、どの部分から始めるかを決定することは、基本的に分解へのインクリメンタルなアプローチを意味します。

何よりもまず、モノリスは敵ではないことを心に留めておいてください。私はそれについて本当に考えてほしい。人々は、モノリシックシステムを問題と見なしています。ここ数年で私が見た中で最も懸念されることの1つは、マイクロサービスが現在多くの人にとってデフォルトの選択肢であるように思われることです。

「IBMを購入したことで誰も解雇されたことはない」という古いことわざを覚えている人もいるかもしれません。この考えは、他のすべての人がIBMを購入していたので、あなたもIBMを購入したほうがよいというものでした。購入したものがうまくいかなかった場合、他のすべての人もそれをしているので、それはあなたのせいではありません。物議をかもすことを言う必要はありませんでした。今、誰もがマイクロサービスを行っているので、同じ問題が発生します。誰もがマイクロサービスを求めています。それは私にとって良いことです。私はこのテーマに関する本を書いています。あなたにとってそれは良くないかもしれません。

基本的に、それは解決しようとしている問題に帰着します。現在のアーキテクチャでは実現できないことを達成しようとしていますか? マイクロサービスが答えかもしれません、あるいは何か他のものが答えかもしれません。何を達成しようとしているのかを理解することが重要です。その理解がなければ、システムを移行する方法を確立するのが困難になるからです。私たちがやっていることは、システムを分解する方法と、その作業に優先順位を付ける方法を変えることです。

マイクロサービスの移行に使う比喩は、それはスイッチのようなものではないということです。オン/オフの切り替えはありません。ダイヤルを回してチューニングするようなものです。マイクロサービスを採用する際には、そのダイヤルを上げて、1つまたは2つのサービスを追加します。サービスが私たちにどのように機能するか、それが私たちに必要なものを提供するかどうか、それが私たちの問題を解決するかどうかを見たいと思っています。そして、それがうまくいき、気に入ったら、そのダイヤルを回し続けることができます。

しかし、多くの人がダイヤルを回して500のサービスを追加し、ヘッドフォンを接続して音量を確認するのを私は見ています。それは鼓膜を吹き飛ばすのに十分な方法です。直面することになる問題を知りません。つまり開発者のラップトップでヒットすることのない問題です。彼らはプロダクション環境でヒットするでしょう。モノリシックシステムから一度に500のサービスに移行すると、すべての問題が一度に発生します。最終的に1つ、2つ、または5つのサービスを提供するか、Monzoのように800または1,500のサービスを提供するかに関係なく、ダイヤルを少し回すだけで開始することが重要です。移行を開始するには、いくつかのサービスを選択する必要があります。私たちはそれらをプロダクション環境で実行し、その経験から学び、その学習を可能な限り迅速に進めます。このダイヤルを徐々に回し、新しいマイクロサービスをインクリメンタルに作成してリリースすることで、問題が発生したときにそれをより適切に検出して処理できます。それぞれのプロジェクトが直面する問題は、非常に多くの異なる要因があります。

モノリシックシステムからいくつかの機能を抽出し、残りのモノリスとの通信と統合をし、できるだけ早く実行したいと考えています。もうビッグバンの書き直しはしたくありません。毎年ユーザにソフトウェアを導入していたときは、12か月の期間がありそれで次のように言うことができました「既存のシステムを非常にひどく扱ったため、今は使用できませんが、次のリリースまで12か月あります。試して、システムを完全に書き直すことができ、過去の間違いを犯すことはなく、既存のすべての機能とさらに多くの機能があり、すべてうまくいくでしょう」

私たちが毎年ソフトウェアをリリースしていたとき、それは決して真実ではありませんでした。ソフトウェアが毎月、毎週、または毎日リリースされることを人々が期待するときに、私たちは今それをどのように正当化するのかわかりません。Martin Fowler氏の言い換えでは、「ビッグバンの書き直しを行う場合、確実なのはビッグバンだけです」。私はアクション映画の爆発が好きですが、ITプロジェクトにおいては好きではありません。これらの変更をどのように行うかについて、少し異なる考え方をする必要があります。

モノリスからの最初のマイクロサービスのデプロイ

私はアーキテクチャの漸進的な進化の大ファンです。アーキテクチャが修正されたと見なすべきではありません。モノリスをマイクロサービスに向けて段階的に移行するのに役立つパターンが必要です。

最初に見たアプリケーションパターンの1つは、木の天蓋に根を下ろし、木の周りに巻きひげを送り、幹に巻き付いた植物にちなんで名付けられた絞め殺しの木 (the strangler fig) です。絞め殺しの木は、それ自体では十分な日光を得るのに十分な日光を得るのに林冠に上ることができなかったので、通常の木のように苗木から成長する代わりに、既存の構造を包み込みます。それは木の既存の高さと強さに依存しています。時間が経つにつれて、これらのイチジクが成熟して大きくなるにつれて、それらは自立することができるかもしれません。下にある木が枯れて腐ってしまうと、イチジクの木は真ん中に中空の柱が残ります。これらのものは、ワックスが他の木の周りに滴り落ちたように見えます — 本当に邪魔な見た目のものです。

ただし、このアイデアは、アプリケーション移行戦略のパターンとして役立ちます。 私たちは、望むすべてのことを実行する既存のシステム、既存のモノリシックアプリケーションに採用し、新しいシステムをその周りにラップし始めます。私たちの場合、それがマイクロサービスアーキテクチャになります。絞め殺しの木のアプリケーションを実装するには、2つの鍵があります。1つはアセットのキャプチャです。これは、マイクロサービスアーキテクチャに移行する機能を特定するプロセスです。次に、呼び出しを転送できるようにする必要があります。モノリシックアプリケーションに行っていた呼び出しは、新しい機能が存在する場所に転送する必要があります。機能が移行されていない場合、それらの呼び出しは転送されません。それはとても簡単です。

機能を移行する方法について混乱する人もいます。運が良ければ、コードをコピーするだけでよいかもしれません。請求サービスのコードがモノリシックコードベースの「請求」と呼ばれる素敵なボックスに入っている場合は、それを切り取って新しいサービスに貼り付けることができます。それがあなたのコードベースの状態であるなら、あなたはおそらく助けを必要としないだろうと私は主張するでしょう。たいていは、請求書のすべてのビットを収集しようとして、システムを急いで通してみる必要があります。おそらく、リファクタリング前の演習を行うことになります。そのコードを再利用できるかもしれませんが、その場合は、カットアンドペーストではなく、コピーアンドペーストになります。後で説明する理由から、機能をモノリスのままにしておきたいと思います。多くの場合、人々はいくつかの書き直しを行います。

絞め殺しの木を実装する方法はたくさんあります。昔ながらのHTTPを使用する単純なものを見てみましょう。

HTTPを介して駆動されるモノリシックシステムがあるとします。これはヘッドレスアプリケーションであっても可能です。ユーザインターフェイスの下にあるAPI境界で呼び出しを受けることができるでしょう。必要なのは、呼び出しをリダイレクトできるものなので、ある種のHTTPプロキシを利用します。HTTPがこれらの種類のアーキテクチャのプロトコルとして非常にうまく機能する理由は、HTTPが呼び出しの透過的なリダイレクトに非常に適しているためです。HTTPを介した呼び出しは、さまざまな場所に転送できます。そこにあるソフトウェアの負荷をあなたのためにこれを行うことができ、それは非常に簡単です。

図6: HTTPプロキシはモノリスへの呼び出しをインターセプトし、ネットワークホップを追加します

最初に行うことは、アップストリームトラフィックとダウンストリームモノリシックシステムの間にプロキシを配置することだけです。このプロキシをプロダクション環境にデプロイします。この時点では、呼び出しは転送されていません。それが機能するかどうかは、プロダクション環境で確認できます。ネットワークホップを追加したため、心配することの1つはネットワークの品質です。呼び出しは通常、モノリシックシステムに直接送信されますが、プロキシを経由するようになりました。これらの状況では、レイテンシーがキラーです。プロキシを介した迂回は、既存の呼び出しにほんのわずかなミリ秒のオーバーヘッドを追加するだけですが、10ミリ秒未満が最適です。ネットワークホップが1つ増えて200ミリ秒の遅延が発生する場合は、最初に解決する必要のある他の大きな問題があるため、マイクロサービスの移行を一時停止する必要があります。

プロキシが機能しているので、次に新しい請求サービスを使用します。それをプロダクション環境にデプロイします。まだ使用されないため、完全には機能していなくても、安全に行うことができます。デプロイメントのアイデアをプロダクション環境に分離し、頭の中で使用する必要があります。マイクロサービスを始めるときは、デプロイメントメカニズムが機能することを確認するために、定期的に機能をプロダクション環境にデプロイしたいと考えています。機能を追加するときに、新しいサービスを個別にテストできます。まだユーザにはリリースされていませんが、プロダクション環境にあります。ダッシュボードに接続したり、ログの集計が機能していることを確認したり、その他の操作を実行したりできます。

重要なのは、1つのサービスで動作することです。その1つのサービスの抽出を、スケルトンサービスの起動、メソッドの実装、プロダクション環境でのテスト、リリースのデプロイなど、多くの小さなステップに分割することもできます。準備ができたら、機能が古いシステムと同等であると考えたら、プロキシを再構成して、呼び出しを古いモノリス機能から新しいマイクロサービスに転送します。

この段階で、モノリスから古い機能を削除する必要があると思うかもしれません。まだこれをしないでください! プロダクション環境で新しいマイクロサービスに問題が発生した場合は、非常に高速に修復手法を使用できます。プロキシ構成を元に戻して、トラフィックを元の機能のモノリスに戻すだけです。ただし、これを機能させるには、データの役割を考慮する必要があります。- すぐに戻ってきます。

この機能をマイクロサービスに抽出することを真のリファクタリングにし、コードの構造を変更しますが、その動作は変更しないでください。マイクロサービスは、モノリスの同等の機能と機能的に同等である必要があります。マイクロサービスが正常に機能していることに満足するまで、それらを切り替えることができるはずです。

機能のどの実装がライブであるかを切り替える機能を保持したい場合は、移行が完了するまで新しい機能を追加したり、既存の機能を変更したりしないことが重要です。また、機能の実装のライブを切り替える機能も必要ありません。。

この単純な絞め殺しの木のテクニックは、多くの状況で驚くほどうまく機能します。この例ではHTTPを使用しましたが、FTPでもこれが機能することを確認しました。これをメッセージインターセプターで行いました。これを固定ファイルのアップロードで行いました。固定ファイルを挿入し、新しいサービスに必要なものを取り除き、残りを渡します。

図7: マイクロサービスアーキテクチャの有向非巡回グラフ

モノリス移行を進化させる「抽象化によるブランチ」パターンの使用

絞め殺しの木は、請求書発行や注文管理など、図7のモノリス内の依存関係のグラフに示されているように、コールスタックの上位に位置する機能の一部として非常にうまく機能します。ロイヤリティに対してポイントを付与する機能、または顧客に通知を送信する機能のようなものはモノリスに入るコールがありません。このコールには「注文する」または「請求書を支払う」があります。これらの操作の副作用としてのみ、ポイントを付与したり、メールを送信したりできます。その結果、たとえば、モノリスの境界外でのロイヤリティや通知への呼び出しを傍受することはできません。モノリスの中でそれをしなければなりません。

通知を抽出するとします。システムの残りの部分を壊さないように、その機能の一部を抽出し、それらのインバウンドリンクを段階的にインターセプトする必要があります。

「抽象化によるブランチ」と呼ばれる手法がうまく機能します。抽象化によるブランチは、トランクベースの開発のコンテキストでよく議論されるパターンであり、ソフトウェアを開発するための優れた方法です。抽象化によるブランチは、このコンテキストのパターンとしても役立ちます。既存のモノリシックシステムに、同じ機能の2つの実装が共存できるスペースを作成します。多くの点で、これはリスコフの置換原則の一例です。これは、まったく同じ抽象化の別個の実装です。この例では、既存のコードから通知機能を抽出します。

図8: マイクロサービスへの移行に使用される抽象化によるブランチ

通知コードはシステム全体に散らばっています。最初に実行したいのは、新しいサービスのために通知機能をすべて収集することです。サービスを抽象化ポイントの背後に隠します。請求コードと注文コードが明確な抽象化ポイントを介してこの機能にアクセスできるようにします。はじめ、その通知抽象化の実装が1つあります。これは、モノリス内にある既存の通知関連機能をすべてカプセル化したものです。SMTPライブラリ、Twilio、SMSの送信など、すべての呼び出しがこの実装にバンドルされます。

この時点で、私たちが行ったのは、コード内に優れた抽象化ポイントを作成することだけです。ここでやめることができます。コードベースを明確にし、テストしやすくなりました。これですでに改善されています。これは、古き良きリファクタリングのビットです。ただし、請求書や注文で使用する通知の実装を変更する機会も作成しました。実際に機能を出荷するなどの他の作業を行いながら、このリファクタリング作業を数日または数週間にわたって行うことができます。

次に、通知サービスの新しい実装の作成を開始します。これは2つのビットに分割されます。モノリス内に存在する新しいインターフェイスの実装がありますが、それはもう1つのビットである新しい通知マイクロサービスを呼び出すクライアントコードになります。これらの実装はまだ使用されないため、安全にデプロイできます。コードをより頻繁に統合し、マージの労力を減らし、すべてが機能することを確認します。

モノリス内の新しいサービスコールの実装とモノリスの外にある通知サービスのペアリングが機能したら、使用している抽象化の実装を切り替えるだけです。フィーチャートグル、テキストファイル、特定のツール、またはこれに必要なものが使用できます。古い機能はまだ削除されていないため、問題が発生した場合は、フリックして元に戻し、古い機能に戻すことができます。繰り返しになりますが、この1つのサービスの移行は多くの小さなステップに分割されており、すべてのステップで可能な限り迅速にプロダクション環境に移行しようとしています。

すべてが機能したら、コードをクリーンアップすることを選択できます。不要になったフィーチャーフラグを削除することも、古いコードを削除することもできます。すべてのコードを1つの素敵なボックスに入れるのに少し時間を費やしただけなので、古いコードを簡単に削除できるようになりました。そのクラスを削除して、なくします。モノリスを小さくし、誰もが自分自身に満足します。

並行稼動を使用したマイクロサービスの移行の検証

コードの再構築またはリファクタリングに関しては、Michael Feathers氏による「Working Effectively with Legacy Code (レガシーコード改善ガイド)」を強くお勧めします。彼のレガシーコードの定義は、テストのないコードです。この本には、既存のシステムを中断することなく、コードベースでこれらの抽象化を見つけて作成する方法についての素晴らしいアイデアがたくさんあります。マイクロサービスにしなくても、抽象化ポイントを作成するだけで、コードがより適切でテスト可能な状態になる可能性があります。

古い実装をすぐに削除しないのは良いことだと強調しました。両方の実装を同時に行うことには利点があります。これにより、ソフトウェアをどのようにデプロイおよびロールアウトするかについての興味深いアプローチが開かれます。呼び出しが抽象化ポイントに入ると、両方の実装への呼び出しがトリガされる可能性があります。これは並行稼動と呼ばれます。これは、新しいマイクロサービスベースの実装が同じ機能的同等性を持っていることを確認するのに役立ちます。その機能の両方のコピーを実行してから、結果を比較します。

比較するには、両方の実装を実行して結果を比較するだけです。両方をカスケードしたくないため、信頼できる情報源としてそれらのみを指定する必要があります。たとえば、通知の場合、1つだけを送信したいときに2つの電子メールを送信する可能性があります。並行稼動は、機能的同等性だけでなく、許容可能な非機能性についても、有用な直接のライブ比較です。適切な電子メールを作成したか、適切なダミーSMTPサーバに送信したかをテストするだけでなく、その新しいサービスが同じくらい迅速に応答するか、許容可能なエラー率の範囲内で応答するかをテストします。

通常、古い機能は信頼できる実装であり、その結果を使用します。それらを一定期間並行に実行し、新しい実装で許容できる結果が得られれば、最終的に古い実装を取り除くことができます。

GitHubではこれを行っています。彼らはGitHub Scientistと呼ばれるライブラリを作成しました。これは、さまざまな抽象化をラップしてスコアリングするための小さなRubyライブラリです。これを使用して、アプリケーションの重要なコードパスをリファクタリングしている場所でライブ比較を行うことができます。GitHub Scientistは、さまざまな言語に移植されており、どういうわけかPerl用に3つの異なる移植が含まれています。明らかに、並行稼動はPerlコミュニティでは重要なことです。アプリケーション内で並行稼動を実行する方法については、たくさんの良いアドバイスがあります。

デプロイメントをリリースのアイデアから分離する: 根本的な変化

基本的に、リリースのアイデアからデプロイメントのアイデアを分離する必要があります。従来、これら2つのアクティビティは1つであると見なされていました。ソフトウェアのデプロイは、プロダクションユーザへのリリースと同じです。これが、誰もがプロダクション環境で何かが起こることを恐れている理由であり、プロダクション環境がゲート環境になる理由です。

これら2つの概念を分離することができます。何かをプロダクション環境にデプロイするという行為は、それをユーザにリリースすることと同じではありません。このアイデアは、現在人々が「プログレッシブデリバリ」と呼んでいるものを支えています。これはカナリアリリース、ブルー/グリーンデプロイ、ダークローンチなど、さまざまな手法の総称です。ソフトウェアはすぐにリリースできますが、顧客に公開する必要はありません。私たちはそれをプロダクション環境に移し、そこでテストし、自分たちでどんな痛みにも耐えることができます。

リリースからデプロイメントを分離すると、デプロイメントのリスクが大幅に減少します。私たちは変化を起こすことに勇敢になります。より頻繁なリリースを行うことができ、リリースのリスクははるかに低くなります。

RedMonkの共同創設者であるJames Governor氏は、同社のブログでプログレッシブデリバリの素晴らしい概要を説明しています。プログレッシブデリバリを検討してください、最も重要なポイントは、アクティブデプロイメントはアクティブなリリースと同じではなく、そのリリースアクティビティがどのように起きるかを制御できることです。

マイクロサービスアプローチでの単純なデータアクセスの移行

図9に示すように、既存のモノリシックアプリケーションとデータがシステムにロックされています。請求機能を抽出することにしましたが、データにアクセスする必要があります。

図9: 新しいマイクロサービスから旧データへのアクセス

オプションの一つは、モノリスのデータに直接アクセスすることです。モノリスでの請求実装とマイクロサービスでの請求実装をテストして切り替える場合は、これら2つの実装間でデータの互換性と整合性が必要であり、それを実現します。これは短期間は許容されますが、データベースの黄金のルールの1つに反します。つまり、データベースを共有してはなりません。それが引き起こす根本的な結合の問題により、長期的に依存するものではありません。独立したデプロイ容易性を維持したいと考えています。

図10: 新しいマイクロサービスから旧データに直接のアクセス

図10では、配送サービスとデータベースがあり、他の誰かがデータにアクセスすることを許可しています。内部実装の詳細を外部の関係者に公開しています。配送サービスの開発者にとって、何が安全に変更できるかを知ることは困難です。共有されたものと隠されたものの間を分けるものはありません。

1970年代に、David Parnas氏は「情報隠蔽」と呼ばれるこの概念を開発しました。これは、モジュラー分解についての考え方です。モジュールまたはマイクロサービスの境界内にできるだけ多くの情報を隠したいと考えています。データベースを直接公開するのではなく、データを共有するための明確に定義されたサービスインターフェイスを作成すると、そのインターフェイスにより、配送サービスの開発者は、契約と外部に公開できる内容について明確に理解できます。開発者がその契約を維持している限り、彼らは配送サービスでやりたいことを何でもすることができます。これは、これらのサービスの独立した進化と開発を可能にします。非常に限られた状況を除いて、データベースに直接アクセスしないでください。

直接アクセスから離れには、2つのオプションがあります。他のデータにアクセスするか、自分のデータを保持するかです。この例で、新しい請求マイクロサービスが信頼できる情報源として十分であると判断したと想像してみてください。

現時点で、使用したいデータが他のデータである場合、それはおそらくデータがモノリスに属していることを意味し、モノリスにそれを要求する必要があります。モノリス自体にある種の明示的なサービスインターフェイス (この例ではAPI) を作成し、必要なデータをフェッチできます。

図11: モノリスの公開されたサービスインターフェイスを使った新しいマイクロサービス

私たちのは請求サービスであり、注文サービスではありませんが、たとえば注文データが必要になる可能性があります。注文機能はモノリスに存在するため、そのデータを取得するためにそこに行きます。この種の観点から、このスキームでは、さまざまなデータセットを公開するときにモノリス上のサービスインターフェイスを定義します。そうすることで、モノリスから出現する他のエンティティの形を確認できます。エイリアンのチェストバスターのように、モノリスの内側を突き破って出てくるのを待っている注文サービスを見つけるかもしれませんが、このコンテキストで、モノリスをJohn Hurt氏が演じると、死んでしまいます。

データのもう1つのオプションは、サービス独自のデータです。この例では、モノリスのデータベース内の請求データです。この時点で、データを請求データベースに移動する必要がありますが、これは非常に困難です。既存のシステム、特にリレーショナルデータベースからデータを取り出すと、多くの問題が発生します。それが生み出す可能性のある課題の簡単な例を見ることにします。結合の処理方法であるディープエンドに私たちを投げ込みます。

最後の試練: 結合操作の処理

図12: コンパクトディスクをオンラインで販売するモノリス

図12は、コンパクトディスクをオンラインで販売する既存のモノリシックアプリケーションを示しています (この例をどのくらい長く使っているかがわかりますね)。カタログ機能は、いろいろなもののコストを認識し、情報を明細テーブルに格納します。財務機能は、財務トランザクションを管理し、データを元帳テーブルに保存します。私たちがしなければならないことの1つは、毎週販売されている上位10枚のアルバムのリストを生成することです。これは、この状況での簡単な結合操作です。元帳テーブルで選択を行い、上位10社を引き出します。行とその他すべてに基づいて、その選択を制限します。これにより、IDのリストを取得できます。

図13: コンパクトディスクをオンラインで販売するマイクロサービスアーキテクチャ

マイクロサービスの世界に移行するときは、アプリケーション層で結合操作を行う必要があります。財務データベースから財務取引を引き出します。私たちが販売する商品に関する情報は、カタログデータベースに掲載されています。そのトップ10リストを生成するには、元帳テーブルからベストセラーのIDを取得してから、カタログマイクロサービスにアクセスして、販売したアイテムに関する情報を要求する必要があります。以前はリレーショナル層で行っていた結合操作が、アプリケーション層に移動します。

これは、レイテンシーなどの点で恐ろしいものになる可能性があります。結合操作のための1回の往復を行う代わりに、財務サービスを1回呼び出して上位10個のIDを取得し、次にカタログサービスに呼び出してそれらの10個のIDを要求し、次にカタログサービスがカタログに要求します。それらの10個のIDのデータベースを作成すると、応答が返されます。図13は、懸念事項を示しています。

図14: より多くのホップとレイテンシーに導くマイクロサービスアーキテクチャ

リレーショナルデータベースが参照整合性をどのように強制できるかという、この状況でのデータ整合性の欠如などの問題についてさえ触れていません。

結論

レイテンシーやデータ整合性の処理などに関する問題をさらに深く掘り下げたい場合は、私の著書「Monolith to Microservices (モノリスからマイクロサービスへ)」で詳細に概説されています。

マイクロサービスへの移行を自分で進めることにしたかどうかにかかわらず、自分が何をしているのか、そしてその理由について慎重に検討することをお勧めします。マイクロサービスを作成するアクティビティに固執しないでください。代わりに、達成しようとしている成果について明確に心がけてください。マイクロサービスはどのような成果をもたらすと思いますか? 代わりにそれに焦点を合わせてください — そもそもマイクロサービスの複雑な世界に入らなくても同じ成果を達成できることに気付くかもしれません。

著者について

Sam Newman氏は、人々がソフトウェアを迅速に出荷するのを支援することを専門とする独立コンサルタントです。氏はクラウド、継続的デリバリー、マイクロサービスを幅広く扱っており、特に、動作するソフトウェアをより簡単にプロダクション環境にデプロイする方法を理解することに夢中になっています。過去数年間、氏はマイクロサービスアーキテクチャの機能を探求してきました。

この記事に星をつける

おすすめ度
スタイル

BT