領域模型跨越邊界持有的每一個 id,有效性由上游行為決定、持有端控制不了。上游把「更新」實作成「刪除重建」是實作自由,下游用推理猜「它應該保留」擋不住——合法的出處只有契約文件或一次實測取證。這條事實推導出本章三個判準的共同源頭:跨邊界參照的設計,從「對方會做什麼」開始、而不是從「我要存什麼」開始。

本章承接 entity 與 value object 的判準 的身份語意討論,延伸到身份跨越系統邊界之後的生存問題;與 狀態轉換與稽核軌跡 的凍結端點段落互補——凍結是時間軸上的問題(何時),本章談的是空間軸上的問題(誰的 id、誰決定它活著)。

參照有效性三問

下游持有上游資料的 id 時,設計階段逐一回答:

  1. 對方的哪些操作會讓這個 id 死亡? 合併、拆分、覆蓋式更新、重建式修復——任何「刪舊建新」的實作都會讓舊 id 失效。盲區集中在業務動詞與實作的語意落差:同一個「合併」,有的實作是搬資料、有的實作是建新刪舊。持有端用前者的假設設計、遇到後者的實作時靜默失效。
  2. 有沒有一個跨越這些操作仍不變的身份? 如果存在——以它為錨、其餘參照動態解析。如果沒有——凍結參照只能當一次性用途、跨操作的功能改走查詢。
  3. 「不變」的出處是什麼? 文件說的、程式碼推理的、實測證實的——只有第三種算數。把實測結果固化為測試資產(語意級假後端與流程測試真實後端驗證測試),從此不依賴任何人的記憶。

三個問題的回答決定參照的設計形態。未回答就動工的代價是靜默失效——操作對著死 id 回寫,回傳值看起來正常(目標不存在也沒有噪音),功能在特定操作之後全部失效、直到使用者回報。凍結參照在測試中的結構盲區另見 T.C5:由測試餵資料的 stub 同時控制凍結值與回應資料,讓「id 死亡」這個狀態在測試裡不可能出現。

穩定身份為錨、解引用延後到使用時

第二題的答案若為「存在」,設計形態收斂為:持有穩定身份、其餘參照在使用時動態解析。

「reference by identity + 解引用時機延後」把「id → 實體」的解析從持有時延後到使用時,讓解析結果反映上游當前的真相。額外的韌性:即使身份轉移發生在本端不知情的時候(另一台裝置觸發合併),使用時解析照樣找到新家。

查無結果時退回凍結值,搭配存在性檢查兜底——穩定身份的值域(如 uuid)讓失效的凍結值只會被擋下、不會誤中別的資料。退路的成本是一次額外查詢;退路被命中的頻率反映的是同步的及時性——頻率高代表催同步的時機需要調整(見下節)。

第二題的答案若為「不存在」,凍結參照是唯一手段,但它的有效期等於上游不做重建操作的期間。有效期內凍結合法;跨越有效期的功能需要改走查詢路徑——以業務鍵(訂單號、合約號這類人為穩定的識別碼)重新定位,而非依賴上游的技術 id。

自持狀態與可導出狀態:遷移分工

上游身份轉移(合併、拆分)發生後,下游持有的多份「以上游 id 為 key」的狀態面臨遷移問題。遷移判準一個問題就夠:這份狀態消失後,能否單靠向上游重新查詢完整重建?

答案分類遷移策略錯分的代價
可導出不搬家,讓既有同步機制自然收斂誤當自持 → 手工搬移引入第二個資料來源、搬移邏輯與同步邏輯分岔
不能自持身份轉移時通知搬家,設明確的轉移入口方法誤當可導出 → 上游沒有這份資料、同步等不到救援、功能級事故

兩類狀態的程式碼形狀差異清楚:自持狀態有一個「身份轉移通知」的入口方法,且它是所有身份轉移操作的必經站;可導出狀態沒有任何遷移程式碼,只有既有的同步迴圈。

可導出狀態「等下一輪同步」的延遲若不可接受,正確的加速做法是催一次既有同步——走同一條同步路徑、只是提早跑。催同步與手工搬移的差異在於:催同步後資料來源仍然只有上游這一份,手工搬移會製造第二份——兩份資料的分岔只是時間問題。催同步時注意同步路徑內部的順序依賴(路徑內的過濾機制可能依賴某份前置資料的新鮮度),順序錯了催出來的是空結果。

自持狀態的每一份都是一筆負債,債主是所有會改變上游身份的操作。盤點自持狀態清單、逐項問「上游為什麼不保存這個」——若上游未來補上這份資料,自持狀態就能降級為投影,遷移程式碼隨之刪除。這個分類判準與 讀模型的升級判準 同構:讀模型問「讀的形狀還是聚合根的形狀」,這裡問「這份狀態的真相在誰手上」。

凍結快照的正當用途

凍結參照應消滅、活解析應取代——這條原則有一個明確的邊界:呈現歷史事實的快照是凍結的正當用途。

用途正確形態
呈現歷史事實(結帳當下的價格)凍結 snapshot——上游變動不該影響它
回寫當前狀態(取消、追加)活解析——必須命中上游當前的資料

同一筆記錄可以同時包含兩種欄位。出事的案例往往是把「顯示用的凍結值」順手拿去做「回寫用的定位」——凍結值在這個用途下跨越了它的有效期,失效的時機由上游的重建行為決定、持有端察覺不到。凍結時機的判準在 entity 與 value object 的判準狀態轉換與稽核軌跡 各自從身份語意和稽核面到達同一個結論:歷史記錄反映事件發生當下的世界。

判讀訊號

  • 特定操作之後某些功能靜默失效(回寫目標不存在、查詢結果為空),且單元測試全綠——跨邊界參照的 id 可能已死亡。先確認上游操作的「保留 vs 重建」語意,再看持有端的解析時機。
  • 修復 bug 只搬了一層 id、遺漏另一層——同一個業務動詞涉及多層資料(單據 + 明細 + 記錄),每層的重建行為要逐層實測、修復的完整性以層數計。
  • 手工搬移的程式碼與同步邏輯在同一份可導出狀態上各寫一次——搬移是多餘的第二份資料來源,改成催一次同步。
  • 同步催了卻拿到空結果——同步路徑有內部順序依賴(過濾器讀的前置資料還是舊的),催同步前先確保前置資料已更新。
  • 自持狀態的數量持續增長——逐項問「上游為什麼不保存」,每消除一項就少一類遷移 bug。

下一步路由