マイクロサービスパターン
マイクロサービスアーキテクチャとは?
1つのアプリケーションを複数のサービスに機能的に分割するためのアーキテクチャスタイル
マイクロ、というサイズはさして重要ではない
複数のサービスに跨がるクエリとトランザクションがむずい
次の特性を推進する
メンテナンス性
テスト容易性
デプロイ容易性
「開発するなら責任を取れ」
アプリケーションのアーキテクチャとは?
アプリケーションの全体構造のこと
コンピュータシステムのソフトウェアアーキテクチャとは
システムを作り上げる上で必要となる一連の構造のことで、ソフトウェア要素とそれらのあいだの関係、両者の性質から構成される
マイクロサービスアーキテクチャを定義するプラクティスをやってみる
システム操作の洗い出し
サービスを洗い出す
メッセージング
メッセージは次の三種類に分類される
ドキュメント、コマンド、イベント
同期的なインタラクションの取り除き方
メッセージングで全部非同期にする
データをレプリケートしてインタラクション自体を消す
同期で直ぐにできる処理だけしてレスポンスを返す、そのあとに他のサービスを使った処理をする
複雑なサーガはオーケストレーション形式のほうがいい
コレオグラフィだとユースケースが各サービスに散らばることになる
サーガは ACID のうち ACD しか保証できない
ので、更新の消失、ダーティリード、ノンリピータブルリードが起きる
そのためのカウンターメジャーがある
状態変更を表すドメインイベントのシーケンスというかたちでアグリゲートを永続化する
クエリーが複雑になることが欠点
WHEREで絞り込みできていたことが高コストになる
マイクロサービスアーキテクチャでのクエリー
基本的には API Composition パターンでよい
しかし、API Composition パターンでは効率が悪い、トランザクションが無い、などの欠点がある
関心の分離の原則からデータの近くでユースケースを実装するのは良くないケースがある
一つのデータモデルで表現するのがしんどいので Write/Read で分割する
Read モデルを構築するときに Write モデルのイベントを受けて更新させることができる
またそのときに Read モデルに適した DB を使うことができる
e.g., Geometry 型が使える DB
利点
実行効率のよいクエリーを実装できる
関心事の分離を徹底する
欠点
複雑
排他制御やイベントの重複除去などを考慮して実装しなくてはならない
イベントを受けてリードモデルを永続化するためタイムラグがある
対策するにはイベントID によりクエリ結果が最新かどうか判定するなどのクライアントの協力が必要
イベントが at least once で配送される場合、べき等にしておいても古い結果に巻き戻りうるので対策が必要
イベントID を控えておくことで対策可能
あとは日付とかも考えうるが time skew を考えるに完全ではなさそう
単調増加な数も使えるとは思うが、それをどこで発行するのか
DBマイグレーションが難しそう?
API Gateway
クライアントのエントリーポイントとして建てる
クライアントの種類ごとにAPIを提供するのがいいらしい
Android/iOS, Web, サードパーティ
責務
リクエストのルーティング
単にプロキシー的な機能のことぽい
API 合成
API Composition パターン
プロトコル変換
gRPC -> RESTfull
クライアント固有APIの提供
発展させると BFF となる
エッジ機能の実装
認証、認可、Rate limit、キャッシュ、メトリクス収集、ロギング
オーナーシップはどうするか?
単一のチームが任せるとそこがボトルネックになり、自主性、自律性を粗大する
クライアントチームに、対応するクライアントのAPI モジュールを任せるほうがいい
共通モジュールと運用を APIゲートウェイチームが担当する
上記のエッジ機能
Netflix がその方式らしいがモノリス的な感じがする
Backends For Frontends (BFF)
API Gateway ではオーナーシップが曖昧になる欠点があった
これの対策としてクライアントアプリケーションごとに独立した API ゲートウェイを作ったものが BFF
共通層は API ゲートウェイチームが担当するらしい
信頼性、可観測性なども向上する
グラフベース API の利点
e.g., GraphQL, Falcor
どのデータを返してもらうかクライアントが指定できる
API 合成と射影をサポートするように設計されたクエリー実行フレームワークを使って API Gateway が実装できる
GraphQL
読み取りを最適化するためにバッチングとキャッシングが必要
DataLoader でやる
別サービスをN+1回呼ばなくても済むようにバッチングする
テスト (前編)
テストの目的はテスト対象の振る舞いを確認すること
テストダブル (Test double)は次の2つに分けられる
スタブ (Stub)
値を返すテストダブル
モック (Mock)
正しく呼び出されることを検証するテストダブル
スタブを兼ねていることもよくあるらしい
テストの種類は次の通り
テストピラミッドに従った比率で構成するとよい
ユニットテスト
クラスなどのサービスを構成するソフトウェアコンポーネントのテスト
ビジネスロジックのテスト
一番高速で信頼性が高く、これをたくさん用意してテストを構成する
インテグレーションテスト (結合テスト)
サービスがデータベースなどのインフラストラクチャサービスや他のアプリケーションサービスと適切にやり取りできることをチェックする
コンポーネントテスト
個々のサービスの受け入れテスト
エンドツーエンドテスト
アプリケーション全体の受け入れテスト
低速で作るコストが高いのでそんなに多く必要ないようにテスト全体を構成するのがよい
主にユニットテストの話
テストダブルを駆使する感じ
テスト (後編)
インテグレーションテスト
他のアプリケーションと適切にやり取りできることをチェックするテスト、と説明されているが、実際はお互いの合意したインターフェースを満たしていることを信側と受信側でテストする、というものだった
HTTP通信、Pubsub、非同期リクエスト/非同期レスポンスなどそれぞれに対応している
Spring Cloud Contract の例ばかりで Java に詳しくないのでなんともである
DB へのデータ永続化のテストは docker を使ってやるといいとのこと
コンポーネントテスト
ビジネス面の要件を満たしていることを確認するテスト
アプリケーションの機能からではなく、ビジネス的なシナリオからテストを作る
しかし、実行コストを加味して程々にスタブなどを使ったりする
本文中ではdocker-compose で他のアプリケーションを起動してテストしていた
Gherkin + Cucumber を使っていた
Gherkin が言語やフレームワークに依存しない仕様
Cucumber が実行フレームワーク
Behavior-Driven Development (BDD) というのがあるらしい
Cucumber がソフトウェアとしてあって、その中のシナリオ記述 DSL が Gherkin という位置づけぽい?
エンドツーエンドテスト
こちらも Cucumber を使ってコンポーネントテストと似たようになるが、コストを抑えるためにユーザージャーニーテストにしている
また docker-compose でサービスの多くを起動している
本番環境に耐えられるサービスの開発
セキュリティ、設定可能性、可観測性の3つをなんとかしないと本番環境での動作に耐えられない
セキュリティ
問題の大半はモノリシックアーキテクチャと同様
認証、認可、監査、セキュアなプロセス間通信、の4つがマイクロサービスアーキテクチャによって特に影響を受ける
API Gateway で認証をして、各マイクロサービスで認可をする
OAuth 2.0 を推奨していたが今どきなら OpenID Connect のほうがよさそう
設定可能性
サービスの設定を簡単に変えられるようにしたり、Credential などを暗号化して一元管理したりする
push 型と pull 型の2つの手段に大別される
可観測性
Health check API
Log aggregation
Distributed tracing
Exception tracking
Application metrics
Audit logging
サービスメッシュ
サービスを出入りするすべてのネットワーク通信が通過するインフラストラクチャソフトウェア階層のこと
マイクロサービスのデプロイ
デプロイメントパターンの中で最も軽いものを選ぶようにするといい
Language-specific packaging format パターン
Service as a VM パターン
Service as a container パターン
Serverless deployment パターン
Lambda よりは Cloud Run などのほうが言語の成約もなくて便利そう
下のパターンから順に適用できないか検討するといい
サービスメッシュを使って段階的に本番環境へサービスをリリースする方法
Cloud Run や ECS Fargate が台頭してきた昨今、 #Kubernetes を自前で運用するメリットはなんだろうか? マイクロサービスのリファクタリング
モノリシック地獄がビジネスに与える問題
時間を要すデリバリー
バグを含むソフトウェアリリース
スケーラビリティの欠如
ストラングラーパターンを使って既存のモノリスを絞め殺す
新機能をマイクロサービスとして実装する
プレゼンテーション層とバックエンドを切り離す
モノリスから機能を抽出してマイクロサービス化することでモノリスを分解する
マイクロサービスを始めるにあたって高級なデプロイメントインフラストラクチャは初手には不要
そこにコストをかけるのはマイクロサービスがわかってきてから
ただし、デプロイメントパイプラインはしっかり揃える
モノリスとマイクロサービスは相互に依存して呼び合うことがある
コードレベルで言えばクラスやモジュールが相互依存するのはアンチパターンだと思う
しかし、マイクロサービスは1モジュールと考えられても、モノリスはその性質上、一つのモジュールではなく、パッケージのようなものだから、相互依存するのはやむを得まい
これを更に切り崩していけば相互依存は理想的には減るはず
モノリスをマイクロサービスとして切り出すために
ドメインの分割と DB のリファクタリングが必要
場合によっては、変更箇所を抑えるためにデータのレプリケートもしなければならない
どのサービスをいつ抽出するか
メリットが大きい順
開発の加速
パフォーマンス、スケーラビリティ、信頼性の問題の解決
サービス家の促進
しかし、ビジネス的に価値を生む可能性のある機能ほどにエンハンス要件が出てきて、リファクタリングをしながら開発もするという苦難の道になりそうである
ので、あまり触られないけど影響の大きな箇所を見つけることを優先したほうがいい気がする
相互にデータを参照し合う、リクエストを送り合う
レプリケーションは紹介されていたが、整合性を取るためのリードタイムについてあまり触れられていないのが心配だった
また、↑でやむなしと書いていたが、相互依存はやっぱりどうだろうか
順次切り出すといっても、マイクロサービスにも負債はあるし、エンハンスによって機能もそこかしこに増えたりするだろう