ドメイン駆動設計にリファクタリングするというタスクをが降りてきてドメイン駆動設計の理解を深めるため、ここでアウトプットをしようかなって軽いモチベーションでたらたら解説していきます。
ドメイン駆動設計(Domain-Driven Design、DDD)は、ソフトウェア開発においてビジネスドメイン(業務領域)の複雑さを適切に扱うための設計手法です。DDDでは、ドメインモデル、ユースケース、リポジトリ、クエリモデルなどの要素を組み合わせて、ビジネスロジックを明確に表現し、柔軟で拡張性の高いシステムを構築します。
以下では、実務でTypeScriptを使っているのでTypeScriptの簡単なコード例を用いて、これらの要素がどのように連携するかを説明します。
1. ドメインモデル(Domain Model)
概要
- ビジネス上の概念やルール、プロセスをソフトウェア上で表現します。
- エンティティ、値オブジェクト、集約などで構成され、業務の核心となるロジックを持ちます。
エンティティの例
// エンティティ: ユーザー
class User {
constructor(
public readonly id: string,
private _name: string,
private _email: string
) {}
// ビジネスロジック: メールアドレスの変更
changeEmail(newEmail: string): void {
if (!this.validateEmail(newEmail)) {
throw new Error('無効なメールアドレスです。');
}
this._email = newEmail;
}
private validateEmail(email: string): boolean {
// メールアドレスの検証ロジック(簡略化)
return email.includes('@');
}
get name(): string {
return this._name;
}
get email(): string {
return this._email;
}
}
-
説明:
User
クラスはエンティティであり、ユーザーに関するビジネスルール(メールアドレスの変更と検証)を持っています。ドメインモデルはビジネスロジックを中心に構築され、業務の複雑さを表現します。
2. ユースケース(Use Case)
概要
- ユーザーがシステムとどのようにやり取りするかを示す具体的なシナリオです。
- アプリケーションサービスとして実装され、ドメインモデルの機能を組み合わせてユーザーの目的を達成します。
アプリケーションサービスの例
// ユースケース: ユーザーのメールアドレスを更新する
class UpdateUserEmailService {
constructor(private userRepository: IUserRepository) {}
async execute(userId: string, newEmail: string): Promise<void> {
const user = await this.userRepository.findById(userId);
if (!user) {
throw new Error('ユーザーが見つかりません。');
}
user.changeEmail(newEmail);
await this.userRepository.save(user);
}
}
-
説明:
UpdateUserEmailService
はユースケースを表し、ユーザーがメールアドレスを更新する操作を実現します。ユースケースを通じてユーザーの操作をモデル化し、必要なドメインロジックを呼び出します。
3. リポジトリ(Repository)
概要
- ドメインオブジェクト(エンティティや集約)を永続化ストア(データベースなど)との間で保存・取得する役割を担います。
- ドメインモデルからデータアクセスの詳細を隠蔽し、ビジネスロジックに集中できるようにします。
リポジトリのインターフェースと実装
// リポジトリのインターフェース
interface IUserRepository {
save(user: User): Promise<void>;
findById(id: string): Promise<User | null>;
}
// リポジトリの実装例(メモリ上のデータストア)
class InMemoryUserRepository implements IUserRepository {
private users: Map<string, User> = new Map();
async save(user: User): Promise<void> {
this.users.set(user.id, user);
}
async findById(id: string): Promise<User | null> {
return this.users.get(id) || null;
}
}
-
説明:
IUserRepository
はリポジトリのインターフェースで、InMemoryUserRepository
はその具体的な実装です。リポジトリを使ってドメインオブジェクトの保存や取得を管理し、ドメインモデルからデータアクセスの詳細を隠します。
4. クエリモデル(Query Model)
概要
- データの読み取りに特化したモデルで、表示やレポート作成のために最適化されています。
- **CQRS(Command Query Responsibility Segregation)**パターンにおいて、読み取り操作を分離するために使用されます。
クエリサービスの例
// クエリモデル: ユーザーデータ転送オブジェクト(DTO)
interface UserDTO {
id: string;
name: string;
email: string;
}
// クエリサービス: データの読み取り専用サービス
class UserQueryService {
// 実際にはデータベース接続やORMを使用
async getUserById(id: string): Promise<UserDTO | null> {
// データベースからユーザー情報を取得(ここでは仮のデータを返す)
return {
id: id,
name: 'Sample User',
email: 'sample@example.com',
};
}
}
-
説明:
UserQueryService
はデータの読み取り専用のサービスで、UserDTO
を返します。クエリモデルを用いてデータの効率的な読み取りを実現し、ユーザーインターフェースやレポートの要件に対応します。
まとめ
ドメイン駆動設計では以下のようにシステムを構築します:
- ドメインモデルを中心にビジネスロジックを実装し、業務の複雑さを表現します。
- ユースケースを通じてユーザーの操作をモデル化し、必要なドメインロジックを呼び出します。
- リポジトリを使ってドメインオブジェクトの保存や取得を管理し、データアクセスの詳細を隠します。
- クエリモデルを用いてデータの効率的な読み取りを実現し、ユーザーインターフェースやレポートの要件に対応します。
これらの要素を組み合わせることで、ビジネスドメインに密着した柔軟で拡張性の高いソフトウェアシステムを構築できます。TypeScriptのコード例を通じて、DDDの各要素がどのように機能し、連携するかを理解することができます。
感想
エンジニアはただコードを書く時代ではないのだと実感しました。最近ジョインしたインターン先では業界の専門用語飛び交っていてとんでもなくアウェイ状態なので、CSや営業の方々とコネクトしていち早く貢献できるように勉強した方がいいなと感じました。
テック記事を書き慣れていないのでうまく表現できなかったらすいません。間違っていたらガンガンご指摘ください。
参考文献
ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本
https://www.seshop.com/product/detail/20675
まさかインターンで成瀬さんにお会いしてすぐに成瀬さんの本を読むことになるとは...