📖
クリーンアーキテクチャ入門 Part 1: 基礎概念と4つの層の全体像
クリーンアーキテクチャ入門 Part 1: 基礎概念と4つの層の全体像
はじめに
クリーンアーキテクチャは、ソフトウェアの保守性と拡張性を向上させるための設計パターンです。このシリーズでは、実際のRustプロジェクトのコードを例に、段階的にクリーンアーキテクチャを学んでいきます。
この記事で学べること:
- クリーンアーキテクチャの基本概念
- 4つの層の役割と関係性
- 依存関係の方向性
- 実際のプロジェクト構造
クリーンアーキテクチャとは
クリーンアーキテクチャは、内側の層が外側の層に依存しない設計原則に基づいています。これにより、ビジネスロジックが技術的な詳細から独立し、テストしやすく、変更しやすいソフトウェアを構築できます。
なぜクリーンアーキテクチャが必要なのか?
従来の設計の問題点:
- ビジネスロジックとデータベース処理が混在
- テストが困難(実際のデータベースが必要)
- 技術スタックの変更が困難
- 機能追加時に既存コードへの影響が大きい
クリーンアーキテクチャの解決策:
- ビジネスロジックの独立
- テストの容易さ
- 技術スタックの変更が容易
- 機能追加の影響範囲を限定
4つの層の全体像
クリーンアーキテクチャは、内側から外側に向かって依存関係が流れる4つの層で構成されています:
┌─────────────────────────────────────┐
│ Presentation Layer │ ← ユーザーインターフェース
├─────────────────────────────────────┤
│ Application Layer │ ← ビジネスロジックの調整
├─────────────────────────────────────┤
│ Domain Layer │ ← ビジネスルール(中心)
├─────────────────────────────────────┤
│ Infrastructure Layer │ ← 外部システムとの連携
└─────────────────────────────────────┘
各層の役割
1. Domain Layer(ドメイン層)- 最も内側の層
- ビジネスルールの定義:アプリケーションの核心となるビジネスロジック
- エンティティの定義:ビジネスオブジェクトの構造と振る舞い
- インターフェースの定義:他の層との契約
2. Application Layer(アプリケーション層)
- ユースケースの実装:特定のビジネスシナリオの実行
- ドメイン層の調整:複数のドメインオブジェクトを組み合わせ
- データの加工・変換:ビジネス要件に合わせたデータ処理
3. Infrastructure Layer(インフラストラクチャ層)
- 外部システムとの連携:データベース、外部API、ファイルシステム
- ドメイン層のインターフェース実装:リポジトリパターンの実装
- 技術的な詳細の隠蔽:SQL、HTTP通信などの詳細を隠す
4. Presentation Layer(プレゼンテーション層)
- ユーザーインターフェース:HTTP API、Web UI、CLI
- リクエスト・レスポンスの処理:データの変換と検証
- アプリケーション層の呼び出し:ユースケースの実行
依存関係の方向性
クリーンアーキテクチャの核心は、依存関係の方向性にあります:
Presentation Layer → Application Layer → Domain Layer ← Infrastructure Layer
重要な原則
-
内側の層は外側の層を知らない
- Domain層はInfrastructure層の存在を知らない
- Application層はPresentation層の詳細を知らない
-
依存関係は内側に向かって流れる
- 外側の層は内側の層に依存できる
- 内側の層は外側の層に依存してはいけない
-
インターフェースによる契約
- 具体的な実装ではなく、抽象(インターフェース)に依存
- 各層は明確な契約(インターフェース)を通じて通信
実際のプロジェクト構造
実際のRustプロジェクトでのディレクトリ構造を見てみましょう:
src/
├── domain/ # ドメイン層
│ ├── entities/ # ビジネスオブジェクト
│ │ ├── task.rs
│ │ └── user.rs
│ ├── repositories/ # リポジトリインターフェース
│ │ ├── task_repository.rs
│ │ └── user_repository.rs
│ └── services/ # ドメインサービス
│ └── task_domain_service.rs
├── application/ # アプリケーション層
│ ├── use_cases/ # ユースケース
│ │ ├── task/
│ │ │ ├── add_task/
│ │ │ └── list_tasks/
│ │ └── user/
│ │ └── register_user/
│ └── adapters/ # アダプター
│ ├── task_adapter.rs
│ └── user_adapter.rs
├── infrastructure/ # インフラストラクチャ層
│ ├── repositories/ # リポジトリ実装
│ │ ├── task_repository_impl.rs
│ │ └── user_repository_impl.rs
│ ├── external_apis/ # 外部API連携
│ │ └── notification_service.rs
│ └── database/ # データベース関連
│ ├── models.rs
│ └── migrations/
└── presentation/ # プレゼンテーション層
├── controllers/ # コントローラー
│ ├── task_controller.rs
│ └── user_controller.rs
├── handlers/ # HTTPハンドラー
│ ├── task_handlers.rs
│ └── user_handlers.rs
└── presenters/ # プレゼンター
├── task_presenter.rs
└── user_presenter.rs
基本的な実装例
Domain層の例
// src/domain/entities/task.rs
#[derive(Debug, Clone)]
pub struct Task {
pub id: Option<i32>,
pub title: String,
pub description: String,
pub status: TaskStatus,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TaskStatus {
Pending,
InProgress,
Completed,
Cancelled,
}
impl Task {
pub fn new(title: String, description: String) -> Result<Self, Box<dyn Error + Send + Sync>> {
if title.trim().is_empty() {
return Err("Task title cannot be empty".into());
}
Ok(Task {
id: None,
title: title.trim().to_string(),
description: description.trim().to_string(),
status: TaskStatus::Pending,
})
}
}
// src/domain/repositories/task_repository.rs
#[async_trait]
pub trait TaskRepository {
async fn save(&self, task: &Task) -> Result<Task, Box<dyn Error + Send + Sync>>;
async fn find_by_id(&self, id: i32) -> Result<Option<Task>, Box<dyn Error + Send + Sync>>;
}
Application層の例
// src/application/use_cases/task/add_task/add_task_use_case.rs
pub struct AddTaskUseCase {
task_repository: Arc<dyn TaskRepository + Send + Sync>,
}
impl AddTaskUseCase {
pub fn new(task_repository: Arc<dyn TaskRepository + Send + Sync>) -> Self {
Self { task_repository }
}
pub async fn execute(&self, input: AddTaskInput) -> Result<AddTaskOutput, Box<dyn Error + Send + Sync>> {
let task = Task::new(input.title, input.description)?;
let saved_task = self.task_repository.save(&task).await?;
Ok(AddTaskOutput {
id: saved_task.id.unwrap(),
title: saved_task.title,
description: saved_task.description,
})
}
}
初心者向けの学習アプローチ
Step 1: 基本的な層分離を理解する
まずは4つの層の役割を理解し、ディレクトリ構造を作成することから始めましょう。
Step 2: 依存関係の方向性を意識する
内側の層が外側の層に依存しないことを常に意識してください。
Step 3: インターフェースを活用する
具体的な実装ではなく、インターフェースに依存する設計を心がけてください。
Step 4: 段階的に実装する
完璧を求めすぎず、小さな機能から始めて段階的に拡張していきましょう。
次のステップ
この記事でクリーンアーキテクチャの基本概念を理解できたら、次は以下の記事に進んでください:
- Part 2: ビジネスロジックの設計(Domain層・Application層)
- Part 3: 外部システムとの連携(Infrastructure層・Presentation層)
- Part 4: 実践的な開発フローとAI活用
まとめ
クリーンアーキテクチャの基本は以下の3つの原則です:
- 層の分離:各層が明確な責任を持つ
- 依存関係の方向性:内側に向かって依存する
- インターフェースによる契約:具体的な実装ではなく抽象に依存
これらの原則を理解することで、保守性が高く、拡張しやすいソフトウェアを構築できるようになります。
次回のPart 2では、Domain層とApplication層の詳細な実装について学んでいきます。
Discussion