書籍「マイクロサービスアーキテクチャ」まとめ（前編）

# はじめに

O`REILLY発行の [マイクロサービスアーキテクチャ](https://www.oreilly.co.jp/books/9784873117607/) を読みました。
非常に興味深く、これまで主に企業向けエンタープライズ開発に関わってきた自分にとっては、今後のソフトウェアエンジニアとしてのキャリアを作っていくには、考え方の転換が必要だと気付かされる内容でした。

本書の内容は広範囲に渡りますが、特に重要と感じた要点を整理(かなり長いですが)しておきたいと思います。（[後編](http://qiita.com/crossroad0201/items/2d115daeddbc884adfc5) との2部構成です）

# 要点

## はじめに

* マイクロサービスを取り巻く技術の進歩は早く、マイクロサービスを実現する**特定の技術を習得することよりも、その本質的な考え方を理解する**ことが重要である。

## １章．マイクロサービス

### マイクロサービスとは

* マイクロサービスはSOA(サービス指向アーキテクチャ)のひとつの実現形態であり、DDD(ドメイン駆動設計)、CI・CD(継続的インテーグレーション・デリバリ)、インフラ仮想化、自動化、アジャイル開発プロセス、といった**さまざまな分野の技術や方法論を組み合わせることで成り立って**おり、これらが総合的に実践できなければその価値を享受できない。

* DDDの**「境界付けられたコンテキスト」が、ひとつのマイクロサービスの単位**になる。
  * 参考「[DDDの構成要素とマイクロサービスの単位をどう合わせるべきか](https://qiita.com/crossroad0201/items/32673d3e52e006205c48)」

* マイクロサービスは、外部から利用されるためのAPIを公開する。その公開APIの**呼び出し元(=コンシューマ)に影響を与えることなく単独で変更・本番リリース可能**である。

* あるマイクロサービスが停止しても、**他のマイクロサービスは停止せずにサービスを提供し続け、**システム全体としては、一部機能を切り離して稼働し続けることができる。

* マイクロサービスは、**必要なサービスだけを、必要なときにスケール**させることができる。

* マイクロサービスは、**自律的なチームによって開発され、所有・維持される。**採用する技術や方法論は所有チームが自由に決定する。

* マイクロサービスは、**簡単に捨てたり、作り直したりできる。**(2週間で作り直せる程度の大きさ)

## ２章．進化的アーキテクト

### マイクロサービスのアーキテクト

* アーキテクトは、個々のマイクロサービスの内部詳細より、各マイクロサービス同士がどう連携するのか、それらのマイクロサービス全体の状態をどう監視するのか、と言った**システム全体に注力**すべきである。(内部詳細は、所有チームに任せれば良い)

* マイクロサービスアーキテクチャでは、予測が困難な状況の下で多くのトレードオフな判断が必要になる。**目標と、それを実現するための原則とプラクティスを定める**ことで対処できる。
  * *戦略的目標*・・・会社や部門レベルで定める、儲けるためには何がどうあるべきか、と言う目標。
  * *原則*・・・戦略的目標を達成するために、関係者が守るべき規則。
  * *プラクティス*・・・原則を守るための具体的な手段、技術的な標準ルールなど。

* 各マイクロサービスで共通の標準として、最低限以下のようなことを定めておく。
  * 各マイクロサービスと、システム全体の状態を**監視するための仕組みやルール。**
  * マイクロサービスが**APIを公開するための標準技術やルール。**
  * あるマイクロサービスの**障害がシステム全体に波及しないようにするための仕組みやルール。**

* マイクロサービスを開発したチームにサービスを所有させ、ビジネス要求から本番運用までの**一連のタスクを持たせることで、チームに自律性が生まれる。**(タスクでチームを分けるべきではない)

## ３章．サービスのモデル化方法

* 良いマイクロサービスを設計するには、他のサービスに影響することなく**独立してデプロイできる疎結合性**と、ドメインの**境界づけられたコンテキストでまとめられた高凝集性**がポイントになる。

* 経験のない新しいドメインを扱う場合、最初からコンテキスト境界を見極めてサービスの単位を決めるのは難しいので、**まずはモノリシックに作って、ドメインの理解が進むにつれて徐々にマイクロサービスに分離していく**ほうが安全。

* コンテキスト境界を分離するときは、**データの観点ではなく、そのコンテキストが外部に公開する振る舞いに注目**したほうが良い。

## ４章．統合

* マイクロサービスにおいては、**各サービスをどのように結合するか？が最も重要**な技術要素となる。

* 可能な限り、サービスの**呼び出し元に影響せずに変更・デプロイできるような結合方式**を模索する。

* サービスが**外部に公開するAPIは、特定の技術に依存しないようにする**ことで、呼び出し元の技術選択の自由度を確保するとともに、進歩する技術を随時導入できるようにする。

* データベース（および、その他のデータストア）の共有によるサービスの連携は、簡単ではあるが疎結合性と高凝集性の両方を失うことになり、**マイクロサービスを採用する意味がなくなるので避けるべき。**

### サービス間の連携方法

* サービス間の通信方法として、*同期*か*非同期*がある。

| 連携方法 | 同期 | 非同期 |
|:-:|:--|:--|
| リクエスト／レスポンス | サービスを呼び出してその応答が返ってくるのを待つ。 | サービスを呼び出す際にコールバックを渡して即時にリターンし、サービスの処理が完了したらコールバックを呼び出してもらう。 |
| イベントベース | なし | サービスを呼び出すのではなく、何らかの事象(イベント)が起こったことを通知する。そのイベントに関心のあるサービスは能動的にそれを受信して処理を行う。(イベントを発生させたサービスは、誰がそのイベントを受信するかは感知しない) |

* あるサービスとその下流となる複数のサービスによってひとつの処理が構成される場合の制御方法として、*オーケストレーション*か*コレオグラフィ*がある。

| 方式 | 説明 |
|:-:|:--|
| オーケストレーション | 基点となるサービスが他のサービスを*リクエスト／レスポンス*で呼び出し、処理全体のフローを制御する。基点となるサービスにロジックが集中してしまい、**サービスの独立性が失われがち**になる。 |
| コレオグラフィ | 基点となるサービスが*イベントベース*でイベントを発行し、他のサービスがそれを受信して個々に処理を行う。 マイクロサービスの原則を満たすには、**コレオグラフィによる連携を模索すべき。**|

* *コレオグラフィ*による連携では、全てのサービスが処理を完了したのか、どこまで処理が進んでいるのか、と言った**イベントの処理状況を監視・追跡するための仕組みづくりが必要**になる。

#### リクエスト／レスポンス

* サービス間の通信プロトコルとしては、**HTTP(S)+RESTがデフォルトの選択肢となる。**
  * さらに*HATEOAS*を採用することで、サービスの利用者は個々のAPIエンドポイントを把握する必要がなくなる。このことで、サービスが公開する**APIエンドポイントを柔軟に変更できる**メリットがある。(が、まだ積極的な採用事例は少ない)

* データフォーマットとしては、**JSONが多数派。**

#### イベントベース

* イベントベースのサービス間通信を実現するために、あるサービスが**イベントを発行(パブリッシュ)し、他のサービスがそのイベントを受信(サブスクライブ)するための基盤の構築が必要**になる。

* 受信した**イベントの処理に失敗した場合を考慮し、その仕組みを構築**する必要がある。
  * 失敗したイベントの処理の**リトライ**。何回リトライするか、どれくらいのインターバルを置くか。
  * リトライしても**最後まで成功しなかったイベントの扱い**。エラーキューに移動させて、後で別途リカバリできるようにするなど。
  * **エラーキュー内のイベントの監視／参照とリカバリ**。個々のマイクロサービス独自ではなく、標準的な仕組みがほしい。

* 複数のサービス、イベントキューにまたがって処理が連動していくので、それらを**紐付けて追跡できる仕組み**がないと運用できない。一連のイベントの最初に**ユニークな*相関ID*を発番し、それをイベントに乗せて伝播させる**ようにする。

### コードの統合

* ロギングなどの汎用的なものを除き、**マイクロサービス(コンテキスト境界)をまたいでコードを共有しない**ほうが、個々のサービスの独立性を維持できる。

* サービスが公開するAPIを利用するための*クライアントライブラリ(SDK)*を提供する場合は、**コンテキストのロジックがクライアントライブラリに漏れないように**注意する。また、クライアントライブラリの**バージョンアップは利用者が任意のタイミングで**行えるようにする。

* 公開したサービスの仕様を変更する場合は、可能な限り下位互換性を保ち、**呼び出し元への破壊的変更を回避**する。
  * 破壊的変更を伴う場合は、一時的に**新旧バージョンを並行運用**して呼び出し元が新バージョンに移行する期間を持たせる。

### ユーザーインターフェースとの統合

複数のマイクロサービスの結果を１つの画面に表示させる場合、いくつかの方法がある。

| 方法 | 概要 |
|:-:|:--|
| API合成 | XMLやJSONデータを返す複数のサービスを画面から呼び出し、フロントエンドで画面を生成する。 |
| UI部品合成 | サービスがUI部品を生成して返し、フロントエンドでそれらを組み合わせて画面を構成する。 |
| BFF(Backend For Frontend) | サーバー側に、複数のサービス呼び出しを画面向けにまとめるゲートウェイ層を設ける。 |

### レガシーシステムとの統合

* 既存システムやプロダクトと統合する場合は、その既存システムへの呼び出しを**マイクロサービスでラップし、徐々に既存システムを新しいサービスに置き換えていく(ストラングラーパターン)**ことでビッグバンな変更を避けてマイクロサービスを導入できる。

## ５章．モノリスの分割

* モノリシックなシステムをマイクロサービスに分割する際は、**境界づけられたコンテキスト** で分割する。

* 一気にマイクロサービス化するのではなく、**影響の小さい機能から徐々にマイクロサービスに分割していく。**こうすることでチームがマイクロサービスを学習する機会を設け、導入のリスクを減らす。

### データベースの分割

モノリシックなシステムを分割するには、コードの分割だけではなく、**データベース(RDB)も分割する必要がある。**

* 境界づけられた**コンテキストをまたぐテーブル共有はせず**、サービスを介してデータをやり取りする。ただし、これによって外部キーによるデータの関連やトランザクション整合性を保証できなくなる(=結果整合性になる)。

* **データ(メタ的なデータなど)もコンテキストをまたいで共有することはできない。**各サービスで同じデータを重複して持つ、列挙型などのコード化して各サービスに取り込む、共有データを扱う独立したサービスにするなど。

* **データベースの分割は段階的に。**まずサービスはモノリシックのままでコンテキストごとにデータベースを分割し、次にサービスを分割する。

* データベースを分割することでデータが結果整合性となる場合は、その**整合性が取れていない状態を表現するドメインの概念(例 処理中の注文など)を導入**する。

### 帳票の分割

一般的に帳票には複数のコンテキストの情報が集約される。サービスを分割することで**帳票のために情報を集約する**方法を検討する必要がある。

* 各サービスを呼び出して帳票で必要な情報を収集する場合、必要に応じてサービスに**情報をバッチ的にまとめて取得するAPI**を設ける。

* 帳票用のデータベースを設けてサービスのデータを連携する(データポンプ)場合、イベントベースで**サービスからの変更イベントをサブスクライブして帳票用データベースにデータ投入**することを検討する。


## ６章．デプロイ

### モジュール

* マイクロサービスのCIでは、個々のサービスを独立してビルド・デプロイできるよう、**サービスごとにコードリポジトリやビルドジョブを構成する**のが良い。

* ビルドのサイクルを最適化するために、ビルドに**ステージを設けてビルドパイプラインを構成**する。ステージの最初のほうでは高速に実行できるテストを実施してフィードバックループが早く細かく回るようにし、ステージが進むにつれて低速で総合的なテストを実施するようにする。

* **サービスごとに独立したビルドパイプラインを構成し、ビルドステージの進行を管理できるCI・CDツール**の選定が望ましい。
手動で実施する受入れテストもステージのひとつとして管理するべき。

### インフラ

* モジュールが稼働するために必要な**インフラやミドルウェアも自動的に構成できる**ようにする必要がある。
  * モジュールを実行環境のプラットフォーム(OS)ネイティブのパッケージ管理ツールにあわせる。
  * モジュールとインフラ、ミドルウェアをひとまとめにしたVMイメージを作る。VMイメージの作成は [Packer](https://www.packer.io) でコード化すると便利。

* CI・CDによって構成され稼働している**サーバは、原則として手作業で変更(SSH接続したり、管理コンソールを使ったり)してはいけない。**サーバの構成を変更するには、ビルドパイプラインを使ってビルド・デプロイを行う。

### 設定項目

* サービスが必要とする設定項目は、DB接続情報のような**環境に依存する情報だけ**とし、必要最小限の項目に留めるようにする。

* **環境依存の情報はモジュールには含めずに設定ファイルなどで別管理**できるようにする。

### サービスとサーバのマッピング

* マイクロサービスでは、**1台のサーバ(およびアプリケーションサーバのようなコンテナ)に複数のサービスを同居させるべきではない。**
  * 各サービスを独立してデプロイし、運用状況を監視することが困難。
  * 共有されるサーバがボトルネックとなって、サービスを開発するチームの自律性を妨げる。
  * サーバのスケーリングが困難。
  * サーバを共有するのは、サーバリソースの最適化(節約)が目的であり、仮想化技術などを使えばその必要はなくなる。

* **1台のサーバに1つのマイクロサービスをデプロイ**することで、上記の問題はすべて解決できる。

### デプロイツール

* モジュールのデプロイは、**以下の情報をパラメタとして受け取るコマンドラインツール**で実行できるが望ましい。これを手元のターミナルやCI・CDツールから実行する。
  * [Fabric](http://www.fabfile.org) を使ったPythonスクリプトを作成すると良い。
  * **モジュールバージョンは、システム全体に対してではなく、個々のサービスごとに**発番する。(項7.6.4より)

| 情報 | 説明 |
|:-:|:--|
| モジュール | デプロイするモジュールの名前や場所。 |
| バージョン | デプロイするモジュールのバージョン。 |
| 環境 | デプロイ先の環境。 |

* 環境のデプロイ(作成)も、コード化して自動化する。
  * [Terraform](https://www.terraform.io) や [AWS CloudFormation](https://aws.amazon.com/cloudformation/?nc1=h_ls) などを使う。

## ７章．テスト

### テスト

* マイクロサービスの利点を得るには、**テストを自動化してサービスを迅速かつ効率的に検証できるようにすることが不可欠。**

* テストをステージに分け、適切かつ**高速にフィードバックループが回るように**する。
  * スコープの大きなテストで不具合が見つかったら、より小さいスコープに確認するテストを追加してテストのフィードバックループを継続的に改善していく。
  * **必要のなくなったテストは削除**して実行時間を短く保つ。

| テスト | テスト対象 | テスト作成者 | スコープ | 実行時間 | 備考 |
|:-:|:--|:-:|:-:|:-:|:--|
| 単体テスト | サービスを構成する各タイプやメソッド。 | 各サービスの開発チーム | 小 | 小 | サービスは起動せず、ネットワークや他サービスの呼び出しはスタブ／モック化することで、どこでも高速に実行できるようにする。 |
| サービステスト | 個々のサービス。 | 各サービスの開発チーム | 中 | 中 | サービスは起動するが、そのサービスから連携される下流サービスはスタブ／モック化する。 |
| エンドツーエンドテスト | 各サービスを組み合わせたシステム全体 | サービスの開発チームで共有 | 大 | 大 | 画面などのユーザーインターフェースを介したテスト。 |
| コンシューマ駆動契約(CDC)テスト | 個々のサービス | サービスの開発チームと、利用者で共有 | 中 | 中 | 利用者からのサービス呼び出しに破壊的影響がないか？の観点のテスト。 |

#### サービステスト

* 下流サービスのスタブ／モック化には、スタブサーバを立てるツール [mountebank](http://www.mbtest.org) が使える。

#### エンドツーエンドテスト

* 各サービスのビルドパイプラインをまとめるビルドステージとすることで、**サービステストを通過した各サービスを組み合わせて実施**できる。

* テストはサービスの開発チームの共同所有とする。専属のテストチームなどを置いて、**開発チームの手からテストが離れるとテストの質が悪化する。**

* 全てのユーザーストーリーをエンドツーエンドテストしていてはテストに時間がかかりすぎて素早いリリースが困難になるので、**システムの中核だけに絞ってテストを行うジャーニーテスト**を検討する。

#### コンシューマ駆動契約テスト

* **利用者からの見た挙動を保証することに焦点を絞ってテスト**することで、比較的速いサイクルで利用者に影響する破壊的変更を検知することができる。

* [Pact](https://github.com/realestate-com-au/pact) のようなCDCテストを支援して自動化するツールもある。

* どれだけテストを厚くしても不具合の混入を100%防止することはできないと認識して、いち早い**本番環境で稼働させることによるフィードバックを優先するなら、エンドツーエンドテストよりもCDCテストに重点を置く。**

### 本番環境でのテスト

* リリース前に手厚くテストすることにリソースを使うよりも、**本番環境での稼働監視や素早い障害復旧のための仕組み作りにリソースを使い、早く本番環境で稼働**させたほうが効率的。

|  | 説明 |
|:-:|:--|
| スモークテスト | 本番環境にデプロイされたシステムが正しく稼働しているかを確認するテスト。 |
| ブルーグリーンデプロイメント | 本番環境上に現行バージョンと並行して新バージョンをデプロイ(トラフィックは現行バージョンで処理)してテストし、問題がなければトラフィックを次バージョンに向ける方法。 |
| カナリアリリース | 本番環境上に現行バージョンと並行して新バージョンをデプロイし、トラフィックの一部または全部(のコピー)を新バージョンに向けてテストする方法。 |

### 非機能テスト

* 非機能テストは厳密には本番環境でなければ検証できないものが多いが、サービス開発の**できるだけ初期段階から非機能テストを設計して継続的に実施できる仕組みを構築し、徐々に育てていく**ほうが良い。

* **性能テストは大量のデータや負荷状態を作り出すことが必要なため頻繁には実施できない。**日々のサイクルではその一部のケースを実施し、全ケースの実施は週次などで行う。


*...８章～１２章は [後編](http://qiita.com/crossroad0201/items/2d115daeddbc884adfc5) に続きます*