martinfowler.com の Microservices を読み解く

こんにちは。Synergy! 開発チームの松本です。

以前の記事でも少し触れたのですが、当社シナジーマーケティングのプロダクトである Synergy! は、2015 年以前に作られたモノリスと、それ以降に作られたマイクロサービスのハイブリッド型として稼働しています。

この数年、マイクロサービスを実践してきてつくづく感じているのは、全てのチームがマイクロサービスアーキテクチャスタイルの本質を理解した上で開発や運用を行うということの難しさです。「分散されたモノリス」なんていう話も聞きますが、これもやはり、本質を理解しないままマイクロサービスを実践した例です。

2014 年に書かれた martinfowler.com の記事 "Microservices" は、マイクロサービスアーキテクチャを 9 つの特性に分解してその本質を述べた素晴らしいドキュメントです。これをチームの教育に使うことで前述の課題を解決しようと考えたのですが、ひとつ大きな問題がありました。記事を読み解くことが非常に難しいのです。

その原因は、"Microservices" が英語の長文だという点もありますが、様々な技術トレンドやソフトウェア工学の知識などを背景に語られている個所が多い点にあるように感じます。そのため、一度読んだだけではドキュメントの各セクションのテーマや、書かれていることの因果関係を理解することが困難なようです。

というわけで今回のブログ執筆の目的は、今後のチームの教育に利用できるよう、"Microservices" を読み解いたものをドキュメントとして残すこと、となります。

マイクロサービスアーキテクチャの 9 つの特性

"Microservices" では、マイクロサービスアーキテクチャスタイルの一般的な特性を次の 9 つに整理しています。

  1. Componentization via Services(サービスによるコンポーネント化)
  2. Organized around Business Capabilities(ビジネスケイパビリティ中心の組織化)
  3. Products not Projects(プロジェクトではなくプロダクト)
  4. Smart endpoints and dumb pipes(スマートエンドポイントとダムパイプ)
  5. Decentralized Governance(ガバナンスの非一元化)
  6. Decentralized Data Management(データマネジメントの非一元化)
  7. Infrastructure Automation(インフラストラクチャーオートメーション)
  8. Design for failure(障害に備えた設計)
  9. Evolutionary Design(進化的設計)

それぞれの関係を図に表すと、次のようになります。

これら全てを備えなければマイクロサービスアーキテクチャスタイルと言えないということではありません。マイクロサービスアーキテクチャスタイルを実践する多くのチームやソフトウェアシステムが持つ特性を抽出すると、このようになるということです。

以下、この 9 つの特性についてひとつひとつ読み解いていきます(翻訳ではありません)。これは、基本的には私自身の解釈に基づくものであり、その正確性を保証することはできませんが、読者の方が "Microservices" を読み解くためのヒントにはなるのではないかと期待しています。

1. Componentization via Services(サービスによるコンポーネント化)

Componentization via Services は、"Microservices" で述べられているマイクロサービスアーキテクチャの 9 つの特性の中で、中核に位置します。

For as long as we've been involved in the software industry, there's been a desire to build systems by plugging together components

(ソフトウェア業界に携わっている限り、コンポーネントを組み合わせてシステムを構築したいという想いを持つ)

ソフトウェアシステムを複数のコンポーネントに分割して構成するという方法論は、ソフトウェア工学において Component-based software engineering (CBSE) と呼ばれています。

CBSE においてコンポーネントとは、関連する機能やデータの集まり(モジュラー)であり、その機能はインタフェースとして表現されます。このインタフェースを境界として、「コンポーネントを利用する側」と「コンポーネントを提供する側」にアーキテクチャを分離することで、カプセル化を実現します。

カプセル化は、コンポーネントを提供する側によるコンポーネントの実装の変更や機能追加を、コンポーネントを利用する側とは独立して実施することを可能にするという恩恵をもたらしますが、これだけではまだいくつかの課題を抱えることになります。

"Microservices" の中では、「ライブラリによるコンポーネント化」と「サービスによるコンポーネント化」という二つのコンポーネント化方式を比較することで、ライブラリ方式における課題を明らかにし、サービス方式がより優れていることを解説しています。

ライブラリとは、プログラムにリンクされ、メモリ内でファンクションとしてコールされるコンポーネントを指します。サービスとは、個別のプロセスとして実行され、Web サービスや RPC などのメカニズムを通して通信するコンポーネントです。

ライブラリ方式でのひとつめの課題は、変更されたライブラリをアプリケーションに組み込むために、アプリケーション全体の再デプロイが必要になることです。これは、デプロイプロセスを鈍重にしてしまいます。一方で、サービス方式であれば、多くの場合、変更されたサービスのみをデプロイするだけで済みます。

ライブラリ方式でのもうひとつの課題は、利用側の規律違反によって、カプセル化が解除されてしまう点にあります。これは皆さんも経験があるのではないでしょうか。意図的にせよ、仕様の理解の低さが原因にせよ、こういったことはよくあります。完全なカプセル化がサポートされていないようなプログラミング言語では、ライブラリ内部の非公開の変数や関数を利用されるようなケースがありえます。また、ライブラリのコードそのものを、利用側の用途にあわせて改変されたり、拡張されることもあり得ます。サービス方式のコンポーネントであれば、リモートコールのメカニズムによって、こういった問題を防ぐことができます。

ファウラー氏は、サービス方式のように、利用側からは実装コードに到達できないようなインタフェースを Published Interface と呼び、そうではないライブラリのようなインタフェースである Public Interface とは区別し、前者がより優れているとしています。

このように読み解くと、記事 "Microservices" 内でのコンポーネントの定義の本質が見え、これが非常に重要な戦略であることに気付きます。

Our definition is that a component is a unit of software that is independently replaceable and upgradeable.

(我々の定義ではコンポーネントとは、独立した交換とアップグレードが可能なソフトウェアの単位である。)

これを実現するコンセプトの中核が Componentization via Services となるわけです。

もちろんサービス方式によるコンポーネント化にも欠点はあります。

リモートコールはパフォーマンス面で高価であるため、API の粒度はライブラリのそれに比べ粗くなります。加えて、ネットワークによる問題を考慮した実装も必要となるため、コンポーネントの利用側にとっては、ライブラリ方式より少々扱いづらいものになります。

【参考文献】
* Componentization via Services
* Component-based software engineering - Wikipedia
* モジュール - Wikipedia
* カプセル化 - Wikipedia
* PublishedInterface

2. Organized around Business Capabilities(ビジネスケイパビリティ中心の組織化)

このセクションは体制についての話です。ここでもやはり、"independently replaceable and upgradeable" という戦略に基づいてチーム編成が語られています。コンポーネントの視点で見ると、Componentization via Services は主にカプセル化が焦点でしたが、本セクションはモジュール性が焦点です。

ソフトウェアシステム開発において開発チームをどのように編成するか、その切り口は様々です。複数のチームに分割することも、ひとつの巨大なチームとしてプロジェクトを運営することもできます。複数のチームに分けるにしても、どのような観点でチームを分割するかは様々です。

チーム編成において重要な観点になるのが、ソフトウェアシステムに対する変更に際し、チーム間のコミュニケーションが大きくて複雑になるような組織構造は、ソフトウェアシステムの構造も複雑になり、プロジェクトにも悪影響を及ぼすという点です。コンウェイの法則です。

Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization's communication structure.

-- Melvyn Conway, 1967

((広義の)システムを設計する組織は、その組織のコミュニケーション構造に似た構造を持つ設計を生み出してしまう -- メルヴィン・コンウェイ 1967年)

例えば組織を UI チーム、サーバサイドロジックチーム、データベースチームの三つに分割して開発を進めると、小さな機能追加ですら、これら全てのチームが協力して対応しなければなりません。そうすると、チーム間での様々な調整(仕様、スケジュール、予算など)が発生してしまいます。

三つのチームが抱える開発が、機能追加ひとつだけなら調整もそれほど面倒にはならないかもしれません。しかし、現実的には同時期に複数の開発を抱えているケースが多いでしょうから、チーム間での調整は更に面倒なことになります。

こうなると、それぞれのチームは、チーム間の調整に時間を割くより、自分たちが受け持つコードベースにロジックを全て埋め込むことで、調整コストを削減しようと意識が働きます。結果、コンウェイの法則の通り、ソフトウェアシステムが、UI、サーバサイドロジック、データベースの三層でサイロ化します。このような、本来あるべきカプセル化が壊れ、ロジックが分散した状態は、ソフトウェアシステムにとって良い状態ではありません。

ここで、Organized around Business Capabilities の登場です。

マイクロサービスアーキテクチャでは、チームの単位とサービスの単位を揃えることで、この問題を解決します。そしてこのアプローチでは、チームやサービスの単位を、前述のようなソフトウェアアーキテクチャ的な視点ではなく、ソフトウェアシステムが対象とするビジネスが持つケイパビリティの視点で設計します。

e コマースビジネスを例にそのケイパビリティを挙げると、仕入れと販売、カタログ製作、マーケティング、注文管理、フルフィルメント、カスタマーサービスなどが含まれます。「業務」と言い換えた方が理解しやすいかもしれません。ビジネスにおける組織は、こういった単位で組織化され、それぞれの業務が遂行されることが多いでしょう。

同様に、マイクロサービスアーキテクチャスタイルのチームもこういったビジネスケイパビリティを単位として組成され、それぞれが担当する領域の実装を担当します。もちろん、そこにはユーザインタフェースもサーバサイドロジックもデータベースアクセスも含まれます。ゆえに、チームはクロスファンクショナルであり、その能力はフルスタックであることが求められます。

ビジネスケイパビリティに基づく単一のチームが、関連する複数のサービスを管理することもあります。こうすることで、チームが担当するビジネス領域の凝集度が高まり、それがチーム間の結合度を押し下げ、それぞれのチームの効率性を向上させることにつながります。

モノリシックアーキテクチャでも、システム内部をビジネスケイパビリティ単位でモジュール化し、同じ単位でチームを分割することができます。しかし、ひとつのコードベースの中でケイパビリティによる境界を明確に分割し続けることは、現実的には困難です。境界を維持するために多くの規律(ルール)を設けたところで、いずれは規律違反によってカプセル化は崩れ、徐々にその境界が曖昧になります。これは、Componentization via Services での「ライブラリ方式」で述べられている通りです。

【参考文献】
* Organized around Business Capabilities
* モジュール#モジュール性 - Wikipedia
* カプセル化 - Wikipedia
* Conway's Law
* BusinessCapabilityCentric

3. Products not Projects(プロジェクトではなくプロダクト)

Organized around Business Capabilities に引き続き、体制の話です。Organized around Business Capabilities は、主に開発の側面についてフォーカスされていましたが、Products not Projects は運用面が話題となっています。

Amazon.com の CTO であり、コンピュータ科学の博士であるワーナー・ヴォゲルス氏は、過去のインタビューでこう話しています。

Giving developers operational responsibilities has greatly enhanced the quality of the services, both from a customer and a technology point of view. The traditional model is that you take your software to the wall that separates development and operations, and throw it over and then forget about it. Not at Amazon. You build it, you run it. This brings developers into contact with the day-to-day operation of their software. It also brings them into day-to-day contact with the customer. This customer feedback loop is essential for improving the quality of the service.

(デベロッパーに運用責任を与えることで、顧客と技術の両面において、サービス品質の大きな向上を得られた。伝統的なモデルでは、ソフトウェアを開発と運用の壁で分離していた。しかし Amazon はそうではない。開発した人たちが、運用も行う。この考えによりデベロッパーは、ソフトウェアの日々の運用に接することになる。この、顧客とのフィードバックループは、サービス品質の改善に不可欠だ。)

Products not Projects における "プロジェクト" モデルとは、インタビューでも述べられている、開発と運用が分離された体制です。開発が完了するとチームが解散し、システムが運用チームに引き継がれるモデルだと考えてください。

一方の "プロダクト" モデルがマイクロサービス流のやり方です。このモデルは開発と運用が一体化した DevOps としての体制のことで、プロダクトのライフサイクルを通してチームが一貫して責任を持ちます。サポート業務の一部も担います。こうすることで、チームは「顧客のビジネスケイパビリティの強化を如何にして支援していくか?」という視点を得て、これが運用の中心となります。

前セクション Organized around Business Capabilities におけるビジネスケイパビリティと、このプロダクトという考え方は、深い関係性があります。martinfowler.com の記事 "BusinessCapabilityCentric" でもビジネスケイパビリティ中心チームを次のように説明しており、前述のワーナー・ヴォゲルス氏の話と同様の考えに基づいていることが読み取れます。

Business-capability centric teams are “think-it, build-it and run-it” teams. They do not hand over to other teams for testing, deploying or supporting what they build. They own problems in their area end-to-end.

(ビジネスケイパビリティ中心チームは、「考え、開発し、運用する」チームだ。彼らは、自らの開発物のテストやデプロイ、サポートを他のチームに引き渡したりはしない。担当領域全体の問題を、自分たちで抱える。)

【参考文献】
* Products not Projects
* A Conversation with Werner Vogels - ACM Queue
* BusinessCapabilityCentric

4. Smart endpoints and dumb pipes(スマートエンドポイントとダムパイプ)

Componentization via Services にある通り、個々のマイクロサービスは、サービスとしてコンポーネント化されたもので、関連する機能やデータの集まり(モジュラー)です。そして、堅牢性、信頼性、再利用性、読みやすさの点で好ましいモジュールは、凝集度(cohesion)が高く、結合度(coupling)が低いものです。

このセクションはマイクロサービス間のコミュニケーション方式について書かれているのですが、そこでの方式選択は、モジュール性能の向上を目的としています。記事 "Microservices" 中の次の文章に、それが表れています。

Applications built from microservices aim to be as decoupled and as cohesive as possible

(マイクロサービスとして構築されるアプリケーションは、可能な限り、それぞれが分離され、凝集されていることを目指している)

スマートエンドポイントとは凝集度の高いサービスを指し、ダムパイプとは結合度を下げるために取るシンプルなコミュニケーション方式を指しています。"Microservices" ではこれらを Unix のパイプラインに例え、前者をフィルター、後者をパイプとしています。

対して、オーケストレーションと呼ばれる、コミュニケーションメカニズム自体にスマートさ(かしこさ)を置いた方式は、様々な機能を兼ね備えた中央制御型のコミュニケーション方式(ダムパイプに対してスマートパイプ)です。それゆえに、サービスの凝集度が低く、サービス間の結合度が高くなります。このように、コンポーネントとしてのサービスのモジュール性能が低いソフトウェアシステムでは、"independently replaceable and upgradeable" の実現は困難となってしまいます。

従って、マイクロサービス間のコミュニケーション方式には、中央制御型のオーケストレーションより、シンプルなコレオグラフィーが適しています。

コレオグラフィーは、オーケストレーションのような中央制御型ではなく、コミュニケーションネットワーク上の各ノード(つまりサービス)が、あらかじめ決められた別のノードに対して直接通信を行います。このように、コレオグラフィーは仲介役(ブローカ)となるサーバーを中央に置かないため、単一障害点がなく、メッセージングの信頼性も向上します。

一般的に使用されるシンプルなコレオグラフィーのひとつが、HTTP 通信による RESTish な API です(完全な REST である RESTful ではなく、広義の意味での REST である API のこと)。

もうひとつが、RabbitMQ や ZeroMQ のような軽量のメッセージキューを介した非同期通信です。この軽量なメッセージングプロトコルは、サービスからサービスへのメッセージ送受信のルーターとして機能することで、メッセージバスとして働きます。

【参考文献】
* Smart endpoints and dumb pipes
* モジュール - Wikipedia
* 凝集度 - Wikipedia
* 結合度 - Wikipedia
* パイプ (コンピュータ) - Wikipedia

5. Decentralized Governance(ガバナンスの非一元化)

decentralized governance の反対は centralized governance となりますが、これは「標準化」と表現した方がわかりやすいでしょう。

ソフトウェアシステム開発において技術面やプロセス面を標準化する目的は、属人性を最小化し、成果物の品質を上げ、生産性を上げることです。しかし、標準化で定められた方式が、チームの直面する課題に対して最適なソリューションにならない場面も多々あります。こういったケースでは標準化の強制が逆に足かせとなって、チームの開発パフォーマンスを貶めてしまいます。

マイクロサービスアーキテクチャのように、システムが個々のサービスとしてパーティション化されていると、標準化よりも良いやり方を選択できます。このセクションでは、それを、Decentralized Governance と呼び、チームへの権限移譲による独立性の向上として解説しています。

You want to use Node.js to standup a simple reports page? Go for it. C++ for a particularly gnarly near-real-time component? Fine. You want to swap in a different flavour of database that better suits the read behaviour of one component? We have the technology to rebuild him.

(Node.js を使ってシンプルなレポートページを作りたい?頑張って。C++ を、高性能な near-real-time コンポーネント開発に?いいね。コンポーネントで使用しているデータベースを、読み取りに適した別のものに交換したいって?それに適したミドルウェアがあるよ。)

Componentization via Services で語られているように、マイクロサービスアーキテクチャにおけるサービスとはコンポーネントであり、カプセル化されています。そのサービスを開発し、運用するチームが守らなければならないのは、サービスクライアントに約束したサービス仕様(service contract)です。一方で、カプセル化された中身をどのように作るかは、それに最も適した方式をチームが選択すれば良いだけです。こうすることが、チーム間で共有しなければならない約束事を最小限に抑え、チームの独立性を確保し、"independently replaceable and upgradeable" を促進します。

サービス仕様の独立した順守と進化には、Consumer-Driven ContractsTolerant Reader 使えます。

Consumer-Driven Contracts を簡単に言うと、Provider (サービスを提供する側)によるサービス仕様の進化や内部実装の変更などが、Consumer (サービスを利用する側)にとって破壊的な変更になっていないかを早期に発見する仕組みです。Consumer が送る要求メッセージに対する、Consumer が期待する Provider からの応答メッセージのペアを Consumer Constract と呼び、Provider は全ての Consumer Contract が守られていることをテストを通して検証し、保証します。

Tolerant Reader は、サービスを通した結合度の低い通信において、次の Postel's Law を哲学として実装する考え方です。

be conservative in what you do, be liberal in what you accept from others.

(あなたがやることはコンサバティブに、他者からの受け入れはリベラルに。)

この "you" と "others" はいずれも、Provider と Consumer のどちらにでも適用できそうですが、ここでは "you" が Consumer であり、"others" が Provider だと考えてください。Consumer は、サービス仕様に厳密に従った要求メッセージを送り、Provider から返される応答メッセージに対しては必要なフィールドにのみアクセスするようにします。こうすることで Consumer は、Provider 側によるサービスの進化や変更に対して強くなり、チームの独立性を維持することができます。

チームへの責任と権限の移譲を追求すると、Products not Projects でも引用された Amazon.com の精神、"You build it, you run it(開発した人たちが、運用も行う)" に辿り着きます。チームが 24 時間体制でソフトウェアシステムを運用し、昼夜問わずに発生する問題に直面すると、高いサービス品質を実現するコードを書くという志向をチーム自身が持つようになります。これを実践する組織はまだ多くはないようですが、"Microservices" では Netflix をその例として説明しています。

Netflix は "You build it, you run it." を実践する中で鍛えた有用なコードの多くを、OSS として世界に公開しています。同社が過去に経験した課題と同じ課題に直面した世界中の組織が、この OSS の恩恵を受けることができます。このような文化が、標準化に重きを置かない、マイクロサービスを志向するチームの哲学を徹底した例です。この哲学を実践するチームは、直面する課題に対し、ドキュメント化された標準に従うのではなく、その解決法をコードに落とし込み、他の開発者も利用できるツールとして共有することを好みます。ここで言うツールや OSS は、コードの中にビジネスドメインを含みません。だからこそ様々なチームで利用することが可能であり、それを利用したチームの独立性を阻害することもありません。もちろん、そのツールや OSS を使うかどうか自体、チームの自由意思に委ねられます。

【参考文献】
* Decentralized Governance
* カプセル化 - Wikipedia
* Consumer-Driven Contracts: A Service Evolution Pattern
* TolerantReader
* A Conversation with Werner Vogels - ACM Queue
* ジョン・ポステル#ポステルの法則 - Wikipedia

6. Decentralized Data Management(データマネジメントの非一元化)

ANSI の定めるデータモデルには、「概念スキーマ」「論理スキーマ」「物理スキーマ」という三通りのインスタンスがあります。このセクションではこれらのうち、概念スキーマと論理スキーマのあり方が、主なテーマとなっています。

概念スキーマ(概念データモデル)は、モデリング対象となる領域(ドメイン)を表現した実体クラス(と関連)の集合です。実体クラスとは例えば、消費者や商品、オペレーター、在庫のようなリソースを表すものや、受注、出荷、配送といったイベントを表すものがあります。

もう一方の論理スキーマ(論理データモデル)とは、概念データモデルを、データを扱うための任意の技術にあわせて構造化/表現したものです。例えばリレーショナルデータベースを使うなら、テーブルやビューとしてデータを表現することになります。

モノリシックアーキテクチャでは、ひとつの概念スキーマをもとに、リレーショナルデータベース用の論理スキーマを定義することが多いのではないでしょうか。しかし、扱うビジネス領域(ドメイン)が大きければ大きいほど、ひとつの概念スキーマで全てを上手く表現することが難しくなります。それらを扱うユーザーの担当領域によって、同じ言葉が指す概念が違っていたり、扱いたいリソースやイベントの単位が異なるからです。

つまり、同じドメインを扱っていても、コンテキストによって、概念やその表現方法が異なるということです。これを無理にひとつの概念スキーマに押し込めようとすると、実体クラスがファットになり過ぎたり、逆に細分化されすぎて、ユーザーにとってもデベロッパーにとっても扱いにくいものになりがちです。

この問題への解決は、それぞれのコンテキストごとに概念スキーマを分離して定義することです。これは、ドメイン駆動設計(DDD)Bounded Context パターンの考え方で、マイクロサービスアーキテクチャとも相性が良い手法です。サービスの分離は、つまりコンテキスト境界による分離であり、これがビジネスケイパビリティの凝集度をより高めるからです。

さて、概念スキーマが分離されたので、論理スキーマも分離するのが自然な流れです。しかし、マイクロサービスアーキテクチャでは更に、データ永続化に使用する論理データストレージ自体も分離します。これもやはり、"independently replaceable and upgradeable" に従った考え方です。同一のデータストレージミドルウェアのそれぞれのデータベース領域を使うこともあれば、異なるインスタンスを使うこともあります。そもそも各々が異なるミドルウェアを使うこともあります。対峙する問題や環境に応じてプログラミング言語を使い分けるように(Polyglot Programming)、データストレージテクノロジーも状況に応じて使い分ければ良いという考えかたです(Polyglot Persistence)。

これまで単一の論理データストレージを使っていたデベロッパーなら、データベースを分離したことによって、一貫性の保証がどうなるのか気になるところです。結論から言うと、サービス間の一貫性は結果整合性(Eventual Consistency)を選択します。問題が生じた場合はオペレーションで補完します。これはトレードオフで、強固な一貫性を実現し運用する中で失うコストより、オペレーションによる補完や補償の方が小さいという考えに基づきます。

【参考文献】
* Decentralized Data Management
* データモデリング - Wikipedia
* ドメイン駆動設計 - Wikipedia
* BoundedContext
* Meme Agora: Polyglot Programming
* PolyglotPersistence

7. Infrastructure Automation(インフラストラクチャーオートメーション)

マイクロサービスアーキテクチャで構築されたソフトウェアシステムの多くは、自動テストを含むビルド、デプロイ、運用の複雑さを軽減するために、インフラストラクチャの自動化技術を幅広く活用しています。

本セクションはいわゆる CI/CD のお話で、マイクロサービスに特化した内容はほとんどありません。

【参考文献】
* Infrastructure Automation
* Continuous Integration
* ContinuousDelivery

8. Design for failure(障害に備えた設計)

"Everything fails, all the time"
-- Werner Vogels, CTO Amazon.com

(どんなものでも失敗し得る、いつだって -- Amazon.com 最高技術責任者 ワーナー・ヴォゲルス)

AWS のクラウドベストプラクティスのひとつ、 "Design for failure and nothing will fail" では、クラウドのアーキテクチャ設計は、障害発生を見越した悲観的な視点に立つべきで、障害から自動的に復旧できることが望ましい、と書かれています。

この考え方は、システムの信頼性(Reliability)を表す指標として、平均故障間隔(MTBF: Mean-Time-Between-Failures)より、平均復旧時間(MTTR: Mean Time To Recovery)を重視すると言えます。

モノリシックアーキテクチャのインプロセスでのファンクションコールに対し、マイクロサービスアーキテクチャでのリモートによるサービス利用は失敗する可能性を常に抱えています。これは、通信経路上の問題の場合もあれば、コールされたサービス側の問題の場合もあります。いずれにせよ、マイクロサービスアーキテクチャでは、リアルタイムモニタリングを通じて障害を即座に検知する仕組みが重要になります。モニタリング対象となるのは、システムメトリクス(例:データベースが秒間毎にいくつのリクエストを受け付けたか)や、ビジネスメトリクス(例:分間毎にいくつの注文を受け付けたか)、サービスの死活などで、ダッシュボードを活用していつでも確認できるようにします。

システムの信頼性に関し、マイクロサービスアーキテクチャで課題となるもうひとつのポイントは、本番環境と同じ環境を、テストのために用意することが難しいという点です。独立した複数のサービスの頻繁なデプロイが、この状況を生んでいます。

これを軽減する方法として、Semantic Monitoring (または Synthetic Monitoring)という考え方があります。自動テストのサブセットを本番環境で実施し、テスト結果をモニタリングするというものです。この究極系が、Netflix が実践している、本番環境でのカオスエンジニアリングです。彼らは就業時間中に、本番環境のサービスやデータセンター全体を意図的に落とし、システムが過酷な状況に耐えられることを日々、自動テストしています。

【参考文献】
* Design for failure
* Amazon Web Services - Architecting for The Cloud: Best Practices
* 平均故障間隔 - Wikipedia
* 平均修復時間 - Wikipedia
* SyntheticMonitoring
* Principles of Chaos Engineering
* NetflixのChaos Engineeringの原則

9. Evolutionary Design(進化的設計)

Responding to change over following a plan

-- Manifesto for Agile Software Development

(計画に従うことよりも変化への対応を -- アジャイルソフトウェア開発宣言)

アジャイル開発では綿密な計画の作成より変化への対応を重視します。Evolutionary Design はこの哲学が核となっています。マーティン・ファウラー氏も記事 "Is Design Dead?" の中で、エクストリームプログラミング(XP)を題材に、planned design(計画的設計)と対比することで、evolutionary design(進化的設計)の本質を紐解いています。

ほとんどのソフトウェアシステムは、そのライフサイクルにおいて変化していくものです。この、将来おこりうる変化を予測し、あらかじめ設計に盛り込むことで、将来の変更コストを抑えようとするのが planned design です。しかしこれには問題点はいくつかあります。

まず、プログラミングで対処しなければならない問題を、設計段階において全て見通すことは不可能です。ここで見逃された問題はプログラミングの段階で出くわすことになります。ここでプログラマーが局所的な対処を取ってしまうと、コードのエントロピー増大につながります。逆に大局的な視点に立って設計からやり直していると、時間とコストの増大に繋がります。しかし、時間とコストを無制限に使う訳にはいきません。結局は、制約の中で出来る範囲で設計の見直しをやることになり、これがまたエントロピーの増大につながってしまいます。

もうひとつ、planned design を困難にすることと言えば、「要求は変化する」という現実です。対処方法として、「どのような変化が起きるか」を洞察し、それを柔軟性(flexibility)として設計に組み込むことが考えられます。しかし、将来の変化を完全に読み切ることは不可能です。予測していなかった変化による変更も起こり得えます。予測が外れて使われないままのコードも出てきます。そもそも、使われるかどうかもわからない柔軟性を持たせた設計やコードが、新たな機能追加などに要するコストを増大させてしまいます。

要求を十分に理解しきれず問題を引き起こすこともあります。要求定義プロセスにフォーカスをあてることで、これを回避しようと考えるかもしれません。しかし要求の変化は、ビジネスの変化からもたらされることが多く、要求定義を強化したところでこれを完全に防ぐことは出来ません。

対する evolutionary design は、ソフトウェアシステムの実装が進むにつれ、設計も成長していくことを意味しています。設計はプログラミングプロセスの一部として実施されます。こう書くと、批判の多いコードアンドフィックスモデルを想起させますが、テストと継続的インテグレーション(CI)、リファクタリングの実践によって evolutionary design の実効性を高めることが出来ます。

テストと継続的インテグレーションは、加えた変更の安全性を保証するために欠かせません。

リファクタリングは、planned design と evolutionary design のバランスを取るために使えます。planned design が完全に無くなることはありませんが、そちらのウェイトを軽くすることで、リファクタリングという手法によって後からコードに(洗練された)設計を組み込む時間を捻出することが可能となります。

このようにして、evolutionary design ではソフトウェアシステムを、その時点で必要最低限なシンプルな状態を維持し(YAGNI)、変化を受け入れるコストを最小限にしようと努めます。簡単に言えば、planned design が変化の削減を試みるアプローチであることに対し、evolutionary design は、迅速かつ頻繁にソフトウェアの変更を適切に実施することを目指すアプローチです。

マイクロサービスアーキテクチャでは、evolutionary design のこのアプローチを、サービス分割の切り口としても見ています。このサービス分割という設計行為も、全てを見通して行うことが不可能だからです。運用を続ける中で漸進的に成長させる方が現実的でしょう。

記事 "Microservices" でも紹介されている Guardian のウェブサイトのように、モノリスとして構築され、マイクロサービスとして進化しているアプリケーションがその例です。逆に、同時に変更することの多い二つのサービスをひとつにまとめるようなケースもあり得ます。長期的に運用するサービスではなく、一時的に立ち上げて、必要がなくなったら削除するようなサービスもあるでしょう。

このような斬新的な進化は、サービスのコンポーネントとしてのモジュール性を高めることに繋がります。サービス分割がはじめから完全に設計できると考えたり、不変だと信じるのではなく、運用する中でサービス(コンポーネント)分割の境界線を調整し、"independently replaceable and upgradeable" を維持、向上させることが、マイクロサービスアーキテクチャにおける evolutionary design と言えるのではないでしょうか。

テストと継続的インテグレーション、リファクタリングが、コード面での evolutionary design の実効性を向上させるのと同様に、Infrastructure Automation や、Decentralized Governance での Consumer-Driven ContractsTolerant Reader が、マイクロサービスにおける evolutionary design を支えてくれるでしょう。

【参考文献】
* Evolutionary Design
* アジャイルソフトウェア開発宣言
* Is Design Dead?
* エクストリーム・プログラミング - Wikipedia
* YAGNI - Wikipedia
* Consumer-Driven Contracts: A Service Evolution Pattern
* TolerantReader

最後に

ブログ記事としてはなかなかの長文になりましたが、いかがだったでしょうか。私自身、本記事を執筆する中で何度も "Microservices" を読み直したことで、全体の関係性や、背後にある技術やプラクティス、パラダイムなどについて、より深く理解できるようになったと感じています。

マイクロサービスアーキテクチャの追求とは常に、「それは、コンポーネント(=サービス)の独立した交換とアップグレードを推進するか?」と問いかけることだ、というのが私の結論です。これは、ソフトウェアシステムに対してだけでなく、チーム編成やプロセス整備に対しても適用される原理です。

今回のブログ記事では、この数年で当社が得た知見などもあわせてご紹介したかったのですが、記事のボリュームをこれ以上大きくするのも良くないので、また別の機会にと思っています。

Comments are closed, but you can leave a trackback: Trackback URL.