3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【DDD】ValueObjectとEntityの違いを理解する

Posted at

はじめに

immutableでないオブジェクトを扱うクラスをvalueディレクトリに置いてしまい、レビューで指摘をいただきました。この経験から学んだことをまとめたいと思います。

  • DDD (Domain-Driven Design): ドメイン駆動設計。ビジネスロジック(ドメイン)を中心にソフトウェア設計を行う手法
  • immutable: 不変。一度作成したら値を変更できないこと

ValueObjectとは

ValueObjectとは、値そのものを表現するオブジェクトのこと。DDDにおけるvalueディレクトリには、このValueObjectを配置します。

ValueObjectは以下の3つの特徴を持ちます

  1. 不変である (Immutable)
  2. 交換可能である (Replaceable)
  3. 等価性によって比較される (Compared by Value)

それぞれの特徴を詳しく見ていきましょう。

1. 不変である (Immutable)

ValueObjectは、一度作成したら内部の値を変更することができません。

例えば、金額を表すMoneyクラスを考えてみましょう

Money.java
public record Money(int amount, String currency) {
}

Java 16から導入されたレコードクラスを使うことで、ValueObjectを非常にシンプルに表現できます。
レコードクラスは以下の特徴があります

  • 全てのフィールドが自動的にprivate finalになる
  • コンストラクタが自動生成される
  • getterが自動生成される(getAmount()ではなくamount()という名前)
  • equals()hashCode()toString()が自動実装される
  • setterは存在しない(不変性が保証される)

値を変更したい場合は、新しいインスタンスを作成して返します。

2. 交換可能である (Replaceable)

price1, price2自体は変更できないものの、同じ箱を使って異なる値を入れることは可能です!

Example.java
Money price1 = new Money(1000, "JPY");
Money price2 = new Money(2000, "JPY");

3. 等価性によって比較される (Compared by Value)

ValueObjectは、オブジェクトの同一性(同じインスタンスか)ではなく、値の等価性(同じ値を持つか)で比較します。

Example.java
Money money1 = new Money(1000, "JPY");
Money money2 = new Money(1000, "JPY");

// 異なるインスタンスだが、値が同じなので等しい
System.out.println(money1.equals(money2));  // true

immutableでないオブジェクトをvalueディレクトリに置くべきでない理由

ValueObjectの最も重要な特徴は「不変性」です。もし可変なオブジェクトをvalueディレクトリに配置してしまうと以下の問題が発生します。

  • コードを読む人が混乱する(「ValueObjectのはずなのに値が変わる?」)
  • 予期しない副作用が発生する可能性がある
  • DDDの設計思想に反する

そのため、可変なオブジェクトは別の場所に配置する必要があります。

Entityとは

immutableでないオブジェクトはEntityとして扱います。

Entityは以下の3つの特徴を持ちます

  1. 可変である (Mutable)
  2. 同じ属性であっても区別される
  3. 同一性により区別される (Compared by Identity)

1. 可変である (Mutable)

Entityは、ライフサイクルの中で状態が変化します。

User.java
public class User {
    private final Long id;  // IDは不変
    private String name;    // 名前は変更可能
    private String email;   // メールアドレスも変更可能
    
    public User(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
    
    // setterを提供し、状態の変更を許可
    public void changeName(String newName) {
        this.name = newName;
    }
    
    public void changeEmail(String newEmail) {
        this.email = newEmail;
    }
}

2. 同じ属性であっても区別される

Entityは、たとえ全ての属性値が同じでも、異なるオブジェクトとして扱われます。

Example.java
User user1 = new User(1L, "田中太郎", "tanaka@example.com");
User user2 = new User(2L, "田中太郎", "tanaka@example.com");

// 名前とメールアドレスが同じでも、異なるユーザー
System.out.println(user1.equals(user2));  // false (IDが異なる)

現実世界で例えるなら、同姓同名の人がいても、それぞれ別の人として扱われますよね。それと同じイメージです。

3. 同一性により区別される (Compared by Identity)

Entityは、一意の識別子(ID)によって区別します。

User.java
@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null || getClass() != obj.getClass()) return false;
    
    User user = (User) obj;
    // IDのみで比較する
    return id.equals(user.id);
}

@Override
public int hashCode() {
    return Objects.hash(id);
}

名前やメールアドレスが変わっても、IDが同じであれば同じユーザーとして扱われます。

ValueObjectとEntityの使い分け

項目 ValueObject Entity
可変性 不変 (Immutable) 可変 (Mutable)
識別方法 値で識別 IDで識別
交換可能性 交換可能 交換不可
金額、日付、住所、色 ユーザー、注文、商品

判断基準としては

  • 「それ自体に個性があるか?」 → Yes ならEntity
  • 「単なる値の組み合わせか?」 → Yes ならValueObject

まとめ

  • ValueObjectは不変で、値そのものを表現するオブジェクト

    • 金額、日付、住所などが該当
    • 一度作成したら変更できない
    • 値が同じなら交換可能
  • Entityは可変で、同一性を持つオブジェクト

    • ユーザー、注文、商品などが該当
    • ライフサイクルの中で状態が変化する
    • IDで識別される

参考資料

  • 『ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本』(成瀬允宣 著)
3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?