architecture-patterns について
ソフトウェアアーキテクチャを選び、発展させる開発者向けのリファレンススキルです。各種パターンや選定基準、トレードオフ、よくある落とし穴を扱います。
主な機能
- 01モノリスとモジュラーモノリスの指針
- 02マイクロサービスとサーバーレスのパターン
- 03イベント駆動設計パターン
- 04クリーンアーキテクチャとヘキサゴナルアーキテクチャ
- 05DDDと移行パターン
活用シーン
- 01新規アプリケーションのアーキテクチャ選定
- 02サービス境界とデプロイの計画
- 03レガシーシステムの段階的な移行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ miles990/ claude-software-skills --skill architecture-patternsインストール後、Claude Code で/architecture-patternsを入力して読み込まれたか確認します。
詳細
アーキテクチャパターン
概要
アーキテクチャパターンは、ソフトウェアシステムを構成するための実証済みの解決策です。適切なアーキテクチャを選ぶことは、スケーラビリティ、保守性、チームの生産性にとって重要です。
パターン
モノリシックアーキテクチャ
説明:アプリケーションのすべての機能を含む、単一のデプロイ可能なユニットです。
主な特徴:
- デプロイと開発が簡単
- データベースとメモリを共有
- デバッグが容易
ユースケース:
- MVPやスタートアップ
- 小規模チーム(開発者10人未満)
- シンプルなドメインロジック
ベストプラクティス:
src/
├── modules/ # Feature-based organization
│ ├── users/
│ ├── orders/
│ └── products/
├── shared/ # Cross-cutting concerns
└── infrastructure/ # External services
マイクロサービスアーキテクチャ
説明:独立してデプロイ可能なサービスで構成される分散システムです。
主な特徴:
- 独立したデプロイとスケーリング
- サービスごとに異なるテクノロジーを採用可能
- 障害の分離
ユースケース:
- 自律性を必要とする大規模チーム
- 明確な境界を持つ複雑なドメイン
- 高いスケーラビリティが求められる場合
主なコンポーネント:
| コンポーネント | 目的 | ツール |
|---|---|---|
| API Gateway | 入口、ルーティング | Kong, AWS API Gateway |
| Service Discovery | サービス登録 | Consul, Kubernetes DNS |
| Config Management | 設定の一元管理 | Spring Cloud Config, Consul |
| Circuit Breaker | フォールトトレランス | Resilience4j, Hystrix |
ベストプラクティス:
- ビジネス機能を中心に設計する
- データ管理を分散化する
- 障害を想定して設計する
- デプロイを自動化する
イベント駆動アーキテクチャ
説明:イベントを介して通信するシステムです。
主なパターン:
| パターン | 説明 | ユースケース |
|---|---|---|
| Event Sourcing | 状態をイベントとして保存 | 監査証跡、時間軸に沿ったクエリ |
| CQRS | 読み取りモデルと書き込みモデルを分離 | 読み取り負荷の高いワークロード |
| Saga | 分散トランザクション | サービスをまたぐワークフロー |
イベントソーシングの例:
// Events are the source of truth
interface OrderEvent {
id: string;
type: 'OrderCreated' | 'ItemAdded' | 'OrderShipped';
timestamp: Date;
payload: unknown;
}
// Rebuild state from events
function rebuildOrder(events: OrderEvent[]): Order {
return events.reduce((order, event) => {
switch (event.type) {
case 'OrderCreated': return { ...event.payload };
case 'ItemAdded': return { ...order, items: [...order.items, event.payload] };
case 'OrderShipped': return { ...order, status: 'shipped' };
}
}, {} as Order);
}
サーバーレスアーキテクチャ
説明:サーバーを管理せずに、クラウドが管理する実行環境を利用します。
主な特徴:
- 実行ごとの従量課金
- ゼロまでの自動スケーリング
- 運用負荷の軽減
考慮事項:
| 項目 | 影響 |
|---|---|
| コールドスタート | 初回呼び出し時に100ms~2sの遅延 |
| タイムアウト | 通常、最大実行時間は15~30分 |
| 状態 | 外部ストレージを使用する必要がある |
| ベンダーロックイン | プラットフォーム固有の機能 |
ベストプラクティス:
- 関数を小さく、目的に絞る
- 依存関係を最小限にする
- データベースに接続プーリングを使用する
- 適切なエラー処理を実装する
クリーンアーキテクチャ
説明:ドメインを中心に置き、依存関係を逆転させたアーキテクチャです。
レイヤー構造:
┌──────────────────────────────────────┐
│ Frameworks & Drivers │ ← External (DB, Web, UI)
├──────────────────────────────────────┤
│ Interface Adapters │ ← Controllers, Gateways
├──────────────────────────────────────┤
│ Application Business │ ← Use Cases
├──────────────────────────────────────┤
│ Enterprise Business │ ← Entities, Domain Rules
└──────────────────────────────────────┘
依存関係のルール:依存関係は内側を向きます。内側のレイヤーは外側のレイヤーについて何も知りません。
ドメイン駆動設計(DDD)
説明:ビジネスドメインに沿ったアーキテクチャです。
戦略的パターン:
| パターン | 目的 |
|---|---|
| 境界づけられたコンテキスト | 明確なドメイン境界 |
| コンテキストマップ | コンテキスト間の関係 |
| ユビキタス言語 | 共有された語彙 |
戦術的パターン:
| パターン | 目的 |
|---|---|
| エンティティ | 識別子を持つオブジェクト |
| 値オブジェクト | 不変の記述子 |
| 集約 | 整合性の境界 |
| リポジトリ | コレクションのような永続化 |
| ドメインイベント | 発生した出来事 |
選択ガイド
START
│
├─ Team size < 10? ──────────────────→ Monolith
│
├─ Need independent deployments? ────→ Microservices
│
├─ Audit trail required? ────────────→ Event Sourcing
│
├─ Variable/unpredictable load? ─────→ Serverless
│
├─ Complex business logic? ──────────→ Clean Architecture + DDD
│
└─ Default ──────────────────────────→ Modular Monolith
よくある落とし穴
1. 早すぎるマイクロサービス化
問題:シンプルなアプリケーションで最初からマイクロサービスを採用する 解決策:モノリシックから始め、境界が明確になったらサービスを切り出す
2. 分散モノリス
問題:同時にデプロイする必要があるマイクロサービス 解決策:明確なAPI契約を設け、サービスが真に独立していることを確認する
3. データ境界の無視
問題:サービス間でデータベースを共有する 解決策:各サービスが自身のデータを所有し、同期にはイベントを使用する
ヘキサゴナルアーキテクチャ(ポートとアダプター)
説明: ポート(インターフェース)とアダプター(実装)を通じて、外部関心事からアプリケーションコアを分離します。
構造:
┌─────────────────────────────────────────────────────────────┐
│ Driving Adapters │
│ (REST API, CLI, GraphQL, Message Consumer) │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ Input Ports │
│ (Use Case Interfaces) │
├─────────────────────────────────────────────────────────────┤
│ │
│ APPLICATION CORE │
│ (Domain Logic, Entities) │
│ │
├─────────────────────────────────────────────────────────────┤
│ Output Ports │
│ (Repository, Gateway Interfaces) │
└──────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────────┐
│ Driven Adapters │
│ (Database, External APIs, Message Publisher) │
└─────────────────────────────────────────────────────────────┘
TypeScript の例:
// Port (Interface)
interface OrderRepository {
save(order: Order): Promise<void>;
findById(id: string): Promise<Order | null>;
}
// Adapter (Implementation)
class PostgresOrderRepository implements OrderRepository {
constructor(private db: Database) {}
async save(order: Order): Promise<void> {
await this.db.query('INSERT INTO orders...', [order]);
}
async findById(id: string): Promise<Order | null> {
const row = await this.db.query('SELECT * FROM orders WHERE id = $1', [id]);
return row ? this.toDomain(row) : null;
}
}
// Use Case (Application Core)
class CreateOrderUseCase {
constructor(private orderRepo: OrderRepository) {} // Depends on Port, not Adapter
async execute(input: CreateOrderInput): Promise<Order> {
const order = new Order(input);
await this.orderRepo.save(order);
return order;
}
}
利点:
- 実装(DB、外部サービス)を簡単に差し替え可能
- 高いテスト容易性(ポートをモック化)
- フレームワークに依存しないドメインロジック
モジュラーモノリス
説明: 厳格なモジュール境界を持つモノリスで、将来的なマイクロサービス抽出に備えます。
主な特徴:
- モジュールは定義されたインターフェースを介して通信する
- 各モジュールが自身のデータを所有する
- 単一ユニットとしてデプロイすることも、抽出することも可能
構造:
src/
├── modules/
│ ├── users/
│ │ ├── api/ # Public API of module
│ │ │ └── UserService.ts
│ │ ├── internal/ # Private implementation
│ │ │ ├── UserRepository.ts
│ │ │ └── UserEntity.ts
│ │ └── index.ts # Only exports public API
│ ├── orders/
│ │ ├── api/
│ │ │ └── OrderService.ts
│ │ ├── internal/
│ │ └── index.ts
│ └── shared/ # Cross-cutting utilities
├── infrastructure/
│ ├── database/
│ ├── messaging/
│ └── http/
└── main.ts
モジュール間通信のルール:
// ✅ Good: Use public API
import { UserService } from '@modules/users';
const user = await userService.getById(id);
// ❌ Bad: Direct access to internal
import { UserRepository } from '@modules/users/internal/UserRepository';
強制:
// eslint rules or ts-paths to prevent internal imports
{
"rules": {
"no-restricted-imports": ["error", {
"patterns": ["@modules/*/internal/*"]
}]
}
}
ストラングラーフィグパターン
説明: トラフィックを新しい実装にルーティングすることで、レガシーシステムを段階的に置き換えます。
移行プロセス:
Phase 1: Facade
┌─────────┐ ┌─────────┐ ┌─────────────┐
│ Client │────→│ Facade │────→│ Legacy │
└─────────┘ └─────────┘ │ System │
└─────────────┘
Phase 2: Partial Migration
┌─────────┐ ┌─────────┐ ┌─────────────┐
│ Client │────→│ Facade │──┬─→│ Legacy │
└─────────┘ └─────────┘ │ └─────────────┘
│ ┌─────────────┐
└─→│ New System │
└─────────────┘
Phase 3: Complete Migration
┌─────────┐ ┌─────────┐ ┌─────────────┐
│ Client │────→│ Facade │────→│ New System │
└─────────┘ └─────────┘ └─────────────┘
実装:
class PaymentFacade {
constructor(
private legacyPayment: LegacyPaymentService,
private newPayment: NewPaymentService,
private featureFlags: FeatureFlags
) {}
async processPayment(payment: Payment): Promise<Result> {
// Gradually migrate traffic
if (this.featureFlags.isEnabled('new-payment-system', payment.userId)) {
return this.newPayment.process(payment);
}
return this.legacyPayment.process(payment);
}
}
Backend for Frontend(BFF)
説明: フロントエンドの種類(Web、モバイルなど)ごとに専用のバックエンドを用意します。
構造:
┌─────────────┐
│ Web Client │
└──────┬──────┘
│
┌──────▼──────┐
│ Web BFF │
└──────┬──────┘
│
┌───────────────────┼───────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ User Service│ │Order Service│ │Product Svc │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
└───────────────────┼───────────────────┘
│
┌──────▼──────┐
│ Mobile BFF │
└──────┬──────┘
│
┌──────▼──────┐
│Mobile Client│
└─────────────┘
利点:
- 各クライアントに最適化されたペイロード
- クライアント固有の認証
- フロントエンドごとの独立したデプロイ
- オーバーフェッチの削減
使用すべき場面:
| シナリオ | 推奨 |
|---|---|
| 単一のクライアントタイプ | BFF を省略 |
| 同じニーズを持つ Web + モバイル | 単一の API ゲートウェイ |
| プラットフォームごとに異なる UX | 個別の BFF |
| フロントエンドごとに複数チーム | 専用の BFF |
アーキテクチャパターンの比較
| パターン | 複雑さ | スケーラビリティ | チーム規模 | 最適な用途 |
|---|---|---|---|---|
| モノリス | 低 | 垂直 | 小規模(2〜10) | MVP、シンプルなアプリ |
| モジュラーモノリス | 中 | 垂直 | 中規模(5〜20) | 成長中のアプリ |
| マイクロサービス | 高 | 水平 | 大規模(20 以上) | 複雑なドメイン |
| サーバーレス | 中 | 自動 | 任意 | イベント駆動、変動する負荷 |
| イベント駆動 | 高 | 水平 | 中〜大規模 | 非同期ワークフロー |
アーキテクチャ決定記録(ADR)テンプレート
アーキテクチャを選択する際は、決定事項を記録します:
# ADR-001: Choose Modular Monolith
## Status
Accepted
## Context
- Team of 8 developers
- MVP deadline in 3 months
- Uncertain about domain boundaries
- Limited DevOps resources
## Decision
Adopt Modular Monolith with strict boundaries
## Consequences
### Positive
- Faster initial development
- Simpler deployment
- Can extract services later
### Negative
- Single point of failure
- Scaling limited to vertical
- Need discipline for module boundaries
## Alternatives Considered
1. Microservices - Too complex for team size
2. Traditional Monolith - No path to scale
進化の道筋
┌─────────────────────────────────────────────────────────────────┐
│ Architecture Evolution │
│ │
│ Monolith ──→ Modular Monolith ──→ Microservices │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ Event-Driven / CQRS │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ [Simple] [Growing] [Complex/Scale] │
│ │
│ Tip: Don't skip steps. Each stage teaches domain boundaries. │
└─────────────────────────────────────────────────────────────────┘
避けるべきアンチパターン
1. 大きな泥団子(Big Ball of Mud)
症状: 明確な構造がなく、すべてがすべてに依存している 修正: モジュール境界を導入し、クリーンアーキテクチャの原則を適用する
2. 金のハンマー
症状: すべてのプロジェクトで同じアーキテクチャを使用する 修正: 要件を評価し、意思決定ガイドを使用する
3. 偶発的な複雑さ
症状: アーキテクチャがドメインの要求よりも複雑になっている 修正: シンプルに始め、必要な場合にのみ複雑さを追加する
4. 履歴書駆動開発
症状: 問題を解決するためではなく、学習のために技術を選定する 修正: アーキテクチャをチームのスキルとプロジェクトのニーズに合わせる
5. ベンダーロックイン
症状: コアロジックがクラウドプロバイダーに密結合している 修正: ヘキサゴナルアーキテクチャを使用し、ベンダー固有のコードを抽象化する
パターン別のパフォーマンスに関する考慮事項
| パターン | レイテンシ | スループット | コールドスタート |
|---|---|---|---|
| モノリス | 低 | 高 | 該当なし |
| マイクロサービス | 中(ネットワーク) | 高(分散) | 該当なし |
| サーバーレス | 変動 | 自動スケール | 100ms〜2s |
| イベント駆動 | 高め(非同期) | 非常に高 | 状況による |
パターン別のテスト戦略
モノリス
Unit Tests → Integration Tests → E2E Tests
70% 20% 10%
マイクロサービス
Unit Tests → Contract Tests → Integration → E2E
60% 20% 15% 5%
// Contract Test Example (Pact)
const provider = new Pact({ consumer: 'OrderService', provider: 'UserService' });
await provider.addInteraction({
state: 'user exists',
uponReceiving: 'get user request',
withRequest: { method: 'GET', path: '/users/123' },
willRespondWith: { status: 200, body: { id: '123', name: 'John' } }
});
イベント駆動
- イベントプロデューサーとコンシューマーを独立してテストする
- イベントスキーマの検証を使用する
- サーガ/ワークフローのオーケストレーションをテストする
関連スキル
- [[api-design]] - サービス間通信のための API 設計
- [[system-design]] - 大規模システムに関する考慮事項
- [[devops-cicd]] - 各パターンのデプロイ戦略
- [[data-design]] - 各アーキテクチャのデータベースパターン