Clean ArchitectureのEntityとDDDのEntityは同じものです


TL;DR

Entityとはシステム化しているしていないに関わらず存在するものという意味としてCAでもDDDでも同じものです。一見違うように見えるのは、CAでは「対象とそれを操作するロジックをEntityにまとめられる」という話に重点を置いているのに対しDDDでは「対象は操作して状態が変わったりValue Objectとかに分割しても同じEntityである」という話に重点を置いている、つまりDDDのが細かい話をしているというだけです。

前置

ドメイン駆動設計のエンティティとクリーンアーキテクチャのエンティティEntity in DDD ≠ Entity in Clean Architectureを読みました。
非常に勉強になる記事なのですがこの記事の中にもあるようにこの分野では大体あってるけど厳密には違うという言葉があります。逆に、ぱっと見別のものに見えるが厳密には同じものであったりします。
存在というものは常に何かしらが省略されて語られるものなのでそこを補わないと厳密な表現にならないのです。
そこでちょっと補足をしたいと思います。
ただしこの記事は先の記事が間違っていることを示すものではありません。
書籍や解説記事でまるで違うように説明されているものがなぜ存在論的には同じものであるか、つまりEntityという共通の言葉を使っているのにはそれなりに根拠があるという事を書いたものです。

Clean ArchitectureのEntityはビジネスロジックを「操作対象の」ビジネスオブジェクトにカプセル化したものですし、このビジネスオブジェクトは同一性を持ちます

最も顕著なのがEntityとは何であるか?という問題です。

例えば英語の説明ではスタンドアロンのソフトウェアにもビジネスロジックが存在していて、それらをカプセル化したものがEntityであるように書いています。書いていますが、「ビジネスロジックは必ず何らかのビジネスオブジェクトを対象にとる」ことが自明として省略されています。
あの英文は「あなたがエンタープライズではなくただのアプリケーションを使っていたとしても、そのアプリケーションは何らかの操作対象を持つし、その操作対象に操作ルールをカプセル化したものがエンティティだよ」くらいに解釈するのがよいかと思います。

書籍(20章)でどう書かれているか見てみましょう。Entityの例としてLoanが挙げられています。

これは必ず「誰かの」Loanです。貸付金残高(principle?)がいくらであろうが、それは必ず「誰かの」残高であり、他の人の残高と混同していいものではありません。逆に「誰のでもない」Loanであることはありません(金融には詳しくないので「誰のでもないLoan」だけで構成された金融商品が有るのなら教えてください!)。つまりこれは誰かの=同一性を持つEntityです。

ではなぜCA上ではIDが書かれていないのか?

私も気になるので誰か著者の方に聞いてみて欲しいのですが、私の解釈としては「相手が実際に誰であるかはビジネスには関係が無いしどんな方法で相手を識別(identify)してるかもビジネスには関係ないから」です。
この章で説明しているのはビジネスロジックがEntityにまとめられるという話、それもたとえシステム化しておらず紙ベースで計算をしていたとしてもEntityがあるという話でありIDの話はまた別の問題なので触れてないのでしょう。
例えばこれが店頭でお客さんにLoanのプランを説明するときに使っているロジックであるならばそれは「相手をしている店頭のお客さんの」Loanであり、それは他のお客さんのLoanと混同してはいけませんが相手は目の前にいるのでIDが必要なことはそうはないでしょう。
実をいうとこのLoanEntityはDDDならば値オブジェクトとして実装する、例えば人をEntityにしてそれに対するLoanの集約にする、ことも考えられます。-ですが、この20章では仮にシステム化していなくてもEntityは存在するとしていて実装レベルの話はしていないことを忘れないでください。

DDDのValue ObjectはClean ArchitectureのEntityではありません

Clean ArchitectureのEntity(Model)はDDDのEntityよりも範囲が広いようにも書いてますが、Clean ArchitectureのEntityはValue Objectやロジックをカプセル化したものであり、DDDのEntityもまた同様です。ただ単にClean ArchitectureはValue Objectに特に言及していないだけです。

DDDのEntityとはなんでしょうか?
書籍内ではEntityとValue Objectは明確に区別されています。
が、ここで注意をしなければならないのはValue Objectを抽出する前もEntityであり抽出した後もやはりEntityであることです。
例えば酒を蒸留したものがやはり酒、より純粋な酒であるように、Entityを蒸留したものはより純粋なEntityです。
Value Objectを分離することでEntityはより単純になり扱いやすくなりますが、AggregationとしてValue Objectを含めた全体としてのEntityは元のEntityと等価です。ていうか等価でなければこのリファクタリングは失敗ですよね?
なのでDDDのValue Objectは別にEntityと独立した存在ではないし並び立つ存在でもありません。
いうならばEntityを構成するもの、であり例えばCAでは特にValue Objectを想定していませんが実装した結果Value ObjectになったとしてもEntityを構成するものであることに変わりはありません。

CAのEntityとは蒸留する前のEntityだということなのでそれをもってしてDDDのEntityと違うという事はできないし、Value ObjectがClean ArchitectureのEntityにあたるということもできないという事です。
これはどちらかというとDDD側の問題と言いますか、DDDで分解した後でルートEntityとかAggregationに含まれるEntityと呼んでいるのを単にEntityと読んだりして区別をしない読者(翻訳者?)側の問題です。
もしEntityを蒸留しAggregationに分解した中にあるEntityに名前を付けるなら蒸留されたEntityあたりが妥当かと思います。

というわけでCAとDDDはまとめてEntityにしているか分割してEntityにしているか、見ている場所がEntityの外か中かという視点の違いでありEntity自体は同じものです。視点が違うから違うものという事もできますが…

以下は「なぜ操作は対象を取ると言えるのか」という話なので読み飛ばしても問題ありません

例えば、何も操作しないロジック、何にも関連しないロジックというものはあり得るでしょうか?何も操作しないソフトウェアは実行する意味がありませんし、何にも関連していないロジックは何ら意味がないのでコード上から削除すべきものです。
例えば文字列を引数としてそれに操作を加えて出力する、こんなソフトウェアがあったとしましょう。例えば書式を整えて出力しているのかもしれませんし、文字列をキーにDBに問い合わせてデータの更新をしたりしてるかもしれません。


fun main(args:Array<String>) {
    val xa = fa(args)
    val xb = fb(xa)
    val xc = fc(xb)
    printf(xc)
}

これは操作対象としている文字列に対する操作としてカプセル化できます


fun main(args:Array<String>) {
    val a = BusinessObject(args)
    val xa = a.fa()
    val xb = xa.fb()
    val xc = xb.fc()
    printf(xc)
}

この、操作の対象となるargsまたはargsにより示される対象を一般的にはArgumentまたはSubjectと呼びます。(Argumentは与えられたことにより同一性を持った何かという意味で、Subjectはこのアプリケーションが対象にしている何かという意味でしたが今そこを呼び分けている人間がいるかは疑問です。)
これらは議論や文章の「主題」や「論点」「対象」と翻訳されることの多い言葉です。例えばメールや論文のSubjectは「本文の対象とする主題」を述べたものです。つまりソフトウェアに限らず何らかの記述はその記述の対象となるオブジェクトを暗黙的に持っており、対象に対する記述としてカプセル化できるという事です。

このArgumentまたはSubjectは関数内では既に与えられたものであるためにローカルな同一性を持ちますし、グローバルなビジネスで対象としているのならばグローバルな同一性を持つでしょう。ビジネス上でも同一性を持たない存在、例えば指定席がIDを持ちエンティティであるのに対して自由席はIDを持たずEntityではありませんがそれらはDDD上でもEntityではありません。
結果として、Entityは明記されてないかもしれませんが操作対象として存在して同一性も持ちます。

余談ですが、Magic: The Gathering というカードゲームが20年ほど前に日本に入ってきたときに「対象」という言葉が正確に共有されないでゲームが混乱するという現象が起きました。例えば「対象にダメージを与え、ついでにカードを1枚引く」というカードがあったとして、これを何も対象を取らずに実行してカードを1枚引くという行動を行おうとしたプレイヤーが続出したのです。
日本のロジックでは何もないものに何かをすることは何も起きないことを意味しますが、西洋のロジックでは何もないものに何かをすることは未定義であり実行できない(か別途便宜的な定義を用意しないといけない)ことを意味します。例えば、0の関連する四則演算は自然数の関連する演算とは別に定義されています。0除算ができないというのは有名な例ですが0乗算も自然数の乗算とは別に便宜的に定義されています。

よく日本語には主語が無いとかも言いますが、日本の哲学では特に対象を取らなくても記述ができるので対象ではなく記述のほう、この場合にはビジネスロジックが実体のように見えるわけですね。
また、この記事ではSubjectを補っていますが文系の研究書などでも対象が欠けている文を解釈する際にはしばしば補っているようです。

余談:そもそもモデルとは何か

余談となりますがモデルというのも存在論では明確に存在しており、対象のinvariantな部分を記述したものです。
例えば色んな大きさの正三角形があったとします。
それらは色んな(variant)な大きさを持ちますが、必ず大きさを持ちます(invariant)。
また、どのような大きさの正三角形も角は60度でありそれ以外の値を取りえません(invariant)。

作図した正三角形は必ずどこかにある(3点の座標を持つことがinvariant)し、どこに描いてあるかは正三角形それぞれで違うでしょう(3点の座標はvariant)
作画した正三角形が大きさを変えたらそれは別の正三角形なので別の同一性を持ちます(Entityではなく3点の座標はvariant かつ immutable であり3点の座標が識別子と同等)が、拡大縮小しても同じ正三角形であるとみなすならば同じ同一性を持ちます(Entityであり3点の座標はvariant かつ mutable であり他の正三角形と区別するためには任意の識別子が必要)

ある大きさの作図していない正三角形は同一性が無いオブジェクト(値オブジェクト)、
作図した正三角形はその3点の座標による同一性を持つオブジェクト(immutable)、
拡大縮小しても同一である正三角形は3点の座標が異なっても同一性を持つのでEntity

ですが、どれもそれぞれvariantであるものとinvariantであるものをもっています。
つまりそれぞれにおいてvariantを含むオブジェクトとinvariantのみであるモデルが存在しているということですね。もちろんモデル化に当たりそれぞれの要素を採用するかどうかという問題は存在しますが、最初の抽象化においてはまず必ず具象が捨象されます。
あるオブジェクトはあるモデル(の具象)です、というのは意味がありますがあるオブジェクトはモデルですというのはあまり意味がありませんし、EntityはIDを持つモデルであるというのは順序が逆かと思います(書籍の中でもそのままでは十分なIdentityがないときにそれを与えるためにIdentifierを振っています)。※座標はユークリッド原論では値ではないけどデカルトが値として扱えるようにしたかも。