第二回目も読書感想文
今回はみんなお世話になっているオライリー社の「マイクロサービスアーキテクチャ」です
きっかけは当プロジェクトのマイクロサービスサービス化検討の際の勉強です
この本読んだからできるようになるわけねーだろっていうツッコミは勘弁してください
感想・まとめ
はじめに
- マイクロサービスを取り巻く技術の進歩は早く、マイクロサービスを実現する特定の技術を習得することよりも、その本質的な考え方を理解することが重要である。
マイクロサービスとは
- SOA(サービス指向アーキテクチャ)のひとつの実現形態であり、DDD(ドメイン駆動設計)、CI・CD(継続的インテーグレーション・デリバリ)、インフラ仮想化、自動化、アジャイル開発プロセス、といったさまざまな分野の技術や方法論を組み合わせることで成り立っている
- これらが総合的に実践できなければその価値を享受できない。
- DDDの「境界付けられたコンテキスト」が、ひとつのマイクロサービスの単位になる。
- 外部から利用されるためのAPIを公開する。
- その公開APIの呼び出し元(=コンシューマ)に影響を与えることなく単独で変更・本番リリース可能である。
- あるマイクロサービスが停止しても、他のマイクロサービスは停止せずにサービスを提供し続け、システム全体としては、一部機能を切り離して稼働し続けることができる。
- 停止した原因をきちんと探る必要がある
- 必要なサービスだけを、必要なときにスケールさせることができる。
- 自律的な生産性の高いチームによって開発され、所有・維持される。採用する技術や方法論は所有チームが自由に決定する。
- 生産性の高いチーム=小規模コードベースで作業する小規模チーム
- 簡単に捨てたり、作り直したりできる。(2週間で作り直せる程度の大きさ)
共有ライブラリやモジュールは?
- チームやサービス間で機能を共有することができる
- 技術多様性が失われる
- 言語やプラットフォームが同じである必要がある
- 独立してスケールすることが難しい
- DLLを使っていない限り全体をデプロイする必要がある
マイクロサービスは銀の弾丸でもなければ、金のハンマーとして使いべきではない
マイクロサービスアーキテクトの振る舞い
- 個々のマイクロサービスの内部詳細より、各マイクロサービス同士がどう連携するのか、それらのマイクロサービス全体の状態をどう監視するのか、と言ったシステム全体に注力すべきである。
- 内部詳細は、所有チームに任せれば良い
- マイクロサービスアーキテクチャでは、予測が困難な状況の下で多くのトレードオフな判断が必要になる。
- 各マイクロサービスで共通の標準として、最低限以下のようなことを定めておく。
- 各マイクロサービスと、システム全体の状態を監視するための仕組みやルール。
- マイクロサービスがAPIを公開するための標準技術やルール。
- 使用する定義済みインターフェース
- 使いすぎない
- 使用する定義済みインターフェース
- あるマイクロサービスの障害がシステム全体に波及しないようにするための仕組みやルール。
- エラーコードなど
- 方針に従っているかを確認する手間を省くために下記を使用する
- 手本(サンプルコード)
- サービステンプレート
- ※開発者に押し付け、やる気を削がしてはいけない
- ユーザだけでなく、開発者たちにも快適に開発できる空間を提供する必要がある
- 近道(方針に外れること)を行ってしまうと将来的な技術的負債になってしまうことを忘れない
- 意思決定する際の注意点
- 技術のリスクはグループで背負う
- 同意できないことがあってもチームの決定には従ったほうが良い
- チームが気づいておらず、確実に自分が正しいときは例外
- システムが方針から逸脱した際はきちんとログに残す
- それをもとに原則とプラクティスを変えていく
サービスのモデル化方法概要
- 良いマイクロサービスを設計するには下記の2点がポイント
- 経験のない新しいドメインを扱う場合、最初からコンテキスト境界を見極めてサービスの単位を決めるのは難しい
- まずはモノリシックに作って、ドメインの理解が進むにつれて徐々にマイクロサービスに分離していくほうが安全。
- まずは大まかな境界から分けていく、どんどん細かく分けていく。
- 細かさの粒度はシステムによって異なる
- コンテキスト境界を分離するときは、データの観点ではなく、そのコンテキストが外部に公開する振る舞いに注目したほうが良い。
結合(最重要パート)
- マイクロサービスにおいては、各サービスをどのように結合するか?が最も重要な技術要素となる。
- 可能な限り、サービスの呼び出し元に影響せずに変更・デプロイできるような結合方式を模索する。
- サービスが外部に公開するAPIは、特定の技術に依存しないようにすることで、呼び出し元の技術選択の自由度を確保するとともに、進歩する技術を随時導入できるようにする。
- データベース(および、その他のデータストア)の共有によるサービスの連携は、簡単ではあるが疎結合性と高凝集性の両方を失うことになり、マイクロサービスを採用する意味がなくなるので避けるべき。
- 破壊的変更を回避するために、内部実装の詳細を隠す
サービス間の連携方法
- サービス間の通信方法として、同期か非同期がある。
- あるサービスとその下流となる複数のサービスによってひとつの処理が構成される場合の制御方法として、オーケストレーションかコレオグラフィがある。
- コレオグラフィによる連携では、全てのサービスが処理を完了したのか、どこまで処理が進んでいるのか、と言ったイベントの処理状況を監視・追跡するための仕組みづくりが必要になる。
- ビジネスプロセスがシステムには暗黙にしか反映されないため
リクエスト/レスポンス
- サービス間の通信プロトコルとしては、HTTP(S)+RESTがデフォルトの選択肢となる。
- RPCは運用の制約、呼び出し、脆弱性などの観点からおすすめはしない
- ただ、HTTPは低遅延には不向きである
- さらにHATEOASを採用することで、サービスの利用者は個々のAPIエンドポイントを把握する必要がなくなる。
- サービスが公開するAPIエンドポイントを柔軟に変更できるメリットがある。(が、まだ積極的な採用事例は少ない)
- 呼び出しが多くなるため、まずはクライアントサイドで実行し、後から移動するほうが良い
データフォーマット
- JSONが多数派。
- linkコントロールが標準ではない
- XMLもおすすめである
- サポートツールが優れているため
- フレームワークの選定には注意
- ひどい密結合を強いられる場合がある
- すべてのHTTPが対応しているとも限らない(PUT、DELETEなど)
イベントベース
- イベントベースのサービス間通信を実現するために、あるサービスがイベントを発行(パブリッシュ)し、他のサービスがそのイベントを受信(サブスクライブ)するための基盤の構築が必要になる。
- 受信したイベントの処理に失敗した場合を考慮し、その仕組みを構築する必要がある。
- 複数のサービス、イベントキューにまたがって処理が連動していくので、それらを紐付けて追跡できる仕組みがないと運用できない。一連のイベントの最初にユニークな相関IDを発番し、それをイベントに乗せて伝播させるようにする。
コードの統合
- ロギングなどの汎用的なものを除き、マイクロサービス(コンテキスト境界)をまたいでコードを共有しないほうが、個々のサービスの独立性を維持できる。
- サービスが公開するAPIを利用するためのクライアントライブラリ(SDK)を提供する場合は、コンテキストのロジックがクライアントライブラリに漏れないように注意する。また、クライアントライブラリのバージョンアップは利用者が任意のタイミングで行えるようにする。
- セマンティックバージョン表記(ex.1.0.2)を使用するとわかりやすい
- URIにバージョン情報を入れるかどうかは好みである。どちらも利点がある
- 公開したサービスの仕様を変更する場合は、可能な限り下位互換性を保ち、呼び出し元への破壊的変更を回避する。
- 同じサービスに異なるエンドポイントを作成する
- 同じサービスで複数のバージョンを実行して古いエンドポイントに対応する
- こちらの場合、サービスの内部バグを修正する必要がある
- 2つの異なるサービスを修正してデプロイする必要がある
- サービスのコードベースを分岐する必要があり、必ず問題となる
- ブルーグリーンデプロイやカナリヤリリースのように、一時的に複数のバージョンが存在するのは問題ない
ユーザーインターフェースとの統合
- ユーザインターフェースを合成レイヤと考える
- 複数のマイクロサービスの結果を1つの画面に表示させる場合、いくつかの方法がある。
- API合成
- UI部品合成
- サービスがUI部品を生成して返し、フロントエンドでそれらを組み合わせて画面を構成する。
- 各部品とチームの担当が一致する場合は良い
- 同じチームがUIもサービスも担当できるので、迅速に公開できる
- 各チームでUIの一貫性を保証する必要がある(CSS、HTMLComponent)
- BFF(Backend For Frontend:フロントエンド向けのバックエンド)
- 上記手法を組み合わせて行う場合もある
レガシーシステムとの統合
- 既存システムやプロダクトと統合する場合は、その既存システムへの呼び出しをマイクロサービスでラップし、徐々に既存システムを新しいサービスに置き換えていく(ストラングラーパターン)ことでビッグバンな変更を避けてマイクロサービスを導入できる。
モノリスの分割
- モノリシックなシステムをマイクロサービスに分割する際は、境界づけられたコンテキスト で分割する。
- 一気にマイクロサービス化するのではなく、影響の小さい機能から徐々にマイクロサービスに分割していく。こうすることでチームがマイクロサービスを学習する機会を設け、導入のリスクを減らす。
データベースの分割
- DBは依存関係の原因となるケースが多い
- 境界づけられたコンテキストをまたぐテーブル共有はせず、サービスを介してデータをやり取りする。
- ただし、これによって外部キーによるデータの関連やトランザクション整合性を保証できなくなる(=結果整合性になる)。
- データ(国コードなどのメタデータなど)もコンテキストをまたいで共有することはできない。
- 解決策
- 各サービスで同じデータを重複して持つ
- 一貫性の課題を招く可能性がある
- 列挙型などのコード化して各サービスに取り込む
- 一貫性の課題は残るが、DBを変更するのは簡単
- 共有データを扱う独立したサービスにするなど。
- データ量や複雑な場合は良い手法(国コードレベルだとやりすぎ)
- 共有データは新パッケージを作成し、それを重複しているパッケージに公開
- 共有テーブルは分割する
- データベースの分割は段階的に。まずサービスはモノリシックのままでコンテキストごとにデータベースを分割し、次にサービスを分割する
- データベースを分割することでデータが結果整合性となる場合は、その整合性が取れていない状態を表現するドメインの概念(例 処理中の注文など)を導入する。
- 下記の解決策もあるが、あまりおすすめはしない
- 操作全体の中止
- 分散トランザクション
帳票の分割
- 一般的に帳票には複数のコンテキストの情報が集約される。サービスを分割することで帳票のために情報を集約する方法を検討する必要がある
- モノリシックサービスでは、全てのデータをまとめたデータベースからリードレプリカを作成し、そこに対してレポートの作成を行っているケースが多い -分割法
- 各サービスを呼び出して帳票で必要な情報を収集する
- 必要に応じてサービスに情報をバッチ的にまとめて取得するAPIを設ける。
- あまりおすすめはしない
- 帳票用のデータベースを設けてサービスのデータを連携する(データポンプ)
- イベントベースでサービスからの変更イベントをサブスクライブして帳票用データベースにデータ投入することを検討する。
- どのイベントが処理済みかを格納しておくと、差分のみを取得して実行することができる
- イベントストリームのタイムスタンプを活用する
- イベントデータポンプはサービス内部とあまり結合しないため、独自の進化が可能
- 必要なすべての情報をイベントとしてブロードキャストしないといけない欠点がある
- Netflixはバックアップするデータのコピーを作成して、安全な場所に格納する手法(バックアップデータポンプ)を採用している
まずはモノリスになった根本原因を理解することが大切
デプロイ
モジュール
- マイクロサービスのCIでは、個々のサービスを独立してビルド・デプロイできるよう、サービスごとにコードリポジトリやビルドジョブを構成するのが良い。
- 1つのソースコードレポジトリからサービスごとにビルドを行うほうが、全てをビルドするよりはマシだが、おすすめはしない
- ビルドのサイクルを最適化するために、ビルドにステージを設けてビルドパイプラインを構成する。ステージの最初のほうでは高速に実行できるテストを実施してフィードバックループが早く細かく回るようにし、ステージが進むにつれて低速で総合的なテストを実施するようにする。
インフラ
- モジュールが稼働するために必要なインフラやミドルウェアも自動的に構成できるようにする必要がある。
- CI・CDによって構成され稼働しているサーバは、原則として手作業で変更(SSH接続したり、管理コンソールを使ったり)してはいけない。サーバの構成を変更するには、ビルドパイプラインを使ってビルド・デプロイを行う。
環境設定
- サービスが必要とする設定項目は、DB接続情報のような環境に依存する情報だけとし、環境ごとに変わる項目は必要最小限に留めるようにする。
- 1つのモジュールに対して構成を別で管理する
- 環境ごとにモジュールを作成するのはNG
- 環境依存の情報はモジュールには含めずに設定ファイルなどで別管理できるようにする。
- プロパティファイルやパラメータなど
サービスからサーバへのマッピング
- 1台のサーバに1つのマイクロサービスをデプロイするべきである
- 1台のサーバ(およびアプリケーションサーバのようなコンテナ)に複数のサービスを同居させるべきではない。
- 各サービスを独立してデプロイし、運用状況を監視することが困難。
- 共有されるサーバがボトルネックとなって、サービスを開発するチームの自律性を妨げる。
- サーバのスケーリングが困難。
- サーバを共有するのは、サーバリソースの最適化(節約)が目的であり、仮想化技術などを使えばその必要はなくなる。
- 技術選択が制限され、イメージベースやイミュターブルサーバなどが使えない
- 1台のサーバ(およびアプリケーションサーバのようなコンテナ)に複数のサービスを同居させるべきではない。
- 仮想環境を構築する際はDockerがおすすめ
デプロイツール
- モジュールのデプロイは、以下の情報をパラメタとして受け取るコマンドラインツールで実行できるが望ましい。
- モジュール
- デプロイするモジュールの名前や場所。
- バージョン
- デプロイするモジュールのバージョン。
- 環境
- デプロイ先の環境。
- モジュール
- これを手元のターミナルやCI・CDツールから実行する。
- Fabric
- コマンドライン呼び出しを関数にマッピングするために設計されたPythonライブラリ
- RubyだとCapistrano、WindowsではPowerShell
- Fabric
マイクロサービスの概要をさらうという意味ではいいとおったけど、悪く言えばその程度ですね
「ああ!なるほど!」と言った新しい発見はなかったイメージ
ただこれは私の実力や経験が追いついていない節はある(ピンときていないだけ)ので、3年後くらいに読んでみようかなと思います
その頃にはマイクロサービスという言葉も死語になってたりしてw(少しあり得そうで笑えない)