Skip to content

保持資料一致

另有 28 種語言版本

English · Español · 日本語 · Português (Brasil) · Français · Deutsch · Italiano · 한국어 · 简体中文 · Português (Portugal) · Bahasa Indonesia · Türkçe · Polski · Tiếng Việt · ไทย · Nederlands · العربية · עברית · Українська · हिन्दी · Русский · Svenska · Dansk · Norsk bokmål · Suomi · Español (México) · Français (Canada) · Deutsch (Schweiz)

An old five-rung wooden ladder against a barn wall, its lowest rungs worn smooth from use and its top rungs barely touched.
圖片由 AI 生成

第 9 個模組,共 10 個,屬於 Data Architect 課程第 3 章Salesforce 資料架構。每個模組都可以單獨閱讀。 上一篇:所有權與生命週期 · 下一篇:設計 Salesforce 物理模型

**本模組回答的問題:**我如何保持資料一致,每一層又有什麼代價?

每個人都想要乾淨的資料。我從沒在哪次需求會議上聽到有人說:「我們真正想要的是不一致的資料。」分歧在後面才出現:我們在哪裡執行規則?在介面上?必填欄位?挑選清單、驗證規則、重複規則、Flow、Apex?它們都有幫助。但它們不能互換,而且每一層都有代價。

我的一般規則是:在合理的範圍內,把規則放得越低越好。如果一個值永遠只能是五種之一,就不要讓人隨便輸入,事後再用自動化去清理。如果兩條記錄絕不能共用一個識別符號,就不要指望有人在報表裡發現它。如果一條規則不管記錄來自介面、API、整合、資料載入還是代理程式都必須成立,就不要只在介面上執行它。規則離資料越近,就越難被繞過。我把它叫作一致性階梯。

第一條一致性規則就是模組 6 裡的那條:**選對類型。**日期用日期,時刻用日期/時間,金額用貨幣。這是在任何人寫規則之前的一致性。

把配送狀態做成一個文字欄位,你就會得到 Delivered、delivered、DELIVERED、Complete、Done 和 Dropped off,然後得有人去弄清這六個是否是同一個意思。或者一次性把詞彙定義好:Delivered,一個值,一個含義。把它做成受限制的挑選清單,這樣它會拒絕任何不在清單上的值,即使是通過 API;當多個欄位共用這份清單時,就用全域值集(細節見模組 6)。

但不要只是為了不讓人打字就用挑選清單。它真的是一個值,還是另一個實體?九個各有一個名字的配送中心也許看起來像挑選清單;九個有地址、經理、容量和卡車的配送中心就是記錄了。規則應該強化模型,而不是彌補一個糟糕的模型。而如果它確實是一套受控詞彙,就在欄位上控制它。不要先放任混亂,再建一個 Flow 去把 AZ、Arizona、Ariz 和 Phoenix area 變成你一開始就想要的那個值。

必填和唯一很有吸引力,因為它們很簡單:勾選核取方塊,問題解決。有時如此。如果每輛卡車從建立那天起就有車隊編號,而且沒有兩輛卡車共用一個,那麼這兩個核取方塊表達的都是真實的業務規則。這是很強的一致性。

但一旦平台執行了一條規則,它就不在乎你的流程在週二下午有個例外。這個例子我在課上用了很多年。一個物件已經存在了一段時間,有大量記錄。有人加了一個欄位並勾選了必填。他們測試建立一條記錄:執行完美。然後週六晚上部署,測試執行,一箇舊的測試類在建立這個物件時沒有填新欄位。砰。或者有人編輯了一條在這個欄位存在之前就有的舊記錄。或者整合發來了它五年來一直在發的同一個資料包。砰。「可是我們沒動那個欄位啊。」平台不在乎:你改變了「什麼是有效記錄」的契約。一條新規則不只影響新資料;它改變了你能對舊資料做什麼。

驗證規則能讓我們表達豐富得多的東西。如果狀態是已送達,實際送達時間就必須填寫。如果停靠點有安裝,檢查清單就必須完成後才能關閉停靠點。它們在記錄儲存時執行,所以無論記錄是從頁面、API、資料載入、整合還是代理程式儲存的,它們都能保護記錄。這正是關鍵。如果規則必須保護記錄,就把它放在每一次寫入都會遇到的地方。

它們也有牙齒。這既是它們的優勢,也是它們的代價。

事情在這裡變得難看。你有一百萬條記錄,今天你加了一條規則:送達時間不能早於路線的出發時間。很合理。但有兩萬條舊記錄違反了它,原因是七年前的一次遷移。一個使用者開啟其中一條,改了電話號碼。儲存。規則觸發了。「我改的是電話號碼,你為什麼跟我說 2019 年的送達時間?」問得有道理。

所以一條驗證規則不應該只說什麼必須為真。它還應該說什麼時候執行。ISNEW() 和 ISCHANGED() 這樣的函式能讓你把規則的作用範圍限定在新記錄上,或者限定在相關欄位確實發生變化的儲存上(Validation Rule Considerations (opens in a new tab),英文)。

但記錄並不只在有人編輯它時才會變化,而其中很多原因不在你的控制之內。整合在夜裡更新狀態。一個 Flow 蓋上一個日期。一位經理重新分配了一個區域,一萬條記錄的所有人隨之改變。有人編輯了一個配送項目,配送上的彙總摘要欄位重新計算,這就儲存了配送,於是配送的驗證規則為一個從沒碰過它的使用者觸發了(Roll-Up Summary Field (opens in a new tab)(英文)提醒,驗證錯誤可能顯示在從記錄上,也可能顯示在主記錄上)。每一次這樣的儲存都會遇到你的新規則。所以對每一條新規則,都要問:這條規則應該適用於那次儲存嗎?

這些就是可選的做法,你把它們編織進規則裡:

  • 始終執行,每次儲存都執行。當規則確實關乎記錄本身、而舊資料也能通過時,這是對的。但要明白「每次儲存」意味著什麼:一個只更改所有人的批次處理作業,或者對記錄上任何欄位的任何小修改,也都必須通過它。
  • 只針對新記錄,用 ISNEW()。規則在記錄建立時檢查一次,之後再也不檢查。過去保持原樣,但未來也是如此:之後的一次編輯可能讓一條新記錄違反規則,卻沒有任何東西會阻止它。如果這很重要,就把它和下一種配合使用。
  • 只在它所關乎的欄位變化時執行,用 ISCHANGED()。編輯電話號碼不會喚醒送達時間那條規則。
  • **對某些寫入者不執行。**一個自訂權限,在公式中用 $Permission 檢查,可以讓整合使用者或一次資料遷移通過,而人仍然要滿足規則(How to Reference a Custom Permission in a Validation Rule (opens in a new tab),英文)。要有意識地授予它,而且只授予少數人。
  • 從某個日期開始執行,把 CreatedDate 與規則上線的日期作比較,適用於「新」意味著「自從我們開始執行這條規則以來」的情況。與 ISNEW() 不同,它在之後每次儲存時都會繼續檢查這些記錄,所以同樣的提醒也適用:一個小改動,比如批次更新所有人,可能會在一條已經偏離規則的記錄上失敗。

**要給已有的記錄加一條規則?**在開啟之前想清楚後果。每一條現有記錄在下一次儲存時都必須通過它,而那次儲存可能來自你意想不到的地方:一個夜間整合、一次批次更換所有人、一次彙總、一次從清單檢視發起的批次更新。其中哪些會碰到它?當一條記錄失敗時,那個作業會怎樣?在開啟之前一節裡有我使用的順序。

不要為了讓規則滿意而改寫歷史

Section titled “不要為了讓規則滿意而改寫歷史 ”

那兩萬條舊記錄怎麼辦?如果資料是錯的,而你知道正確的值,就更正它。但我見過另一種「修復」:沒人知道歷史的真相,他們只是需要這些記錄能通過,於是用滿足今天規則的值去更新它們。我不喜歡這樣。為了合規去更新舊記錄,會改變 LastModifiedDate,毀掉關鍵的歷史資訊,而編造值就意味著假裝我們知道一些關於過去其實並不知道的事。給規則一個針對它之前就存在的記錄的繞過機制,而不是改寫歷史。這也許難看,但它是誠實的。

在頁面配置上把一個欄位設為必填,使用者不填就無法儲存。然後夜間整合插入了 5 萬條記錄。它用了你的頁面嗎?沒有。批次載入、API 用戶端和代理程式也都沒有。介面上的必填要求保護的是那個介面,而不是資料。動態表單上的欄位驗證也可以用同樣的方式被繞過。所以要問:**這些資料能從哪裡進來?**Lightning、入口網站、移動端、整合、API、批次載入、Flow、Apex、代理程式。門越多,我就越不信任一條只守著其中一扇門的規則。

現在我們從記錄內部的一致性,轉向記錄之間的一致性。Acme Corporation、ACME Corp、Acme Inc.:是四個客戶還是一個?沒有哪條欄位規則能回答這個問題。比對規則定義記錄如何比較;重複規則決定發現可能的重複時怎麼辦:允許並給出警告、阻止,或者報告。每個物件最多可以有五條有效的重複規則,每條規則最多三條比對規則,而比對鍵會把每次比較縮小到最可能重複的 100 條記錄(Things to Know About Duplicate Rules (opens in a new tab),英文)。

它們的代價是摩擦。如果銷售代表建立的每個客戶都提示發現可能的重複,有時這正是你想要的,有時人們會學會不看就點過去。合法的記錄可能看起來很像,而整合可能需要和人不同的行為。而且這個警告是 Salesforce 自己的:它的提醒和可能重複的清單,可能和你構建的自訂介面並不協調;而當儲存是無介面的(headless),比如整合、資料載入、代理程式,就根本沒有可以顯示它的介面。所以要決定重複在每一條路徑上如何呈現:對人給出警告,對 API 阻止儲存並返回錯誤,或者用「報告」選項和重複記錄集,留給某人以後審查。好的架構不是最大程度的執行,而是恰當的執行。

還要檢查一條重複規則實際守住了哪幾扇門(Things to Know About Duplicate Rules (opens in a new tab),英文)。通過 API 和資料匯入工具時,沒人會看到警告:設為提醒的規則會直接阻止儲存。有些路徑會完全跳過這些規則:快速建立、「恢復」按鈕、手動合併、由 Lightning Sync 或 Einstein Activity Capture 新增的記錄。而且同一時刻儲存的記錄不會彼此比較,只會與 Salesforce 中已有的記錄比較。載入一個包含同一客戶兩次的檔案,兩行都可能通過。重複規則是一張好網。它不是門上的鎖。

考點。重複規則不會把同一時刻儲存的記錄彼此比較,只會與 Salesforce 中已有的記錄比較。通過 API 和匯入工具時沒有提醒:被規則攔下的重複記錄只是不會被儲存。而一次儲存以及它引發的所有操作作為一個事務執行:任何一部分失敗,全部回滾。

有一條邊界。重複規則保持的是一個 org 的乾淨,它們解決不了企業層面的身分問題。如果 Salesforce 裡有 Acme Corporation,ERP 裡有 ACME-10382,計費系統裡有客戶 77193,重複規則無法告訴你這些是同一家公司。那是主資料的問題,第 6 章。

有些規則對較低的幾級來說太複雜了:它們需要相關記錄、另一個系統、依賴記錄、一個多步驟的流程。那就是 Flow、Apex,有時還有整合邏輯。(不是工作流規則或 Process Builder;它們已不再受支援。)自動化很強大,也更昂貴:更多的活動部件,更多的測試,更多的錯誤處理,以及一個自動化絆倒另一個自動化的更多可能。而程式碼會帶來它自己的錯誤。如果欄位類型能執行,我就不寫 Apex。如果一條驗證規則能把它說清楚,我就不會因為有 Flow 就去建一個 Flow。不要用流程引擎去解決一個欄位問題。

全有或全無:當記錄一起變化時

Section titled “全有或全無:當記錄一起變化時 ”

到目前為止,每一級保護的是一次儲存。但記錄會一起變化:一次配送和它的項目,一個訂單和它的明細行,一個更新三個物件的 Flow。在這裡,平台給了你一個很強的保證。一次儲存以及它引發的所有操作,包括觸發器、Flow 以及它們呼叫的類,都作為一個事務執行:任何一部分失敗,整個事務都會回滾,什麼都不會提交(Apex Transactions (opens in a new tab),英文)。你不會得到半次配送。

批次操作是你主動選擇的例外。資料載入或 Apex 的 Database 方法可以允許部分成功:好的記錄儲存下來,壞的記錄帶著錯誤返回,然後得有人處理它們(DML Statements vs. Database Class Methods (opens in a new tab),英文)。對一次一百萬行的載入來說,這往往是對的,但這時一致性就成了一個流程:誰來讀錯誤檔案,失敗的記錄又怎麼辦?

然後是時機。同一時刻的兩次儲存,可能都通過了一項各自單獨時會失敗的檢查。一個先查閱已有訂單、再建立訂單的 Flow,可能會放過兩個,而我們剛剛看到,重複規則不會比較同時儲存的記錄。一個標記為唯一的欄位,或者一個外部 ID,在每次儲存時都由平台檢查,所以只有一個能勝出。這又是一個把唯一性下沉到欄位上的理由。鎖、爭用和重試是一個單獨的話題;第 11 章 事務、併發與可靠性 會深入討論。

把規則往下放,直到它不再屬於那裡

Section titled “把規則往下放,直到它不再屬於那裡 ”

「在合理的範圍內越低越好」並不意味著「總是最低」。如果一個欄位只有在商機到達某個階段後才是必填的,那麼在結構中把它設為必填就是錯的;這條規則是有條件的,所以驗證規則才是正確的那一級。如果一個重複應該提醒人,但一個經過批准的整合有它自己的對賬機制,那就為兩條路徑都做設計。目標是:能正確表達業務規則的最低一層。

一致性階梯 以矩陣呈現的一致性階梯,從最低一級開始,對照四項檢驗:每條寫入路徑、豐富的條件、跨記錄、易於共處。第 1 級,欄位類型與挑選清單:在每條路徑上都成立,包括 UI、API、資料載入和代理程式,而且易於共處,但只能表達欄位能表達的東西;最強。第 2 級,必填與唯一:適用每條路徑,而唯一能跨記錄生效,但它一律生效,沒有例外;生硬。第 3 級,驗證規則:適用每條路徑,能表達豐富的條件,可以查看記錄及其父記錄,但必須為舊記錄限定範圍;限定範圍。第 4 級,重複規則:跨記錄的模糊比對,但它會略過合併和取消刪除,還會增加摩擦,透過 API 時也沒有提示;一張網。第 5 級,Flow 或 Apex 中的自動化:若為記錄觸發,什麼都能處理,但測試、錯誤和維護都得由你負責;最後手段。作為對照的頁面配置必填要求:只保護這一個畫面;不是資料。底部註明:把規則往下放到能正確表達它的最低一級。 每條寫入路徑 豐富的條件 跨記錄 易於共處 它是 欄位類型與挑選清單 第 1 級:詞彙 UI、API、載入、代理程式 只限欄位能表達的 最強 必填與唯一 第 2 級:欄位 一律生效,沒有例外 唯一:只有一筆勝出 生硬 驗證規則 第 3 級:公式 記錄及其父記錄 限定範圍:舊記錄 限定範圍 重複規則 第 4 級:比對 略過合併、取消刪除 模糊比對 增加摩擦,API 無 UI 提示 一張網 自動化 第 5 級:Flow、Apex 若為記錄觸發 測試、錯誤、維護 最後手段 頁面配置必填 對照:一個畫面 僅限此畫面 不是資料 把規則往下放到能正確表達它的最低一級。 © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

這是我希望你看到的權衡。欄位類型和受控值簡單而有力,但只限於一個欄位能表達的東西。必填和唯一很生硬:一旦開啟,每一次寫入都必須滿足它們。驗證規則能表達更豐富的條件,但可能阻止對舊記錄的編輯,並讓一次部署措手不及。重複規則跨記錄提供保護,但給使用者和整合增加了摩擦。自動化什麼都能處理,但你要為編排、錯誤、測試和效能負責。沒有哪一層是免費的。在知道它會如何失效的前提下,為每條規則選擇它所在的層。

在一個已有的 org 中新增一條規則是一次推廣,而不是勾一個核取方塊。今天有多少條記錄違反它,為什麼?它們真的錯了嗎?我們能知道正確的值嗎?這條規則對歷史資料也成立,還是隻從現在開始成立?哪些整合、測試和自動化會碰到它?一個好的順序是:度量,清理那些你能證明是錯的,限定規則的範圍,為合法的歷史資料有意地加一個繞過機制,測試每一條寫入路徑,然後再執行。度量和清理本身就是專門的學問,包括資料剖析、標準化、監控和資料管理,第 5 章 資料品質與治理 會講到它們。這比周五勾一個核取方塊、週六晚上才發現後果要好得多。

**考試之外:AI 改變了什麼。**代理程式建立記錄的速度比任何人都快,建立壞記錄也一樣快。當用戶是一個代理程式時,「使用者知道不要那樣做」就算不上一種保護。把重要的規則放在資料層,讓每一個寫入者都會遇到它們,這樣代理程式就不必去記住公司那些不成文的規矩。

拿一次配送來說。它的狀態來自一套詞彙:一個挑選清單,受限的。零售合作夥伴的訂單編號在每個合作夥伴內必須唯一,所以它是一個外部 ID,標記為唯一,用於對裝貨清單做 upsert。狀態變為已送達後,實際送達時間就是必填的:一條驗證規則,限定在這個變化上。一個安裝停靠點要等檢查清單完成、所需照片都拍好(對於洗碗機,是進水管、排水管和水平)才能關閉:一條驗證規則,如果要檢查存放在平台之外的照片,就用自動化。同一個訂單因為裝貨清單重發而到達兩次,會被外部 ID 攔住;兩個看起來很像的不同訂單,則是一個比對問題。

注意我沒有說「把它們全放進一個 Flow 裡」。每條規則都待在它自己的那一級上。對每一條規則,都問一問:對每一條重要的寫入路徑來說,能正確執行它的最低一層是什麼?

  • 每條規則屬於哪一級:欄位類型、受控詞彙、必填或唯一、驗證規則、重複規則,還是自動化。
  • 哪些寫入路徑必須受到保護:介面、入口網站、移動端、整合、批次載入、API、自動化和代理程式。
  • 一條新規則是否適用於歷史記錄,還是限定在新資料和變化的資料上,併為過去的資料提供一個繞過機制。
  • 現有的壞資料能否根據證據來更正,而不是為了通過檢驗去改寫。
  • 一條規則值得付出多少摩擦、複雜度和維護成本。

乾淨的資料不是靠要求人們輸入好資料得來的。它來自一種架構,讓正確的事情容易做,讓錯誤的事情難做,讓重要的規則難以繞過。接下來,我們在 Steve Trucking 的一個切面上把本章的內容串起來。