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 / lifecycle | child 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-tenant 的 tenantId 是資料隔離 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 時回答:
- 兩端是否在同一 aggregate?
- 誰擁有 child lifecycle?
- DB 是否必須阻止 dangling reference?
- Target 刪除後應 restrict、soft delete、null、失效或保留 snapshot?
- 是否需要由兩個方向 traversal?
- 查詢如何避免 N+1?
- tenant 模式下如何保證同 tenant?
完整 operational rules 見 JPA Entity 欄位型別規範。
Change Log
| 日期 | 變更 |
|---|---|
| 2025-12-23 | 初版依核心/強依賴/弱關聯分類 |
| 2026-07-29 | 改以 aggregate boundary、integrity、lifecycle、history 為選型依據 |