目次
- マイクロサービス化の前に考えること
- サービス境界の設計
- 段階的な移行戦略
- データ管理
- サービス間通信
- 耐障害性
- 可観測性(オブザーバビリティ)
- デプロイとインフラ
- セキュリティ
- テスト戦略
- 組織とチーム
- よくあるアンチパターン
- 移行チェックリスト
1. マイクロサービス化の前に考えること
本当にマイクロサービスが必要か
マイクロサービスは「スケールの問題」と「組織の問題」を解くための手段であり、それ自体が目的ではありません。導入すると分散システム特有の複雑さ(ネットワーク障害、データ整合性、運用負荷)を抱えることになります。
マイクロサービスが向いているケース
- 複数チームが同じコードベースで作業し、リリースが互いにブロックし合っている
- 機能ごとにスケール要件が大きく異なる(例: 検索だけ負荷が桁違い)
- 一部の機能だけ異なる技術スタックやリリース頻度が必要
- ドメインがある程度安定しており、境界が見えている
まだ早いケース
- チームが小さい(目安として1〜2チーム程度)
- プロダクトの方向性が固まっておらず、ドメインが頻繁に変わる
- CI/CD、監視、コンテナ基盤などの運用基盤が未整備
- 現状の課題が「コードが汚い」だけ(分割しても汚い分散システムになるだけ)
モジュラーモノリスという選択肢
いきなり分割する前に、モノリス内部をモジュール単位で明確に分離する「モジュラーモノリス」を経由するのが安全です。
- モジュール間の依存を公開インターフェース経由に限定する
- モジュールごとにDBスキーマ(テーブル群)の所有者を決める
- 境界が正しいと確認できたモジュールから順に切り出す
境界の誤りはモノリス内なら低コストで直せますが、サービス分割後に直すのは非常に高コストです。
2. サービス境界の設計
ドメイン駆動設計(DDD)で境界を決める
- 境界づけられたコンテキスト(Bounded Context) を単位にサービスを切る
- 技術レイヤー(UIサービス、DBサービスなど)ではなく、ビジネス能力(注文、在庫、決済、配送など)で分ける
- イベントストーミングなどのワークショップでドメインエキスパートと一緒に境界を洗い出す
良い境界の判断基準
| 観点 | 良い状態 |
|---|---|
| 凝集度 | 一緒に変更されるものが同じサービスにある |
| 結合度 | 1つの機能追加で複数サービスの同時変更がほぼ不要 |
| 自律性 | 他サービスが落ちていても主要機能が動く |
| データ所有 | データの「正」の持ち主が1サービスに決まっている |
| チーム | 1サービスを1チームが責任を持って運用できる |
サイズの目安
「何行以下」といった基準はあまり意味がありません。目安は以下です。
- 1チーム(5〜9人程度)が理解・運用しきれる大きさ
- 独立してデプロイしても他に影響が出ない単位
- 細かくしすぎると通信コストと運用負荷が爆発する(ナノサービス化に注意)
3. 段階的な移行戦略
ストラングラーフィグパターン
一括書き換え(ビッグバンリライト)は避け、既存システムを少しずつ置き換えます。
- モノリスの前段にプロキシ / APIゲートウェイを置く
- 切り出す機能を新サービスとして実装する
- ルーティングを新サービスに段階的に切り替える
- 旧実装を削除する
- 次の機能で繰り返す
切り出す順番
- 最初は: 依存が少なく、変更頻度が高く、失敗しても影響が小さい機能
- 後回し: ドメインの中核で依存が多い機能(境界の理解が深まってから)
- ビジネス価値(スケール・リリース速度の改善)が明確なものを優先する
移行中の注意点
- 新旧並行稼働期間は、フィーチャーフラグやカナリアリリースで切り替える
- 新旧の結果を比較する「シャドーイング(ダークローンチ)」で正しさを検証する
- 移行の完了条件(旧コードの削除)まで計画に含める。並行稼働の放置は負債になる
4. データ管理
Database per Service
- 各サービスが自分のデータストアを所有し、他サービスのDBに直接アクセスしない
- 他サービスのデータが必要ならAPIかイベント経由で取得する
- 共有DBのままだとスキーマ変更が全サービスに波及し、分割の意味がなくなる
移行時のデータ分離手順(例)
- 同一DB内でスキーマ(テーブル群)の所有者を決める
- 他サービスのテーブルへの直接JOINをAPI呼び出しに置き換える
- 論理的に分離できたら物理的に別DBへ移す
分散トランザクションへの対処
2フェーズコミットは可用性とスケーラビリティを損なうため、基本的に避けます。
- Sagaパターン: ローカルトランザクションの連鎖で処理し、失敗時は補償トランザクションで取り消す
- コレオグラフィ型: 各サービスがイベントに反応して次の処理を行う(単純なフロー向け)
- オーケストレーション型: オーケストレーターが全体を制御する(複雑なフロー向け、可視性が高い)
- Transactional Outboxパターン: DB更新とイベント発行を同一トランザクションで確実に行う
- 結果整合性を前提に、ビジネス側と「どこまでの遅延を許容するか」を合意しておく
その他
- CQRS: 読み取りと書き込みのモデルを分離し、参照用に他サービスのデータを非正規化して持つ
- べき等性: メッセージの重複配信を前提に、同じ処理を複数回受けても結果が変わらないよう設計する
5. サービス間通信
同期 vs 非同期
| 方式 | 例 | 向いている用途 | 注意点 |
|---|---|---|---|
| 同期 | REST, gRPC | 即時に結果が必要な問い合わせ | 呼び出し先の障害が伝播しやすい |
| 非同期 | Kafka, RabbitMQ, SQS など | 状態変更の通知、後続処理のトリガー | 順序保証・重複・デバッグの難しさ |
- 状態変更の伝播は非同期イベントを基本にすると結合度が下がる
- 同期呼び出しの連鎖(A→B→C→D)は可用性を掛け算で下げるため避ける
API設計
- 契約を明確にする: OpenAPI / Protocol Buffers などでスキーマを定義し、コードより先に合意する
- 後方互換性を守る: フィールドの追加はOK、削除・型変更は新バージョンで行う
- バージョニング: URLやヘッダーでバージョンを管理し、旧バージョンの廃止期限を周知する
- トレラントリーダー: 受信側は知らないフィールドを無視し、必要な項目だけ読む
APIゲートウェイとBFF
- 外部クライアントからの入口はAPIゲートウェイに集約し、認証・レート制限・ルーティングを担わせる
- クライアント種別(Web、モバイルなど)ごとに BFF(Backend for Frontend) を置くと、クライアント都合のAPI変更がバックエンドに波及しにくい
- ゲートウェイにビジネスロジックを入れすぎない
6. 耐障害性
分散システムでは「障害は必ず起きる」前提で設計します。
- タイムアウト: すべての外部呼び出しに設定する(デフォルト無制限は厳禁)
- リトライ: 指数バックオフ+ジッターで行い、べき等な操作に限定する
- サーキットブレーカー: 失敗が続く呼び出し先を一時的に遮断し、障害の連鎖を防ぐ
- バルクヘッド: 呼び出し先ごとにスレッドプールや接続数を分け、1つの障害が全体を巻き込まないようにする
- フォールバック / グレースフルデグラデーション: キャッシュ値やデフォルト値で機能を縮退させて提供する
- レート制限・負荷制御: 過負荷時はリクエストを早めに拒否し、システム全体の崩壊を防ぐ
- カオスエンジニアリング: 本番相当の環境で意図的に障害を注入し、耐性を検証する
7. 可観測性(オブザーバビリティ)
サービスが増えると「どこで何が起きているか」が分からなくなります。分割前に整備しておくべき最重要項目です。
3本柱
- ログ: 構造化ログ(JSON等)で出力し、集中管理基盤に集約する
- メトリクス: RED(Rate / Errors / Duration)やUSE(Utilization / Saturation / Errors)を各サービスで計測する
- 分散トレーシング: リクエストIDをサービス間で伝播し、1リクエストの全経路を追跡できるようにする(OpenTelemetryが事実上の標準)
運用面
- 相関ID: すべてのログ・トレースに共通のリクエストIDを付与する
- SLI / SLO: サービスごとに目標値を定義し、エラーバジェットでリリース判断を行う
- アラート: 原因ではなく症状(ユーザー影響)ベースで設定し、アラート疲れを防ぐ
- ヘルスチェック: liveness(生きているか)と readiness(リクエストを受けられるか)を分けて実装する
- サービスカタログ: 各サービスのオーナー、依存関係、ドキュメント、Runbookを一元管理する
8. デプロイとインフラ
- 独立デプロイ: 各サービスを他と調整せずにリリースできることがマイクロサービスの前提条件
- CI/CDパイプライン: サービスごとにビルド・テスト・デプロイを自動化する
- コンテナ化とオーケストレーション: Docker+Kubernetesなどで実行環境を標準化する
- Infrastructure as Code: Terraform等でインフラをコード管理し、環境差異をなくす
- 安全なリリース手法: カナリアリリース、ブルーグリーンデプロイ、フィーチャーフラグを使い分ける
- ゴールデンパス / プラットフォームエンジニアリング: サービスのテンプレート(ログ、監視、CI設定込み)を用意し、新サービス立ち上げのコストを下げる
- 設定とシークレット: 環境ごとの設定はコードから分離し、シークレットは専用の管理基盤(Vault、クラウドのSecret Managerなど)で扱う
- サービスメッシュ: サービス数が多くなったら、mTLS・リトライ・トラフィック制御をIstio / Linkerd等で共通化することも検討する(導入・運用コストとのトレードオフ)
9. セキュリティ
- ゼロトラスト: 内部ネットワークだからといって信頼しない。サービス間通信もmTLSなどで認証・暗号化する
- 認証・認可:
- 外部からの認証はゲートウェイ(またはIdP)で集中して行う
- ユーザー情報はJWT等のトークンで伝播し、各サービスで認可を判断する
- サービス間の呼び出し権限も最小権限で定義する
- シークレット管理: コードやイメージにシークレットを埋め込まない
- サプライチェーン対策: 依存ライブラリやコンテナイメージの脆弱性スキャンをCIに組み込む
- 監査ログ: 誰がいつ何をしたかを追跡できるようにする
10. テスト戦略
- ユニットテスト: 各サービス内部のロジックを高速に検証する(土台として最も厚く)
- コンポーネントテスト: サービス単体を、依存先をモック/スタブにして検証する
- コントラクトテスト: Pact等で提供側と利用側のAPI契約を検証し、互換性破壊をCIで検知する。E2Eテストの多くを置き換えられる
- E2Eテスト: 重要なユーザーフローに絞って最小限にする(遅く壊れやすいため増やしすぎない)
- 本番でのテスト: カナリア、合成監視、カオスエンジニアリングで本番環境の健全性を継続的に確認する
11. 組織とチーム
- コンウェイの法則: システム構造は組織のコミュニケーション構造を反映する。サービス境界とチーム境界を一致させる(逆コンウェイ戦略)
- You build it, you run it: サービスを作ったチームが運用・オンコールまで責任を持つ
- チームの認知負荷: 1チームが抱えるサービス数を管理可能な範囲に抑える
- チームトポロジー: ストリームアラインドチームを中心に、プラットフォームチームが共通基盤を提供し、イネーブリングチームが技術支援する体制が有効
- 標準化と自由のバランス: 言語・フレームワークの自由度は認めつつ、ログ形式・監視・API規約・セキュリティ要件などの「横串」は標準化する
- ドキュメント: ADR(Architecture Decision Records)で設計判断の理由を残す
12. よくあるアンチパターン
| アンチパターン | 症状 | 対策 |
|---|---|---|
| 分散モノリス | 1機能の変更で複数サービスの同時デプロイが必要 | 境界を見直し、非同期化・データ所有の明確化を行う |
| 共有データベース | 複数サービスが同じテーブルを読み書きする | Database per Serviceへ段階移行する |
| ナノサービス | サービスが細かすぎて通信と運用が過剰 | 関連するサービスを統合する |
| 同期呼び出しの連鎖 | 1つの障害が全体に伝播、レイテンシ悪化 | 非同期イベント化、データのキャッシュ・複製 |
| 共通ライブラリの肥大化 | 共有ライブラリの更新で全サービスの改修が必要 | 共有はロギング等の横断的関心事に限定し、ドメインロジックは共有しない |
| 技術先行の分割 | Kubernetesを入れたい、が動機になっている | ビジネス・組織課題から出発する |
| 可観測性の後回し | 障害時に原因箇所が特定できない | 分割前にログ・メトリクス・トレースを整備する |
| ビッグバン移行 | 全面書き換えが長期化・頓挫する | ストラングラーフィグで段階移行する |
13. 移行チェックリスト
着手前
- マイクロサービス化で解決したい課題(ビジネス・組織)が明文化されている
- モジュラーモノリスなど、より軽い代替案を検討した
- ドメイン分析を行い、サービス境界の候補がある
- CI/CDパイプラインが整備されている
- ログ集約・メトリクス・分散トレーシングの基盤がある
- コンテナ基盤・IaCが用意されている
サービスごと
- オーナーチームが明確
- データの所有権が明確で、他サービスのDBに直接アクセスしていない
- API契約(スキーマ)が定義され、バージョニング方針がある
- タイムアウト・リトライ・サーキットブレーカーが実装されている
- ヘルスチェック(liveness / readiness)がある
- SLOとアラートが設定されている
- コントラクトテストがCIで実行されている
- Runbook(障害対応手順)がある
- 独立してデプロイ・ロールバックできる
移行完了時
- 旧実装(モノリス側のコード・テーブル)が削除されている
- 移行前に設定した課題が改善したかを計測・振り返りした