ドメイン駆動設計に感する考察置き場

このページは何?
ドメイン駆動設計について学ぶにあたり、 Fendo181が考えた事を書き出しておく作業ページになります。
参考
書籍
- ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本 電子書籍|翔泳社の本
- ドメイン駆動設計 モデリング/実装ガイド - little-hands - BOOTH
- ドメイン駆動設計 サンプルコード&FAQ - little-hands - BOOTH
- わかる!ドメイン駆動設計 ~もちこちゃんの大冒険~【C91新刊】 - TechBooster - BOOTH
- Amazon | Domain-Driven Design in PHP | Buenosvinos, Carlos, Soronellas, Christian, Akbary, Keyvan | PHP
web記事

エンティティとValueオブジェクトの違いについて、ChattGPTに聞いてみて内容を書いてみる。
ドメイン駆動設計(DDD)における「値オブジェクト(Value Object)」と「エンティティ(Entity)」の違いは、それらがドメイン内でどのように使われるかに基づいています。以下にPHPを使用してこれらの違いを説明する例を示します。
エンティティ (Entity)
エンティティは、一意の識別子(ID)を持つオブジェクトです。これにより、エンティティはそのライフサイクルを通じて追跡され、その状態が変化しても同一のものとして認識されます。例えば、ユーザーアカウントや注文などがエンティティに該当します。
class User {
private $id;
private $name;
public function __construct($id, $name) {
$this->id = $id;
$this->name = $name;
}
public function getId() {
return $this->id;
}
public function getName() {
return $this->name;
}
public function setName($name) {
$this->name = $name;
}
}
この例では、User クラスはエンティティです。
値オブジェクト (Value Object)
値オブジェクトは、属性によって定義される不変のオブジェクトです。これらは通常、小さく、単純なオブジェクトで、識別子を持ちません。値オブジェクトの例には、金額、住所、日付範囲などがあります。
Copy code
class Address {
private $street;
private $city;
private $postalCode;
public function __construct($street, $city, $postalCode) {
$this->street = $street;
$this->city = $city;
$this->postalCode = $postalCode;
}
// ここではゲッターメソッドのみを提供し、セッターメソッドは提供しないことで不変性を保証します
public function getStreet() {
return $this->street;
}
public function getCity() {
return $this->city;
}
public function getPostalCode() {
return $this->postalCode;
}
}
この例では、Address クラスは値オブジェクトです。値オブジェクトは一度作成されると変更されないため、セッターメソッドを持たず、ゲッターメソッドのみが提供されています。また、Address オブジェクトは street、city、postalCode の組み合わせによって定義され、一意の識別子は持ちません。
まとめ
-
エンティティ
は一意の識別子によって定義され、その状態が変化しても追跡されます。 -
値オブジェクト
は属性によって定義され、不変であり、識別子を持ちません。

ただ、値オブジェクトにもなりえるし、エンティティにもなりえる概念が存在するので、必ずどちらかに決めつけるのは難しそう。

ドメインサービスが出てきた。
これまでは値オブジェクトや、エンティティはそのままの特性が書かれていたが、ドメインオブジェクトにかかれてしまうと不自然なふるまいが出てしまうので、そこを解消するためにドメインサービスが存在する。

ChatGPTに聞いてみた。
ドメイン駆動設計(DDD)において、ドメインサービスはエンティティや値オブジェクトに含めるには不適切なドメイン固有の操作やビジネスロジックをカプセル化します。ドメインサービスは通常、アプリケーションのビジネスルールや操作を実行するためのインターフェースを提供します。
以下に、PHPを用いてドメインサービスのサンプルコードを示します。この例では、注文の処理に関するビジネスロジックを表すドメインサービスを作成します。
ドメインエンティティ
まず、ドメインエンティティを定義します。ここでは Order
クラスを例にします。
class Order {
private $id;
private $totalAmount;
public function __construct($id, $totalAmount) {
$this->id = $id;
$this->totalAmount = $totalAmount;
}
public function getId() {
return $this->id;
}
public function getTotalAmount() {
return $this->totalAmount;
}
}
ドメインサービス
次に、ドメインサービスを定義します。この例では、注文に対して割引を適用するロジックを実装する OrderDiscountService
を作成します。
class OrderDiscountService {
// 割引ロジックを実行するメソッド
public function applyDiscount(Order $order, $discountRate) {
if ($discountRate < 0 || $discountRate > 1) {
throw new InvalidArgumentException("Invalid discount rate");
}
$discountedAmount = $order->getTotalAmount() * (1 - $discountRate);
return new Order($order->getId(), $discountedAmount);
}
}
このサービスでは、注文 (Order
オブジェクト) と割引率を引数に取り、割引後の金額で新しい注文オブジェクトを生成して返します。ドメインサービスはビジネスロジック(この場合は割引の計算)をカプセル化し、エンティティや値オブジェクトが持つべきではない複雑な操作を担当します。
まとめ
- ドメインサービスは、エンティティや値オブジェクトに属さないビジネスロジックをカプセル化します。
- この例では
OrderDiscountService
がドメインサービスであり、注文に割引を適用する責務を持っています。 - ドメインサービスは、ドメインモデルの整合性を保つために重要な役割を果たします。

ドメインサービスはあくまでも、ドメインオブジェクトの 不自然なふるまい だけに集中をするのが大事

Userエンティティ(ドメインオブジェクト)に例えると、名前を変更するだけだったらエンティティだけで良い。そこは自然な流れだと思う。しかし、これをドメインサービス上でやろうとすると「不自然」が発生して、躓くと考える。

ただ、できなくはないのでエンティティ(ドメインオブジェクト)には定義せずに、ドメインサービスに全部もっていく事もできる。そうなると、今度は ドメインモデル貧血病 になるため、ロジックの見落としが悪くなってしまう問題がある。

その処理がドメインサービスかどうかを見極める際に筆者が重要視している事は、ドメインに基づくものかそうでないかという点です。たとえば、「ユーザー重複」がドメインに関連するものであればそれはドメインサービスにです。それをこーどとして表現するためには、インフラストラクチャーのサービスの協力 = Databaseへの接続などが必要になります。逆にアプリケーションであれば、アプリケーションサービスにもっていくのが良いです。
ref: ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本 電子書籍|翔泳社の本 : p78

ドメインサービスへの切り出しを行う基準がわからなかったので、ChatGPTについて聞いてみて。
ドメインサービスとドメインオブジェクト(エンティティや値オブジェクト)の違いを理解し、それぞれに適切な責任を割り当てることはドメイン駆動設計(DDD)において非常に重要です。どのようなロジックをドメインサービスに定義し、どのようなロジックをドメインオブジェクトに定義するかを決定する際に考慮すべきポイントをいくつか挙げます。
1. ドメインの中心的な概念かどうか
- ドメインオブジェクト: ドメインの中心的な概念やビジネスルールをモデル化する場合に適しています。エンティティや値オブジェクトは、ドメインの基本的な要素(例:ユーザー、製品、注文など)を表し、その状態と行動をカプセル化します。
- ドメインサービス: ドメインオブジェクトに自然に属さない、または複数のドメインオブジェクト間で実行されるべきビジネスロジックに適しています。例えば、複雑な計算や外部システムとの連携を行うロジックは、ドメインサービスに属することが多いです。
2. 状態の所有
- ドメインオブジェクト: 特定の状態を持ち、その状態に基づいて振る舞いを定義する場合に適しています。エンティティは一意の識別子により区別され、値オブジェクトは属性の組み合わせにより定義されます。
- ドメインサービス: 状態を持たない(または限定的な状態を持つ)操作を行う場合に適しています。ドメインサービスは状態を持たないことが多く、与えられた入力に基づいて操作を行います。
3. 単一責任の原則
- ドメインオブジェクト: 単一の責任原則に従い、オブジェクト自身の属性や行動に焦点を当てるべきです。オブジェクトが複数の責任を持ち始めたら、それはドメインサービスに分割されるべきサインかもしれません。
- ドメインサービス: 複数のドメインオブジェクトをまたがる、またはドメインオブジェクトに自然に属さないロジックに責任を持ちます。
4. ドメインの専門知識
- ドメインオブジェクトとドメインサービスの両方: ドメインの専門知識を反映する必要があります。どちらの選択も、ドメインエキスパート(ビジネスアナリスト、ドメインの専門家など)との密接なコラボレーションを通じて、ドメインの実際のニーズと複雑さを正確に反映していることが重要です。
まとめ
ドメインオブジェクト(エンティティや値オブジェクト)は、ドメインの基本的な概念とビジネス
ルールを表します。一方、ドメインサービスは、これらのオブジェクトに属さない、または複数のオブジェクトにまたがるビジネスロジックを担います。これらの選択は、ドメインの中心的な概念、状態の所有、単一責任の原則、およびドメインの専門知識に基づいて行われるべきです。

リポジトリについて。なぜ「永続化」と「復元」という言葉、ドメイン駆動設計に頻繁にでてくるかがわからなかったが、この章の始まりの説明でわかった気がする。
一文の理由はエンティティの概念にあると思う。
エンティティは値オブジェクトと違い、ライフサイクルがある。
データが生成されて、なんらかの処理が走り、最後にはプログラムが終わると破棄される。
つまりはどこかでデータを保存しておかないと、1回の処理で終わってしまう。
繰り返しオブジェクトを使うためには処理を、なんらかのデータストアに保存しておいて、それを取り出して使うといった事を行えば繰り返し利用ができると判断している。
この役割を行うのがリポジトリである。
リポジトリはサービスで行ったデータの処理を、データストア(DB)に保存をする。これを 永続化 という。また、データを取り出して復元をした場合にもリポジトリを扱いデータを復元を行う。これは 復元 と呼ぶ。