マイクロサービスパターン の 概念的な話をしている部分を要約
MSA = Micro Service Architecture
Chapter1 モノリシック地獄からの脱出
モノリシックアーキテクチャの利点
-
アプリケーション が小さいうちは以下のような利点がある
- 開発しやすい
- 大きな変更をくわえやすい
- テストしやすい
- デプロイしやすい
- スケーリングしやすい
-
しかし、次第に開発、テスト、デプロイ、スケーリングが難しくなる
モノリシック地獄の実際
- 複雑なシステムに開発者が萎縮する
- コードベースが簡単には理解、把握できなくなり正しく修正することが難しくなる
- 開発ペースが遅い
- 大規模になるとコードの改修だけでなく、ビルド、テストにも時間がかかる
- コミットから本番デプロイまでの道のりが長く険しい
- 多くの開発者が同じコードベースにコミットするので、ビルドが通らずにマージに時間がかかる
- 依存関係が密になると、テストスイート全体を実行しなければならなく、テストに時間がかかる
- スケーリングが難しい
- モジュール同士でリソースの要件が矛盾し合う
- あるモジュールは大規模なインメモリを要求し、あるモジュールはマルチなプロセッサを要求する
- 信頼性の高いモノリスを提供することの難しさ
- アプリケーションのサイズが巨大で、徹底的なテストが難しい
- 障害の分離(fault isolation)ができず、一部が落ちると全体が落ちる
- 次第に陳腐化していくテクノロジスタックに縛り付けられる
- FWや言語を変えることは、システム全体を書き換えることを意味する
- 新しいモノを採用するコストよりも、手を加えるリスクの方が高くなる
MSAで状況打開
スケールキューブとMSA
-
スケールキューブ
- X軸スケーリング = 複数インスタンスで負荷分散
- Y軸スケーリング = リクエスト属性に基づいたルーティングで負荷分散
- Z軸スケーリング = 機能に基づいてアプリケーションをサービスに分散して負荷分散
マイクロサービスはモジュール性の一形態
- 複雑で巨大なアプリケーションは一人では開発できない
- アプリケーションは、別々の人が開発し、理解しているモジュールに分割するべき
- モジュール同士はAPIを介してやりとりし、内部実装の独立性を保たなければならない
個々のサービスは専用データベースを持つ
- 疎結合を保つために、データベーススキーマの共有はしない
- これは専用データベースサーバーを持たなければならない、ということではない
MSAとSOAの比較
- テクノロジスタックが一般的に異なる
- SOAはSOAPとその他のWS標準といった重量級のテクノロジを使用するが、MSAは軽量級のオープンソーステクノロジを使う傾向がある
- SOAはサービス間通信にスマートパイプであるESBを使うこちが多いが、MSAはメッセージブローカーやREST、gRPCなどの軽量級プロトコルによるダムタイプが使われる
MSAの利点と欠点
利点
- 大規模で複雑なアプリケーションのCI/CDを可能にする
- MSAの個々のサービスは比較的小さいので、大規模システムより自動テストが書きやすく短時間で実行される
- サービス単位でデプロイできる
- (ピザ2枚分の)小さな開発チームの集合体という形で組織が構成でき、開発チーム間が疎結合になるので、意思決定から実行までが迅速になる
- 個々のサービスが小さく、簡単にメンテナンスできる
- サービスを個別にスケーリングできる
- 他のサービスから独立してX軸のクローニング、Z軸のパーティショニングでスケーリング可能
- 障害分離に優れている
- 新しいテクノロジを簡単に実験、採用できる
欠点
- サービスの適切な分割方法を見つけるのが難しい
- 明確な分割の定義がなく、職人技になる
- 適切な分割に失敗すると分散モノリスが出来上がり、これはモノリシックアーキテクチャとMSAの欠点を併せ持つ
- 分散システムは複雑になる
- サービスは単純なメソッド呼び出しよりも複雑なIPCメカニズムを使う必要がある
- 部分的な失敗や、利用不能なリモートサービス、レイテンシが異常に高いリモートサービスに個々に対応しなければならない
- 複数サービスにまたがるユースケースの実装に、馴染みのないテクノロジを使う必要がある
- サービスをまたがるトランザクション処理
- IDEが分散アプリケーション開発を表立ってサポートしていない
- 運用が大幅に複雑になるので、自動デプロイツールやコンテナオーケストレーションを利用して高度な自動化を行うことが必須である
- 複数サービスにまたがって使われる機能のデプロイには綿密な調整が必要
- いつMSAを採用すべきかの判断が難しい
- スタートアップのように製品を市場に出すことがハイプライオリティの場合、ほぼ確実にモノリシックアプリケーションでスタートすべき
- 凝った分散アーキテクチャを使うことは、開発をスローダウンさせがち
- だが、巨大で複雑になるにつれ、モノリシックであるより分散化することで得られるメリットが大きくなっていく
- スタートアップのように製品を市場に出すことがハイプライオリティの場合、ほぼ確実にモノリシックアプリケーションでスタートすべき
MSAのパターン言語
- MSAは万能薬ではない
- 色々なパターンがあるねという話
マイクロサービスを超えて:プロセスと組織
ソフトウェア開発とデリバリー組織
- 大きなチームを分割する
- システムが成功を収めると、開発チームは拡大していく
- チームのコミュニケーションオーバーヘッドは、チームの規模に対して指数関数的に増えていく
- チームを分割するときは疎結合にし、開発する際のチーム間コミュニケーションが増えないようにする
ソフトウェア開発とデリバリープロセス
- 継続的デリバリーを実現するためには、Scrumやカンバンなどのアジャイル方式、デプロイの手法を取り入れるべし
- ウォーターフォールでは利点の大半が失われる
- 壊さず早く、を実現するために以下の指標が参考になる
- デプロイ頻度(deployment frequency)
- リードタイム(lead time)
- 平均修復時間(mean time to recover)
- 変更失敗率(change failure rate)
Chapter2 サービスへの分割
ソフトウェアアーキテクチャとは何か、なぜ大切なのか
- ソフトウェアアーキテクチャの定義
- 定義は無数にある
- アプリケーションのアーキテクチャとは部品(要素)への分割とそれらの部品の間の関係
- これがイリティ -ility(品質属性)を決める
- ソフトウェアアーキテクチャの 4 + 1 ビューモデル
- 論理ビュー
- 開発者によって作られるソフトウェア要素であり、OOPではクラスとパッケージがこれに当たる
- 関係には継承や結合、依存などがある
- 実装ビュー
- ビルドシステムの出力であり、パッケージングされたコードを表すモジュールと、一つ以上のモジュールから作られる実行可能ファイルまたはデプロイ可能ユニットのコンポーネント
- モジュール間やコンポーネントモジュール間の依存関係がある
- プロセスビュー
- 実行時のコンポーネントであり、要素はプロセス
- プロセス間の関係はプロセス間通信
- デプロイビュー
- プロセスがマシンにどのように展開されるかであり、要素はマシンとプロセス
- マシン間の関係はネットワーク
- ビューを動かすシナリオ(+1 部分)
- 特定のビューの中で、様々なアーキテクチャ要素がリクエストを処理するためにどのように連携されるかを記述する
- 論理ビュー
- アーキテクチャが重要なのは、サービス品質要件を満たす鍵を握っているから
- アプリケーションには機能要件とサービス品質という2種類の要件がある
- 機能要件は、アーキテクチャとはほぼ無関係
- サービス品質は、品質属性とかイリティと呼ばれ、スケーラビリティやメンテナンス性などは選択されたアーキテクチャに大きく依存する
- アプリケーションには機能要件とサービス品質という2種類の要件がある
アーキテクチャスタイルの概要
- アーキテクチャスタイルとは、組織構造のパターンの観点から、システムの全体像を定義するものであり、以下はその一例
- 階層化アーキテクチャスタイル
- 要素を明確に役割定義された階層にまとめ、階層が依存できるのは直下の階層のみ
- よく使われる3ティアアーキテクチャは以下の層に階層を分類する
- プレゼンテーション層
- ビジネスロジック層
- 永続記憶層
- よく使われる3ティアアーキテクチャは以下の層に階層を分類する
- この欠点はプレゼンテーション層と永続記憶層が単一であること、また依存の関係上DBなしでビジネスロジックがテストできなくなる
- 要素を明確に役割定義された階層にまとめ、階層が依存できるのは直下の階層のみ
- ヘキサゴナルアーキテクチャスタイル
- 中央にビジネスロジックを配置して論理ビューを組む
- インバウンドとアウトバウンドのアダプタがあり、ビジネスロジックがアダプタに依存せず、アダプタがビジネスロジックに依存する
- ビジネスロジックはインバウンドとアウトバウンドのポートを持つ
- インバウンドはサービスの公開メソッドを定義するシステムインターフェースなど
- アウトバウンドはデータアクセス操作のコレクションを定義するリポジトリインターフェースなど
- ビジネスロジックが外側に依存しないのでテストがしやすくなるかつ、変更に強くなる
MSAはアーキテクチャスタイルの一つ
- モノリシックアーキテクチャもMSAもアーキテクチャスタイルの一つ
- モノリシックアーキテクチャは単一のコンポーネントで実装ビューを構成するアーキテクチャで他のビューについては定義していない
- 例えばヘキサゴナルアーキテクチャに従って構成された論理ビューを持つことができる
- MSAは複数のコンポーネントで実装ビューを構成する
- コンポーネントはサービスであり、コネクタはサービスの連携を可能にする通信プロトコル
- 個々のサービスは、自分の論理ビューを持っていて、それは通常ヘキサゴナルアーキテクチャである
- サービスは疎結合でなければならない
サービスとは何か
- 何らかの機能を実装するスタンドアロンの個別にデプロイ可能なソフトウェアのこと
- クライアントが機能を利用できるよう、APIを提供する
- APIはコマンド、クエリー、イベントから構成される
- コマンドとクエリーは操作であり、前者はデータの更新、後者はデータの取得を意味する
- イベントはデータが変わった時にパブリッシュされ、クライアントに消費される
- APIは内部実装をカプセル化し、モジュール性を強制する構造
疎結合とは何か
- サービスとのやりとりがAPI経由でなされ、実装の詳細がカプセル化された状態
- サービスを理解、変更、テストすることがより容易になる
- 反面、各サービス間でのデータの整合性やサービスをまたがるクエリが複雑になるという欠点
共有ライブラリの役割
- 共有ライブラリ化するときは、そのライブラリの変更可能性に注意
- 頻繁に変更されるビジネスロジックを孕んだコードをライブラリにすると、そこに変更が加わった場合に、それに依存する全てのサービスに影響が出る
- ライブラリは、変更されそうにない機能だけで使うべき
サービスのサイズはあまり重要ではない
- サービスのサイズではなく、小規模なチームで素早く開発のできる設計になっているかが重要
- サービス間の依存が強かったり、大規模なチームが必要とされた場合は、疎結合になってない兆候
- まずはサービスを洗い出し、サービス間の連携の形を明らかにすることが重要
MSAの定義の方法
システム操作の洗い出し
- 出発点はユーザーストーリー、それに対応するユーザーシナリオなどのアプリケーション要件
- ドメインモデルの作成と、そのドメインモデルに基づいたシステム操作を定義する
- システム操作は、アプリケーションが処理しなければならないリクエストを抽象化したものでコマンドかクエリになる
- MSAの定義は職人技の域なので、以下のステップは助けにはなるが、機械的に従うだけで良いプロセスではない
高水準ドメインモデルの作成
- ドメインモデルは最終的に実装されるものと比べてはるかに単純
- 個々のサービスがそれぞれドメインモデルを持つので、アプリケーションのドメインモデルは一つに絞られない
- ストーリーやシナリオに含まれる名詞の解析やドメインエキスパートとの対話で抽出する
システム操作の定義
- ほとんどのリクエストはHTTPベースだが、そうでないクライアントもいるので、抽象的な概念を使って表現する
- コマンドとクエリーの二つのタイプに分類できる
- 最終的には REST、RPC、メッセージングエンドポイントに対応することになる
- コマンドは、引数、戻り値、ドメインモデルに対する振る舞いを定義する仕様を持つ
- 振る舞いとは、操作が呼び出された時に満たしていなければならない前条件、操作終了後に満たしていなければならない後条件のこと
- 前条件と後条件はユーザーのシナリオを反映したものになっている
- クエリーは、ユーザーが判断を下すための情報をUIに提供する
Decompose by business capability パターンによるサービスの定義
- 業務による分割
- 業務とは、企業が価値を生成するために行う仕事の種類
- 企業によって異なり、例えばオンラインストアの業務はオーダー管理や在庫管理になる
- 業務は一般的に安定している
- 預金という業務に関して、窓口で小切手を差し出していた時代からATM、オンラインサービスと、その方法は変化しているが、預金という業務自体は変わらない
- 技術的な概念ではなく、ビジネス上の概念
- よくビジネスオブジェクトにまとめられる
- 業務を洗い出したら、関連する業務のグループのためにサービスを定義する
- ここは結構主観的になるかもしれない
Decomposed by sub-domain パターンによるサービスの定義
- DDD のサブドメインと bounded context の概念を使って定義する
- bounded context はサービス、またはサービスのグループに置き換えられる
分割のガイドライン
単一責任の原則
-
クラスを書き換える理由は一つだけでなければならない(ロバート・C・マーティン)
- 責務が一つしかなければ、安定性が増す
閉鎖性共通の原則
-
一つのパッケージにまとめられるクラス群は、同じ種類の変更に対して共に影響を受けるものであるべきだ。つまり、一つのパッケージに影響を与える変更は、そのパッケージに含まれる全てのクラスに影響を与えるべきだ。(ロバート・C・マーティン)
- これに従うとアプリケーションのメンテナンス性は大幅に向上し、変更があった際にデプロイするサービスの数を抑えることができる
アプリケーションをサービスに分割する上で障害となるもの
ネットワークレイテンシ
- APIを介するとラウンドトリップが増える
- レイテンシを下げるには以下のような方法がある
- 一度のラウンドトリップで複数のオブジェクトを取得するバッチAPIを作る
- 複数のサービスを結合し、IPCの代わりに言語レベルのメソッド呼び出しを行う
同期通信による可用性の低下
- RESTを使って他のサービスを同期的に呼び出す場合、呼び出し先が落ちていると呼び出し元も落ちる
- 非同期メッセージングを使用することで、密結合を断ち切り、可用性を向上させることができる
サービス間でのデータ整合性の維持
- システム操作の中には、複数のサービスのデータを更新しなければならないものもある
- データの更新はアトミックに行わなければならないが、分散トランザクションなどの複雑な機構が必要となる
整合性の取れたビュー
- DBが複数になると、ビューの整合性が取りづらくなる
- モノリシックならトランザクションのACID属性により、クエリーがDBの整合性を取れる
分割を妨害するゴッドクラス
- ゴッドクラス(アプリケーション全体で使われる巨大クラス)は分割の障害となる
- 銀行の口座やECの注文データなど
- ゴッドクラスはアプリケーションの様々な側面の状態、振る舞いをひとまとめにしているので、ビジネスロジックをサービスに分割する際の究極の障害となる
- ゴッドクラスをライブラリ化すると、変更の影響を各チームが受けてしまう
- ゴッドクラスを操作するサービスを作り、データの読み書きをそのサービス経由で行う方法では、そのサービスがビジネスロジックをほとんど含まず、ドメインモデルとして精彩を欠いたただのデータサービスになってしまう
- DDDの思想で、各サービスがゴッドクラスの役割を持つ独自バージョンのドメインモデルを定義すれば、ある程度は解消できる
サービスAPIの定義
- ここまでできたら次は個々のサービスのAPI、すなわち操作とイベントを定義すること
- APIの存在意義
- システム操作に対応させて何らかの操作を用意すること
- サービス間の連携のサポート
- 個々のシステム操作をサービスに割り当てていく
- システム操作の実装のために他のサービスとの連携が必要かどうかを判断する