LESSON 25分

ストーリー

佐藤CTO
技術的負債の可視化も依存関係の分析もできた。でも、ここからが一番大事なところだ

佐藤CTOがホワイトボードに大きく書きました。

佐藤CTO
移行すべきか、すべきでないか
佐藤CTO
マイクロサービスへの移行は、成功すれば大きな恩恵がある。でも失敗すれば、モノリスよりも遥かに悪い状態になる。“分散モノリス”と呼ばれる最悪のパターンだ
あなた
判断を間違えないために、フレームワークがあるんですね
佐藤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分