第 II 部 実装
(ここから詳細の話に入るため、ハイライトは結構雑になります)
5章 マイクロサービスの通信の実装
技術の選択においては、通信の後方互換性、明示的なインタフェース、特定の技術に非依存である点を重視する。通信技術には次のような例がある
- REST
- 効率的なバイナリを(デフォルトでは)使わないためパフォーマンスが落ちる
- GraphQL
- データのラッパーサービスに見えがち
- RPC
- バイナリを使って高パフォーマンスな通信
- メッセージブローカー(Kafka など(らしい))
- キュー
- 各サービス用のキューにイベントを格納し、それぞれが読み取る
- トピック
- 複数のコンシューマが subscribe でき、プロバイダがイベントを通知できる
(単純なリクエスト・レスポンスには gRPC が適していそう?)
破壊的変更
スキーマが生成されることで、構造的破壊(インタフェースの変更)を検知できる
(↔ 意味的破壊 = インタフェースは変わらないが動作が変わる)
gPRC にはスキーマからクライアントコードを生成する機能があり、クライアントが変化を検知できるのでこの点で有利
サービスをデプロイすることで破壊的変更が起きないように、次のような対処法がある
- 拡張変更 = 新しいインタフェースを追加するだけで、更新・削除しない
- Tolerant reader = 送信するものは厳密に、受信するものは寛容に
破壊的変更が起きてしまう場合、次のような手がある
- ロックステップデプロイ = インタフェースとコンシューマ全てを同時に変更する
- 独立デプロイ可能性に反するのがデメリット
- チーム間で調整できるなら OK
- バージョンの共存 = 新しいバージョンと古いバージョンを共存させる
- デプロイやコードベースの点で技術的難易度が高い
- 旧インタフェースのシミュレーション
- 同じサービス上に新旧インタフェースを両方作り、徐々に移行する
- おすすめ
DRY
DRY = 「振る舞いと知識」の重複を避ける、コードの重複を避けることではない
マイクロサービスでは、DRY によるコードの再利用で結合が発生する可能性があるため、これを避けるためにも上の理解が特に重要
ライブラリのバージョンを更新したとき、全ての使用者を同時にアップデートはできないので、クライアント側でアップデートをコントロールできるようにし、構造的破壊に注意する
6章 ワークフロー
単一プロセス単一サービスでは、DB のトランザクションにより一連の操作を保証できる
一方マイクロサービスでは DB が分散していることが多い。分散トランザクションはあるが、推奨しない(というスタンス)
そこでサーガという手法がおすすめ
サーガ
一連のトランザクションに分解し、それぞれ処理していく
Transaction A → Transaction B → Transaction C → …(図解むずい)
障害発生時には、後方復旧(前のトランザクションを順に取り消していく)か、前方復旧(リトライ時に障害発生時点から処理を継続する)という手段をとる
ロールバックは基本できないので、ビジネスロジックに合わせて手順を調整することで、しなくて良いようにする
サーガの進捗の管理は2パターンある
- オーケストレーションサーが
- 1つのサービスがサーガを管理する
- 結合が増え、ロジックが分離しきれないデメリット
- リクエスト・レスポンス方式に向いている
- コレオグラフィサーガ
- 呼び出し先のサービスを信頼し、自分の処理のみ管理することで、責務を分散させる
- イベント駆動に向いている
7章 ビルド
(Skip)
8章 デプロイ
(ほぼ Skip)
PasS を使う際は、できる限りそちらに制御を任せるのが良い
プログレッシブデリバリ
デプロイ(反映)とリリース(利用可能)を分離することで、影響する範囲を制御する手法
デプロイの影響範囲を抑えるのに有効
- Feature Flag = デプロイされた機能を隠す
- カナリアリリース = カナリアグループに対する最小限のリリース
- 並列実行 = バージョンの異なる同じ機能を同時に実行
9章 テスト
分散システムの E2E は難易度が高く、複雑なのでテスト環境で全てを見るのは不可能。各サービスが契約テスト(外部に決めているふるまい)を確認するに留めるのがおすすめ
Mean Time Between Failures よりも Mean Time To Repair を重視し、復旧に力をいれるのも手
10章 監視から可観測性へ
「マイクロサービスの障害は殺人ミステリのよう」
可観測性のために、以下が重要
- ログ集約 = 複数サービスのログをまとめる
- 分散システムの呼び出しをトレースし、相関 ID(一連サービスの呼び出しで共通したログ ID をもたせる)が良い
- メトリック集約 = メトリック(以下同文
- (など…
11章 セキュリティ
(Skip)
「マイクロサービスのセキュリティは最もセキュアでない部分に依存する」
12章 レジリエンス
マイクロサービスのレジリエンスに冠する4つの概念がある
- 堅牢性
- 想定される混乱に対処するメカニズムをソフトウェアに組み込む
- 回復性
- 障害からいかにうまく復旧するか
- グレースフルな拡張性
- 想定できない問題への対処
- 持続的な適応性
- これまで止まったことがないからといって明日止まらないとは限らない
堅牢性確保のためには、一部のサービスが停止したときに安全に機能低下できるようにする
障害が起きてもすぐに問題が発生しないように、システム設計では次の点を考慮する
- タイムアウト
- 複数サービスが通信するので、考慮が必須
- 長いとシステム全体の処理が長引き、短いと失敗と判断されやすくなる
- 短めが良い
- リトライ
- パケットロスやゲートウェイ負荷の急増などのエラーに対しては、繰り返すことが理にかなっている
- バルクヘッド
- リソースを分けることで、障害から他のサービスを隔離する
- サーキットブレーカー
- 一定の条件で問題が発生している次のサービスへリクエストを送らなくなる
- 下流サービスのレスポンスが遅いときに、リクエスト全体が遅くなるのを防ぐ
- (システム的には失敗する)
- 冪等性
- 何度実行しても結果が変わらない冪等性をもたせる
- 障害発生時のデータの扱いに関する CAP 定理
- 一貫性、可用性、分断耐性のうち2つを選ぶ
- 一貫性を犠牲 = 片方のサービスを停止させ、同期が取れていない可能性のあるもう片方のサービスのデータを信用する
- 可用性を犠牲 = 同期が取れるまでサービスを停止する
第 III 部 人
14章 UI
専門スキルの必要性、UI の一貫性のために、フロントは専任チームが生まれがち。イネイブリングチームで対応すべき
専門チームにはモノリシックフロントエンドで十分だが、複数のストリームアラインドチームを設けるにはマイクロフロントエンドが不可欠
15章 組織構造
疎結合な組織を作ることで、高速な開発・デプロイが可能になる
マイクロサービスアーキテクチャの恩恵を得るには、それに合わせた組織構造の変革が必要(逆コンウェイの法則)
サービスの所有権も決める必要がある
- 強い所有権
- サービスは所有するチームが支配する
- それぞれのチームで完結する必要があり、メンバーを揃えるのが大変
- マイクロサービスの恩恵を最大限に得られる
- 著者のおすすめ、イネイブリングチームをうまく使う
- 共同所有権
- 任意のチームが任意のサービスを変更できる
- 人数が増えると一貫性のための調整が重荷になる
- 技術的一貫性がないとスイッチングコストが無視できない
16章 進化的アーキテクト
(Skip)
感想
- 読めば読むほど、導入するコストと出来上がるシステムの複雑さが想像できた
- 日本中にリニアモーターカーの交通網を整備すれば速くなるよね的な
- 整備にどれだけ時間がかかるのか、整備し切るまでに世界が変わっていないか etc…
- 会社のマイクロサービス詳しい方も、マイクロサービスには懸念を挙げていたのもわかる(もちろん会社の規模・フェーズにも依存するが)
- すでに会社で分散システムチックなサービスの設計を参考にしたので、本に語られていることは割と「進研ゼミでやったことあるやつだ!」状態だったが、用語や比較対象が整理できたのは大きな収穫だった
- これでいいんだという安心感(そもそも会社の強いエンジニアが設計したものなので筋が間違っていることはほぼないはずだが)
- 本を選ぶときに「モノリスからマイクロサービスへ」と迷ったが、分散システムを学ぶうえではこちらで正解だったと思う
- (厚さに圧倒されてもう片方にするところだった)
- どこまで分散システムと付き合っていくかわからないが、自分の設計の選択肢の幅は確実に広がったと感じる