ストーリー
演習の概要
既存の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を直接new | DI未適用 | 高 |
| ビジネスロジックがServiceに集中 | 貧血ドメインモデル | 高 |
| any型の多用 | 型安全性 | 中 |
| バリデーションがServiceに混在 | 関心の分離 | 中 |
| テスト時にDB/Stripeが必要 | テスト容易性 | 高 |
| ControllerがServiceを直接new | DI未適用 | 中 |
Mission 2: 品質特性を評価しよう(15分)
現在のアーキテクチャの品質特性を5段階で評価してください。
解答例
| 品質特性 | 評価 | 理由 |
|---|---|---|
| 変更容易性 | 2/5 | Serviceの変更が全体に波及、境界が不明確 |
| テスト容易性 | 1/5 | DB/Stripe接続なしにテスト不可 |
| パフォーマンス | 4/5 | 変換層がなく直接操作のため高速 |
| スケーラビリティ | 2/5 | モジュール分離がなく、部分的なスケールが困難 |
| セキュリティ | 2/5 | any型の多用で型によるバリデーションが効かない |
| デプロイ容易性 | 3/5 | 1つのアプリなのでデプロイ自体はシンプル |
| 可観測性 | 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
主な改善点:
- ドメインモデルの隔離(Orderにビジネスロジックを移動)
- Port/Adapterによる外部依存の分離
- DIによるテスト容易性の確保
- モジュラーモノリスでBC間の境界を明確化
- 型安全な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 | フィットネス関数設計 | [ ] |
| 5 | ADR作成 | [ ] |
| 6 | 移行計画 | [ ] |
チェックリスト
- 既存アーキテクチャの問題点を具体的に特定できた
- 品質特性を数値で評価できた
- 改善案をヘキサゴナル + DDDで設計できた
- フィットネス関数を3つ以上定義できた
- ADRにトレードオフを明記できた
次のステップへ
演習お疲れさまでした。次はStep 5のチェックポイントクイズに挑戦しましょう。
推定所要時間: 90分