#結論
copy した Entity は Entity ではなく複写(スナップショット)。オリジナルと同じIDを持ち、Entity として永続化するのは危険。
duplicate した Entity は複製であり別の Entity。集約内全てに新規にIDを払い出し、永続化する。
#アイデンティティはやっかいな概念
DDD では Entity を中心にしてモデルを考えますが、Entity は明確にアイデンティティを持つとされながらアイデンティティそのものについてはあまり注意を払われることが無いというか ID を持つとしか言及されないことが多いと思われます。
ですが、これが思わぬ問題を引き起こすというか、言及漏れを招くことがあります。
例えば、Entity に copy メソッドを作成してコピーを作るとします。
class EntityX(val id:Int, var name:String) {
fun copy():EntityX = EntityX(id,name)
}
val xa = EntityX(0)
val xb = xa.copy()
xb.name = "new name" //xa.nameは…?
さて問題です。このコピーされた xb は xa と同一の Entity でしょうか? xb の name を変更したとき、 xa の name も変更されるべきでしょうか?
恐らく、そんな挙動を望むケースは多くは無いと思われます。例えば一度買った商品を再度買う場合、おそらく「注文」をコピーして新しい「注文」を作ることになるでしょう。ですが、新しい注文で数量を変更したときに元の「注文」が変わってしまっては困るからです。
そんな時は、対象を duplicate します。
class EntityX(val id:Int, var name:String) {
fun duplicate():EntityX = EntityX(EntityXRepository.newId(),name)
}
val xa = EntityX(0)
val xb = xa.duplicate()
xb.name = "new name" //xa とは明確に別 Entity
この二つの違いは何でしょうか?なぜ duplicate は ID を払い出すと言えるのでしょうか?
#copy と duplicate の違いを英英辞典で調べてみる。
duplicate : to copy something exactly
exactly!「まさに!それそのもの!」としてコピーする、ということですね。逆に言えば、 copy は「まさに!」ではないのでしょうか?実のところ滅多に気にする人はいませんが、 copy したものは同格のアイデンティティにならないことがあります。偽物というのではなく、 copy 先のアイデンティティと同格になります。
copy to はあるが duplicate to はない
copy は、正確には「ある対象に copy する。ある対象が存在しなかった場合、適宜新しいアイデンティティを作成する」という意味になります。
例えばある文章をある記事に copy した場合、その文章はその記事の一部になります。
例えばある文章を印刷(copy)した場合、「新しい紙や本」に複写することになり、「印刷物」としてのアイデンティティを新たに持ちます。
一方、 duplicate は対象を取りません。必ず複製として「作成」され、新しいアイデンティティを得ます。
なお、 copy of と duplicate of は両方とも有ります。
copies は「複写が複数」という意味だが duplicates は重複を意味し、「複製は一つであり得る」
例えば、同じ文章が2回繰り返された場合、同じ文章が2回繰り返された場合、これは重複 = duplicates です。重複した文章はそれぞれ存在する同じ文章、同じ値だけど別のアイデンティティを持つという事ですね。これは複製が同格という事です。一方、copies は単に印刷物が複数あることを示し、copy 元の原稿と印刷物は同格ではありません。
##プログラム上のオブジェクトの copy はどうするべきか?
以上のことを踏まえると、あるオブジェクトを copy したらそれは永続化せずに使い捨てろ、という話になります。
ある Entity を copy して操作しても、それをそのまま捨ててしまえば何の問題もありません。
逆に一時的に copy を作って、仮に操作した結果を見たい、それで良いと思ったら commit したい、というケースがあります。その時は copy を Entity に「反映」しましょう。
例えば、JPAなどの明示的な更新を行わない永続化システムを使うときには、仮の操作をする対象は copy を取ったり永続化から切り離す(detached)必要があります。
明示的な更新が必要な永続化システム、例えば DB フレームワークは実際に永続化されてる Entity はDB内にあり、 select した結果は「DB 内で永続化されてる Entity の値」、Entity を表すものであって Entity 自体ではありません。なにもしなければそのまま捨てられてしまうので、DB 内に永続化されている Entity に反映、つまり update します。
##集約内の Entity の copy / duplicate はどうするべきか?
まず、値・バリューオブジェクトはアイデンティティを持たないので単に同じ値をとるようにします。
集合内に子 Entity を含む場合、それは copy の際はそのままシャロー/ディープコピーすればいいでしょう。どちらがいいかは深いところまで操作したいか?という別の話です。
duplicate する場合は、必ず子 Entity にも新しく ID を振り出す必要があります。そうしなければ集約が壊れてしまいます!
もし Entity が別の Entity を参照することでオブジェクトツリーに入っている場合、それは参照であり実体ではないので永続化する際には ID を通しての参照に置き換えるのが良いでしょう。