Skip to content

设计 Salesforce 物理模型

另有 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)

A section model cut straight through a small building, showing every floor, room, stair and pipe in one slice.
图片由 AI 生成

第 10 个模块,共 10 个,属于 Data Architect 课程第 3 章Salesforce 数据架构。每个模块都可以单独阅读。 上一篇:保持数据一致 · 下一篇:本章学习指南,然后是下一章理解企业数据

**本模块回答的问题:**我们如何把逻辑模型变成一个 Salesforce 架构?

这一章从头到尾都在论证一件事:平台有它的形状:标准对象和自定义对象,数据可以存放的地方,字段类型,关系,所有权,许可,一致性规则,存储和规模。每一样都可能改变第 2 章的逻辑模型被构建的方式。现在,我们在 Steve Trucking 的一个切面上把它们串起来。

一个切面,而不是整个公司。我看到架构师犯的一个错误,是试图在一张图里解决整个企业。我要的是一个我们能推理清楚的东西:一个零售合作伙伴发来一份装货清单,Steve Trucking 把清单上的东西送到,我们保留每次配送所需的信息。然后我们一次做一个物理层面的决定。完整的设计留给第 1 部分的检查点。

第 2 章完成了概念层面的工作:零售合作伙伴、收货人、装货清单、配送、配送项目、停靠点类型、配送小组、卡车、配送中心、照片。我们给关系起了名字,并问了它们意味着什么。我不再重复。现在的问题是**这些概念如何在 Salesforce 上存在。**简介里有一个事实马上就很重要:Steve Trucking 已经在用 Field Service 来管理预约、配送小组和移动应用。

零售合作伙伴:不要发明一个客户对象

Section titled “零售合作伙伴:不要发明一个客户对象 ”

零售合作伙伴是 Steve Trucking 为之工作的公司。Salesforce 已经有一个为公司设计的对象:**客户(Account)。**干净契合。所以不要因为业务方说了“合作伙伴”就建 Retail_Partner__c。它就是一个客户,用一个零售合作伙伴记录类型把它和其他组织区分开,随之而来的还有合同、个案、作为联系人的配送中心经理,以及这些联系人的门户访问。很简单的决定。但不是每个决定都这么简单。

合作伙伴的订单号不是我们的身份标识

Section titled “合作伙伴的订单号不是我们的身份标识 ”

每个零售合作伙伴都会发来它自己的编号。一个合作伙伴的订单长这样:8843119,另一个长这样:A-984732。这些编号很重要,但它们是谁的?是合作伙伴的。Salesforce 有自己的记录 ID,合作伙伴则有它在和我们沟通时使用的编号。两者可以并存;这就是第 2 章的“一样东西,好几个标识符”。

所以合作伙伴的编号成为一个外部 ID,集成用它来匹配更新。而且因为两个合作伙伴可能使用同一个编号,身份标识是合作伙伴加编号,而不是单独的编号。不要因为某个合作伙伴的编号先到,就把它当作配送的通用身份标识,也不要让集成每次需要找一条记录时都去搜索一个文本字段。

在这里,人们很容易就去创建 Delivery__c。业务方说了“配送”,所以我们需要一个配送对象。这正是我不希望你首先去做的事。看向母舰,就像我们在模块 2 里做的那样。对于在某个地点执行、有生命周期、有项目、需要排期、有一个外勤小组的工作,Salesforce 已经有什么?Field Service 的工作订单(Work Order):Salesforce 把它描述为要为客户执行的现场服务工作(WorkOrder (opens in a new tab),英文)。排定的上门服务及其时间窗口是服务预约(Service Appointment),而这正是调度排期的对象,也是配送小组的应用里显示的东西。

所以配送 → 工作订单。不是因为词对得上;它们对不上。而是因为它的行为与我们已经付费并打算使用的平台能力相吻合。一个标准对象不必和业务用同样的词汇。它必须正确地表示这个概念。

Steve Trucking 物理模型 Steve Trucking 的物理模型,每张卡片是一个业务概念,下方是它对应的 Salesforce 对象。核心之上:零售合作伙伴是带记录类型的客户,它发送装货清单,即自定义对象 Manifest__c,而每次配送都对应一个合作伙伴。每份装货清单列出若干次配送;配送是工作订单,通过外部 ID 匹配。一次配送排期为若干服务预约,并包含若干配送项目,即工作订单行项目,每个项目由一个停靠点类型(即工作类型)来描述。三年后,配送移入配送历史,这是一个大对象,在核心之上归档。核心之外:每次配送履行一个合作伙伴订单,这是一个位于零售商系统中的外部对象,并链接到照片存储中的配送照片。每条连线都是一个查找关系:Salesforce 用查找关系构建了 Field Service,而我们连同它的决定一起采用。 核心之上 核心之外 对应 排期为 零售合作伙伴 Account · 记录类型 配送 WorkOrder · 外部 ID 预约 ServiceAppointment 每条连线都是一个查找关系:Salesforce 用查找关系构建了 Field Service,而我们连同它的决定一起采用。 恰好一个 零个或一个 一个或多个 零个或多个 查找 © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

第 1 步,共 4 步:母舰。每张卡片是业务概念,下面是它对应的 Salesforce 对象。

另一面是:工作订单不是免费的。它带来了 Field Service 的许可、它自己的关系、安全行为、必填字段、自动化和生命周期,而平台的其他部分期望它以某种方式表现。我们选择它,是因为契合是干净的,而且我们在使用围绕它构建的东西。如果 Steve Trucking 完全不想要 Field Service,而模型又处处和流程作对,我就会重新考虑。架构在于契合,而不在于标签。

一旦配送成为工作订单,在发明任何东西之前,先看看那个模型的其余部分。工作订单行项目(Work Order Line Item)是工作订单上的一个子任务(WorkOrderLineItem (opens in a new tab),英文):冰箱、安装、拉走旧货。所以**配送项目 → 工作订单行项目。**注意,第一个决定塑造了第二个决定。我们不是在为每个实体单独选择对象。采用一个模型,并在围绕它构建自定义结构之前,先理解它的其余部分。

在 Steve Trucking,一个停靠点是仅配送、配送加安装,或者配送加拉走旧货,而每一种都有自己的时长、配送小组技能和安装检查清单。在第 2 章里,我们认定这是一个实体,而不是一个标签。Field Service 已经有一个:工作类型(Work Type),一种可以应用到工作订单和行项目上的工作类别模板(WorkType (opens in a new tab),英文)。所以**停靠点类型 → 工作类型。**同样,不是因为存在一个叫工作类型的东西,而是因为概念和行为对得上。

Steve Trucking 物理模型 Steve Trucking 的物理模型,每张卡片是一个业务概念,下方是它对应的 Salesforce 对象。核心之上:零售合作伙伴是带记录类型的客户,它发送装货清单,即自定义对象 Manifest__c,而每次配送都对应一个合作伙伴。每份装货清单列出若干次配送;配送是工作订单,通过外部 ID 匹配。一次配送排期为若干服务预约,并包含若干配送项目,即工作订单行项目,每个项目由一个停靠点类型(即工作类型)来描述。三年后,配送移入配送历史,这是一个大对象,在核心之上归档。核心之外:每次配送履行一个合作伙伴订单,这是一个位于零售商系统中的外部对象,并链接到照片存储中的配送照片。每条连线都是一个查找关系:Salesforce 用查找关系构建了 Field Service,而我们连同它的决定一起采用。 核心之上 核心之外 对应 排期为 包含 描述 零售合作伙伴 Account · 记录类型 配送 WorkOrder · 外部 ID 配送项目 WorkOrderLineItem 预约 ServiceAppointment 停靠点类型 WorkType 每条连线都是一个查找关系:Salesforce 用查找关系构建了 Field Service,而我们连同它的决定一起采用。 恰好一个 零个或一个 一个或多个 零个或多个 查找 © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

第 2 步,共 4 步:顺着模型走。行项目和工作类型随工作订单一起到来。

装货清单:现在我想要一个自定义对象

Section titled “装货清单:现在我想要一个自定义对象 ”

有没有一个标准对象来表示“合作伙伴在凤凰城配送中心周二的那份清单”?其实没有,而我也不会为了说我们坚持用了标准对象,就把它硬塞进一个不相干的对象里。**装货清单 → Manifest__c。**它是接收的单位:它从哪里来(一个文件、一批 API 数据),什么时候来的,它的状态和总数,以及当合作伙伴当天重发时,要比较些什么。清单上的每一行都成为一个工作订单,并链接回这份清单。

**用自定义对象不是失败。**当一个概念没有干净的标准归宿时,这正是平台给我们的东西。错误不在于创建自定义对象,而在于没有先检查平台是否已经有这个概念就去创建它。

Steve Trucking 物理模型 Steve Trucking 的物理模型,每张卡片是一个业务概念,下方是它对应的 Salesforce 对象。核心之上:零售合作伙伴是带记录类型的客户,它发送装货清单,即自定义对象 Manifest__c,而每次配送都对应一个合作伙伴。每份装货清单列出若干次配送;配送是工作订单,通过外部 ID 匹配。一次配送排期为若干服务预约,并包含若干配送项目,即工作订单行项目,每个项目由一个停靠点类型(即工作类型)来描述。三年后,配送移入配送历史,这是一个大对象,在核心之上归档。核心之外:每次配送履行一个合作伙伴订单,这是一个位于零售商系统中的外部对象,并链接到照片存储中的配送照片。每条连线都是一个查找关系:Salesforce 用查找关系构建了 Field Service,而我们连同它的决定一起采用。 核心之上 核心之外 发送 对应 列出 排期为 包含 描述 零售合作伙伴 Account · 记录类型 装货清单 Manifest__c · 自定义 配送 WorkOrder · 外部 ID 配送项目 WorkOrderLineItem 预约 ServiceAppointment 停靠点类型 WorkType 每条连线都是一个查找关系:Salesforce 用查找关系构建了 Field Service,而我们连同它的决定一起采用。 恰好一个 零个或一个 一个或多个 零个或多个 查找 © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

第 3 步,共 4 步:唯一的一个自定义对象,用在没有标准对象契合的地方。

照片:Salesforce 能存,不代表它应该存

Section titled “照片:Salesforce 能存,不代表它应该存 ”

“我们需要把配送照片放在 Salesforce 里。”“放在 Salesforce 里”是什么意思?用户需要从 Salesforce 中看到它们,没错。这是否意味着图片文件必须存放在核心存储里?不一定。数一数零的个数:每次配送大约四张照片,有安装的更多,一年一百万次配送,就是每年四五百万张照片,而配送量每年增长 30%。

所以我的出发点是:**不要因为用户需要访问照片,就把核心 org 变成一个巨大的照片仓库。**文件应该放在为这种数据量而构建的存储中,在核心平台之外,由 Salesforce 保留它找到并显示这些照片所需的东西:一个引用、一个数量,或者每张照片一条小小的索引记录,外加一个表示所需安装照片已齐全的标志。访问数据,和数据在物理上存放在哪里,是两个不同的问题。这是模块 4 的内容又回来了。

工作订单需要字段:合作伙伴的编号、配送时间窗口、实际送达时间、状态,还有“门禁密码”或“院子里有狗”之类的备注。我不会把这里变成一份字段目录;模块 6 已经做过了。对每个字段都过一遍那些问题:它意味着什么,谁拥有它,它会不会变,它是不是一个标识符、一套受控词汇,是存储的还是推导的,是否敏感。合作伙伴的编号是外部 ID,所以它的唯一性是集成契约的一部分,而不只是一个复选框。实际送达时间是一个时刻,所以用日期/时间。状态驱动行为,所以用受控列表,绝不用自由文本。而关于某个人家里的配送备注是个人数据,所以要对它们分类。每个字段都能追溯到含义。

客户到工作订单,工作订单到它的行项目,装货清单到工作订单,工作类型描述停靠点。然后对每一条线都问模块 7 的问题:子记录能单独存在吗?谁拥有它?访问权限是否继承?它能移动吗?删除时会怎样?子记录会堆积到多少?每个工作订单都挂在少数几个零售合作伙伴客户下面,意味着每个父记录下有几十万条子记录:这是一个我们会在第 2 部分回答的倾斜问题。而对于 Field Service 的标准关系,要查看 Salesforce 已经是怎么构建它们的,而不是假设它们和我们会设计的一样。当你采用一个标准模型时,你也采用了 Salesforce 在其中做出的决定。在其上构建之前,先理解它们。

所有权和收货人:不要让默认值替你决定

Section titled “所有权和收货人:不要让默认值替你决定 ”

有两个决定,默认值会很乐意替我们做出。

**一次配送归谁所有?我们在模块 8 里回答过这个问题,而答案可以直接沿用到工作订单上:在这里,所有权并不决定任何人的访问权限,所以集成用户拥有它所创建的工作订单。**调度员看到自己配送中心的工作订单,配送小组看到自己路线上的,每个零售合作伙伴通过门户看到自己的,这一切都通过共享实现,而不是通过所有权。集成用户没有角色,也不进入我们的共享规则所用的公用组,因为按每天 3,300 条计算,它三天就会超过 10,000 条记录。我们还要写下:如果这个用户有一天被替换,会发生什么。

**收货人是什么?一个联系人?一个个人客户?还是只保存在配送上的某样东西?这取决于 Steve Trucking 是否和这些人有任何关系(他们是零售合作伙伴的客户),一个合作伙伴是否可能看到另一个合作伙伴的收货人,他们的个人数据可以保留多久,还有规模:每年大约一百万个新收货人。这些我们还没有全部掌握,这没关系。有时候正确的答案是我们知道的还不够。**把问题写下来,然后去获取信息;不要把不确定性埋进一个自定义对象里。我们会在第 1 部分的检查点回答它,那时第 4 到第 6 章已经给了我们企业数据和主数据的全貌。

模块 9 的阶梯在这里适用:合作伙伴的编号是外部 ID,唯一,所以重发的装货清单会做 upsert,而不是产生重复;状态是一个受限列表;实际送达时间只在状态变为已送达后才是必填的;安装停靠点在检查清单和照片齐全之前不能关闭。不是一个巨大的 Flow。每条规则都在它自己的那一级上。

再把这个切面过一遍,带着模块 4 的问题:**每一块数据存放在哪里?**在我的课堂幻灯片里,这是一张幻灯片,逻辑对照物理:第 2 章的逻辑实体,以及每一个在物理上落在哪里:核心之上,作为一个 sObject(如果是自定义的,就是 __c)或一个大对象(__b);或者核心之外,作为一个外部对象(__x),或者是根本不进入 Salesforce 的数据。

  • **核心之上,每天都在用:**客户、工作订单及其行项目、服务预约、工作类型、Manifest__c。
  • **核心之上,已归档:**超过三年的配送移到配送历史(Delivery History),一个大对象,仍然留着以备索赔,放在后院的棚子里。
  • **引用的:**零售合作伙伴自己的订单详情留在合作伙伴的系统里;我们把合作伙伴的编号保存为外部 ID,有人需要其余信息时,再通过一个外部对象或集成去获取。
  • **核心之外:**照片,放在为数以百万计的图片而构建的存储中,工作订单上保留一个链接。

这就是物理模型:不仅是哪个对象,还有哪个归宿。

  • 零售合作伙伴 → 客户,带有零售合作伙伴记录类型;配送中心经理作为联系人。
  • 配送 → Field Service 工作订单,通过服务预约排期。
  • 配送项目 → 工作订单行项目。
  • 停靠点类型 → 工作类型。
  • 装货清单 → 自定义对象 Manifest__c。
  • 合作伙伴编号 → 工作订单上的外部 ID,在每个合作伙伴内唯一。
  • 照片 → 存放在核心平台之外;Salesforce 保留引用和齐全标志。
  • 配送历史 → 三年后归档到一个大对象。
  • 所有权 → 集成用户,访问通过共享实现(模块 8)。
  • 收货人 → 未决,留给第 1 部分的检查点。
Steve Trucking 物理模型 Steve Trucking 的物理模型,每张卡片是一个业务概念,下方是它对应的 Salesforce 对象。核心之上:零售合作伙伴是带记录类型的客户,它发送装货清单,即自定义对象 Manifest__c,而每次配送都对应一个合作伙伴。每份装货清单列出若干次配送;配送是工作订单,通过外部 ID 匹配。一次配送排期为若干服务预约,并包含若干配送项目,即工作订单行项目,每个项目由一个停靠点类型(即工作类型)来描述。三年后,配送移入配送历史,这是一个大对象,在核心之上归档。核心之外:每次配送履行一个合作伙伴订单,这是一个位于零售商系统中的外部对象,并链接到照片存储中的配送照片。每条连线都是一个查找关系:Salesforce 用查找关系构建了 Field Service,而我们连同它的决定一起采用。 核心之上 核心之外 发送 对应 列出 排期为 包含 描述 履行方为 链接到 移入 零售合作伙伴 Account · 记录类型 装货清单 Manifest__c · 自定义 配送 WorkOrder · 外部 ID 配送项目 WorkOrderLineItem 配送历史 Delivery_History__b 预约 ServiceAppointment 停靠点类型 WorkType 合作伙伴订单 Partner_Order__x 配送照片 照片存储,链接 每条连线都是一个查找关系:Salesforce 用查找关系构建了 Field Service,而我们连同它的决定一起采用。 恰好一个 零个或一个 一个或多个 零个或多个 查找 © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

第 4 步,共 4 步:整个切面,包括核心之上、已归档和核心之外的部分。每条连线都是一个查找关系:Salesforce 用查找关系构建了 Field Service,而我们连同它的决定一起采用。

我可以把那张图交给你,但这还不够。**架构是它背后的推理过程。**为什么用客户?一家干净契合的公司。为什么用工作订单而不是 Delivery__c?配送的行为像现场工作,而 Field Service 已经到位。为什么用自定义的装货清单?没有干净的标准归宿。为什么照片不直接用文件?数据量和存储的经济账。为什么用外部 ID?另一个系统需要一个跨越边界的稳定身份标识。为什么由集成用户拥有工作订单?在这里,所有权并不决定访问权限,而这样能让审计轨迹保持真实。为什么收货人仍然未决?我们回答的问题还不够多。连线是产出;是那些决定产生了它们。

所以这是我希望你带着离开第 3 章的习惯。指着物理模型里的任何东西问**为什么?**为什么是这个对象、这个类型、这个关系、这个所有人、这个地方、这条规则?你应该能把答案追溯回去:从配置,到物理模型,到逻辑模型,再到业务需求。如果你做不到,那就是有人从设置界面开始做了设计。

你也会注意到那些我们还没完全算清的数字:每个合作伙伴的配送量、行项目、照片、保留、归档。很好。在锁定设计之前,先问零的个数:一个在五万条记录下运行得很漂亮的模型,在五亿条记录下可能表现得截然不同。那是第 2 部分的内容。

**考试之外:AI 改变了什么。**把 Steve Trucking 的简介交给 AI,它会在几秒内生成一套结构:客户、工作订单、自定义对象、字段,甚至部署元数据。这是一个出色的加速器,但它不是评审。架构师的工作转向检查这些映射及其后果:契合度、许可、所有权、生命周期、规模、身份、安全,以及什么留在平台之外。AI 是火箭助推器;它不会告诉你方向是否对准了。

  • 每个业务概念如何映射到 Salesforce:一个标准对象、一个自定义对象,还是核心平台之外的某样东西。
  • 哪些标识符、字段和关系实现了业务含义,而没有悄悄改变它。
  • 你所采用的标准模型附带了哪些平台行为和许可。
  • 在设计完成之前,必须做出哪些关于所有权、生命周期、一致性、存储和规模的决定。
  • 哪些问题要明确地保持开放,而不是藏在一个随意的实现选择背后。

在本章开头,我们问的是模型应该如何在 Salesforce 上存在。这就是 Salesforce 数据架构:不是创建对象,不是背熟设置界面,也不是画更漂亮的方框,而是有意识地让业务模型契合平台的形状。**每个结构都是一份承诺。**做那些你能解释清楚的承诺。