📖

クリーンアーキテクチャ入門 Part 4: 実践的な開発フローとAI活用

に公開

クリーンアーキテクチャ入門 Part 4: 実践的な開発フローとAI活用

はじめに

Part1〜3でクリーンアーキテクチャの理論と実装について学んだら、最後は実践的な開発フローとAI活用について詳しく学んでいきます。この記事では、実際の開発現場でクリーンアーキテクチャを活用する方法と、AIツールを活用して効率的に開発を進める方法について理解を深めていきます。

この記事で学べること:

  • 実践的な開発フロー
  • AIを活用した構成チェック
  • よくある問題と解決策
  • 段階的な学習アプローチ
  • 成功の指標とベストプラクティス

実践的な開発フロー

開発時のチェックリスト

新しい機能を実装する際は、以下のチェックリストを使用してクリーンアーキテクチャの原則に従っているか確認しましょう:

  • 新しい機能をどの層に実装するか明確にしたか?
  • 依存関係の方向性は正しいか?
  • インターフェースを適切に定義したか?
  • テストは各層で独立して書けるか?
  • 技術的詳細は適切に隠蔽されているか?
  • ビジネスロジックは適切な層に配置されているか?

機能追加の流れ

Step 1: 要件分析と層の決定

新しい機能を追加する際は、まずどの層に実装すべきかを決定します:

// 例:タスクの優先度変更機能を追加する場合

// 1. Domain層:ビジネスルールの定義
impl Task {
    pub fn change_priority(&mut self, new_priority: TaskPriority) -> Result<(), Box<dyn Error + Send + Sync>> {
        // ビジネスルール:優先度の変更条件
        if self.status == TaskStatus::Completed {
            return Err("Cannot change priority of completed task".into());
        }

        self.priority = new_priority;
        Ok(())
    }
}

// 2. Application層:ユースケースの実装
pub struct ChangeTaskPriorityUseCase {
    task_repository: Arc<dyn TaskRepository + Send + Sync>,
}

// 3. Infrastructure層:永続化の実装
impl TaskRepository for TaskRepositoryImpl {
    async fn update_priority(&self, task_id: i32, priority: TaskPriority) -> Result<Task, Box<dyn Error + Send + Sync>> {
        // SQLクエリの実装
    }
}

// 4. Presentation層:HTTPエンドポイントの実装
pub async fn change_task_priority(
    State(state): State<Arc<TaskAppState>>,
    Path(task_id): Path<i32>,
    Json(request): Json<ChangePriorityRequest>,
) -> impl axum::response::IntoResponse {
    // HTTPハンドラーの実装
}

Step 2: インターフェースの定義

Domain層でインターフェースを定義します:

// src/domain/repositories/task_repository.rs
#[async_trait]
pub trait TaskRepository {
    // 既存のメソッド...
    async fn update_priority(&self, task_id: i32, priority: TaskPriority) -> Result<Task, Box<dyn Error + Send + Sync>>;
}

Step 3: 実装の順序

  1. Domain層から実装:ビジネスルールとインターフェース
  2. Application層の実装:ユースケース
  3. Infrastructure層の実装:具体的な実装
  4. Presentation層の実装:HTTPエンドポイント

AIを活用した開発支援

機能追加時の質問例

開発中に迷った際は、AIに以下のような質問を投げかけてください:

# 設計に関する質問
"タスクの優先度変更機能を追加したい。どの層に実装すべきですか?"
"外部APIとの連携が必要です。Infrastructure層の実装例を教えてください"
"このビジネスルールはDomain層に属しますか?"

# 実装に関する質問
"このコードはクリーンアーキテクチャの原則に従っていますか?"
"依存関係の方向性は正しいですか?"
"この層の責任は適切に分離されていますか?"

# テストに関する質問
"このUseCaseのテストを書くにはどうすればいいですか?"
"モックオブジェクトを使ったテスト例を教えてください"

コードレビューの自動化

GitHub CopilotやCursor AIを使ったコードレビュー:

# 以下のような質問で構成をチェック
"このコードはクリーンアーキテクチャの原則に従っていますか?"
"依存関係の方向性は正しいですか?"
"この層の責任は適切に分離されていますか?"
"技術的詳細は適切に隠蔽されていますか?"

アーキテクチャの検証

AIに以下のような質問を投げかけてアーキテクチャを検証:

// AIに以下のような質問を投げかける
"このUseCaseは適切にインターフェースに依存していますか?"
"Domain層に外部依存が混入していませんか?"
"各層の責任は明確に分離されていますか?"
"この実装はテストしやすいですか?"

よくある問題と解決策

問題1: 層の責任が曖昧

問題点

  • ビジネスロジックが複数の層に散らばっている
  • データベース処理がApplication層に混入している
  • HTTPの詳細がDomain層に混入している

解決策

# AIに質問
"このコードはどの層に属すべきですか?"
"このビジネスロジックは適切な層に配置されていますか?"

具体的な修正例

// ❌ 悪い例:Application層にデータベース処理が混入
impl AddTaskUseCase {
    pub async fn execute(&self, input: AddTaskInput) -> Result<AddTaskOutput, Box<dyn Error + Send + Sync>> {
        // ビジネスロジック
        let task = Task::new(input.title, input.description)?;

        // ❌ データベース処理が混入
        let query = "INSERT INTO tasks (title, description) VALUES (?, ?)";
        let result = sqlx::query(query)
            .bind(&task.title)
            .bind(&task.description)
            .execute(&self.pool)
            .await?;

        Ok(AddTaskOutput::from(task))
    }
}

// ✅ 良い例:リポジトリパターンを使用
impl AddTaskUseCase {
    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::from(saved_task))
    }
}

問題2: 依存関係が逆になっている

問題点

  • Domain層がInfrastructure層に依存している
  • Application層がPresentation層の詳細を知っている

解決策

# AIに質問
"この依存関係は正しい方向ですか?"
"依存関係の方向性を修正するにはどうすればいいですか?"

具体的な修正例

// ❌ 悪い例:Domain層がInfrastructure層に依存
use sqlx::MySqlPool; // ❌ 外部依存

pub struct Task {
    pub id: Option<i32>,
    pub title: String,
    pub description: String,
}

impl Task {
    pub async fn save(&self, pool: &MySqlPool) -> Result<(), Box<dyn Error + Send + Sync>> {
        // ❌ データベース処理がDomain層に混入
        let query = "INSERT INTO tasks (title, description) VALUES (?, ?)";
        sqlx::query(query)
            .bind(&self.title)
            .bind(&self.description)
            .execute(pool)
            .await?;
        Ok(())
    }
}

// ✅ 良い例:Domain層は純粋なビジネスロジックのみ
pub struct Task {
    pub id: Option<i32>,
    pub title: String,
    pub description: String,
}

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(),
        })
    }
}

問題3: テストが書きにくい

問題点

  • 実際のデータベースが必要
  • 外部APIの呼び出しが必要
  • テストが不安定

解決策

# AIに質問
"このコードをテストしやすくするにはどうすればいいですか?"
"モックオブジェクトを使ったテスト例を教えてください"

具体的な修正例

// ❌ 悪い例:テストが困難
pub struct AddTaskUseCase {
    pool: MySqlPool, // ❌ 具体的な実装に依存
}

// テストするには実際のデータベースが必要

// ✅ 良い例:テストしやすい
pub struct AddTaskUseCase {
    task_repository: Arc<dyn TaskRepository + Send + Sync>, // ✅ インターフェースに依存
}

// テスト例
#[tokio::test]
async fn test_add_task_use_case() {
    // モックリポジトリを作成
    let mock_repository = Arc::new(MockTaskRepository::new());

    // テスト用のデータを設定
    mock_repository.expect_save()
        .times(1)
        .returning(|task| Ok(task.clone()));

    // UseCaseにモックを注入
    let use_case = AddTaskUseCase::new(mock_repository);

    // 実際のデータベースなしでテスト可能
    let result = use_case.execute(test_input).await.unwrap();
    assert_eq!(result.title, "Test Task");
}

段階的な学習アプローチ

Step 1: 基本的な層分離

まずは4つの層を意識してディレクトリ構造を作成することから始めましょう:

// まずは4つの層を意識してディレクトリ構造を作る
src/
├── domain/          // ビジネスルール
├── application/     // ユースケース
├── infrastructure/  // 外部システム
└── presentation/    // ユーザーインターフェース

Step 2: 依存関係の方向性

依存関係を可視化し、正しい方向性を意識します:

// 依存関係を可視化する
// AIに「このファイルはどの層に依存していますか?」と質問

Step 3: インターフェースの導入

段階的にインターフェースを導入します:

// 段階的にインターフェースを導入
// まずは1つのリポジトリから始める

Step 4: テストの実装

各層で独立したテストを実装します:

// Domain層のテスト
#[test]
fn test_task_creation() {
    let task = Task::new("Test".to_string(), "Description".to_string()).unwrap();
    assert_eq!(task.title, "Test");
}

// Application層のテスト
#[tokio::test]
async fn test_add_task_use_case() {
    let mock_repository = Arc::new(MockTaskRepository::new());
    let use_case = AddTaskUseCase::new(mock_repository);
    // テストの実装
}

学習リソースの活用

オンライン学習

  • GitHub Copilot: コード生成とレビュー
  • Cursor AI: アーキテクチャの検証
  • ChatGPT: 概念の理解と実装例の取得

実践的な練習

// 小さなプロジェクトから始める
// 例:TODOアプリ → タスク管理システム → 複雑なビジネスアプリケーション

継続的な改善

定期的な振り返り

  • 週1回:アーキテクチャの見直し
  • 月1回:依存関係の可視化
  • 四半期:全体設計の評価

AIを活用した改善

# 定期的な質問
"このコードベースはクリーンアーキテクチャの原則に従っていますか?"
"改善すべき点はありますか?"
"新しい技術を導入する際の影響範囲は?"

初心者向けのベストプラクティス

シンプルから始める

  1. まずは基本的なCRUD操作から実装
  2. 段階的に複雑なビジネスロジックを追加
  3. 外部システムとの連携は最後に実装

AIの活用ポイント

  • 概念の理解: AIに基本的な概念を質問
  • 実装例の取得: 具体的なコード例を要求
  • 問題の解決: エラーや設計上の問題を相談
  • コードレビュー: 実装したコードの品質チェック

よくある質問と回答

Q: どの層に実装すべきか迷います

A: 以下の質問で判断してください:

  • ビジネスルールですか? → Domain層
  • 複数のドメインオブジェクトを組み合わせますか? → Application層
  • 外部システムと連携しますか? → Infrastructure層
  • ユーザーインターフェースですか? → Presentation層

Q: テストが書きにくいです

A: 以下の点を確認してください:

  • インターフェースに依存していますか?
  • 具体的な実装に依存していませんか?
  • モックオブジェクトを使えますか?

Q: 依存関係が複雑になってきました

A: 以下の点を確認してください:

  • 依存関係の方向性は正しいですか?
  • 循環依存はありませんか?
  • インターフェースを適切に定義していますか?

成功の指標

短期的な指標

  • 各層の責任が明確
  • テストが独立して書ける
  • 新しい機能の追加が容易
  • コードの可読性が向上

長期的な指標

  • コードベースの保守性が向上
  • チーム開発での理解しやすさ
  • 技術スタックの変更が容易
  • 機能追加の影響範囲が限定される

測定方法

コードメトリクス

# 依存関係の複雑さを測定
"このファイルの依存関係は複雑すぎませんか?"
"循環依存はありませんか?"

# テストカバレッジを確認
"各層のテストカバレッジは十分ですか?"
"統合テストは必要ですか?"

開発効率の測定

  • 新機能の実装時間
  • バグの発生頻度
  • コードレビューの時間
  • テストの実行時間

まとめ

クリーンアーキテクチャは、最初は複雑に感じるかもしれませんが、段階的に学習し、AIツールを活用することで、初心者でも効果的に実践できると思います。

重要なポイント

  1. 完璧を求めすぎない:段階的に学習し、小さな改善を積み重ねる
  2. AIツールを活用:設計の判断に迷った際の相談相手として活用
  3. 継続的な改善:定期的にアーキテクチャを見直し、改善する
  4. 実践的な学習:小さなプロジェクトから始めて、徐々に複雑なシステムに適用

次のステップ

このシリーズで学んだことを実践するために:

  1. 小さなプロジェクトから始める:TODOアプリなど
  2. AIツールを活用する:設計の判断に迷った際はAIに相談
  3. 定期的に振り返る:週次・月次でアーキテクチャを見直す
  4. チームで共有する:学んだことをチームメンバーと共有

参考資料

  • 書籍: "Clean Architecture" by Robert C. Martin
  • オンライン: GitHub Copilot、Cursor AI、ChatGPT
  • コミュニティ: Rustコミュニティ、クリーンアーキテクチャ関連のブログ
  • 記事: https://nrslib.com/clean-architecture-with-java/

クリーンアーキテクチャの実践は、一朝一夕には身につきませんが、継続的な学習と実践を通じて、確実にスキルを向上させることができます。AIツールを活用することで、より効率的に学習を進めることができるでしょう。

重要なのは完璧を求めすぎないことです。
まずは基本的な原則を理解し、小さなプロジェクトから始めて、徐々に複雑なシステムに適用していきましょう。

置き換えるシステム規模が大きすぎるのであれば、現状のビジネスロジックを整理するために、ドメイン概念をmiroなどを活用してチームメンバーで協力しフローチャートを作成するところから始めるのも良いと思います。
段階的にクリーンアーキテクチャ構成を試作し、議論しながらすすめるとナレッジ共有などしやすく、クリーンアーキテクチャに対する知識の格差が減らせると思います。

AIツールを活用することで、設計の判断に迷った際の相談相手として活用でき、より効率的にクリーンアーキテクチャを習得できます。

コラボスタイル Developers

Discussion