【DDD】値オブジェクト - Value Object
値オブジェクトとは
業務で使う単位で値のルールをクラスとして表現したものです。例えば年齢は通常、number型のようなプリミティブな値で表現されることが多いですが、値オブジェクトではarg型として業務で扱う単位の型で表現します。
書籍「実践ドメイン駆動設計」[1]では、値オブジェクトの特徴について、このように書かれています。
- ドメイン内の何かを計測したり定量化したり説明したりする
- 状態を不変に保つことができる
- 関連する属性を不可欠な単位として組み合わせることで、概念的な統一体を形成する
- 計測値や説明が変わったときには、全体を完全に置き換えられる
- 値が等しいかどうかを、他と比較できる
- 協力関係にあるその他の概念に「副作用のない振る舞い」を提供する
次のサンプルコードを見て、特徴と照らし合わせながら確認していきます。
/**
* 預けているお金の操作を行うクラス
*/
class Balance {
private readonly amount: number
private readonly currency: string
get Amount() {
return this.amount
}
get Currency() {
return this.currency
}
constructor(amount: number, currency: string) {
if (amount === null || amount === undefined || !currency) throw new Error("未入力の項目があります")
this.amount = amount
this.currency = currency
}
equals(money: Balance) {
return this.amount === money.amount && this.currency === money.currency
}
add(money: Balance) {
if (this.currency !== money.currency) throw new Error("通貨が異なります")
return new Balance(this.amount + money.amount, this.currency)
}
sub(money: Balance) {
if (this.amount < money.amount) throw new Error("残高より多く引き出すことは出来ません")
if (this.currency !== money.currency) throw new Error("通貨が異なります")
return new Balance(this.amount - money.amount, this.currency)
}
}
const myMoney = new Balance(1000, "JPY")
const deposit = new Balance(1500, "JPY")
const result = myMoney.add(deposit)
console.log(result.Amount) // 2500
1. ドメイン内の何かを計測したり定量化したり説明したりする
Balanceクラスは、「今預けているお金はいくらなのか」を定量化した値で表現しています。そして、異なる通貨同士の加算や減算が行えないことを説明しています。
2. 状態を不変に保つことができる
各プロパティはprivate readonly
で宣言をしているため、状態を不変に保つことができます。
private readonly amount: number
private readonly currency: string
3. 関連する属性を不可欠な単位として組み合わせることで、概念的な統一体を形成する
値オブジェクトは複数のプロパティを保持しており、それらが相互に関係して値を説明することができます。今回のコードでは金額の数量を表す「amount」と円や、ドルなどの通貨の単位を表す「currency」があり、これらを組み合わせることによって、適切な値を表します。
4. 計測値や説明が変わったときには、全体を完全に置き換えられる
値オブジェクトは「状態を不変に保つことができる」という特徴を持つため、途中で値の変更を行えません。しかし、実際のプログラムでは値の変更を行いたいことが多々あります。
そのような時は、変更後の値を設定した新しいオブジェクトを生成して置き換えます。通貨の金額を変更したい場合、現在の値オブジェクトの値を元に、新しい通貨オブジェクトを生成し置き換えます。
const myMoney = new Balance(1000, "JPY")
const deposit = new Balance(1500, "JPY")
const result = myMoney.add(deposit)
5. 値が等しいかどうかを、他と比較できる
値オブジェクト同士のインスタンスを比較する場合、等しいかどうかを判断する等価性の判定出来なければいけません。エンティティは一意な識別子で判定できますが、値オブジェクトでは各属性が持つすべての値を判定します。
equalsメソッドが「すべての値が同じか」を判定する役割を担っています。
equals(money: Balance) {
return this.amount === money.amount && this.currency === money.currency
}
6. 協力関係にあるその他の概念に「副作用のない振る舞い」を提供する
値オブジェクトを扱うメソッドは、全て副作用のない関数でなくてはなりません。addメソッドやsubメソッドは、自身のプロパティを変更せずに新しいインスタンスを返すようにしています。
add(money: Balance) {
return new Balance(this.amount + money.amount, this.currency)
}
これが実践ドメイン駆動設計で言われている6つの特徴です。
参考
-
ヴァーン・ヴァーノン 実践ドメイン駆動設計 ↩︎
Discussion