はじめに
本記事では、ドメイン駆動設計のパターンとして用いられるエンティティについて、初心者向けに解説したものです
対象読者
- ドメイン駆動設計における エンティティが何かがわからない方
- 値オブジェクトとの違いがわからない方
- どのようなものをエンティティとして扱うべきかわからない方
前提条件
値オブジェクトの概念について理解していること
エンティティと値オブジェクトは比較されやすいため
警告
ドメイン駆動設計でのエンティティは、
データベースにおけるER図で登場するようなエンティティとは別物です。
なぜエンティティというパターンが存在するのか
エンティティは、単なる属性の一致だけでは「同じものである」と判断できないものを識別するために必要です。
例えば、社員をイメージしてください。
ある会社には、田中太郎という名前の社員が二人存在します。
偶然にも、二人の田中太郎さんは生年月日まで一緒です。
しかし、二人の田中太郎さんは別人です
名前や生年月日等の属性は一致していますが、同じ人ではありません。
別の例を見てみましょう。
ある会社に、小林花子さんという社員がいます。
花子さんは、今年結婚して「大林花子」さんになりました。
名前という属性は変わりましたが、どちらも花子さんという同一人物を指しています
- 属性が一致していても、別物として扱われる
- 属性は一致していなくても、同一として扱われる
上記の概念を、ドメインオブジェクトとして表現することがエンティティの目的になります。
エンティティとはなにか
エンティティとは、主に以下の特徴を持つものになります。
- 可変である
- 同じ属性を持っていても、違うものとして扱われる
- 属性が変わっても、同じものとして扱われる
では、2つのエンティティが同じものであるか を判定するにはどうすればようでしょうか?
ずばり、固有の識別子を使って判定します。
サンプルコードを見てみましょう。
エンティティを表現したサンプルコード
import java.time.LocalDate;
import java.util.Objects;
// 社員クラス
public class Employee {
private final String employeeId; // 社員ID(エンティティの一意識別子)
private String name; // 従業員の名前
private LocalDate birthDate; // 生年月日
private String position; // 役職
// コンストラクタ
public Employee(String employeeId, String name, LocalDate birthDate, String position) {
if (employeeId == null || employeeId.isEmpty()) {
throw new IllegalArgumentException("社員IDは必須です。");
}
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("名前は必須です。");
}
if (birthDate == null) {
throw new IllegalArgumentException("生年月日は必須です。");
}
if (position == null || position.isEmpty()) {
throw new IllegalArgumentException("役職は必須です。");
}
this.employeeId = employeeId;
this.name = name;
this.birthDate = birthDate;
this.position = position;
}
// 名前を更新するメソッド
public void updateName(String newName) {
if (newName == null || newName.isEmpty()) {
throw new IllegalArgumentException("新しい名前は必須です。");
}
this.name = newName;
}
// 役職を更新するメソッド
public void updatePosition(String newPosition) {
if (newPosition == null || newPosition.isEmpty()) {
throw new IllegalArgumentException("新しい役職は必須です。");
}
this.position = newPosition;
}
// 社員IDの取得(不変のため更新メソッドは不要)
public String getEmployeeId() {
return employeeId;
}
// 名前の取得
public String getName() {
return name;
}
// 生年月日の取得
public LocalDate getBirthDate() {
return birthDate;
}
// 役職の取得
public String getPosition() {
return position;
}
// 等価性の比較(社員IDで判定)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee)) return false;
Employee employee = (Employee) o;
return employeeId.equals(employee.employeeId);
}
}
サンプルコードの説明
サンプルコードは、従業員という概念をエンティティで表現しています。
特徴的なのは、社員IDのみ不変とし、それ以外の属性を可変としているところです。
(社員IDはfinalで定義し、更新用メソッドも用意していない)
そして、オブジェクト同士を比較する手段には、社員IDを使っています。
つまり、他の属性が変化しても社員IDを見て同じ社員であるかを判断しています。
値オブジェクトとエンティティの違い
値オブジェクトとエンティティの違いを簡単に表すと、以下になります。
- 値オブジェクトは識別子不要・不変性・単純なデータ表現を重視する
- エンティティは一意性・ライフサイクル管理(時間経過で値が変化する)・関係性を重視する
特性 | 値オブジェクト (Value Object) | エンティティ (Entity) |
---|---|---|
識別子 | 識別子を持たない | 一意の識別子を持つ |
同一性 | 属性が同じであれば同一オブジェクトとみなされる | 識別子が同じであれば同一オブジェクトとみなされる |
状態の変化 | 不変である(変更できない) | 状態が変化することがある |
目的 | 属性や振る舞いを集約して特定の値を表現する | ビジネスの主要な概念やライフサイクルを表現する |
例 | 金額、住所、生年月日などの単純データ | 社員、注文、商品など概念的なデータ |
値オブジェクト・エンティティのどちらで表現するか
以下のフローチャートにしたがっておおよその判別ができます。
さいごに
値オブジェクトやエンティティの本質的な目的は、業務で登場する固有の値をコードで表現することにあります。
コードで表現することによるメリットは、次の通りです。
- 値の表現力が増す(String型やInt型より具体的になる)
- その値に関係するロジックの分散を防ぐ
- 不正な値を存在させない
システム固有の値を型として表現することで上記のメリットが生まれます。
これらのメリットは最終的に、変更に強くバグの少ないシステムを育てることに繋がります。
ここまで読んでいただきありがとうございました!