導入
ドメイン駆動設計(DDD)は、複雑なビジネスロジックを効果的に管理するための強力なアプローチです。しかし、実際の開発現場では、設計の意図を誤解したり、誤った実装を行うことが少なくありません。このような失敗は、アンチパターンと呼ばれ、プロジェクトの進行を妨げる要因となります。本記事では、TypeScriptを用いたドメイン駆動設計における一般的なアンチパターンを取り上げ、その改善策を考察します。
教科書レベルの解説(ドメイン駆動設計)
重要な概念の整理
ドメイン駆動設計の中心には、ビジネスドメインを深く理解し、それに基づいたモデルを構築することがあります。エンティティ、バリューオブジェクト、アグリゲート、リポジトリといった概念は、DDDの基盤を成します。これらの要素を適切に利用することで、システムの可読性や保守性が向上します。
コード例(TypeScript)
class User {
constructor(public id: string, public name: string) {}
}
class UserRepository {
private users: User[] = [];
add(user: User) {
this.users.push(user);
}
findById(id: string): User | undefined {
return this.users.find(user => user.id === id);
}
}
コードの行ごとの解説
- クラスの定義: Userクラスは、ユーザーのエンティティを表現しています。
- コンストラクタ: idとnameを受け取り、それぞれのプロパティに格納します。
- UserRepositoryクラス: ユーザーの管理を行うリポジトリです。
- addメソッド: 新しいユーザーをリポジトリに追加します。
- findByIdメソッド: 指定されたIDを持つユーザーを検索します。
アンチパターン編
このセクションでは、ドメイン駆動設計におけるアンチパターンの一例を取り上げます。特に「リポジトリの肥大化」という問題が挙げられます。リポジトリは、本来、データの永続化を担当する役割を持ちますが、ビジネスロジックを含めてしまうと、責務が曖昧になり、テストや保守が難しくなります。
例えば、以下のようにリポジトリ内でビジネスロジックを実装してしまうケースが見受けられます。
class UserRepository {
// 省略
updateUserName(id: string, newName: string) {
const user = this.findById(id);
if (user) {
user.name = newName;
// ここでビジネスロジックを実行している
if (newName.length < 3) {
throw new Error("名前は3文字以上でなければなりません。");
}
}
}
}
このようにリポジトリがビジネスロジックを持つと、リポジトリの役割が不明確になり、他のコンポーネントとの依存関係が増加します。解決策としては、ビジネスロジックを専用のサービスクラスに移動させることが考えられます。
class UserService {
constructor(private userRepository: UserRepository) {}
changeUserName(id: string, newName: string) {
if (newName.length < 3) {
throw new Error("名前は3文字以上でなければなりません。");
}
const user = this.userRepository.findById(id);
if (user) {
user.name = newName;
}
}
}
このようにすることで、リポジトリはデータの取得と保存に専念し、ビジネスロジックはサービス層に移されます。これにより、システム全体の可読性と保守性が向上します。
まとめ
- リポジトリにビジネスロジックを持たせると、責務が曖昧になり、保守が難しくなる。
- ビジネスロジックはサービスクラスに移動させることで、リポジトリの役割を明確にし、テストしやすくなる。
- ドメイン駆動設計を実践する際は、責務の分離を意識することが重要。