0. はじめに
ドメイン駆動設計(DDD : Domain Driven Design)について少し触れる機会があったので記事にまとめてみました。
この記事では「アプリケーションサービス」「リポジトリ」「ファクトリ」「集約」「仕様」に関しての説明は記載していません。予めご了承ください🙇♀️
1. ドメイン駆動設計とは
そもそも、システムを作る目的というのは、
「①ある領域(=ドメイン)における ②特定の課題を ③解決するため」
と言えます。例えば、
「①保育園の業務 において ②子供の出欠状況が分かりにくいので ③パソコンやスマホで出欠状況を把握できるようにする」
といったイメージです。
で、園児出欠管理システムのようなものが作成されるわけですね。
ドメイン駆動設計は、保守性・再利用性・拡張性の高いシステムを作成することが目的です。
上記を踏まえ、(俺的に)ドメイン駆動設計の手法を2行でまとめると以下になります。
- ドメインの理解を深め、ドメインの知識とコードを結び付ける (2. ユビキタス言語)
- リッチなドメインモデルを作成し、アプリケーション層と分離する (3. ドメインモデル)
ドメイン駆動設計の概念は2003年頃からあり、近年注目されています。
また、ドメインに直接かかわる人(システムを実際に使用する人)のことを、ドメインエキスパートと呼びます。
2. ユビキタス言語
意味は同じであっても、使用する用語が違うことにより、コミュニケーションがうまくいかなかったり、バグを生み出したりした経験はありませんでしょうか?
園児出欠管理システムを例にすると、
- Aさん「園児が登園した」、Bさん「園児が出席した」
- Aさん「更新する」、Bさん「変更する」
みたいな感じです。
このように用語の違いによる問題を減らすため、ドメインエキスパートも開発者も共通に使う言語を定義します。これをユビキタス言語といいます。
※ユビキタスとは「いつでもどこでも存在する」という意味合いになります。
ユビキタス言語は、会話やドキュメントだけでなく、コード(クラス、メソッド、変数名など)でも利用することを心がけます。
これにより、保守性・再利用性・拡張性のアップに繋がります。
// 状態を更新する
void UpdateStatus(...) // OK:ユビキタス言語で「更新」と決めたならこっち!
void ChangeStatus(...) // NG
3. ドメインモデル
ドメイン駆動設計において、このドメインモデルが肝となります。
先ほど「リッチなドメインモデルを作成し~」と記載しましたが、そもそもドメインモデルとは何でしょうか?
(俺的に)3行でまとめると以下になります。
- システムの鍵となる概念と用語をモデル化したもの
- 「エンティティ」「値オブジェクト」「ドメインサービス」に分離される
- 開発者とドメインエキスパートが協力して継続的に改善(よりリッチに)していく
3-1. エンティティ
- エンティティは3-2. 値オブジェクトと並んでドメインオブジェクトに分類されます。
- エンティティとは、同一性によって識別されるオブジェクトのことを言います。
- そのため、エンティティは必ず識別子を持ち、2つと同じオブジェクトは存在しません。
- つまり、全プロパティが同じでも、識別子が異なれば別オブジェクトとして扱われます。
園児出欠管理システムで考えると、園児や担任がエンティティに該当します。
同姓・同名・同年齢の園児がいても、当然別々の園児になりますからね。
--- 以下は別々の園児(エンティティ)でござる ---
kindergartener
┗ Id : 0001 (識別子)
┗ Name : 田中太郎
┗ Age : 3
kindergartener
┗ Id : 0002 (識別子)
┗ Name : 田中太郎
┗ Age : 3
3-2. 値オブジェクト
- 値オブジェクトは3-1. エンティティと並んでドメインオブジェクトに分類されます。
- 値オブジェクトとは、通常はプリミティブに扱われる値を、オブジェクトとして扱います。
- プリミティブなんだけどクラスを作ろうという方針です。
- 値オブジェクトはエンティティとは反対に、識別子を持ちません。
園児出欠管理システムで考えると、名前や年齢が値オブジェクトになります。
そのため、Nameクラス、Ageクラスを作成することになります。
「そんなもん面倒くさいじゃん!」て感じると思いますが、しっかりと恩恵があります。
値の代入ミスがなくなる🎉
Nameを単純な文字列(プリミティブ)として扱った場合、文字列であればなんでも代入可能です。
つまり名前でない概要、備考、etc...も代入できてしまうため、実装不具合に繋がってしまいます。
これを値オブジェクトとして扱うと、NameにはName型以外は代入できなくなるため、実装ミスを防ぐことができます。
オブジェクトにルールを定義できる🎉
値オブジェクトとして定義することにより、
- Nameは〇文字以上~〇文字以内でないといけない
- nullを設定してはいけない
といったルール(振る舞い)をオブジェクト自身に持たせることができます。
これにより、システムに不正なデータが存在を存在しにくくなります。
もちろん、全ての値を値オブジェクトをする必要はありません。
ある値にルールが存在する場合は値オブジェクト定義すべきかを検討するとよいかと思います。
3-3. ドメインサービス
先ほどちょっと記載しましが、エンティティや値オブジェクトにはルール(振る舞い)を定義します。
ドメインサービスでは、エンティティや値オブジェクトに定義すると違和感のあるルール(振る舞い)を定義します。
例えば、「園児を登録する際、重複してはいけない」というルールが存在したとします。
これを園児クラス(kindergartener)に定義するとどうでしょうか?
園児オブジェクトが、園児全員の登録状態を知ることになってしまいます。
public class kindergartener
{
public string Name { get; }
public int age { get; }
public bool Existkindergartener(string id)
{
// 既に存在するかをチェック
}
}
これでは違和感MAXなので、ここでドメインサービスが登場し違和感を解消します。
public class kindergartenerService
{
public bool Existkindergartener(string id)
{
// 既に存在するかをチェック
}
}
アプリケーションからkindergartenerService(ドメインサービス)を呼び出し、問題なければkindergartener(エンティティ)を生成するとうイメージです。
また、ドメインサービスはエンティティや値オブジェクトのように、自身の状態は持ちません。これはステートレスと言われます。
4. おわりに
- 当記事ではドメイン駆動設計の第一歩となるように、(俺的に)重要と思われる「エンティティ」「値オブジェクト」「ドメインサービス」絞って記載してみました😇
- ドメイン駆動設計の概念全てでなく、一部の概念のみ取り入れて設計するのも勿論良いと思います😇
- 冒頭で記載しましたが、ドメイン駆動設定には「アプリケーションサービス」「リポジトリ」「ファクトリ」「集約」「仕様」といった概念がよく登場しますので、興味のある方は是非調べてみてください😇