Value Object
Value Objectとは
Value Objectは、申込期限や金額など、業務で意味を持つ値を表すドメインモデルの構成要素です。値が正しいかを確認するルールと、その値に関する判断をまとめます。
Entityが識別子で区別されるのに対し、Value Objectは持っている値で区別されます。たとえば、日時が同じ二つの申込期限は、同じ申込期限として扱います。
値で区別する
例えば、1,000円という金額は、どこで作られたかに関係なく同じ1,000円です。個別のIDは必要なく、金額と通貨が同じなら同じ金額として扱います。
Value Objectの特徴
- 値が同じなら同じものとして扱う
- 二つのValue Objectが同じかどうかは、識別子ではなく、持っている値で判断します。
- 作った後は値を書き換えない
- 申込期限を18時から19時へ変更する場合、18時の申込期限を書き換えるのではなく、19時の申込期限を新しく作って置き換えます。
- 値の振る舞いをまとめる
- 値の振る舞いとは、その値を使って行う判断・比較・計算などです。これらをValue Objectにまとめます。
具体例で整理する
前のページで見つけた「申込期限」を、Value Objectとして表します。申込期限は識別子ではなく日時によって区別し、日時が同じなら同じ申込期限として扱います。
申込期限には、「有効な日時である」という条件があります。また、期限を過ぎているか、指定した日時と同じかそれより後か、別の申込期限と同じ日時かを判断する振る舞いがあります。こうした条件と振る舞いを、申込期限のValue Objectとしてまとめます。
値
2026-08-11 18:00
満たす条件
有効な日時である
値の振る舞い
- 現在時刻が期限を過ぎているか判断する
- 指定した日時と同じか、それより後か判断する
- 別の申込期限と同じ日時か判断する
コードで表す
申込期限をValue Objectとして表すと、例えば次のようになります。
TypeScript
// 「申込期限」を表すValue Object
class ParticipationRequestDeadlineValueObject {
private readonly value: Date
constructor(value: Date) {
// 有効な日時か確認する
if (Number.isNaN(value.getTime())) {
throw new Error("申込期限には有効な日時を指定してください")
}
// 外から渡されたDateを複製し、後から変更されないようにする
this.value = new Date(value)
}
// 現在時刻が申込期限を過ぎたか判断する
hasPassed(now: Date) {
return now > this.value
}
// 指定した日時と同じか、それより後か判断する
isOnOrAfter(date: Date) {
return this.value >= date
}
// 日時が同じなら、同じ申込期限と判断する
equals(other: ParticipationRequestDeadlineValueObject) {
return this.value.getTime() === other.value.getTime()
}
}EntityでValue Objectを使う
募集を表すEntityのRecruitmentEntityでは、申込期限にParticipationRequestDeadlineValueObjectを使います。期限を過ぎているかなど、申込期限に関する判断はParticipationRequestDeadlineValueObjectが行います。
TypeScript
// RecruitmentEntityの一部を抜粋
class RecruitmentEntity {
constructor(
private readonly participationRequestDeadline: ParticipationRequestDeadlineValueObject,
eventDateTime: Date,
) {
// 「申込期限は開催日時より前」という募集のルールを守る
if (participationRequestDeadline.isOnOrAfter(eventDateTime)) {
throw new Error("申込期限は開催日時より前に設定してください")
}
}
apply(now: Date) {
// 日時を直接比較せず、申込期限に判断を任せる
if (this.participationRequestDeadline.hasPassed(now)) {
throw new Error("申込期限を過ぎています")
}
// 参加申請を受け付ける
}
}ParticipationRequestDeadlineValueObjectは日時の検証や比較を担当します。RecruitmentEntityはその振る舞いを使い、「申込期限は開催日時より前」「期限を過ぎたら申し込めない」という募集のルールを守ります。
フットサルNOWでの配置例
src/features/futsal/
└── domain/ ← ドメインモデルを置くディレクトリ
├── recruitment/
│ ├── RecruitmentEntity.ts ← Aggregate Root(Entity)
│ ├── RecruitmentStatusValueObject.ts ← Value Object
│ ├── RecruitmentCapacityValueObject.ts ← Value Object
│ ├── ParticipantIdValueObject.ts ← Value Object
│ └── ParticipationRequestDeadlineValueObject.ts ← Value Object
└── ... ← ほかのドメインモデル※ これはフットサルNOWでの配置例です。DDDが特定のディレクトリ構成を定めているわけではありません。
※ このガイドラインでは、ファイル名やクラス名だけで役割を見分けやすくするため、DDD上の役割を名前に付けています。