Skip to main content

エンティティの同一性は「属性の一致」ではなく「層ごとのルールと振る舞い」で決まる

TL;DR​

  • エンティティの同一性は「属性が似ているか」ではなく「ルールと振る舞いの単位」で引く。値が一致しても同じ事実とは限らない。
  • 関心が複数あるなら層に分け、層ごとに別々に同一性を問う。「ID を統一するか分離するか」は、本来別の層に属するものを 1 つの軸で比べているために生まれる偽の対立であることが多い。
  • 不変条件・定義が揺れそうなら、可逆な側=情報を捨てない側に倒す。分離は後から統合できるが、統合してしまったものを分離するのは難しい。
  • 土台にイミュータブルデータモデル(リソース/イベントの分類、1 つの事実は 1 つの場所に、UPDATE を避け INSERT で積む、事実は捨てない)を置くと、上の判断がぶれにくくなる。

遭遇した問題​

「ほぼ同じ設定のレコードを、一部の属性だけ変えて複数登録したい」という要件が来た。 具体例として、通知ルールを考える(業務固有の情報は抽象化している)。

  • ベースとなる設定(条件・しきい値・テンプレート)はほぼ共通。
  • 適用先や有効期間だけが少しずつ違うものを複数作りたい。

ここで「これらは同じエンティティとして 1 つの ID で束ねるべきか、それとも別エンティティとして個別の ID を振るべきか」が決められず手が止まった。

属性を見比べると 9 割同じなので「同じものに見える」。一方で運用上は別々に有効化・無効化したい。属性の一致を根拠にすると統一したくなり、運用を根拠にすると分離したくなる。判断軸が定まらない。

原因(なぜ決められないのか)​

決められない本当の理由は、同一性の判定基準を「属性の類似度」に置いてしまっていること。

属性が似ていることと、同じ事実であることは別物である。たとえば住所が同じ 2 人は同居人であって同一人物ではない。属性の一致で束ねると「同じ値だから同じレコード」という正規化の誤用に陥る。

さらに、この要件には複数の関心が混ざっている。

  • 「共有したい意図・テンプレート」という関心(変えると全体に効かせたい)
  • 「個別に適用した事実」という関心(1 つだけ止めたい)

この 2 つを 1 枚のテーブル・1 本の ID 軸で扱おうとするから、「統一 vs 分離」という二者択一に見えてしまう。実際には層が違うものを同じ土俵で比べているだけで、対立そのものが偽である。

解決(層に分けて、層ごとに同一性を問う)​

1. 関心を層に分ける​

混ざっている関心を、別々のエンティティ(層)に分解する。

  • テンプレート層: 共有したい設定の「意図」。これ自体が 1 つの事実で、変更すると参照側に効かせたい。
  • 適用インスタンス層: テンプレートを特定の対象・期間に適用した「個別の事実」。独立に有効化・無効化・破棄される。

2. 層ごとに同一性を引く​

観点BEFORE(1 層で考える)AFTER(層を分ける)
モデル設定レコードを属性ごと複製テンプレート 1 件 + 適用インスタンス N 件
同一性の根拠属性が同じ=同じ「独立に作られ・変わり・消えるか」で判定
共通設定を直したいN 件すべてを更新(漏れる)テンプレート 1 件を更新(参照側に波及)
1 件だけ止めたいどれが「その 1 件」か曖昧適用インスタンス 1 件を無効化
同じ属性の重複バグか仕様か区別できない別インスタンスなら正当な事実

「統一か分離か」という 1 つの問いが、層を分けた瞬間に 「テンプレートは 1 つに統一」「適用インスタンスは個別に分離」と、両立する 2 つの答えに解けるのがポイント。

3. 同一性の判定テスト​

ある 2 つのレコードを「同じエンティティにすべきか」迷ったら、属性ではなく次を問う。

  1. ライフサイクルは独立か — 片方を作る/消すとき、もう片方も必ず一緒か? 別々に生き死にするなら別エンティティ。
  2. 不変条件(ルール)は同じか — 守るべき制約が両者で違うなら、振る舞いの単位が違う=別エンティティ。
  3. 変更の波及はどちらに効かせたいか — 片方の変更をもう片方に自動で効かせたいなら共有(同一)、効かせたくないなら分離。
  4. 「同じ値の 2 件」は正当か事故か — 正当な事実として並びうるなら、値の一致で束ねてはいけない(分離)。

属性ではなく、この「ルールと振る舞い」で線を引く。

4. 迷ったら可逆な側へ​

判定がどうしても割り切れない/要件が固まりきっていないときは、情報を捨てない側=分離に倒す。

  • 分離しておけば、後で「これは実質同じ」と分かったときに統合できる(ビューや集約で束ねられる)。
  • 先に統合してしまうと、後で「やっぱり別物だった」と分かっても、捨てた区別を復元できない。

不変条件は永遠の真理ではなく、要件が動けば変わる。可逆な選択はその変化を吸収する保険になる。

土台:イミュータブルデータモデル​

上の判断をぶれさせないために、データの持ち方そのものを次の原則で支える。

  • リソースとイベントを分類する — 「状態(リソース)」と「起きたこと(イベント)」を混ぜない。上の例ならテンプレート=リソース、適用や無効化=イベント。
  • 1 つの事実は 1 つの場所に — 同じ事実を複数箇所に複製しない。複製は更新漏れと不整合の温床。
  • UPDATE を避け INSERT で積む — 状態を上書きせず、変化をイベントとして追記する。履歴がそのまま残る。
  • 事実は捨てない — 削除は「削除されたという事実」を積むこと。物理削除で過去を消さない。

この土台があると、「属性が同じだから 1 行に潰す」という誘惑が自然に弱まり、層と同一性の判断がしやすくなる。

落とし穴​

  1. 正規化の誤用 — 値の一致で束ねること(DRY)と、同じ事実を 1 箇所に置くこと(正規化)は別。似た値を機械的にまとめると、本来別の事実を消してしまう。
  2. 不変条件を永遠の真理と思い込む — 「この属性は必ず一意」「この 2 つは常に一致」といった現時点の前提をスキーマに焼き込むと、前提が動いた瞬間に壊れる。前提は変わりうるものとして扱う。
  3. 未確定の意味をモデルに書く — 定義がまだ揺れているものを確定したかのように格納すると、定義が変わったときデータに「嘘」が残る。確定していない意味は、確定させてから書く。
  4. 要件の小出しに気づかない — 「実は一部だけ違って」「実はこれも別管理で」と情報が出るたび、正解の境界が動く。最初の 1 情報で同一性を確定させず、ライフサイクルと振る舞いが見えるまで分離側に倒しておく。

まとめ​

  • 同一性は属性ではなくルールと振る舞いで引く。
  • 関心が複数あるなら層に分け、層ごとに同一性を問う。多くの「統一 vs 分離」はこれで偽の対立だと分かる。
  • 迷ったら情報を捨てない=分離へ。可逆な選択を選ぶ。
  • 土台にイミュータブルデータモデルを置き、事実を上書き・複製・消去しない。

参考​

  • イミュータブルデータモデル(リソース/イベントの分類、UPDATE を避け事実を積む考え方)
  • ドメイン駆動設計におけるエンティティと値オブジェクト(同一性 identity と等価性 equality の区別)