DDDとは
解決したいビジネス上の課題(ドメイン)を中心にして行う設計手法。
ドメインエキスパートと開発者が共通言語(ユビキタス言語)を用いてモデルを構築し、ビジネス要件の変化に強いシステムを実現する。
ドメインモデリング
-
コアドメイン
ビジネスが持つ最も重要な価値や競争優位性を生み出す部分 -
サブドメイン
ビジネスの主要な業務領域をサポートする部分
コアドメインを補完する
汎用サブドメインと支援サブドメインの2種類に分けられる -
境界付けられたコンテキスト
特定のドメインモデルが適用される明確な境界を示すもの
イベントストーミング
複雑なビジネスプロセスを短時間で深く理解し、関係者間で共有・可視化するワークショップ形式のモデリング手法。
イベントストーミングのルール
- ドメインイベント
過去に発生した出来事 - ポリシー
- コマンド
- アクター
- ビュー
- 集約
関連するデータとそのデータを操作するビジネスルールや制約を一つのグループにまとめたもの
イベントストーミングの進め方
- 参加者の選定
ドメインエキスパート、PM、PdM、ソフトウェア開発者、デザイナー等 - ツールの選定
Miro、FigJam など - ドメインイベントの洗い出し
- ドメインイベント同士を時系列で繋げる
- ドメインイベント同士のギャップを埋める
疑問、懸念事項等はホットスポットとしてログを残す - 集約を特定する
コマンドの目的語=集約である可能性が高い - 境界付けられたコンテキスト・コア/サブドメインの定義
集約特定後に同一名称の集約が複数ある場合、それぞれがコンテキストの境界になる可能性がある
ドメインモデル図の作成
特定のドメインの主要な概念、エンティティ、その属性、およびこれらの要素間の関係性を視覚的に表現した図。
- 集約の定義
集約をクラス図で表現 - 属性の定義
エンティティが持つ属性をクラス図で表現 - ビジネスルールの追加
- 関連性の定義
アーキテクチャ
ドメイン駆動設計には固有のアーキテクチャはない。
値オブジェクト(ValueObject)
特定の概念を表したオブジェクト。
プリミティブ型の代替としてオリジナルの便利な変数型を用いる。
- 一意性を持たない
- イミュータブルオブジェクトである(インスタンス生成時に値が固定され、不変である)
- 副作用を持たない
例として名前、年齢、メールアドレス、お金などが挙げられる。
export class EmailAddress {
private readonly value: string;
constructor(value: string) {
// ビジネスルール
if (!value.includes("@")) {
throw new Error("メールアドレス形式が不正です");
}
this.value = value;
}
// 値の取得
getValue(): string {
return this.value;
}
// 値同士の比較
equals(other: EmailAddress): boolean {
return this.value === other.value;
}
}
値の特徴
- 不変に保つことができる
値自体を変更することはできない
実装ではprivate readonly等を用いる - 値同士が等しいか比較できる
equalsメソッド等で同クラスの別インスタンスとの等価性を比較できる - 副作用がない
ある操作が他の状態に予期せぬ状態を与えない
値オブジェクトに適した値の特徴
値オブジェクトの実装にはコストがかかるため、対象を絞る必要がある。
- 意味のある単一の概念を表す値
- ビジネスルールを持つ
- 再利用性
上記の特徴を持つ値は値オブジェクトに適している
あるモデル要素について、その属性しか関心の対象とならないなら、その要素は値オブジェクトとして分類されるべき。
値オブジェクトの意義
値オブジェクト自身がドメイン内の値のドキュメントになる。
エンティティ
識別子(ID)によって区別されるオブジェクト。
ドメイン内のビジネス概念をモデル化するのに使われる。
- 可変である
- ライフサイクルがある
時間の経過とともにビジネスプロセスに沿って生成、変更、削除というプロセスを経る。
例としてユーザー、社員等がよく挙げられる。
例えば、山田さんという社員は年齢を重ねても同一人物であり、また同姓同名の人ともIDで区別される。
export class User {
constructor(
public readonly id: string,
private name: string,
private age: number
) {}
// 状態変更
changeName(newName: string): void {
if (!newName) {
throw new Error("名前は必須です");
}
this.name = newName;
}
// 状態取得
getName(): string {
return this.name;
}
// 同一性判定
equals(other: User): boolean {
return this.id === other.id;
}
}
値オブジェクトとエンティティの違い
どちらもドメインモデルの中心的な要素だが、同一性の判定基準や可変性の有無によって両者は分けられる
| エンティティ | 値オブジェクト | |
|---|---|---|
| 同一性判定 | 識別子が同一であること | 保持する属性が全て同一であること |
| 可変性 | 可変でもよい | 必ず不変である |
集約
リポジトリへの入出力単位。
関連するオブジェクト群を1つのユニットとして管理するために用いられる。
各集約にはルートと境界がある。
境界はデータの一貫性(不変条件)を保つためのエンティティと値オブジェクトの集合体。
ルートは集約に含まれている代表的な1エンティティ。
ルートのみが外部と通信し、境界内の不整合を防ぐ。
// Orderが集約ルートであり、境界としてOrderItemを定義する例
export class OrderItem {
constructor(
public readonly productName: string,
public readonly price: number,
public readonly quantity: number
) {
if (quantity <= 0) {
throw new Error("数量は1以上である必要があります");
}
}
getSubtotal(): number {
return this.price * this.quantity;
}
}
export class Order {
private items: OrderItem[] = [];
constructor(
public readonly id: string
) {}
// Order経由でのみ商品追加
addItem(item: OrderItem): void {
this.items.push(item);
}
// 合計金額算出
getTotalPrice(): number {
return this.items.reduce(
(sum, item) => sum + item.getSubtotal(),
0
);
}
// 商品一覧取得
getItems(): readonly OrderItem[] {
return this.items;
}
}
Repository
集約の永続化を抽象化するインフラ層のオブジェクト
- 集約の永続化
エンティティの新規作成、更新 - 集約の復元
DBからエンティティを取得し、ドメインオブジェクトとしてアプリケーションサービスに渡す
参考記事
ふわっと理解するDDD ~ドメイン駆動設計~
https://qiita.com/yu-saito-ceres/items/f73262cedcdd6e8e75c8#
DDD に入門するなら、まずは ValueObject だけでもいいんじゃない
https://qiita.com/t2-kob/items/9d9dd038fe7497756dbf
【DDD入門】TypeScript × ドメイン駆動設計ハンズオン
https://zenn.dev/yamachan0625/books/ddd-hands-on
DDD基礎解説:エンティティ、値オブジェクトってなんなんだ
https://little-hands.hatenablog.com/entry/2018/12/09/entity-value-object