導入
クリーンアーキテクチャは、ソフトウェアの保守性や拡張性を高めるためのアプローチとして広く知られています。しかし、実際のプロジェクトでは、理論通りに実装することが難しい場合が多々あります。特に、アンチパターンに陥ることで、意図した通りにアーキテクチャが機能しなくなることがあります。本記事では、上級者向けに、クリーンアーキテクチャの実装における具体的なアンチパターンを取り上げ、それに対する改善策を考察します。
教科書レベルの解説(クリーンアーキテクチャ)
重要な概念の整理
クリーンアーキテクチャは、ソフトウェアの各層を明確に分離することによって、依存関係を逆転させ、ビジネスロジックを外部の影響から守ることを目的としています。これにより、テスト可能性や可読性が向上します。特に、ドメイン層とインフラ層の分離は、アプリケーションの堅牢性を高める鍵となります。
コード例(JavaScript)
// ユーザー情報を取得するサービス
class UserService {
constructor(userRepository) {
this.userRepository = userRepository;
}
async getUser(userId) {
const user = await this.userRepository.findById(userId);
if (!user) {
throw new Error('User not found');
}
return user;
}
}
// ユーザー情報を保存するリポジトリ
class UserRepository {
constructor(database) {
this.database = database;
}
async findById(userId) {
return this.database.users.find(user => user.id === userId);
}
}
コードの行ごとの解説
- UserServiceクラスは、ユーザー情報を取得するためのサービスです。リポジトリを依存性注入により受け取ります。
- getUserメソッドは、ユーザーIDを受け取り、リポジトリからユーザー情報を取得します。ユーザーが見つからない場合はエラーを投げます。
- UserRepositoryクラスは、データベースからユーザー情報を取得する役割を持ちます。
- リポジトリは、データベースの具体的な実装に依存せず、ビジネスロジックを維持します。
アンチパターン編
多くのプロジェクトで見られるアンチパターンの一つに、「リポジトリの責務が肥大化する」という問題があります。例えば、リポジトリがデータベースへのクエリを直接記述し、ビジネスロジックも混在してしまうケースです。以下のコードは、その典型的な例です。
// 複雑なロジックを含むリポジトリ
class UserRepository {
constructor(database) {
this.database = database;
}
async findUserWithDetails(userId) {
const user = await this.database.users.find(user => user.id === userId);
if (user) {
user.details = await this.database.userDetails.find(details => details.userId === userId);
}
return user;
}
}
このコードの問題は、リポジトリがユーザー情報の取得だけでなく、詳細情報の取得も担っている点です。これにより、リポジトリはビジネスロジックを持つことになり、再利用性が低下します。改善策としては、ユーザーの詳細情報を取得するための別のサービスを作成し、リポジトリは単一の責務に徹することが望ましいです。
まとめ
- クリーンアーキテクチャでは、各層の責務を明確に分けることが重要です。
- リポジトリの肥大化を防ぐために、ビジネスロジックはサービス層に移動させるべきです。
- 依存性注入を活用し、テスト可能なコードを実現することが、クリーンな設計に寄与します。