clean-architecture について
Clean Architecture、Hexagonal Architecture、Onion Architecture と DDD パターンを扱うアーキテクチャ資料です。開発者やチームが業務ロジックを分離し、システム境界を設計して、ドメインルールをテスト可能に保つために役立ちます。
主な機能
- 01内向きの依存関係とレイヤーの責務を解説
- 02ポート、アダプター、アーキテクチャ境界を説明
- 03DDD の戦術・戦略パターンを紹介
- 04CQRS とイベントソーシングの判断を支援
- 05複雑さを増やす設計を避ける基準を提示
活用シーン
- 01ドメインが複雑なアプリケーションの境界を設計
- 02インフラから独立して業務ルールをテスト
- 03複数の入口を持つシステムを構成
- 04読み書きの要件に CQRS が適するか評価
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ krzysztofsurdy/ code-virtuoso --skill clean-architectureインストール後、Claude Code で/clean-architectureを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/context-mapping.mdddd-patterns.mdlayers.md詳細
クリーンアーキテクチャとDDD
ビジネスロジックを分離し、テスト可能で、フレームワーク、データベース、提供手段に依存しないシステムを構築するための、アーキテクチャパターンと実践的な設計手法です。Robert C. Martin、Alistair Cockburn、Eric Evansの著作を基盤としています。
使用する場面
- 明確にモデル化する価値のある重要なビジネスルールがドメインにある
- フレームワークのアップグレード、データベースの移行、提供手段の変更にシステムが耐える必要がある
- 複数のエントリーポイント(API、CLI、メッセージコンシューマー、スケジュールされたジョブ)で同じビジネスロジックを共有する
- 初期開発の速度よりも保守コストが重視される、長期運用を見込むプロダクトである
- チームの規模や入れ替わりを考慮し、明確な境界と徹底可能な規約が求められる
- テスト可能性が優先され、ビジネスルールをインフラストラクチャなしで検証する必要がある
使用しない場面
すべてのシステムに、これほど厳格なアーキテクチャが適しているわけではありません。次のような場合は、過剰な設計を避けてください。
- 意味のあるビジネスロジックのない、単純なCRUDラッパーである
- プロジェクトが短命のプロトタイプや概念実証である
- チームが非常に小さく、ドメインが十分に理解されており、ルールもほとんどない
- レイヤー分離のオーバーヘッドが、問題の複雑さを上回る
- シンプルなフレームワーク中心のアプローチ(例:アクティブレコードを使ったMVC)でニーズを十分に満たせる
重要な問いは、**投資を正当化するだけの複雑さがドメインにあるか?**です。答えが「いいえ」であれば、よりシンプルなアーキテクチャが適切です。複雑さが増してから、いつでも境界を導入できます。
アーキテクチャスタイル
よく知られた3つのスタイルには、依存関係は内側を向く、そしてドメインが中心に位置するという共通の原則があります。
| スタイル | 起源 | 主なメタファー | 特徴的な考え方 |
|---|---|---|---|
| クリーンアーキテクチャ | Robert C. Martin(2012) | 同心円 | エンティティ、ユースケース、インターフェースアダプター、フレームワークという明確なレイヤー名 |
| ヘキサゴナル(ポートとアダプター) | Alistair Cockburn(2005) | ポートを備えた六角形 | 駆動(プライマリ)アダプターと駆動される(セカンダリ)アダプターの対称性 |
| オニオンアーキテクチャ | Jeffrey Palermo(2008) | タマネギの層 | 中核のドメインモデルと、外側のリングに位置するインフラストラクチャを重視 |
共通点:
- 外部依存のないドメインロジックを中心に置く
- 依存関係のルール:ソースコードの依存関係は常に内側を向く
- インフラストラクチャと提供に関する関心事は外側のレイヤーに置く
- 境界を越えるやり取りには抽象化(インターフェース/ポート)を使う
実用上、違いは命名規則にあります。以下の原則は3つすべてに当てはまります。
ポートとアダプターの概念
ヘキサゴナルアーキテクチャが導入した有用な用語は、3つすべてのスタイルに当てはまります。
- ポートは、アプリケーションが定義するインターフェースです。外部に何を求めるか(駆動される/セカンダリポート)、または外部からアプリケーションに何を要求できるか(駆動する/プライマリポート)を記述します。
- アダプターは、ポートを特定のテクノロジーに接続する実装です。データベースアダプターはリポジトリポートを実装します。HTTPコントローラーは駆動ポート用のアダプターです。
- 駆動する(プライマリ)アダプターは、アプリケーションとのやり取りを開始します。HTTPコントローラー、CLIコマンド、メッセージコンシューマー、スケジュールされたジョブなどがあります。
- 駆動される(セカンダリ)アダプターは、アプリケーションから呼び出されます。データベースリポジトリ、メール送信機能、決済ゲートウェイ、ファイルストレージなどがあります。
この対称性には大きな利点があります。アプリケーションは、REST APIとCLIコマンドのどちらから駆動されているかを知る必要がなく、リレーショナルデータベースとインメモリストアのどちらに永続化しているかも知る必要がありません。
依存関係のルール
ソースコードの依存関係は内側を向く必要があります。内側のレイヤーは、外側のレイヤーについて何も知ってはなりません。名前、型、インターフェース、概念のいずれも例外ではありません。
これが最も重要なルールです。ほかのすべてのガイドラインは、このルールに基づいています。
「内側を向く」とは:
- 内側のレイヤーがインターフェース(ポート)を定義し、外側のレイヤーがそれを実装します
- データは単純な構造体(DTO、プリミティブ型)として境界を越え、フレームワークのオブジェクトは使用しません
- ドメインレイヤーは、インフラストラクチャ、プレゼンテーション、アプリケーションの各レイヤーからインポートしません
- アプリケーションレイヤーは、インフラストラクチャレイヤーやプレゼンテーションレイヤーからインポートしません
重要な理由:
- データベース、HTTP サーバー、メッセージブローカーがなくても、ドメインをテストできます
- フレームワークは交換可能な実装の詳細になります
- ビジネスルールを、デプロイの構成を理解せずに読み取れます
適用方法:
- 静的解析ツールやアーキテクチャテストツールを使って、インポートの方向を検証します
- レイヤーを反映したモジュールやパッケージにコードを整理し、違反を見つけやすくします
- コードレビューでは、内側のレイヤーが外側のレイヤーの型をインポートしていないか確認します
- コンポジションルート(アプリケーションのエントリーポイント)は、すべてのレイヤーを結び付ける唯一の場所です。すべてを把握する例外的な場所です
レイヤーのリファレンス
| レイヤー | 依存の方向 | 責務 | 含まれるもの | 含まれないもの |
|---|---|---|---|---|
| ドメイン(最内層) | 何にも依存しない | ビジネスルール、不変条件 | エンティティ、値オブジェクト、集約、ドメインサービス、ドメインイベント、リポジトリインターフェース | フレームワークのインポート、データベースクエリ、HTTP の概念 |
| アプリケーション | ドメインに依存 | ユースケースのオーケストレーション | アプリケーションサービス、コマンド/クエリーオブジェクト、DTO、ポートインターフェース | ビジネスロジック、インフラストラクチャへの直接呼び出し |
| インフラストラクチャ | アプリケーションとドメインに依存 | 技術的な機能 | リポジトリの実装、API クライアント、メッセージキューアダプター、ファイルシステムへのアクセス | ビジネスルール、ユースケースのオーケストレーション |
| プレゼンテーション(最外層) | アプリケーションに依存 | ユーザー/システムとのやり取り | コントローラー、CLI コマンド、API エンドポイント、ビューモデル | ビジネスロジック、データベースへの直接アクセス |
各レイヤーの詳しいガイダンスについては、レイヤーの詳細を参照してください。
DDD の戦術パターン
戦術パターンは、ドメインレイヤーに構造を与えます。各パターンには、アーキテクチャ内での明確な役割と配置があります。
| パターン | 目的 | レイヤー | 主なルール |
|---|---|---|---|
| エンティティ | 識別子とライフサイクルを持つオブジェクト | ドメイン | 識別子によって等価性が決まる |
| 値オブジェクト | 不変で識別子を持たない概念 | ドメイン | 属性で等価性を判定し、副作用を持たない |
| 集約 | 整合性の境界 | ドメイン | 1 つのトランザクションにつき集約は 1 つとし、ほかの集約は ID で参照する |
| リポジトリ | 集約へのコレクションのようなアクセス | ドメインにインターフェース、インフラストラクチャに実装 | 集約ルートごとにリポジトリを 1 つ設ける |
| ドメインサービス | エンティティをまたぐステートレスなロジック | ドメイン | ロジックが単一のエンティティに属さない場合にのみ使用する |
| ドメインイベント | 発生した事象の記録 | ドメイン | 過去形の名前を付け、不変のペイロードを使用する |
| アプリケーションサービス | ユースケースのオーケストレーター | アプリケーション | ビジネスロジックを持たず、ドメインオブジェクトに委譲する |
| ファクトリー | 複雑なオブジェクトの生成 | ドメイン | 構築時の不変条件をカプセル化する |
各パターンの詳しいガイダンスについては、DDD の戦術パターンを参照してください。
DDD の戦略パターン
戦略パターンは、システムの大規模な構造、つまり複雑なドメインを管理しやすい部分に分割する方法と、それらの部分がどのように連携するかを扱います。
境界づけられたコンテキスト
境界づけられたコンテキストとは、特定のドメインモデルが定義され、一貫性を保つ境界です。各コンテキストには独自のユビキタス言語があり、同じ言葉でもコンテキストによって意味が異なる場合があります。
境界の特定:
- チームや部門が異なる場合、それぞれ別のコンテキストであることがよくあります
- 同じ用語(例:「アカウント」)がステークホルダーによって異なる意味を持つ場合、それぞれ別のコンテキストに属します
- 1つのチームが所有できる規模にコンテキストをとどめます
- 技術レイヤーではなく、ビジネス能力に沿ってコンテキストを定義します
ユビキタス言語
各境界づけられたコンテキスト内では、チーム(開発者とドメインエキスパート)が正確な語彙を共有します。コードではこの言語をそのまま使用します。クラス名、メソッド名、変数名には、ドメインエキスパートが説明するドメイン概念を正確に反映させます。
ユビキタス言語を維持するための実践的なヒント:
- 開発者がクラス名をドメインエキスパートに説明できない場合、その名前は適切ではありません
- 言語が進化したらコードをリファクタリングします。名前の変更は見た目だけの問題ではなく、設計活動です
- ドメインレイヤーのコードでは技術的な専門用語を避け、実装用語ではなくビジネス用語を使います
- 開発者とドメインエキスパートが共有する用語集に言語を記録します
コンテキストマップ
コンテキストマップは、境界づけられたコンテキスト間の関係を記録したものです。統合戦略を偶然に任せず、明示的に定義できます。
| パターン | 関係 | 使用する場面 |
|---|---|---|
| 共有カーネル | 2つのコンテキストがモデルの一部を共有する | 変更の調整に前向きで、緊密に連携するチーム |
| 顧客・供給者 | 上流コンテキストが下流コンテキストにサービスを提供する | 下流側が上流側と交渉できる場合 |
| 順応者 | 下流側が上流側のモデルに順応する | 交渉力がなく、上流側が変更に応じない場合 |
| 腐敗防止層 | 変換レイヤーが下流側を保護する | レガシーシステムや外部システムと統合する場合 |
| オープンホストサービス | 上流側が明確に定義されたプロトコルを提供する | 複数の利用者が安定したアクセスを必要とする場合 |
| 公開言語 | 共通の交換形式 | 標準スキーマを介してコンテキスト間で通信する場合 |
統合パターンと境界の決定については、コンテキストマッピングを参照してください。
腐敗防止層
外部システムやレガシーシステムと統合する際、腐敗防止層(ACL)は外部モデルと内部ドメインモデルの間を変換します。ACL によって、外部の概念がドメインに漏れ出すのを防ぎます。
構造: 外部システム -> ACL(アダプター + 変換器)-> ドメインモデル
ACL はインフラストラクチャレイヤーに配置し、アプリケーションレイヤーまたはドメインレイヤーで定義されたポートを実装します。
CQRS — コマンドクエリ責務分離
CQRS はクリーンアーキテクチャと DDD を自然に補完します。書き込みモデル(コマンド)と読み取りモデル(クエリ)を分離し、それぞれを独立して最適化できるようにします。
アーキテクチャとの関係:
- コマンドはアプリケーションレイヤーを通り、集約を変更し、リポジトリを通じて永続化されます
- クエリはドメインレイヤーを完全に迂回し、最適化されたプロジェクションやビューから読み取ることができます
- 書き込み側はドメインモデルを通じてビジネスルールを適用します
- 読み取り側は利用者のニーズに合わせて最適化されます。表示のために完全な集約を再構築する必要はありません
CQRS を検討する場面:
- 読み取りと書き込みのパターンが大きく異なる場合(例:複雑な書き込みと単純な読み取り、またはその逆)
- 読み取りと書き込みでパフォーマンス要件が異なる場合
- 読み取りモデルに、ドメインモデルに対応しない非正規化ビューが必要な場合
- イベントソーシングを使用している場合(CQRS はイベントソーシングとほぼ常に併用されます)
CQRS を避ける場面:
- 読み取りと書き込みが対称的な、単純な CRUD アプリケーション
- ドメインのニーズに対して、追加される複雑さが見合わない場合
イベントソーシング — 概要
イベントソーシングでは、集約の状態を現在の状態のスナップショットではなく、一連のドメインイベントとして保存します。現在の状態は、イベントを再生することで導出されます。
クリーンアーキテクチャとの関係:
- イベントはドメイン層の一部です
- イベントストアはインフラストラクチャ上の関心事です(特殊なリポジトリ)
- プロジェクション(読み取りモデル)は、インフラストラクチャ層またはアプリケーション層でイベントから構築されます
- ドメインモデルは、イベントストアの実装に依存しません
検討すべき場合: 監査要件がある場合、状態遷移が複雑な場合、時間に関するクエリ(「時刻 X の時点で状態はどうなっていたか」)が必要な場合、またはドメインイベントがすでに設計の中心となっている場合です。
よくある誤解
- 「クリーンアーキテクチャはボイラープレートが多い」 — 各レイヤーによって構造を整えるためのコードは多少増えますが、その代わりに境界が明確になります。ボイラープレートが負担になりすぎる場合は、そのアプローチを正当化できるほどドメインが複雑ではない可能性があります。
- 「すべてのアプリケーションに4つのレイヤーが必要」 — 小規模なアプリケーションでは、アプリケーション層とプレゼンテーション層を統合してもかまいません。重要な境界は、ドメイン層とインフラストラクチャ層の間です。
- 「あらゆる場所で DTO を使えばクリーンアーキテクチャになる」 — DTO は境界を越えるための仕組みであり、それ自体がアーキテクチャではありません。アーキテクチャの要点は依存関係の方向です。
- 「ドメインモデルはデータベーススキーマを反映しなければならない」 — 実際は逆です。ドメインモデルはビジネス上の概念を反映します。インフラストラクチャ層が、ドメインモデルとストレージスキーマの間をマッピングします。
- 「ヘキサゴナルアーキテクチャとクリーンアーキテクチャは別物」 — どちらも同じ中核的な考え方を異なる形で表現したものです。チームにとってしっくりくる用語を選んでください。
- 「DDD にはクリーンアーキテクチャが必要」 — DDD の戦術的パターンは、厳密なレイヤー分離がなくても使用できますが、両者を組み合わせると最も効果を発揮します。逆に、クリーンアーキテクチャに DDD のパターンは必須ではありません。
- 「1つの境界づけられたコンテキストは1つのマイクロサービス」 — 境界づけられたコンテキストは論理的な境界です。マイクロサービス、モノリス内のモジュール、その他のデプロイ単位のいずれにも対応できます。
よくある違反
| 違反 | 症状 | 修正方法 |
|---|---|---|
| ドメインがフレームワークに依存している | ドメインクラスが HTTP、ORM、またはフレームワークのアノテーションをインポートしている | インフラストラクチャ上の関心事を外側のレイヤーに移し、ドメインではプレーンなオブジェクトを使用します |
| コントローラーにビジネスロジックがある | コントローラーに条件分岐、検証ルール、計算が含まれている | ロジックをドメインオブジェクトまたはアプリケーションサービスに切り出します |
| 貧血ドメインモデル | エンティティがゲッター/セッターしか持たないデータの入れ物で、すべてのロジックがサービスにある | 振る舞いをエンティティと値オブジェクトに移し、集約内で不変条件を保証します |
| アプリケーションサービスにビジネスルールが含まれている | アプリケーション層に複雑な条件分岐やドメイン計算がある | ルールをドメインサービスまたはエンティティのメソッドに移します |
| インフラストラクチャが内側に漏れ出している | ドメイン層がデータベースのカラム名や API レスポンスの形式を参照している | インフラストラクチャ層にマッピングを導入し、ドメインモデルを純粋に保ちます |
| 巨大な集約 | 1つの集約が多くの関心事を担い、際限なく大きくなっている | より小さな集約に分割し、集約間の通信にはドメインイベントを使用します |
| コンテキスト間でデータベースを共有している | 複数の境界づけられたコンテキストが同じテーブルを読み書きしている | コンテキストごとにストレージを分離し、イベントまたは API を介して連携します |
| レイヤー間に循環依存がある | 内側のレイヤーが外側のレイヤーをインポートしている。コンパイルエラーや実行時の結合が発生する | インターフェースをどのレイヤーが所有するかを再検討し、依存関係を逆転させます |
| 境界を越えて DTO を再利用している | API レスポンス、ユースケースの入力、ドメインロジックで同じデータ構造を使用している | 境界ごとに別個の DTO を作成し、それらの間をマッピングします |
| 腐敗防止層がない | 外部システムのモデルがドメインの語彙に混入している | インフラストラクチャ層に変換レイヤーを追加します |
テスト戦略
階層化アーキテクチャにより、各レイヤーに異なるテスト方法を割り当てる、明確なテスト戦略を実現できます。
| レイヤー | テストの種類 | 依存関係 | 検証する内容 |
|---|---|---|---|
| ドメイン | 単体テスト | なし — 純粋なロジック | ビジネスルール、不変条件、値オブジェクトの等価性、集約の整合性 |
| アプリケーション | モック化したポートを使った単体テスト | ドメインレイヤー+モック化したインフラストラクチャポート | ユースケースのオーケストレーション、正しいドメインメソッドの呼び出し、トランザクション境界 |
| インフラストラクチャ | 統合テスト | 実際の外部システム(データベース、API、キュー) | 正しいマッピング、クエリの動作、アダプターの忠実性 |
| プレゼンテーション | 機能テスト/受け入れテスト | フルスタックまたはモック化したアプリケーションレイヤー | リクエストの解析、レスポンスのフォーマット、エラー処理、ステータスコード |
重要な原則: テストの大半は、高速なドメイン単体テストにします。インフラストラクチャの統合テストは数を絞りつつ、実際のシステムが想定どおりに動作することを確認します。エンドツーエンドテストでは重要な経路をカバーしますが、最小限にとどめます。
品質チェックリスト
アーキテクチャが適切に構成されているかを評価するには、次のチェックリストを使用してください。
依存関係のルール
- ドメインレイヤーは、アプリケーション、インフラストラクチャ、プレゼンテーションの各レイヤーから一切インポートしていない
- アプリケーションレイヤーは、インフラストラクチャまたはプレゼンテーションレイヤーから一切インポートしていない
- 境界を越えるすべての通信に、内側のレイヤーで定義されたインターフェース/ポートを使用している
ドメインレイヤー
- エンティティが独自の不変条件を適用し、無効な状態を表現できない
- 値オブジェクトが不変である
- 整合性を保つ単位は集約であり、1つのトランザクションにつき集約は1つである
- リポジトリインターフェースがインフラストラクチャではなく、ドメインで定義されている
- ドメインイベントが意味のある状態変化を記録している
アプリケーションレイヤー
- アプリケーションサービスはオーケストレーションを行うが、ビジネスロジックは含まない
- ユースケースが明示されており、それぞれに明確な入力、出力、単一の責務がある
- 境界を越える際にはDTOを使用し、ドメインオブジェクトが外側のレイヤーに漏れない
インフラストラクチャレイヤー
- リポジトリの実装がドメインではなく、ここに配置されている
- 外部サービスとの統合に、ポートを実装するアダプターを使用している
- フレームワーク固有のコードが分離され、置き換え可能である
プレゼンテーションレイヤー
- コントローラーは薄く、直ちにアプリケーションサービスへ処理を委譲する
- コントローラー、ビュー、CLI コマンドにビジネスロジックがない
- 入力の検証(形式、型)はここで行い、ビジネス上の検証はドメインで行う
境界づけられたコンテキスト
- 各コンテキストに独自のユビキタス言語がある
- コンテキスト同士は、共有データベースではなく、明確に定義されたインターフェース(イベント、API)を介して通信する
- 腐敗防止層が、外部モデルによる汚染を防いでいる
テスト
- インフラストラクチャなしでドメインロジックをテストできる
- モック化したポートを使ってアプリケーションサービスをテストできる
- インフラストラクチャアダプターに、実際の依存先を使った統合テストがある
- ビジネスルールの検証に、実行中のフレームワークを必要とするテストがない
ベストプラクティス
- ドメインから始めます — まずビジネスルールをモデル化し、インフラストラクチャは後から追加します
- 技術的なパターンではなく、ドメインの概念に基づいて命名します(SomethingManager、SomethingHelper などは避けます)
- 集約は小さく保ちます — 大きな集約は、境界の配置が適切でないことを示す設計上の危険信号です
- すべてのレイヤーで、継承よりコンポジションを優先します
- 直接結合するのではなく、ドメインイベントを使って集約間で通信します
- 無効な状態を表現できないようにします — 値オブジェクトやエンティティのコンストラクターに検証を組み込みます
- アプリケーションレイヤーは薄く保ちます — ビジネスロジックが含まれているなら、ドメインレイヤーが貧血状態です
- より深い洞察に向けてリファクタリングします — 最初のドメインモデルが最善であることはまれです。理解が深まるにつれて進化させます
- アーキテクチャテストを適用し、依存関係のルールを自動的に適用します
- クリーンアーキテクチャは段階的に導入します — 最も複雑なサブドメインから始め、範囲を広げます