LESSON 30分

ストーリー

佐藤CTO
Step 3ではアーキテクチャスタイルの選択肢を比較検討していく
佐藤CTO
最初のテーマは”モノリスとマイクロサービス”だ。業界では”マイクロサービスにしなきゃ”という風潮があるが、それは本当か?正解は”場合による”だ
佐藤CTO
Netflixがマイクロサービスで成功した話は有名だが、彼らが最初からマイクロサービスだったわけではない。モノリスから始めて、必要に迫られて移行した。この”なぜ移行が必要だったのか”を理解することが重要だ

モノリシックアーキテクチャ

モノリスとは

モノリスとは、アプリケーション全体が単一のデプロイメント単位として構成されるアーキテクチャスタイルです。全ての機能が1つのプロセスで動作します。

graph TD
    subgraph Mono["モノリシックアプリケーション"]
        U["ユーザー管理<br/>Service<br/>Repository"]
        O["注文管理<br/>Service<br/>Repository"]
        P["決済処理<br/>Service<br/>Repository"]
        DB["共有データベース"]
        U --> DB
        O --> DB
        P --> DB
    end

    classDef moduleStyle fill:#4a90d9,stroke:#2c5f8a,color:#fff
    classDef dbStyle fill:#e8a838,stroke:#b07c1e,color:#fff

    class U,O,P moduleStyle
    class DB dbStyle

モノリスの利点

利点説明
開発の単純さ1つのコードベースで全体を把握できる
デプロイの容易さ1つのアーティファクトをデプロイするだけ
テストの容易さEnd-to-Endテストが簡単に実行できる
デバッグの容易さスタックトレースが1プロセス内で完結
トランザクションの容易さACIDトランザクションがそのまま使える
チーム立ち上げの速さ分散システムの知識が不要

モノリスの欠点

欠点説明
スケーリングの制限特定の機能だけをスケールできない
デプロイリスク小さな変更でも全体の再デプロイが必要
技術選択の制約全体で同一の言語・フレームワークを使用
コードの肥大化成長とともにビルド・起動時間が増大
チーム間の干渉変更が他チームの機能に影響する可能性
障害の伝播1箇所の障害が全体に波及する

モノリスが適しているケース

// モノリスが適切な判断基準
const shouldUseMonolith = (context: ProjectContext): boolean => {
  return (
    context.teamSize <= 10 &&                    // 小規模チーム
    context.domainComplexity === 'LOW_TO_MEDIUM' && // ドメインが比較的単純
    context.scalingRequirements === 'UNIFORM' &&  // 均一なスケーリングで十分
    context.deploymentFrequency <= 'WEEKLY' &&    // 週次以下のデプロイ頻度
    context.timeToMarket === 'CRITICAL'           // 早期リリースが最優先
  );
};

マイクロサービスアーキテクチャ

マイクロサービスとは

マイクロサービスとは、アプリケーションをビジネス機能ごとに独立したサービス群として構成するアーキテクチャスタイルです。各サービスは独立してデプロイ・スケール可能です。

graph TD
    subgraph US["ユーザーサービス"]
        UA["API"] --> UD["Domain"] --> UDB["DB<br/>PostgreSQL"]
    end

    subgraph OS["注文サービス"]
        OA["API"] --> OD["Domain"] --> ODB["DB<br/>MongoDB"]
    end

    subgraph PS["決済サービス"]
        PA["API"] --> PD["Domain"] --> PDB["DB<br/>PostgreSQL"]
    end

    classDef apiStyle fill:#67b7dc,stroke:#3a8ab5,color:#fff
    classDef domainStyle fill:#4a90d9,stroke:#2c5f8a,color:#fff
    classDef dbStyle fill:#e8a838,stroke:#b07c1e,color:#fff

    class UA,OA,PA apiStyle
    class UD,OD,PD domainStyle
    class UDB,ODB,PDB dbStyle

マイクロサービスの利点

利点説明
独立デプロイサービスごとに個別のデプロイサイクル
独立スケーリング負荷の高いサービスだけスケール可能
技術の多様性サービスごとに最適な技術を選択
障害の隔離1サービスの障害が他に波及しにくい
チームの自律性各チームが独立して意思決定・開発
コードの理解容易性個々のサービスは小さく理解しやすい

マイクロサービスの欠点

欠点説明
分散システムの複雑さネットワーク障害、遅延、データ整合性
運用コスト監視、ログ集約、デプロイパイプラインが複雑化
テストの困難さサービス間の統合テストが複雑
データ整合性分散トランザクションの管理が必要
サービス間通信レイテンシの増加、通信障害への対策
デバッグの困難さ分散トレーシングが必須

マイクロサービスが適しているケース

// マイクロサービスが適切な判断基準
const shouldUseMicroservices = (context: ProjectContext): boolean => {
  return (
    context.teamSize > 20 &&                      // 複数チームが必要な規模
    context.domainComplexity === 'HIGH' &&         // 境界が明確な複雑ドメイン
    context.scalingRequirements === 'SELECTIVE' && // 部分的なスケーリングが必要
    context.deploymentFrequency >= 'DAILY' &&     // 高頻度デプロイ
    context.organizationalMaturity === 'HIGH'      // DevOps・SREの成熟度が高い
  );
};

モジュラーモノリス:中間地点

モジュラーモノリスとは

モジュラーモノリスは、単一のデプロイメント単位を維持しながら、内部をモジュール単位に分割するアーキテクチャです。モノリスの利点を保ちつつ、マイクロサービスへの移行を容易にします。

graph TD
    subgraph MM["モジュラーモノリス"]
        subgraph UM["User Module"]
            UAPI["Public API"]
            UDomain["Internal Domain"]
            UData["Data Access"]
            UAPI --> UDomain --> UData
        end

        subgraph OM["Order Module"]
            OAPI["Public API"]
            ODomain["Internal Domain"]
            OData["Data Access"]
            OAPI --> ODomain --> OData
        end

        UAPI <-->|"Public API"| OAPI

        DB["共有DB(スキーマは分離)"]
        UData --> DB
        OData --> DB
    end

    classDef apiStyle fill:#67b7dc,stroke:#3a8ab5,color:#fff
    classDef domainStyle fill:#4a90d9,stroke:#2c5f8a,color:#fff
    classDef dataStyle fill:#5cb85c,stroke:#3d8b3d,color:#fff
    classDef dbStyle fill:#e8a838,stroke:#b07c1e,color:#fff

    class UAPI,OAPI apiStyle
    class UDomain,ODomain domainStyle
    class UData,OData dataStyle
    class DB dbStyle

TypeScriptでのモジュール分離

// modules/user/public-api.ts
// 各モジュールは明示的なPublic APIを通じてのみアクセス可能
export interface UserModule {
  findUserById(id: string): Promise<UserDto | null>;
  createUser(command: CreateUserCommand): Promise<string>;
}

// modules/user/internal/UserModuleImpl.ts
class UserModuleImpl implements UserModule {
  constructor(private userRepo: UserRepository) {}

  async findUserById(id: string): Promise<UserDto | null> {
    const user = await this.userRepo.findById(id);
    return user ? this.toDto(user) : null;
  }

  async createUser(command: CreateUserCommand): Promise<string> {
    const user = User.create(command.name, Email.of(command.email));
    await this.userRepo.save(user);
    return user.id.value;
  }

  private toDto(user: User): UserDto {
    return { id: user.id.value, name: user.name, email: user.email.value };
  }
}

// modules/order/internal/OrderService.ts
// OrderモジュールはUserモジュールのPublic APIのみに依存
class OrderService {
  constructor(
    private orderRepo: OrderRepository,
    private userModule: UserModule  // Public APIを通じてアクセス
  ) {}

  async createOrder(userId: string, items: OrderItem[]): Promise<string> {
    // 直接UserRepositoryにアクセスしない
    const user = await this.userModule.findUserById(userId);
    if (!user) throw new Error('ユーザーが見つかりません');

    const order = Order.create(userId, items);
    await this.orderRepo.save(order);
    return order.id.value;
  }
}

モジュラーモノリスの利点

利点説明
モジュール境界の明確化マイクロサービス移行時の分割単位が明確
デプロイの単純さモノリスと同じく1つのデプロイ単位
データ整合性ACID トランザクションが使える
段階的な移行準備ができたモジュールから順にサービス化
低い運用コスト分散システムのインフラが不要

移行パターン:Strangler Fig

Strangler Figとは

Strangler Fig(絞め殺しの木)パターンは、既存のモノリスを段階的に新しいアーキテクチャに置き換える移行戦略です。名前はイチジクの木が宿主の木を徐々に覆い尽くす様子に由来します。

graph TD
    subgraph Phase1["Phase 1: ルーティング層を追加"]
        GW1["API Gateway / Router"]
        GW1 --> NEW1["新サービス
(注文)"] GW1 --> OLD1["旧モノリス
(全機能)"] end subgraph Phase2["Phase 2: 段階的に機能を移行"] GW2["API Gateway"] GW2 --> O2["注文"] GW2 --> P2["決済"] GW2 --> I2["在庫"] GW2 --> OLD2["旧モノリス
(残機能)"] end subgraph Phase3["Phase 3: 完了"] GW3["API Gateway"] GW3 --> O3["注文"] GW3 --> P3["決済"] GW3 --> I3["在庫"] GW3 --> U3["ユーザー"] end Phase1 ~~~ Phase2 ~~~ Phase3 classDef gwStyle fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#92400e classDef newStyle fill:#d1fae5,stroke:#059669,color:#065f46 classDef oldStyle fill:#fee2e2,stroke:#dc2626,color:#991b1b classDef svcStyle fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e40af class GW1,GW2,GW3 gwStyle class NEW1,O2,P2,I2,O3,P3,I3,U3 newStyle class OLD1,OLD2 oldStyle

実装例:API Gatewayによるルーティング

// API Gateway(Express例)
import express from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';

const app = express();

// Phase 1: 注文サービスだけ新サービスにルーティング
app.use('/api/orders', createProxyMiddleware({
  target: 'http://order-service:3001',
  changeOrigin: true,
}));

// その他はまだモノリスにルーティング
app.use('/api', createProxyMiddleware({
  target: 'http://legacy-monolith:3000',
  changeOrigin: true,
}));

// Phase 2: 決済サービスも移行完了
app.use('/api/payments', createProxyMiddleware({
  target: 'http://payment-service:3002',
  changeOrigin: true,
}));

// Feature flagによる段階的切り替え
app.use('/api/inventory', (req, res, next) => {
  const useNewService = featureFlags.isEnabled('new-inventory-service', {
    userId: req.headers['x-user-id'],
  });

  if (useNewService) {
    return createProxyMiddleware({
      target: 'http://inventory-service:3003',
    })(req, res, next);
  }

  return createProxyMiddleware({
    target: 'http://legacy-monolith:3000',
  })(req, res, next);
});

実際の移行事例

Netflix

段階内容
初期(2007年以前)モノリシックなデータセンターアプリケーション
きっかけ(2008年)データベース障害で3日間サービス停止
移行開始(2009年)AWSへの移行と同時にマイクロサービス化開始
移行完了(2016年)約700以上のマイクロサービスで運用
教訓「マイクロサービスが正解ではなく、Netflixの規模・課題に対して必要だった」

Amazon

段階内容
初期(2001年以前)巨大なモノリス(ビルドに数時間)
きっかけ開発速度の著しい低下、チーム間の依存関係
アプローチ「Two-Pizza Team」ルール、サービス指向アーキテクチャ
成果各チームが独立してデプロイ、開発速度が大幅に向上
教訓「組織構造とアーキテクチャは密接に関連する(コンウェイの法則)」

Shopify(モジュラーモノリスの成功例)

段階内容
選択マイクロサービスではなくモジュラーモノリスを選択
理由組織の規模と運用能力に対してマイクロサービスは過剰
実装Railsモノリス内でのコンポーネント分離
成果開発速度を維持しつつ、モジュール境界を明確化
教訓「マイクロサービスへの移行は必要になるまで待つべき」

判断基準の比較表

判断基準モノリスモジュラーモノリスマイクロサービス
チーム規模1-10人10-30人30人以上
ドメインの複雑さ低〜中中〜高
デプロイ頻度週次以下週次〜日次日次〜1日複数回
スケーリング要件均一部分的高度に部分的
運用成熟度高(DevOps/SRE必須)
初期コスト低〜中
長期保守性低下しやすい良好良好
データ整合性容易(ACID)容易(ACID)困難(結果整合性)
障害影響範囲全体全体サービス単位

意思決定フローチャート

                  チーム規模 > 20人?
                  /              \
               No                Yes
              /                    \
     モノリスで開始          ドメイン境界が明確?
     将来の拡張性が心配?     /              \
     /          \         No                Yes
   No           Yes       /                    \
   /              \    モジュラーモノリス     DevOps成熟度高い?
モノリス     モジュラー    で境界を探る        /          \
              モノリス                      No           Yes
                                          /               \
                                    モジュラーモノリス  マイクロサービス
                                    でまず始める

まとめ

ポイント内容
モノリス小規模チーム・初期段階で最適、シンプルだが拡張に限界
マイクロサービス大規模・高成熟度の組織向け、独立性は高いが複雑
モジュラーモノリス両者の中間、段階的移行の出発点として優秀
Strangler Fig既存システムを段階的に置き換える移行戦略
コンウェイの法則組織構造がアーキテクチャを決定する
重要な原則「必要になるまでマイクロサービスにしない」

チェックリスト

  • モノリスの利点と欠点を説明できる
  • マイクロサービスの利点と欠点を説明できる
  • モジュラーモノリスの位置づけを理解した
  • Strangler Figパターンの概要を説明できる
  • プロジェクトの状況に応じた選択基準を理解した
  • コンウェイの法則とアーキテクチャの関係を理解した

次のステップへ

次は「イベント駆動とリクエスト応答」を学びます。サービス間の通信パターンを理解し、どのような場面でイベント駆動を選択すべきかを判断できるようになりましょう。


推定読了時間: 30分