architecture-patterns について
Clean Architecture、Hexagonal Architecture、ドメイン駆動設計でバックエンドサービスを構成するスキルです。レイヤー境界やインターフェース、テスト境界を定め、ビジネスロジックをインフラから分離します。
主な機能
- 01Clean Architectureのレイヤー指針
- 02ポートとアダプターの設計
- 03DDDの境界づけられたコンテキスト
- 04ドメインモデリングパターン
- 05インメモリーアダプターのテスト
- 06依存関係の境界に関する問題解決
活用シーン
- 01バックエンドサービスの設計
- 02ビジネスロジックとORM・HTTPコードの分離
- 03サービスの境界づけられたコンテキストの定義
- 04データベースなしでのユースケーステスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wshobson/ agents --skill architecture-patternsインストール後、Claude Code で/architecture-patternsを入力して読み込まれたか確認します。
同梱ファイルreferences/advanced-patterns.mdreferences/details.md
詳細
アーキテクチャパターン
クリーンアーキテクチャ、ヘキサゴナルアーキテクチャ、ドメイン駆動設計など、実証済みのバックエンドアーキテクチャパターンを習得し、保守性が高く、テスト可能で、スケーラブルなシステムを構築しましょう。
入力: アーキテクチャ設計を行うサービス境界またはモジュール。 出力: 明確な依存関係ルール、インターフェース定義、テスト境界を備えたレイヤー構造。
このスキルを使う場面
- 新しいバックエンドサービスやマイクロサービスをゼロから設計するとき
- ビジネスロジックが ORM モデルや HTTP の関心事と絡み合っているモノリシックアプリケーションをリファクタリングするとき
- システムをサービスに分割する前に境界づけられたコンテキストを確立するとき
- インフラストラクチャのコードがドメイン層に染み出している依存関係の循環をデバッグするとき
- ユースケースのテストに稼働中のデータベースを必要としない、テスト可能なコードベースを作成するとき
- ドメイン駆動設計の戦術的パターン(集約、値オブジェクト、ドメインイベント)を実装するとき
中核となる概念
1. クリーンアーキテクチャ(Uncle Bob)
レイヤー(依存関係は内側へ流れます):
- エンティティ: 中核となるビジネスモデル。フレームワークのインポートなし
- ユースケース: アプリケーションのビジネスルール。エンティティをオーケストレーションする
- インターフェースアダプター: コントローラー、プレゼンター、ゲートウェイ — ユースケースと外部フォーマットの間を変換する
- フレームワークとドライバー: UI、データベース、外部サービス — すべて最も外側のリングにある
主要な原則:
- 依存関係は内側のみを指す。内側のレイヤーは外側のレイヤーについて何も知らない
- ビジネスロジックはフレームワーク、データベース、配信メカニズムから独立している
- すべてのレイヤー境界は抽象インターフェースを介して越えられる
- UI、データベース、外部サービスなしでテスト可能
2. ヘキサゴナルアーキテクチャ(ポートとアダプター)
構成要素:
- ドメインコア: ビジネスロジックがここに存在し、フレームワークに依存しない
- ポート: コアが外界とどのようにやり取りするかを定義する抽象インターフェース(駆動側と被駆動側)
- アダプター: ポートの具体的な実装(PostgreSQL アダプター、Stripe アダプター、REST アダプター)
メリット:
- コアに触れることなく実装を差し替えられる(例: PostgreSQL を DynamoDB に置き換える)
- テストでインメモリアダプターを使用できる — Docker は不要
- 技術選定をエッジに先送りできる
3. ドメイン駆動設計(DDD)
戦略的パターン:
- 境界づけられたコンテキスト: 1 つのサブドメインに対して一貫性のあるモデルを分離する。システム全体で単一のモデルを共有することは避ける
- コンテキストマッピング: コンテキスト間の関係を定義する(腐敗防止層、共有カーネル、オープンホストサービス)
- ユビキタス言語: コード内のすべての用語が、ドメインエキスパートが使う用語と一致する
戦術的パターン:
- エンティティ: 時間の経過とともに変化する、安定した同一性を持つオブジェクト
- 値オブジェクト: 属性によって識別される不変のオブジェクト(Email、Money、Address)
- 集約: 一貫性の境界。外部からアクセスできるのはルートのみ
- リポジトリ: 集約を永続化および復元する。ストレージメカニズムを抽象化する
- ドメインイベント: ドメイン内で発生した事象を捕捉する。集約をまたぐ調整に使用される
詳細なパターンと実践例
詳細なパターンのドキュメントは references/details.md にあります。上記のナビゲーション層では不十分な場合は、そのファイルを参照してください。
テスト — インメモリアダプター
クリーンアーキテクチャが正しく適用されていることの証は、すべてのユースケースが実際のデータベース、Docker、ネットワークなしの純粋な単体テストで実行できることです:
# tests/unit/test_create_user.py
import asyncio
from typing import Dict, Optional
from domain.entities.user import User
from domain.interfaces.user_repository import IUserRepository
from use_cases.create_user import CreateUserUseCase, CreateUserRequest
class InMemoryUserRepository(IUserRepository):
def __init__(self):
self._store: Dict[str, User] = {}
async def find_by_id(self, user_id: str) -> Optional[User]:
return self._store.get(user_id)
async def find_by_email(self, email: str) -> Optional[User]:
return next((u for u in self._store.values() if u.email == email), None)
async def save(self, user: User) -> User:
self._store[user.id] = user
return user
async def delete(self, user_id: str) -> bool:
return self._store.pop(user_id, None) is not None
async def test_create_user_succeeds():
repo = InMemoryUserRepository()
use_case = CreateUserUseCase(user_repository=repo)
response = await use_case.execute(CreateUserRequest(email="[email protected]", name="Alice"))
assert response.success
assert response.user.email == "[email protected]"
assert response.user.id is not None
async def test_duplicate_email_rejected():
repo = InMemoryUserRepository()
use_case = CreateUserUseCase(user_repository=repo)
await use_case.execute(CreateUserRequest(email="[email protected]", name="Alice"))
response = await use_case.execute(CreateUserRequest(email="[email protected]", name="Alice2"))
assert not response.success
assert "already exists" in response.error
トラブルシューティング
ユースケースのテストに稼働中のデータベースが必要になる
ビジネスロジックがインフラストラクチャ層に漏れ出しています。すべてのデータベース呼び出しを IRepository インターフェースの背後に移動し、テストではインメモリ実装を注入してください(上記のテストセクションを参照)。ユースケースのコンストラクターは、具象クラスではなく抽象ポートを受け取る必要があります。
レイヤー間の循環インポート
よくある症状は、use_cases と adapters の間で ImportError: cannot import name X が発生することです。これは、ユースケースが抽象ポートではなく具体的なアダプタークラスをインポートしている場合に起こります。ルールを徹底してください: use_cases/ は domain/(エンティティとインターフェース)からのみインポートします。adapters/ や infrastructure/ からインポートしてはいけません。
ドメインエンティティに現れるフレームワークのデコレーター
SQLAlchemy の Column() や Pydantic の Field() アノテーションがドメインエンティティに現れている場合、そのエンティティはもはや純粋ではありません。adapters/repositories/ に別の ORM モデルを作成し、リポジトリの _to_entity() メソッドでドメインエンティティとの相互変換を行ってください。
すべてのロジックがコントローラーに入り込んでしまう
コントローラーが HTTP のパースとレスポンスのフォーマットを超えて肥大化したら、そのロジックをユースケースクラスに抽出してください。コントローラーのメソッドは次の 3 つだけを行うべきです: リクエストをパースする、ユースケースを呼び出す、レスポンスをマッピングする。
値オブジェクトのエラー発生が遅すぎる
不変条件を __post_init__(Python)またはコンストラクターで検証し、不正な Email や Money がそもそも構築できないようにしてください。これにより、ビジネスロジックの奥深くではなく、境界で不正なデータを検出できます。
境界付けられたコンテキスト間のコンテキスト漏れ
Order コンテキストが Identity コンテキストから User エンティティをインポートしている場合は、腐敗防止層を導入してください。Order コンテキストは独自の軽量な CustomerId 値オブジェクトを保持し、明示的なインターフェースを通じてのみ Identity コンテキストを呼び出すべきです。
高度なパターン
DDD の境界付けられたコンテキストマッピングの詳細、マルチサービスプロジェクトツリーの全体像、腐敗防止層の実装、オニオンアーキテクチャとの比較については、以下を参照してください:
関連スキル
microservices-patterns— モノリスをサービスに分解する際に、これらのアーキテクチャパターンを適用しますcqrs-implementation— CQRS のコマンド/クエリ分離の構造的基盤としてクリーンアーキテクチャを使用しますsaga-orchestration— サーガには明確に定義された集約境界が必要であり、DDD の戦術的パターンがそれを提供しますevent-store-design— 集約によって生成されたドメインイベントは、そのままイベントストアに供給されます