ddd-value-object について
ドメイン駆動設計の値オブジェクトをモデル化するスキルです。ID ではなく属性で定義される不変の概念を扱い、検証やドメイン操作、プリミティブ型を包む判断を導きます。
主な機能
- 01不変で値によって等しい概念のモデル化
- 02構築時の値検証
- 03値オブジェクトへのドメイン操作の配置
- 04同じプリミティブ型を持つ概念の区別
活用シーン
- 01Money、Address、DateRange のモデル化
- 02ドメイン不変条件を持つプリミティブの置き換え
- 03同じ型を使う識別子の区別
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ yonatankarp/ software-design-skills --skill ddd-value-objectインストール後、Claude Code で/ddd-value-objectを入力して読み込まれたか確認します。
詳細
1 行でまとめると
値オブジェクトとは、その属性だけで定義される概念です。アイデンティティを持たず、不変で、値によって等しいかどうかが決まります。
このスキルを使う場面
Money、Address、DateRange、EmailAddress、ProductCode、Percentage、PhoneNumberをモデル化する場合。- 型システムでは強制できない不変条件を持つプリミティブ(
String、Long、BigDecimal)を置き換える場合。 - 内部表現のプリミティブ型を共有する 2 つの概念を区別する場合(
CustomerIdとOrderIdはどちらも UUID ですが、相互に置き換えられません)。 - 概念に属するドメイン操作をカプセル化する場合:
Money.add(Money)、Address.normalize()、DateRange.contains(Instant)。
このスキルを使わない場面
- その概念にライフサイクルやアイデンティティがある場合 →
ddd-entityを使います。 - ラップしても不変条件も振る舞いも追加されない場合 — ラップすること自体が目的になるのは形式主義です。
- プリミティブが 1 か所でしか使われず、境界を越えることもない場合 — ラップのコストがメリットを上回る可能性があります。
核となる内容
値オブジェクトは不変です。一度構築したら、そのフィールドが変わることはありません。値オブジェクトに対する操作は、レシーバーを変更するのではなく、新しい値オブジェクトを返します。また、値オブジェクトは値によって等しいかどうかが決まるため、属性値が同じ 2 つの値オブジェクトは相互に置き換え可能です。
値オブジェクトは、ドメイン操作を置く場所としても自然です。Money には、USD と EUR の加算を拒否する add メソッドを持たせます。Address には、正規化された形式を返す normalize メソッドを持たせてもよいでしょう。DateRange は、ある Instant がその範囲に含まれるかどうかを判定できるべきです。こうした操作を MoneyUtils の静的ヘルパーではなく値オブジェクトに置くことで、ドメインの表現力が高まります。
検証は構築時に行います。EmailAddress("notanemail") は、後から何かに使われるときではなく、構築時に失敗するべきです。これが「無効な状態を表現できないようにする」という意味です。
判断の目安
- 次のいずれかに当てはまるプリミティブはラップします。(a)言語で表現できない不変条件がある。(b)ドメイン固有の操作がある。(c)2 つのドメイン概念がプリミティブ型を共有しており、相互に置き換えられないようにする必要がある。
- 変更可能な値オブジェクトを使える言語でも、不変の値オブジェクトを優先します。
MoneyUtils.add(money1, money2)と書こうとしているなら、いったん立ち止まりましょう — その操作はMoneyに属します。
アンチパターン
- 変更可能な値オブジェクト — セッターは等価性の意味を損ない、共有状態に起因するバグを招きます。
- アイデンティティフィールドを持つ値オブジェクト — 実態はエンティティです。
- すべてのプリミティブをラップすべきものとして扱うこと — 過剰設計です。反射的にラップするのではなく、ドメイン上の意味を持つ場合にラップします。
- メッセージのない汎用的な
IllegalArgumentExceptionを投げる検証 — 意味の明確なドメイン例外を定義するか、結果型を使います。
関連項目
ddd-entity— 反対のケースである、アイデンティティを持つ概念です。ddd-anti-patterns— プリミティブ型への過度な依存、変更可能な値オブジェクトです。ddd-kotlin-idioms— Kotlin で値オブジェクトを慣用的に表現する方法(data class、value class)です。
参考文献
- Evans、『Domain-Driven Design』(2003 年)、第 5 章(ソフトウェアで表現するモデル)。
コメント
Markdown 対応