mcpField
D
ddd-value-object

ddd-value-object

github.com/yonatankarp開発144 回インストール23 週間前に更新
リポジトリ

ddd-value-object について

ドメイン駆動設計の値オブジェクトをモデル化するスキルです。ID ではなく属性で定義される不変の概念を扱い、検証やドメイン操作、プリミティブ型を包む判断を導きます。

主な機能

  1. 01不変で値によって等しい概念のモデル化
  2. 02構築時の値検証
  3. 03値オブジェクトへのドメイン操作の配置
  4. 04同じプリミティブ型を持つ概念の区別

活用シーン

  1. 01Money、Address、DateRange のモデル化
  2. 02ドメイン不変条件を持つプリミティブの置き換え
  3. 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 章(ソフトウェアで表現するモデル)。

コメント