導入
クリーンアーキテクチャは、ソフトウェア開発におけるアーキテクチャの設計原則を提供し、メンテナンス性や拡張性を高めるためのフレームワークです。しかし、実際の業務で遭遇するアンチパターンは、この理想から遠ざかる要因となります。本記事では、TypeScriptを用いた具体的なシナリオを通じて、クリーンアーキテクチャの実践における典型的なアンチパターンを取り上げ、その解決策を探ります。
教科書レベルの解説(クリーンアーキテクチャ)
重要な概念の整理
クリーンアーキテクチャは、アプリケーションを層に分け、依存関係を逆転させることで、ビジネスロジックをインフラストラクチャから分離します。これにより、テストが容易になり、変更に強いシステムを構築することが可能です。アーキテクチャの中心には、エンティティ、ユースケース、インターフェースアダプター、フレームワークが存在します。各層は独立しており、特にビジネスロジックを含むユースケース層は、他の層からの影響を受けにくい設計が求められます。
コード例(TypeScript)
class User {
constructor(public id: number, public name: string) {}
}
interface UserRepository {
findById(id: number): User | null;
}
class InMemoryUserRepository implements UserRepository {
private users: User[] = [];
addUser(user: User) {
this.users.push(user);
}
findById(id: number): User | null {
return this.users.find(user => user.id === id) || null;
}
}
コードの行ごとの解説
- Userクラスは、ユーザー情報を保持します。
- UserRepositoryインターフェースは、ユーザーを取得するためのメソッドを定義しています。
- InMemoryUserRepositoryクラスは、ユーザー情報をメモリ内で管理する実装です。
- addUserメソッドは、新しいユーザーを追加する機能を提供します。
- findByIdメソッドは、指定したIDのユーザーを取得します。
アンチパターン編
クリーンアーキテクチャの実装において、よく見られるアンチパターンの一つは、ビジネスロジックがインフラ層に依存してしまうことです。例えば、InMemoryUserRepositoryクラス内で、ユーザー情報を直接操作するロジックが混在してしまうケースです。このような実装では、テストが困難になり、将来的な変更にも脆弱です。
この問題を解決するためには、ビジネスロジックをユースケース層に移動させ、リポジトリは単にデータの取得・保存を担当するようにすることが重要です。以下は改善されたコード例です。
class UserService {
constructor(private userRepository: UserRepository) {}
getUserById(id: number): User | null {
return this.userRepository.findById(id);
}
}
ここでは、UserServiceクラスがユースケース層として機能し、リポジトリからのデータ取得を担当します。この設計により、ビジネスロジックがインフラ層に依存することなく、テスト可能な状態を保つことができます。
まとめ
- クリーンアーキテクチャにおいて、ビジネスロジックがインフラ層に依存することは避けるべきです。
- ユースケース層を設けることで、テスト容易性と変更への耐性を向上させることができます。
- 具体的なシナリオに基づいた改善策を適用することで、クリーンなアーキテクチャを実現しましょう。