アジャイル開発のためのクリスマスプレゼント、クリーンアーキテクチャの魅力

この記事は[NTTコムウェア Advent Calendar 2024](https://qiita.com/advent-calendar/2024/nttcomware)の16日目の記事です。

## 1. **はじめに**

### **自己紹介**  
こんにちは、NTTコムウェアの秋元です。
普段はスクラムマスターとして、アジャイル開発の現場でチームのプロセス改善や課題解決をサポートしています。また、これまでのディベロッパー経験を活かし、設計や実装の相談に乗ることもあります。その中で、**設計の複雑化**や**変更への柔軟な対応**といった課題に直面することが多く、これらを解決する方法を日々模索しています。  

最近、その解決の糸口として注目しているのが **「クリーンアーキテクチャ」** という設計手法です。  

この記事では、アジャイル開発を進める中で感じた課題と、それに対するクリーンアーキテクチャの可能性についてお話しします。 **「こういうアプローチもあるんだな」と気軽に読んでいただければ嬉しいです！**

## 2. **アジャイルとアーキテクチャ設計の関係性**

アジャイル開発は、要件や技術が変化しやすい環境に柔軟に対応するための手法として広く採用されています。その中核にあるのが「経験主義」という考え方です。これは、最初から完璧な計画を立てるのではなく、プロジェクトの進行中に得られる知見を基に、要件や設計を適応させていくアプローチです。

ただ、この柔軟性が時に「アジャイルでは設計が軽視される」という誤解を生むことがあります。しかし実際には、アジャイル開発では変化に強い設計が不可欠であり、それがプロジェクトの成功を支える重要な要素です。

### **なぜ設計が重要なのか？**
アジャイル開発では、次のようなシナリオがよく発生します：
- 途中で要件が変更され、新しい機能が追加される  
- 技術的な選択肢（例：データベースや外部API）がプロジェクト途中で変更される  
- チームの規模やスキルセットが変化する  

これらに柔軟に対応するためには、アーキテクチャ設計が**適切に柔軟性を持っている**必要があります。設計がしっかりしていないと、以下のような問題に直面することがあります：
- 小さな変更が大規模な修正を引き起こす
- モジュール間の依存関係が強く、改修が困難になる  
- 新機能の追加が開発スピードを著しく低下させる  

ここで直面するのが **「変更に耐えられる設計をいかに実現するか」** という課題です。この課題を解決するための1つのアプローチとして注目されるのが「クリーンアーキテクチャ」です。次のセクションでは、その概要についてお話しします。

## 3. **クリーンアーキテクチャの概要**

クリーンアーキテクチャは、Robert C. Martin（通称「Uncle Bob」）によって提唱されたソフトウェア設計手法です。このアーキテクチャの目的は、**柔軟性と保守性を高めること**にあります。

その中心にある考え方は、**「依存関係を逆転させる」** ことです。つまり、具体的な実装（フレームワークやデータベース）に依存するのではなく、アプリケーションの中核部分が主導権を持つ構造を作るというものです。例えば、従来の設計では「データベースの構造にビジネスロジックが依存する」ことが一般的ですが、クリーンアーキテクチャではこれを逆転させ、「ビジネスロジックがデータベースの実装に依存しない」構造を目指します。

### **クリーンアーキテクチャの層構造**
クリーンアーキテクチャでは、ソフトウェアを複数の層に分割します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3938983/b8e36024-9d55-24cc-0b13-deae43272c24.png)
引用元 : Robert C. Martin "The Clean Code Blog" https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html （参照日：2024.12.9）


それぞれの層には明確な役割と依存関係のルールがあります：

- **エンティティ層**  
  ビジネスルールやドメインロジックを含む層で、最も変更されにくい部分です。他の層には依存せず、システム全体で重要なドメインの概念を表現します
- **ユースケース層**  
  アプリケーション固有のビジネスロジックを扱います。この層は、外部のデータやインターフェースに依存せず、エンティティ層だけに依存します。具体的なアクションや操作の流れを定義します
- **インターフェースアダプタ層**  
  ユースケース層と外部世界（データベース、UI、Webなど）をつなげる役割を果たします。具体的なデータ形式や外部システムとの接続を調整します
- **外部層（フレームワーク＆ドライバ）**  
  フレームワークやデータベース、UIなどの具体的な実装が含まれます。この層は最も変更されやすく、外部層は直接的にエンティティ層やユースケース層には依存せず、インターフェースアダプタ層を介して依存します

### **依存関係のルール**
層ごとに依存方向が決まっており、内側の層が外側の層に依存することは許されません。このルールにより、たとえ外部の技術や要件が変更されても、内側のビジネスロジックは影響を受けにくくなります。

次のセクションでは、アジャイルとクリーンアーキテクチャの具体的な相性について掘り下げていきます。

## 4. **アジャイルとクリーンアーキテクチャの相性**

アジャイル開発とクリーンアーキテクチャは、とても相性が良い組み合わせと考えています。ここでは、具体的にどのようにして両者が補完し合うのかを掘り下げていきます。

### **1. 変更の局所化**
アジャイル開発では、要件変更が頻繁に発生します。このとき、変更の影響範囲が広がりすぎると、開発速度が落ちたりバグが増えたりします。  
クリーンアーキテクチャでは、関心を層ごとに分離しているため、例えばデータベースの仕様が変更されても、ビジネスロジックに影響を与えません。これにより、変更を局所的にとどめ、チームの柔軟性を保つことができます。

### **2. テストしやすい設計**
アジャイルのプラクティスの1つとして「継続的インテグレーション」や「テスト駆動開発（TDD）」があります。クリーンアーキテクチャでは、ビジネスロジックが他の層に依存しないため、ユースケースやエンティティ層のテストが簡単に行えます。  
例えば、インターフェースアダプタ層をモック化することで、ビジネスロジックのテストに集中することが可能です。

### **3. 外部依存からの解放**
アジャイル開発では、利用するフレームワークやツールがプロジェクトの途中で変わることも珍しくありません。クリーンアーキテクチャの依存関係の逆転により、こうした外部の変更がプロジェクト全体に影響を与えにくくなります。

## 5. **仮想シナリオで考える：ログイン機能の設計と変更**

アジャイル開発において、要件の変更に柔軟に対応するために、クリーンアーキテクチャをどう活用するかを具体的なシナリオで考えてみましょう。

あなたのチームでは、アジャイル開発を進める中で以下の状況に直面したとします：

### **初期要件**
最初は、メールアドレスとパスワードで認証するシンプルなログイン機能を実装する必要があります。この段階では、認証ロジックがUI（コントローラ）や外部APIの実装に依存することなく、シンプルな設計で進める予定です。

### **要件変更**
スプリント途中で、「ソーシャルログイン（Google認証）を追加する」という新たな要件が発生しました。この要件変更に対し、どう対応するかがポイントです。

### **問題**
もし認証ロジックがUIや外部APIの実装に依存していると、要件が変更された際に以下の問題が発生します：
- ログイン機能に新しい認証方法を追加するのが難しくなる
- 変更によってテストが複雑化し、アプリケーション全体への影響範囲が広がる
- 追加や変更のたびにUIや外部APIとの結合部分を修正する必要があり、効率的に変更を加えることができない

### **クリーンアーキテクチャによる解決策**
クリーンアーキテクチャを活用すると、層ごとに関心事を分離し、変更に強い設計を実現できます。具体的には、以下のように設計することが可能です：

1. **層の分離**
   - **エンティティ層**: 「ユーザー」という概念を定義。認証自体には依存しない
   - **ユースケース層**: 「ログイン」というユースケースを定義。入力データ（メールアドレス、パスワード、認証プロバイダー）を受け取り、認証ロジックを実行
   - **インターフェース層**: ユースケースが利用する認証サービスを抽象化（インターフェースを定義）。ユースケース層と外部層（認証処理）の間の橋渡しをする
   - **外部層**: 実際の認証処理を行うサービス（例: ローカル認証、Google API）

2. **コード例**
   ここで、TypeScriptを使った具体的な実装例を挙げます

   - **ユースケース層**

   ```typescript
   export class LoginUseCase {
     constructor(private readonly authProvider: AuthProvider) {}

     async execute(email: string, password: string): Promise<User> {
       return this.authProvider.authenticate(email, password);
     }
   }
   ```

   - **インターフェース層**

   ```typescript
   export interface AuthProvider {
     authenticate(email: string, password: string): Promise<User>;
   }
   ```

   - **外部層**

   ```typescript
   export class LocalAuthProvider implements AuthProvider {
     async authenticate(email: string, password: string): Promise<User> {
       // パスワードをデータベースで確認
       // ユーザー情報を返す
       return new User(email);
     }
   }

   export class GoogleAuthProvider implements AuthProvider {
     async authenticate(email: string, password: string): Promise<User> {
       // Google APIで認証
       return new User(email);
     }
   }
   ```

    まず、`LocalAuthProvider` を使って認証の実装を選択します。そして、その認証処理を `LoginUseCase` に渡して、`LoginUseCase` が認証機能を利用できるようにします。
    
    ```typescript
    const authProvider = new LocalAuthProvider();  // 認証方法としてLocalAuthProviderを選択
    const loginUseCase = new LoginUseCase(authProvider);  // 認証処理をLoginUseCaseに渡す
    ```
    
    このように、`LoginUseCase` は認証方法を自分で作成するのではなく、外部から認証処理を受け取り、その認証機能を利用します。
    
    将来的に、Google認証に切り替えたい場合は、次のように認証方法を変更できます。
    
    ```typescript
    // 認証方法としてGoogleAuthProviderを選択
    const authProvider = new GoogleAuthProvider();
    const loginUseCase = new LoginUseCase(authProvider);  // 新しい認証方法をLoginUseCaseに渡す
    ```
    
    この方法により、認証方法を変更しても、`LoginUseCase` 自体を変更することなく、簡単に切り替えることができます。

### **結果**
- **変更の容易さ**
  ログインロジックの中心である`LoginUseCase`は、外部の依存（`LocalAuthProvider`や`GoogleAuthProvider`）に影響されません。新しい認証方式を追加したり、既存方式を変更する場合、`AuthProvider`を実装するだけで対応可能です

- **アジャイル開発への適合**
  初期スプリントではシンプルな`LocalAuthProvider`でリリース可能。次のスプリントで`GoogleAuthProvider`を追加して対応できます。これにより、スプリントごとに段階的に機能を追加でき、柔軟に対応できます

このように、クリーンアーキテクチャを使うことで、ログイン機能のようなシンプルな機能でも、将来的な変更に強い設計を構築することができます。また、変更を加えるたびにコード全体を見直す必要がなく、アジャイル開発のスピード感を保ちながら、スムーズに変更を取り入れることが可能になります。

## 6. **クリーンアーキテクチャ導入の課題**

クリーンアーキテクチャは柔軟で変更に強い設計手法ですが、導入にはいくつかの課題が予想されます。ここでは、考えられる課題について挙げてみます。

### **1. 初期導入のハードルが高い**  
クリーンアーキテクチャは層ごとに役割を分離するため、初期段階ではその構造を整えるのに時間がかかる可能性があります。アジャイル開発ではスピード感が求められるため、この初期コストがネックになるかもしれません。

### **2. チーム全体での理解が必要**  
設計思想をチーム全員で共有しないと、コードの一貫性が失われてしまう恐れがあります。特にクリーンアーキテクチャのように層を分離するアプローチは、初めて触れるメンバーには難しく感じられることもあります。

### **3. 短期的な効果が見えにくい**  
クリーンアーキテクチャは中長期的な視点でメリットが現れる設計手法ですが、短期的には「これをやる意味」が見えにくい場合もあります。初期段階では、むしろ手間が増えているように感じるかもしれません。

### **4. 既存プロジェクトとの共存**  
すでに稼働しているコードベースに対して新しい設計を適用するのは容易ではありません。一部をリファクタリングするだけでも、どう既存コードと連携させるか悩む場面が出てきそうです。

### **5. 過剰設計のリスク**  
クリーンアーキテクチャは柔軟性を重視する分、必要以上に複雑な構造になるリスクもあります。特に小規模なプロジェクトや頻繁に変更がない部分では、「そこまでやる必要があるのか？」という疑問が生じることもあるかもしれません。

こうした課題がある一方で、長期的には「柔軟性」や「変更への強さ」といったメリットが期待できます。このような設計手法を取り入れることで、アジャイル開発の本質である適応力をさらに高められるのではないかと思います。

## 7. **まとめ**

アジャイル開発において、要件の変化に柔軟に対応できるアーキテクチャは重要です。今回、クリーンアーキテクチャについて学びながらその考え方を取り入れることで、変更に強い設計ができる可能性があると感じました。

### **クリーンアーキテクチャから学んだこと**
- **層の分離**による変更の影響範囲の限定  
- **依存の逆転**による外部依存の最小化  
- **テストのしやすさ**の向上  

こうした特徴が、アジャイル開発のような変化の多いプロジェクトに適しているように思います。

### **実際に試してみて感じたこと**
今回の仮想シナリオでは、要件変更への対応が容易になることを実感できました。特に、認証機能の例では、各層が独立していることで、新しい認証方式を追加する際にも手戻りが少なく済みそうだと感じました。

### **今後について**
まだ実業務でクリーンアーキテクチャを本格的に適用してはいませんが、考え方として取り入れるだけでも設計や議論の軸が整うと感じています。これからさらに学びを深め、徐々にプロジェクトで実践できる場面を増やしていければと思っています。

---

今回の記事が、同じようにアーキテクチャについて悩んでいる方々の参考になれば幸いです。私自身もまだ学び始めたばかりですが、少しでも皆さんのヒントになるような内容であれば嬉しいです！

記載されている会社名、製品名、サービス名は、各社の商標または登録商標です。