跳至主要内容

ADR-028: Entity 關聯設計策略

ADR 編號: 028 狀態: 已接受 (Accepted) 決策日期: 2026-07-29 決策者: Development Team

Context

本 ADR 取代 ADR-003;原 ADR 保留當時的方案比較與決策歷史。

JPA association、scalar ID、embedded value 與 JSON 都能表達「某資料與另一 資料有關」,但它們提供的完整性、生命週期、查詢與載入語意不同。單純把 JOIN 視為效能問題,或把關聯分成「重要/不重要」,不足以穩定選型。

參考實作同時支援 tenant 與 tenantless server;關聯設計不得繞過 aggregate boundary 或租戶隔離。

Decision

依 identity、aggregate boundary、integrity、lifecycle 與 history 選擇:

語意Mapping
同 aggregate、child 有 identity / lifecyclechild Entity + FK
跨 aggregate 且 DB 必須保證 reference 完整LAZY Entity association + FK
跨 aggregate,只需要穩定 identity、外部 reference 或歷史快照scalar ID;必要時另存 snapshot value
固定結構、無 identity、需查詢內部欄位@Embeddable / @ElementCollection
原子、無內部 SQL 查詢、大小有界typed JSON converter

選 scalar ID 必須能說明 aggregate boundary、外部 ownership、保留歷史或 刻意降低一致性耦合的理由;「避免 JOIN」本身不是充分理由。

Association rules

  • 所有 association 明確指定 LAZY
  • Cascade 只授予真正管理 child lifecycle 的 owner。
  • orphanRemoval = true 只用於 owner 私有、離開 aggregate 即應刪除的 child。
  • Shared Entity 不使用 cascade remove。
  • @ManyToMany 只限沒有額外屬性的對稱關係;一旦關係有狀態、順序、時間 或 audit 欄位,就建立 join Entity。
  • optional@JoinColumn(nullable=...) 與 Bean Validation 必須一致。
  • 關聯方向保持最少;沒有 traversal use case 就不建立反向 collection。
  • API 回傳 DTO,不直接序列化 Entity graph。
  • Fetch plan 由 query / repository use case 決定,不以 EAGER 修 N+1。

Tenant boundary

server-tenanttenantId 是資料隔離 discriminator,不是指向 Tenant 的 一般 JPA association。業務 association 的兩端必須由應用層確認屬於同一 tenant;FK 本身不能表達這項規則。

server-tenantless 不得以固定 tenant 值模擬 scope;ownership 必須由各 domain 明確建模。

History and deletion

需要保留「當時看到的名稱、地址、價格」時,reference ID 與 snapshot value 是不同資料:

  • ID 表達指向誰。
  • Snapshot 表達事件發生當時的事實。

不要只因被參照資料可能刪除就取消所有 FK。先決定 retention / soft-delete / restrict-delete policy;只有跨 aggregate 或外部資料確實允許 reference 失效時才用 scalar ID,並明確處理 missing target。

Consequences

Positive

  • Mapping 反映 domain boundary,而非偶然的 query 形狀。
  • Lifecycle cascade、完整性與歷史資料的責任清楚。
  • LAZY baseline 避免無意載入 Entity graph。
  • Scalar ID 仍可用於真正的跨 aggregate / external reference。

Trade-offs

  • Use case 需要明確 fetch plan,不能依賴 EAGER。
  • Scalar ID 不具 DB FK 保護,應用層需驗證 target 與 tenant。
  • Snapshot 會刻意複製資料,必須標示它不隨來源更新。

Verification

每個 relationship 在 review 時回答:

  1. 兩端是否在同一 aggregate?
  2. 誰擁有 child lifecycle?
  3. DB 是否必須阻止 dangling reference?
  4. Target 刪除後應 restrict、soft delete、null、失效或保留 snapshot?
  5. 是否需要由兩個方向 traversal?
  6. 查詢如何避免 N+1?
  7. tenant 模式下如何保證同 tenant?

完整 operational rules 見 JPA Entity 欄位型別規範

Change Log

日期變更
2025-12-23初版依核心/強依賴/弱關聯分類
2026-07-29改以 aggregate boundary、integrity、lifecycle、history 為選型依據