ストーリー
佐藤CTOがホワイトボードに大きく書きました。
移行すべきか、留まるべきか
マイクロサービスに移行すべきケース
| 条件 | 具体的な指標 |
|---|---|
| チームが大きくなった | 20名以上で、デプロイの待ちが頻繁に発生 |
| スケーリング要件がある | コンポーネントごとに異なるスケーリングが必要 |
| デプロイ頻度を上げたい | 現在月1-2回 → 日に数回にしたい |
| 技術的多様性が必要 | 特定機能に最適な言語・フレームワークを使いたい |
| チーム独立性が必要 | チーム間の調整コストがボトルネック |
モノリスに留まるべきケース
| 条件 | 理由 |
|---|---|
| チームが小さい(10名以下) | マイクロサービスの運用コストがメリットを上回る |
| ドメインが未成熟 | 間違った境界で分割すると、修正コストが膨大 |
| 運用体制が未整備 | CI/CD、監視、ログ集約の基盤がない |
| 強い一貫性が必要 | 分散トランザクションの複雑さが許容できない |
| 「流行だから」が理由 | 技術的な根拠のない移行は失敗する |
移行準備度評価
移行を決断する前に、組織の準備度を評価します。
チーム成熟度の評価
interface TeamMaturityAssessment {
// 技術スキル(1-5)
technicalSkills: {
containerization: number; // Docker/Kubernetesの経験
cicd: number; // CI/CDパイプラインの構築経験
distributedSystems: number; // 分散システムの理解
apiDesign: number; // API設計の経験
monitoring: number; // 監視・オブザーバビリティの経験
};
// 組織文化(1-5)
culture: {
autonomy: number; // チームの自律性
devopsCollaboration: number; // Dev/Ops の協力体制
changeAppetite: number; // 変化への積極性
learningCulture: number; // 学習文化の成熟度
incidentResponse: number; // インシデント対応の成熟度
};
// プロセス(1-5)
process: {
automatedTesting: number; // テスト自動化の成熟度
automatedDeployment: number; // デプロイ自動化の成熟度
versionControl: number; // バージョン管理の成熟度
documentation: number; // ドキュメントの整備度
apiGovernance: number; // API管理の成熟度
};
}
function evaluateReadiness(assessment: TeamMaturityAssessment): ReadinessResult {
const techAvg = average(Object.values(assessment.technicalSkills));
const cultureAvg = average(Object.values(assessment.culture));
const processAvg = average(Object.values(assessment.process));
const overall = (techAvg + cultureAvg + processAvg) / 3;
if (overall >= 4) return { level: 'ready', message: '移行の準備が整っています' };
if (overall >= 3) return { level: 'conditionally-ready', message: '一部の準備が必要です' };
if (overall >= 2) return { level: 'not-ready', message: 'まず基盤を整える必要があります' };
return { level: 'far-from-ready', message: 'モノリスの改善を優先してください' };
}
インフラ準備度の評価
| 要件 | 必須レベル | 現状確認のポイント |
|---|---|---|
| コンテナ基盤 | 必須 | Docker, Kubernetes の運用実績があるか |
| CI/CDパイプライン | 必須 | サービスごとの独立デプロイが可能か |
| サービスディスカバリ | 必須 | サービス間の動的な発見機構があるか |
| 分散ログ | 必須 | ログの集約と横断検索が可能か |
| 分散トレーシング | 推奨 | リクエストのサービス間追跡が可能か |
| メトリクス収集 | 必須 | サービスごとのメトリクス監視が可能か |
| アラート | 必須 | 異常の自動検知と通知が設定されているか |
| 秘密管理 | 必須 | 認証情報の安全な管理機構があるか |
コスト・ベネフィット分析
コストの試算
interface MigrationCostEstimate {
// 直接コスト
directCosts: {
engineeringHours: number; // エンジニアの工数(人月)
infrastructureSetup: number; // インフラ構築コスト(円)
toolingLicenses: number; // ツールライセンス費用(年間)
training: number; // トレーニング費用
};
// 間接コスト
indirectCosts: {
productivityDipDuration: number; // 一時的な生産性低下期間(月)
productivityDipPercentage: number; // 生産性低下率(%)
riskBuffer: number; // リスクバッファ(直接コストの %)
};
// ランニングコスト(年間)
runningCosts: {
additionalInfrastructure: number; // 追加インフラコスト
additionalOperations: number; // 追加運用コスト
additionalTooling: number; // 追加ツールコスト
};
}
// コスト試算の例
const estimate: MigrationCostEstimate = {
directCosts: {
engineeringHours: 24, // 24人月
infrastructureSetup: 5000000, // 500万円
toolingLicenses: 3000000, // 300万円/年
training: 2000000, // 200万円
},
indirectCosts: {
productivityDipDuration: 3, // 3ヶ月
productivityDipPercentage: 30, // 30%低下
riskBuffer: 30, // 30%のバッファ
},
runningCosts: {
additionalInfrastructure: 6000000, // 600万円/年
additionalOperations: 4000000, // 400万円/年
additionalTooling: 3000000, // 300万円/年
},
};
ベネフィットの試算
interface MigrationBenefitEstimate {
// 定量的なベネフィット
quantitative: {
deployFrequencyImprovement: number; // デプロイ頻度の向上倍率
leadTimeReduction: number; // リードタイム短縮率(%)
incidentReduction: number; // 障害削減率(%)
scalingCostReduction: number; // スケーリングコスト削減率(%)
velocityImprovement: number; // 開発ベロシティ向上率(%)
};
// 定性的なベネフィット
qualitative: {
teamAutonomy: string; // チーム独立性の向上
technologyFlexibility: string; // 技術選定の自由度
faultIsolation: string; // 障害の分離
scalability: string; // スケーラビリティ
talentAttraction: string; // 人材採用への好影響
};
}
損益分岐点の計算
graph LR
I["初期投資\nインフラ構築"] --> P1["0〜18ヶ月\nコスト > ベネフィット"]
P1 --> BP["損益分岐点\n18ヶ月後"]
BP --> P2["18〜30ヶ月\nベネフィット > コスト\n投資回収フェーズ"]
style I fill:#fee2e2,stroke:#dc2626,color:#991b1b
style P1 fill:#fef3c7,stroke:#d97706,color:#92400e
style BP fill:#dbeafe,stroke:#2563eb,stroke-width:3px,color:#1e40af
style P2 fill:#d1fae5,stroke:#059669,color:#065f46
初期投資の回収にかかる期間を計算し、経営陣に「いつから投資が回収できるか」を示す
リスク評価
移行リスクマトリクス
| リスク | 発生確率 | 影響度 | 対策 |
|---|---|---|---|
| 分散モノリスになる | 高 | 致命的 | DDD に基づく境界定義、段階的移行 |
| データ不整合 | 中 | 高 | Saga パターン、結果整合性の設計 |
| 運用複雑度の爆発 | 高 | 高 | オブザーバビリティ基盤の先行整備 |
| チームスキル不足 | 中 | 高 | 段階的な学習、外部専門家の活用 |
| 移行期間の長期化 | 高 | 中 | 小さく始めて学習を蓄積 |
| パフォーマンス低下 | 中 | 中 | ネットワーク遅延の事前測定と最適化 |
分散モノリスとは
移行の最大の失敗パターンです。
モノリスの欠点 + 分散システムの欠点 = 分散モノリス
特徴:
- サービスに分割したが、同時にデプロイしないと動かない
- サービス間の結合が強く、1つの変更が複数サービスに波及
- 共有データベースのまま
- ネットワーク遅延が追加されただけで、メリットがない
原因:
- ドメイン理解なしに技術的な都合で分割した
- サービス間でORMモデルを共有している
- 同期通信に過度に依存している
移行パターン概要
Strangler Fig パターン
graph TD
subgraph P1["Phase 1: プロキシ配置"]
C1["Client"] --> GW1["API Gateway"]
GW1 --> M1["Monolith\n/orders, /users, /all"]
end
subgraph P2["Phase 2: 一部移行"]
C2["Client"] --> GW2["API Gateway"]
GW2 --> M2["Monolith\n/orders, /users"]
GW2 --> NS["通知サービス\n/notify"]
end
subgraph PN["Phase N: 完了"]
C3["Client"] --> GW3["API Gateway"]
GW3 --> FN["各サービス\nモノリス消滅"]
end
style P1 fill:#f3f4f6,stroke:#9ca3af,color:#374151
style P2 fill:#fef3c7,stroke:#d97706,color:#92400e
style PN fill:#d1fae5,stroke:#059669,color:#065f46
style M1 fill:#fee2e2,stroke:#dc2626,color:#991b1b
style M2 fill:#fef3c7,stroke:#d97706,color:#92400e
style NS fill:#d1fae5,stroke:#059669,color:#065f46
style FN fill:#d1fae5,stroke:#059669,color:#065f46
style C1 fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C2 fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C3 fill:#dbeafe,stroke:#2563eb,color:#1e40af
style GW1 fill:#f3e8ff,stroke:#7c3aed,color:#5b21b6
style GW2 fill:#f3e8ff,stroke:#7c3aed,color:#5b21b6
style GW3 fill:#f3e8ff,stroke:#7c3aed,color:#5b21b6
Branch by Abstraction パターン
// Step 1: 既存実装の前にインターフェースを導入
interface NotificationService {
send(userId: string, message: string): Promise<void>;
}
// Step 2: 既存実装をインターフェースに適合させる
class MonolithNotificationService implements NotificationService {
async send(userId: string, message: string): Promise<void> {
// 既存のモノリス内の通知ロジック
}
}
// Step 3: 新しい実装を開発(別サービスへのAPI呼び出し)
class MicroserviceNotificationService implements NotificationService {
constructor(private httpClient: HttpClient) {}
async send(userId: string, message: string): Promise<void> {
await this.httpClient.post('http://notification-service/send', {
userId, message,
});
}
}
// Step 4: フィーチャーフラグで切り替え
class NotificationServiceFactory {
create(): NotificationService {
if (featureFlags.isEnabled('use-notification-microservice')) {
return new MicroserviceNotificationService(httpClient);
}
return new MonolithNotificationService();
}
}
Parallel Run パターン
// 新旧両方の実装を同時に実行し、結果を比較する
class ParallelRunNotificationService implements NotificationService {
constructor(
private oldService: MonolithNotificationService,
private newService: MicroserviceNotificationService,
private comparisonLogger: ComparisonLogger,
) {}
async send(userId: string, message: string): Promise<void> {
// 旧実装を実行(結果はこちらを使用)
const oldResult = await this.oldService.send(userId, message);
// 新実装を非同期で実行(結果は比較用)
this.newService.send(userId, message)
.then(newResult => {
this.comparisonLogger.log({
operation: 'notification.send',
oldResult,
newResult,
match: JSON.stringify(oldResult) === JSON.stringify(newResult),
});
})
.catch(error => {
this.comparisonLogger.logError('notification.send', error);
});
return oldResult;
}
}
移行判断のデシジョンツリー
graph TD
Start["開始"] --> Q1{"チームサイズ < 10名?"}
Q1 -->|YES| A1["モノリスを維持\nモジュール化を強化"]
Q1 -->|NO| Q2{"ドメイン境界が明確?"}
Q2 -->|NO| A2["まずDDDで\nドメインモデリングを実施"]
Q2 -->|YES| Q3{"運用基盤が整っている?\nCI/CD, 監視"}
Q3 -->|NO| A3["まずDevOps基盤を整備"]
Q3 -->|YES| Q4{"分散システムの経験がある?"}
Q4 -->|NO| A4["小さなサービスで\n実験してから判断"]
Q4 -->|YES| Q5{"ビジネス上の動機がある?"}
Q5 -->|NO| A5["移行しない\nモノリスを改善"]
Q5 -->|YES| Go["段階的移行を開始"]
Go --> S1["最も独立したモジュールから開始"]
Go --> S2["Strangler Fig パターンを適用"]
Go --> S3["各フェーズで評価・学習・調整"]
style Start fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e40af
style Q1 fill:#fef3c7,stroke:#d97706,color:#92400e
style Q2 fill:#fef3c7,stroke:#d97706,color:#92400e
style Q3 fill:#fef3c7,stroke:#d97706,color:#92400e
style Q4 fill:#fef3c7,stroke:#d97706,color:#92400e
style Q5 fill:#fef3c7,stroke:#d97706,color:#92400e
style A1 fill:#f3f4f6,stroke:#9ca3af,color:#374151
style A2 fill:#f3f4f6,stroke:#9ca3af,color:#374151
style A3 fill:#f3f4f6,stroke:#9ca3af,color:#374151
style A4 fill:#f3f4f6,stroke:#9ca3af,color:#374151
style A5 fill:#fee2e2,stroke:#dc2626,color:#991b1b
style Go fill:#d1fae5,stroke:#059669,stroke-width:2px,color:#065f46
style S1 fill:#d1fae5,stroke:#059669,color:#065f46
style S2 fill:#d1fae5,stroke:#059669,color:#065f46
style S3 fill:#d1fae5,stroke:#059669,color:#065f46
まとめ
| ポイント | 内容 |
|---|---|
| 移行判断 | チームサイズ、ドメイン成熟度、運用基盤で判断 |
| 準備度評価 | 技術スキル、組織文化、プロセスの3軸 |
| コスト・ベネフィット | 定量的な試算で損益分岐点を明確化 |
| リスク | 分散モノリスが最大のリスク |
| 移行パターン | Strangler Fig、Branch by Abstraction、Parallel Run |
| デシジョンツリー | 体系的な判断プロセスに従う |
チェックリスト
- 移行すべきケースとすべきでないケースを判別できる
- チーム成熟度とインフラ準備度を評価できる
- コスト・ベネフィット分析の枠組みを理解した
- 3つの移行パターンの特徴と使い分けを説明できる
- デシジョンツリーに基づいた判断ができる
次のステップへ
次は「演習:モノリスを分析しよう」です。ここまで学んだ分析手法とフレームワークを、実際のモノリスシステムの分析に適用してみましょう。
推定読了時間: 25分