【読書】ドメイン駆動設計入門を読み始めた# 3章
DDDについて、本を読みながら勉強中です。
今回はエンティティについて学んだことの忘備録になります。
エンティティとは
- ドメインモデルを実装したドメインオブジェクト
- 値オブジェクトとの違いは、同一性によって識別されるか
同一性
が担保されるオブジェクトは属性が変更されても、オブジェクト自体は変更されない- 例えばユーザ情報
- ユーザの属性(体重、メールアドレス)が変更されても、別のユーザオブジェクトにはならない
- 属性ではなく、
同一性
(idみたいな物?)によって識別される
- 例えばユーザ情報
値オブジェクトは属性が変更されたら、全く異なるものになる。
(例えば、ユーザ名オブジェクトは、属性が変更されたら異なるものとして認識される。)
エンティティの性質
エンティティは、値オブジェクトとはま反対の下記の性質を持つ。
- 可変である(属性の変更ができる)
- 値オブジェクトは、オブジェクト自体の入れ替えによって変更を実現していた
- 同じ属性であっても、区別される
- 同一性によって区別される
可変であることをちょっと詳しく
ユーザ名を持つUserオブジェクトをエンティティとして実装したのが下のコード。
class User attr_reader :name def initialize(name) change_name(name) end # ユーザ名を変更するメソッド(パブリック) def change_name(name) raise Exception.new("ユーザ名が指定されていません") unless name raise Exception.new("ユーザ名は3文字以上入力してください") unless name.length < 3 @name = name end end
Userオブジェクトが持つname
は、change_name
メソッドを通じて、
直接、属性を変更することできる。
第2章の値オブジェクトを変更する時のコードと比較するとわかりやすい。
class FullName attr_accessor :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end end full_name = FullName.new('masaru', 'hogehoge') # 代入によって値を交換する full_name = FullName.new('masaru', 'fugafuga')
「同じ属性であっても、区別される」をちょっと詳しく
前回の値オブジェクトの比較する処理の場合は、属性を比較していた。
class FullName attr_accessor :first_name, :last_name def initialize(first_name, last_name) @first_name = first_name @last_name = last_name end def equals(full_name) return false full_name.null? || !full_name.is_a(FullName) return first_name.eql?(full_name.first_name) && last_name.eql?(full_name.last_name) end end
Userモデルを考えると、同様に属性で比較した場合、同姓同名を持つUserは同じという判定になってしまう。
エンティティ同士の比較には、識別子
(id)が利用される。
class UserId attr_reader :value def initialize(value) raise Exception.new('ユーザidが指定されていません') unless value @value = value end end class User attr_reader :user_id, :name def initialize(user_id, name) raise Exception.new('ユーザidが不正です') unless user_id.is_a(UserId) @user_id = user_id change_name(name) end # ユーザ名を変更するメソッド(パブリック) def change_name(name) raise Exception.new("ユーザ名が指定されていません") unless name raise Exception.new("ユーザ名は3文字以上入力してください") unless name.length < 3 @name = name end # 比較メソッドのオーバライド def eql?(user_object) return false if user_object.nil? || !user_object.is_a(User) return user_id == user_object.user_id end end
change_name
でユーザ名が変更されたとしても、識別子(UserId
)が変更されていないので同一性が担保される。
エンティティと値オブジェクトどっちを選択するべきか
エンティティと値オブジェクトは共に、ドメインの概念を表現するオブジェクトとして似ている。
判断基準は、オブジェクトにライフサイクル
が存在するかどうか。
ライフサイクルが存在する場合は、エンティティとして表現する。
ライフサイクルが存在しない場合(もしくは表現することが無意味な場合)は、値オブジェクトとして表現する。
例えば、Userという概念の場合、
- システム開始時に
利用者
として作成される - システム利用中に、属性(メールアドレスやユーザ名)が変更されることもある
利用者
がシステムの利用をやめる時に、削除される
という感じで、ライフサイクル(生成〜削除)が存在する。
ドメインオブジェクトを定義するメリット
まとめ
- エンティティも値オブジェクトもドメインモデル
- エンティティはライフサイクルをもち、識別子(id)によって等価判定される
- 属性は可変