🙄

DDD 本に載っている『エンティティではないものをエンティティとして扱ってしまうこと』の問題点とは?

2022/09/11に公開

Evans 本、IDDD 本ともに、エンティティではないものをエンティティとして扱うことに対して、注意喚起がされています。

Evans 本 第5章(値オブジェクトの節):

エンティティの同一性を追跡するのは本質的なことだが、それ以外のオブジェクトに同一性を与えてしまうと、システムの性能を損なうことになり、分析作業が増え、さらに、すべてのオブジェクトの見た目が同じになってしまうことでモデルが台無しになりかねない。ソフトウェア設計は、複雑さとの恒常的な戦いである。特別な処理が必要な場所だけで行われるように、区別しなければならない。

IDDD 本 第6章:

可能な限り、エンティティよりは値オブジェクトを使ってモデリングすべきだと聞いたら、驚くかもしれない。ドメインの概念をエンティティとしてモデリングしなければいけないとしても、そのエンティティの設計は、子エンティティのコンテナではなく値のコンテナとして組み立てるよう心がけるべきだ。このアドバイスは、単なる気まぐれによるものではない。値型は何かを計測したり定量化したり説明したりするときに使うもので、作成やテストがしやすいし、使うのも最適化するのも保守するのも楽だ。

ただ、エンティティではないものをエンティティとして扱ってしまうとどんな問題が起きるのかについて、本を読んだだけではいまいちピンとこなかったため、自分なりに咀嚼してみました。

エンティティの説明とこの記事のスコープについて

まず、エンティティとは?ですが、Evans 本には「同一性によって定義されるオブジェクト」と書かれていました。

Evans 本 第5章(エンティティの節):

主として同一性によって定義されるオブジェクトはエンティティと呼ばれる。エンティティはモデリングと設計に際して、特別な考慮が必要だ。ライフサイクルにおいて、エンティティの形態と内容は根本的に変わることがあるが、連続性のつながりは維持されなければならない。同一性は、効果的に追跡できるように定義しなければならない。

また、この記事では、値オブジェクトとは?については扱わず、あくまで 「エンティティ」と「本来エンティティとして扱う必要はないがエンティティとして扱わってしまうもの」を対象にします。

「値オブジェクト」については最近も盛んに議論されているので、気になる方は以下の記事やその派生記事を参照してください。

問題点は何なのか

では、タイトルに書いた「問題点」についてです。

私は「不要な同一性の追跡によって無駄なコストがかかってしまう」ことが問題だと理解しました。
同一性を追跡するには何かしらの識別子が必要になり、操作をおこなう際には識別子を含めたやり取りが必要になります。

結果、Evans が書いている「それ以外のオブジェクトに同一性を与えてしまうと、システムの性能を損なうことになり、分析作業が増え」とほぼ同義ですが、具体的な例で説明してみます。

一般的な Web サービスで、あるエンティティを更新する場合、

  1. 該当のエンティティの識別子をフロントエンドに伝達
  2. フロントエンドは更新リクエストに上記の識別子を含める
  3. バックエンドでは識別子からエンティティを特定して更新処理を行う

といったやり取りをおこなうと思いますが、これらの「無駄なコスト」は何かというと、

  1. 該当のエンティティの識別子をフロントエンドに伝達

→ 識別子を含めてもあまりコストは変わらない。
(識別子がある/なしに関わらず、更新対象の属性情報なども送る必要があるため)

  1. フロントエンドは更新リクエストに上記の識別子を含める

→ フロントエンドでユーザーの操作ハンドリングする際に、操作対象の識別子を追跡する必要があるので、コストは上がる。

例えばリストに対する操作を提供する場合に、 リストの行ごとに固有の識別子が振られている状況で、

  • 行を入れ替える
  • 入れ替えた行の情報を編集する
  • 行を削除する
  • 行を追加する

といった操作を行った場合、識別子の追跡やバックエンドへの更新リクエストの構築は、単に操作後のリストの情報でまるっと上書く処理に比べて大変そうです。
このあたりは要件にもよる話なので、前者のやり方が必要になるケースもある認識です。

  1. バックエンドでは識別子からエンティティを特定して更新処理を行う

→ 対象エンティティの特定や、競合操作に備えてロックを取ったりすることでコストが上がる。
(処理自体のコストだけでなく、考慮点が増えることなども含む)

エンティティとして扱わない場合でも、親のエンティティを特定したり、親のエンティティに対してロックを取ることでむしろ競合の可能性を高めることもあると思うので、エンティティとして扱うことで逆に処理コストが下がることもあるとは思います。
(集約ルートを分割することで、不変条件を維持する範囲が狭まる)

もう一つの問題点

実は前項で検討した問題点以外にも Evans 本に明記されている内容があります。
冒頭でも紹介した内容一部を再掲しますが、

Evans 本 第5章(値オブジェクトの節):

すべてのオブジェクトの見た目が同じになってしまうことでモデルが台無しになりかねない。

とあります。「モデルが台無し」になるという言葉に含む意味は深く、DDD においてはもう一つの問題というよりも主要な問題点と言った方がよい気もしますが、この記事で書きたい内容から外れてしまうので、割愛します。

不要な同一性をもたせてしまう理由

最後に、そもそもなぜエンティティとして無駄に扱ってしまうことが起きがちか、についても軽くまとめます。

Evans 本 第5章(値オブジェクトの節):

モデルで最も目立つオブジェクトが、通常はエンティティであり、また、各エンティティの同一性を追跡することが非常に重要であることから、あらゆるドメインオブジェクトに同一性を割り当てようと考えるのは自然なことである。実際、フレームワークの中には、あらゆるオブジェクトに一意のIDを割り当てるものもある。

IDDD 本 第6章:

「ドメインモデルのすべての要素はデータベースのテーブルにマッピングする必要がある。そしてドメインモデルのすべての属性は、publicなアクセサメソッドで操作できなければいけない」という考えにとらわれていた。すべてのオブジェクトはデータベースの主キーを持っており、モデルは大規模で複雑なグラフの中にしっかりと組み込まれていた。このアイデアはそもそも、多くの開発者にとって馴染み深い、データモデリングの視点に由来するものだ。リレーショナルデータベースの影響を過度に受けてしまっている。すべてを正規化し、外部キーを使って参照せよという考え方だ。

Evans が想定する領域は広そうなのですが、どちらにも共通するものとして、データモデリングの影響を受けすぎた時に不要な同一性を持たせてしまう、ということがありそうです。
(ここでいう「データモデリング」は「DB の論理設計」あたりだと捉えています)

要は、ER 図における Entity として配置されると、ドメインモデルにおけるエンティティに見えてくると言った趣旨です。

データモデリングも色々と深みのある世界なので、あまり踏み込み(め)ません。

感想

DDD 関連のことを書くのは心理的ハードルが高かった。

参考書籍

Discussion