EXERCISE 90分

ストーリー

高橋アーキテクト
今月学んだことの集大成として、アーキテクチャ評価レポートを書いてもらう
高橋アーキテクト
あるプロジェクトのアーキテクチャを分析し、品質特性の評価、問題点の特定、改善提案をレポートにまとめてくれ。これは実際のアーキテクトが日常的に行う仕事だ

演習の概要

既存のECサイトのアーキテクチャを評価し、レポートを作成してください。

項目内容
テーマECサイトのアーキテクチャ評価
成果物アーキテクチャ評価レポート
ミッション数6
推定時間90分

対象システムの概要

ECサイト「BookMart」
- チーム: 8名
- 技術: TypeScript, Express, Prisma, PostgreSQL
- 現在のアーキテクチャ: レイヤードアーキテクチャ
- 問題: 変更に時間がかかる、テストが遅い、新機能追加が困難

Mission 1: 現状のアーキテクチャを分析しよう(15分)

以下のコードを分析し、問題点を特定してください。

// 現在の構造
src/
├── controllers/
│   └── OrderController.ts
├── services/
│   └── OrderService.ts
├── repositories/
│   └── OrderRepository.ts
├── models/
│   └── Order.ts
└── utils/
    └── stripe.ts

// controllers/OrderController.ts
class OrderController {
  async create(req: Request, res: Response) {
    const orderService = new OrderService();
    const order = await orderService.createOrder(req.body);
    res.json(order);
  }
}

// services/OrderService.ts
class OrderService {
  private orderRepo = new OrderRepository();
  private stripe = new Stripe(process.env.STRIPE_KEY!);

  async createOrder(data: any) {
    if (!data.items || data.items.length === 0) {
      throw new Error('Items required');
    }
    const total = data.items.reduce((s: number, i: any) => s + i.price * i.qty, 0);
    const payment = await this.stripe.paymentIntents.create({
      amount: total * 100, currency: 'jpy',
    });
    const order = await this.orderRepo.create({
      ...data, totalAmount: total, paymentId: payment.id, status: 'pending',
    });
    return order;
  }
}

// repositories/OrderRepository.ts
class OrderRepository {
  async create(data: any) {
    return prisma.order.create({ data });
  }
}
解答例

問題点一覧:

問題カテゴリ重要度
ServiceがStripeに直接依存依存性の方向
ServiceがRepositoryを直接newDI未適用
ビジネスロジックがServiceに集中貧血ドメインモデル
any型の多用型安全性
バリデーションがServiceに混在関心の分離
テスト時にDB/Stripeが必要テスト容易性
ControllerがServiceを直接newDI未適用

Mission 2: 品質特性を評価しよう(15分)

現在のアーキテクチャの品質特性を5段階で評価してください。

解答例
品質特性評価理由
変更容易性2/5Serviceの変更が全体に波及、境界が不明確
テスト容易性1/5DB/Stripe接続なしにテスト不可
パフォーマンス4/5変換層がなく直接操作のため高速
スケーラビリティ2/5モジュール分離がなく、部分的なスケールが困難
セキュリティ2/5any型の多用で型によるバリデーションが効かない
デプロイ容易性3/51つのアプリなのでデプロイ自体はシンプル
可観測性2/5構造化されたログやメトリクスが未導入
シンプルさ4/5レイヤードは理解しやすい

総合評価: 2.5/5 — テスト容易性と変更容易性に重大な問題あり


Mission 3: 改善アーキテクチャを設計しよう(20分)

ヘキサゴナルアーキテクチャ + DDDを適用した改善案を設計してください。

解答例
改善後の構造:
src/
├── modules/
│   ├── ordering/
│   │   ├── domain/
│   │   │   ├── entities/
│   │   │   │   ├── Order.ts            # Aggregate Root
│   │   │   │   └── OrderItem.ts
│   │   │   ├── value-objects/
│   │   │   │   ├── OrderId.ts
│   │   │   │   └── Money.ts
│   │   │   └── ports/
│   │   │       ├── in/
│   │   │       │   ├── CreateOrderUseCase.ts
│   │   │       │   └── CancelOrderUseCase.ts
│   │   │       └── out/
│   │   │           ├── OrderRepository.ts
│   │   │           └── PaymentGateway.ts
│   │   ├── application/
│   │   │   └── CreateOrderUseCaseImpl.ts
│   │   ├── adapters/
│   │   │   ├── in/OrderController.ts
│   │   │   └── out/
│   │   │       ├── PrismaOrderRepository.ts
│   │   │       ├── InMemoryOrderRepository.ts
│   │   │       └── StripePaymentGateway.ts
│   │   └── index.ts                     # 公開API
│   └── payment/
│       └── ...
├── shared/
│   └── domain-event.ts
├── composition-root.ts
└── main.ts

主な改善点:

  1. ドメインモデルの隔離(Orderにビジネスロジックを移動)
  2. Port/Adapterによる外部依存の分離
  3. DIによるテスト容易性の確保
  4. モジュラーモノリスでBC間の境界を明確化
  5. 型安全なInput/Output型の導入

Mission 4: フィットネス関数を設計しよう(15分)

改善後のアーキテクチャを維持するためのフィットネス関数を3つ以上設計してください。

解答例
// 1. 依存性ルールチェック
describe('依存性ルール', () => {
  it('domain層はadapters層に依存しない', () => {
    const domainFiles = getAllFilesIn('src/modules/ordering/domain/');
    for (const file of domainFiles) {
      const imports = getImports(file);
      expect(imports.filter(i => i.includes('/adapters/'))).toEqual([]);
    }
  });

  it('domain層は外部ライブラリに依存しない', () => {
    const domainFiles = getAllFilesIn('src/modules/ordering/domain/');
    for (const file of domainFiles) {
      const content = readFileSync(file, 'utf-8');
      expect(content).not.toContain('from "express"');
      expect(content).not.toContain('from "@prisma/client"');
      expect(content).not.toContain('from "stripe"');
    }
  });
});

// 2. モジュール境界チェック
describe('モジュール境界', () => {
  it('他モジュールの内部に直接アクセスしない', () => {
    const orderingFiles = getAllFilesIn('src/modules/ordering/');
    for (const file of orderingFiles) {
      const imports = getImports(file);
      const directPaymentImports = imports.filter(i =>
        i.includes('modules/payment/domain/') ||
        i.includes('modules/payment/application/') ||
        i.includes('modules/payment/adapters/')
      );
      expect(directPaymentImports).toEqual([]);
    }
  });
});

// 3. any型禁止チェック
describe('型安全性', () => {
  it('domain層にany型が存在しない', () => {
    const domainFiles = getAllFilesIn('src/modules/*/domain/');
    for (const file of domainFiles) {
      const content = readFileSync(file, 'utf-8');
      // コメント行を除外してanyの使用をチェック
      const codeLines = content.split('\n')
        .filter(l => !l.trim().startsWith('//'));
      const anyUsages = codeLines.filter(l => /:\s*any\b/.test(l));
      expect(anyUsages).toEqual([]);
    }
  });
});

// 4. テスト実行速度チェック
describe('テスト速度', () => {
  it('ドメインテストは5秒以内に完了する', () => {
    const start = Date.now();
    // ドメインテストを実行
    execSync('npx jest --testPathPattern=domain --silent');
    expect(Date.now() - start).toBeLessThan(5000);
  });
});

Mission 5: ADRを書こう(15分)

この改善に関するADRを書いてください。

解答例
# ADR-004: BookMartのモジュラーモノリス + ヘキサゴナルアーキテクチャへの移行

## ステータス
提案中

## コンテキスト
BookMartのECサイトはレイヤードアーキテクチャで構築されているが、
以下の問題が顕在化している:
- 変更の影響範囲が予測困難
- DB/外部API接続なしにテスト不可(テスト実行に5分以上)
- ビジネスロジックがService層に分散し、把握が困難

チーム規模は8名で、マイクロサービスは運用負荷の観点から時期尚早。

## 決定
モジュラーモノリス + ヘキサゴナルアーキテクチャ + DDDに移行する。

## 理由
- テスト容易性の大幅な改善(InMemory Adapterで独立テスト可能)
- 変更容易性の向上(BCごとのモジュール分離で影響範囲が限定)
- ドメインモデルの充実(貧血モデルからリッチモデルへ)
- 将来のマイクロサービス化への移行パスを確保
- チーム規模に適した複雑さ

## 却下した選択肢
- マイクロサービス: 8名チームには運用コストが高い
- レイヤードの改善のみ: 根本的な構造問題が解決しない
- CQRS + イベントソーシング: 現段階ではオーバーエンジニアリング

## トレードオフ
- 得るもの: テスト容易性、変更容易性、ドメインの明確化
- 失うもの: 初期移行コスト(推定2ヶ月)、学習コスト、一部パフォーマンス

## 結果
(移行完了後に記載予定)

Mission 6: 移行計画を立てよう(10分)

段階的な移行計画を立ててください。

解答例
Phase 1(2週間): 基盤整備
- ディレクトリ構造の変更
- Composition Rootの導入
- フィットネス関数のCI設定

Phase 2(3週間): 注文BC(最も問題が大きいモジュール)
- Order Aggregate の設計・実装
- Use Caseの分離
- Port/Adapterの定義
- InMemoryRepositoryでのテスト追加

Phase 3(2週間): 決済BC
- PaymentGateway Portの定義
- StripePaymentGateway Adapterの分離
- テストでのStub化

Phase 4(1週間): 統合・検証
- 全モジュール間の結合テスト
- パフォーマンス回帰テスト
- フィットネス関数の全項目パス確認

移行原則:
- Strangler Figパターン: 新機能は新アーキテクチャで、既存は段階的に移行
- 常にデプロイ可能な状態を維持する
- 各Phase完了後にチーム内レビュー

達成度チェック

Mission内容完了
1現状分析[ ]
2品質特性評価[ ]
3改善アーキテクチャ設計[ ]
4フィットネス関数設計[ ]
5ADR作成[ ]
6移行計画[ ]

チェックリスト

  • 既存アーキテクチャの問題点を具体的に特定できた
  • 品質特性を数値で評価できた
  • 改善案をヘキサゴナル + DDDで設計できた
  • フィットネス関数を3つ以上定義できた
  • ADRにトレードオフを明記できた

次のステップへ

演習お疲れさまでした。次はStep 5のチェックポイントクイズに挑戦しましょう。


推定所要時間: 90分