📕 ドメイン駆動設計入門
https://scrapbox.io/files/6365c775b940df0023529264.jpeg
Chapter1 ドメイン駆動開発とは
知識を表現するパターン
値オブジェクト
エンティティ
ドメインサービス
アプリケーションを実現するためのパターン
リポジトリ
アプリケーションサービス
ファクトリ
知識を表現する、より発展的なパターン
集約
仕様
Chapter2 システム固有の値を表現する「値オブジェクト」
fullNameからlastNameを取得したい
code:c#
var fullName = 'naruse masanobu'
var lastName = tokens0 // naruse // うまく姓を表示できないパターン
var fullName = 'john smith';
var lastName = tokens0 // john 上記を値オブジェクトで解決する
code:csharp
class FullName {
public FullName(string firstName, string lastName) {
FirstName = firstName;
LastName = lastName;
}
public string FirstName {get;}
public string LastName {get;}
}
姓を取得
code:csharp
var fullName = new FullName('masanobu', 'naruse');
console.WriteLine(fullName.LastName) // naruse
code:csharp
var fullname = new FullName('john', 'smith');
console.WriteLine(fullName.LastName) // smith
1. 値の性質をもっている
値オブジェクトはオブジェクトでもあり、値でもある
値の性質とは
不変である
交換が可能である
値は不変だが、値を変更することは必要
等価性によって比較される
上記の性質は値オブジェクトにそのまま適用される
不変である
code:csharp
var fullName = new FullName('masanobu', 'naruse');
fullName.ChangeLastName('sato') // こういう値自体を書き換えるメソッドは値オブジェクトに生やしてはいけない
交換が可能である
値オブジェクト自体は変更できない
変更するとしたら値そのものを代入しなおす
code:csharp
var fullName = new FullName('masanobu', 'naruse');
fullName = new FullName('masanobu', 'sato');
等価性によって比較される
値は値自身ではなくそれを構成する属性によって比較される
code:csharp
Console.WriteLine(0 == 0); // true // インスタンス自体は別だが、等価として扱われる
Console.WriteLine(0 == 1); //false
Console.WriteLine('a' == 'a') // true
Console.WriteLine('a' == 'b') // false
code:csharp
var nameA = new FullName('masanobu', 'naruse');
var nameB = new FullName('masanobu', 'naruse');
Console.WriteLine(nameA.Equals(nameB)); // インスタンスを構成する属性が等価なのでtrue
値オブジェクトを値と考えると、以下の比較は不自然な実装となる
-> firstName + lastNameで1つの値と考えているから
code:csharp
var nameA = new FullName('masanobu', 'naruse');
var nameB = new FullName('masanobu', 'sato');
var compareResult = nameA.FirstName === nameB.FirstName && nameA.LastName === nameB.LastName
比較するためには、FullNameクラスに比較するためのメソッドを生やす
code:csharp
class FullName : IEquatable<FullName>
{
public FullName(string fisrtName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public string FirstName { get; }
public string LastName { get; }
public bool Equals(FullName other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true; // インスタンス同士の比較?
return string.Equals(FirstName, other.FirstName) && string.Equals(LastName, other.LastName);
}
public override bool Equals(object obj) {
// ....
}
}
メリット
属性が追加されても修正不要
MiddleNameという属性を追加する+属性を直接オブジェクトから参照している比較のコードを色々なところに書いていると、改修のコストが大きくかかる
code:csharp
var compareResult = nameA.FirstName === nameB.FirstName
&& nameA.LastName === nameB.LastName
&& nameA.MiddleName === nameB.MiddleName // <-追加しなくてはいけない
値オブジェクトにする基準
fullNameだったら個別に lastName + firstNameと定義できる
それがやりすぎかどうかは、コンテキストによる
判断基準
そこにルールが存在しているか
それ単体で取り扱いたいか
氏名には、「姓と名で構成される」というルール
姓、名はどうだろう>姓、名だけを利用するシーンはない
姓名にバリデーションをかけたい場合があるとしたら、それは値オブジェクトにせずともルールを担保することができる
FullNameでルールを担保する
名前のクラス(型として使用する)をつくる
2. 独自のふるまいを定義できる
お金のオブジェクトの例
属性 -> 量、通貨単位(円やドル)
code:csharp
class Money
{
private readonly decimal amount;
private readonly string currency;
public Money(decimal amount, string currency)
{
if (currency == null) throw new ArugmentNullException(nameof(currency));
this.amount = amount;
this.currency = currency;
}
// 加算するふるまい
public Money Add(Money arg)
{
if (arg === null) throw new ArgumentNullException(nameof(arg));
// お金を加算するときは通貨単位をそろえるため、通貨単位が同一かどうか確認する
if (currency != arg.currency) throw new ArgumentException("通貨単位が異なります")
// 値オブジェクトは不変であるため、計算を行った結果は新たなインスタンスとして返す
return new Money(amount + arg.amount, currency);
}
}
値オブジェクトに定義されていないふるまいはできない(暗に明示している)
100円×100円は10000円にならない
表現力を増す
不正な値を存在させない
誤った代入を防ぐ
ロジックの散在を防ぐ
Chapter3 ライフサイクルのあるオブジェクト「エンティティ」
Chapter4 不自然さを解決する「ドメインサービス」
Chapter5 データにまつわる処理を分離する「リポジトリ」
Chapter6 ユースケースを実現する「アプリケーションサービス」
Chapter7 柔軟性をもたらず依存関係のコントロール
...