Skip to content

Steve Trucking,如果由我来设计

另有 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 row of plain white delivery trucks ready at the loading doors at first light, a clipboard on the front of the nearest one.
图片由 AI 生成

第 6 个模块,共 6 个,属于 Data Architect 课程第 6 章主数据与身份,也是第 1 部分的结尾。 上一篇:当权威被共享 · 下一篇:第 2 部分从Designing for Scale *(coming soon)*开始

**本检查点回答的问题:**你能在一个真实的情景中把第 1 部分串起来吗?

**先尝试挑战。**这是我对检查点 1 挑战的讲解。如果你还没有画出自己的设计,在继续读之前先去画。价值在于把你的推理和我的进行比较,而不在于读答案。

这是“如果由我来设计”:一个可行的方案,而不是唯一的答案,其中的权衡都已点明,我的立场也已表明。我们还没有讲大数据量、查询设计、集成模式、迁移或事务,这是有意的:架构会随着你学到更多而发展。但第 1 部分给了我们足够的东西来搭好骨架。

来自简介:每个工作日大约 3,300 次配送,每年一百万次,每年增长 30%。每次配送大约 1.6 个行项目(每年 160 万个);每次配送大约八次状态变更(每年 800 万次);每年四五百万张照片;每天大约 20 万条 GPS 读数;每年大约 18 万个个案;三个零售合作伙伴用三种不同的方式发送装货清单;九个配送中心;330 辆卡车,配两人小组。而且 Steve Trucking 已经在运行一个 Salesforce org,有 Service Cloud、Field Service 和一个合作伙伴门户。在画任何一个方框之前,先数零。它们会决定下面好几件事。

配送是核心,而 Field Service 已经在做调度了。所以我从 Field Service 的标准模型开始,就像第 3 章的那个切面一样:

业务概念Salesforce 对象原因
零售合作伙伴客户(Account)(零售合作伙伴记录类型)Steve Trucking 与之签约的企业;承载合同、个案和门户访问
合作伙伴员工(配送中心经理、门店员工)联系人(Contact)门户用户必须是联系人
一次配送:一位收货人、一个地址、一个合作伙伴订单、一天工作订单(Work Order)Field Service 的工作单位;排期、移动应用和服务报告都挂在它上面
每件商品或服务:冰箱、安装、拉走旧货工作订单行项目(Work Order Line Item)划分工作,驱动检查清单和开票
停靠点类型:仅配送、加安装、加拉走旧货工作类型(Work Type)设定时长、技能和安装检查清单
排定的上门服务及其时间窗口服务预约(Service Appointment)Field Service 排期的对象,也是配送小组的应用里显示的东西
配送小组和卡车;配送中心的调度区域服务资源、服务小组、服务区域(Service Resource、Service Crew、Service Territory)Field Service 把一个配送小组作为一个资源来排期
收货人的咨询、合作伙伴的索赔个案(Case)服务控制台、权利、电子邮件转个案
合作伙伴在凤凰城配送中心周二的那份清单Manifest__c(自定义)没有标准对象能表示接收、对账和重放的单位

其他一切都保持标准。抵制更多的自定义对象是有意的:第 1 章的问题是架构师什么时候应该抵制再加一个,而在这里答案是“几乎总是”。比如,合作伙伴的订单行就不是一个新对象。它就是工作订单,由合作伙伴的编号来识别。

检查点 1:配送核心 检查点 1 为 Steve Trucking 设计的物理核心。核心之上:零售合作伙伴是带有记录类型的客户(Account),它发送装货清单,这是唯一的自定义对象;每次配送对应一个合作伙伴。装货清单列出配送。配送是一个工作订单(Work Order),以字段形式承载收货人;它被排期为预约,即服务预约(Service Appointment),并包含配送项目,即工作订单行项目(Work Order Line Item),每个配送项目由一种停靠点类型描述,即工作类型(Work Type)。配送小组,即服务小组(Service Crew),被分配到预约,并在一个配送中心调度区域中工作,即服务区域(Service Territory)。咨询或索赔,即个案(Case),可以涉及配送。核心之外:配送小组的 GPS 定位信号进入数据仓库,配送链接到照片存储中的配送照片,每个预约把它的状态历史作为事件发布到数据仓库。下方写着:看向母舰:Field Service 就是配送核心。 核心之上 核心之外 发送 对应 列出 涉及 排期为 包含 描述 小组工作区 分配到 发送定位 链接到 发布 零售合作伙伴 Account · 记录类型 装货清单 Manifest__c · 自定义 咨询或索赔 Case 配送 WorkOrder · 收货人字段 配送项目 WorkOrderLineItem 预约 ServiceAppointment 停靠点类型 WorkType 配送中心调度区域 ServiceTerritory 配送小组 ServiceCrew 状态历史 事件,数据仓库 配送照片 照片存储,链接 GPS 定位信号 数据仓库,按引用 看向母舰:Field Service 就是配送核心。 恰好一个 零个或一个 一个或多个 零个或多个 查找 © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

它由第 3 章的最终图(设计 Salesforce 物理模型)发展而来:同样的对象在同样的位置,再加上配送小组、调度区域、个案,以及检查点 1 放在核心之外的东西。

把合作伙伴的订单与配送匹配起来的键

Section titled “把合作伙伴的订单与配送匹配起来的键 ”

每一行装货清单都变成一个工作订单,并链接到它的装货清单。键是我最想得到回答的问题:我们怎么知道合作伙伴的这一行就是那一次配送?我的答案是:工作订单上的一个外部 ID,由零售合作伙伴加上合作伙伴自己的订单和配送编号组成(类似 KEYSTONE-PHX-8843119-1),标记为唯一。每个来源一个字段,一一对应。它是合作伙伴的标识符,而不是我们的:这是第 2 章的“一样东西,好几个标识符”,也是模块 1 的“合作伙伴拥有它的订单编号”。

为什么这很重要:装货清单提前一到两天到达,并会在当天修改。有了这个键,每一份重发或修改后的装货清单都会做 upsert,而不是产生重复。修改会更新工作订单,一个字段记录下这次修改,而对一次已经排好路线的配送的修改会提醒协调员:合作伙伴的修改被应用了,但在调度确认之前会被标记出来。这也是与第 4 章那张图相比发生变化的地方。如今每一份装货清单都先进入 RouteDesk,协调员再把路线重新录入 Salesforce,并把已完成的停靠点录回去。在这个设计中,装货清单直接进入 Salesforce,按合作伙伴的键做 upsert;RouteDesk 退役,重复录入也就结束了。集成那一章会设计三种入站格式。数据层面的决定就是这个键。

收货人:个人客户、联系人,还是都不是?

Section titled “收货人:个人客户、联系人,还是都不是? ”

这是第 1 部分中最有意思的数据决定,所以这里先列出选项,再给出我的选择。

选项 A:个人客户。每位收货人成为一个个人客户,链接到每一次配送。*支持的理由:*标准,能与 Service Cloud、消息和控制台自然配合,能识别重复的收货人。*反对的理由:*每年大约一百万位新收货人,每一位都是一个个人客户,存储大约是企业客户的两倍(Estimated Record Size by Object Type (opens in a new tab),英文),所以两年内就会成为 org 中最大的对象之一。而且收货人不是 Steve Trucking 的客户。他们是每个零售合作伙伴的客户,而 Steve Trucking 绝不能把一个合作伙伴的客户和另一个合作伙伴的客户混在一起(S2)。

**选项 B:零售合作伙伴客户下的联系人。***反对的理由:*三四个客户下面挂一百万个联系人,是大规模的父子数据倾斜,远远超过 Salesforce 关于每个父记录大约 10,000 条子记录的建议(Considerations for Object Relationships (opens in a new tab);Parent-Child Data Skew (opens in a new tab),英文)。这是一个需要展示出来、然后否决的选项。

选项 C:收货人信息放在配送上。收货人的姓名、电话、电子邮件、地址和配送备注是工作订单上的字段(地址字段在那里本来就是标准字段),从装货清单中采集。*支持的理由:*没有新的大对象;数据的存在时间恰好与配送所需的一样长;删除按每次配送、按合作伙伴的时间表进行;而合作伙伴隔离是自然而然的,因为每次配送只属于一个合作伙伴。*反对的理由:*重复的收货人不会被识别为同一个人。

这就是需要把握的平衡。是否会有需要把好几次配送识别为同一个人、在同一个地点?是否要给这个人一个登录账号?这就是两种情况的分界线:一种是收货人需要一条属于自己的记录,另一种是收货人信息只是关于这次配送的数据。而且还有一条折中的路。把零售合作伙伴自己的客户 ID 作为配送上的一个字段,即零售合作伙伴客户 ID(Retail Partner Customer ID)。它不会创建收货人记录,但能把一个合作伙伴为同一位客户发来的多次配送串在一起,这是更统一的客户体验的起点,而且它直接指回零售合作伙伴的系统,也就是那位客户真正所在的地方。这是合作伙伴的标识符,所以它只有和合作伙伴放在一起才有意义:一个合作伙伴的客户 4417 和另一个合作伙伴的客户 4417 毫无关系。

依我的专业判断,选项 C 是正确的选择。不能识别重复这一点是可以接受的,甚至是可取的:除了这次配送,Steve Trucking 和这些人没有任何关系。这又是模块 1:收货人是合作伙伴的客户,而合作伙伴是他们身份的权威。这也是第 5 章:落在一个地方、与一次配送绑定的个人数据,保护、保留和删除起来都要容易得多。而且它也经得起第 2 章范式的检验:收货人的信息与配送是一对一的,所以它们属于配送。一个单独的收货人子对象能省下几个重复的值,代价却是多一个对象、多一个关系以及随之而来的开销;而它唯一能省下更多的方式,是把跨零售合作伙伴的同一个人当作一个人,而我们不会这么做。所以由配送承载收货人信息,再用零售合作伙伴客户 ID 指回合作伙伴自己的系统。

收货人是谁? 收货人的三个选项,各自列出支持和反对的理由。选项 A,每位收货人一个个人客户:在关系上不成立,因为收货人是合作伙伴的客户;在数据量上不成立,每年大约 100 万个,存储是企业客户的两倍;在数据倾斜上成立,每个都是独立客户;在合作伙伴隔离上不成立,因为它会混合各合作伙伴的客户;在删除上不成立,比配送存在得更久;在识别重复上成立。否决。选项 B,零售合作伙伴下的联系人:在关系上不成立,因为他们不是合作伙伴的员工;在数据量上不成立;在数据倾斜上不成立,100 万个挂在三四个客户下;在隔离上成立,位于一个合作伙伴的客户之下;在删除上不成立;能在一个合作伙伴内识别重复。展示并否决。选项 C,工作订单上的收货人字段,以高亮标为检查点 1 的选择:唯一的关系就是这次配送;没有新对象;没有新父记录;每次配送一个合作伙伴;按合作伙伴的时间表删除;重复的收货人不会被识别,这没有问题。下方写着:按照你们实际拥有的关系为这个人建模。 A:个人客户 否决 支持 能识别重复的收货人 没有父记录倾斜:每个都是独立客户 反对 是合作伙伴的客户,不是我们的 每年约 100 万个,存储翻倍 混合各合作伙伴的客户(S2) 比配送存在得更久 B:联系人 在零售合作伙伴下:展示并否决 支持 保持在一个合作伙伴之下(S2) 在一个合作伙伴内能识别重复 反对 不是合作伙伴的员工 100 万个挂在 3 或 4 个客户下:倾斜 比配送存在得更久 C:放在配送上 工作订单上的字段:选定方案 支持 唯一的关系就是这次配送 没有新对象,没有新父记录 每次配送一个合作伙伴(S2) 按合作伙伴的时间表删除 反对 不识别重复的收货人:没问题 按照你们实际拥有的关系为这个人建模。 © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

我会跨合作伙伴统一收货人吗?

Section titled “我会跨合作伙伴统一收货人吗? ”

John Smith,123 Main Street,同时出现在 Redrock 和 Brightwave 的装货清单中。多半是同一个人。Steve Trucking 应该统一他们吗?**不,有意不统一。**跨零售合作伙伴统一收货人会违反 S2:一个合作伙伴绝不能看到另一个合作伙伴的收货人,而一个统一的身份恰恰就是那座会泄露信息的桥。这正是本章立场的运用:在混合代价高昂的地方少匹配,而在这里,混合的代价非常高。

如果某个零售合作伙伴以后希望在它自己的配送范围内识别重复的收货人,那是限定在该合作伙伴范围内的身份解析工具(比如 Data 360 的身份解析)的工作:一张注册式的地图,而不是一个个人客户,而且永远不跨合作伙伴。一张谁是谁的地图不授予所有权,也不授予访问权限。

第 3 章问的是数据应该放在哪里。在这里,零的个数给出了答案:

  • 当前状态放在服务预约和工作订单上。状态变更的历史,每年大约 800 万次,而且还在增长,不需要放在核心 org 里。每次变更都作为一个事件发布出去,保存在数据仓库 Snowflake 中。org 保留当前状态;历史仍然可供服务、运营和分析使用,只是不在运营数据库里。
  • 照片(每年四五百万张,有些拍到了人或住宅内部)放在核心平台之外为这种数据量而构建的存储中,直接从配送小组的应用上传。org 只保留它需要的东西:每张照片一条小小的索引记录,或者只是一个数量和一个“所需安装照片已齐全”的标志。我更倾向于较轻的那种:让 org 保持精简,由照片存储自己拥有它的索引和保留策略。
  • GPS 读数(每天 20 万条)留在 FleetLink 中,并进入 Snowflake;org 只需要最新的位置,供跟踪页面和调度员的地图使用。
  • RouteDesk 八年的历史(大约 500 万次配送)进入数据仓库或档案库,供索赔时查阅。哪些数据(如果有的话)要进入 org,是第 2 部分的一个迁移决定。
检查点 1:如果由我来设计 检查点 1 的全景图,如果由我来设计,各部分的位置与第 4 章的当前全景图相同。左边是人:在 9 个合作伙伴配送中心内工作的 45 名调度协调员,330 辆卡车上大约 700 名配送小组成员,100 名客服和办公室员工,他们都使用 Salesforce;3 个零售合作伙伴的大约 300 名用户,使用零售合作伙伴门户;以及每年大约 100 万名收货人,他们通过一个链接打开跟踪页面,无需登录,并通过电话联系客服人员。中间是一个带有 Field Service 的美国 Salesforce org,它上方是企业身份提供商,只供办公室员工使用,邮件转个案把个案带进来。右下方是三个零售合作伙伴:Redrock 使用 REST API 和 webhook,Keystone 通过 SFTP 传 CSV,Brightwave 通过第三方网络传 EDI;它们把装货清单发送到一个集成层,集成层在第 2 部分设计,它按合作伙伴的键把装货清单 upsert 进 Salesforce,并把状态传回给每个合作伙伴。右边:Salesforce 把状态事件发送到 Snowflake,由它保存状态历史并做分析;原始 GPS 留在 FleetLink 中,它只把最新位置发送给 Salesforce;Salesforce 通过集成把配送数量发送到财务 ERP,由它向合作伙伴开票、向配送小组付款;照片每年 400 万到 500 万张,从配送小组的应用进入核心之外的照片存储;RouteDesk 退役,变成一个保存其 8 年历史的归档。Salesforce 通过短信和电子邮件服务商发送附带链接的前一天消息。下方写着:与第 4 章是同一家公司;重新录入和电子表格都没有了;历史和照片放在核心之外。 上方:身份、APPEXCHANGE、WEB 人员 右侧:内部系统 下方:客户渠道 右下:合作伙伴和服务商 调度协调员 45 名,在 9 个合作伙伴配送中心内 配送小组 约 700 人,330 辆卡车 客服和办公室员工 100 名:服务、运营、财务 零售合作伙伴用户 约 300 名,来自 3 个合作伙伴 收货人 每年约 100 万 企业 IdP SSO 仅限办公室员工 零售合作伙伴门户 跟踪页面 集成 Snowflake 状态历史、分析 FleetLink 原始 GPS 留在这里 财务 ERP 向合作伙伴开票,向配送小组付款 照片存储 每年 400–500 万张,核心之外 RouteDesk 归档 已退役;8 年历史 电话 客服人员 电子邮件 邮件转个案 短信和电子邮件服务商 前一天消息,附链接 Redrock REST API、webhook Keystone 通过 SFTP 传 CSV Brightwave 通过第三方网络传 EDI Salesforce Field Service,一个美国 org 链接,无需登录 仅限办公室员工 来电 个案进入 装货清单进入 状态发出 装货清单进入 状态发出 装货清单进入 状态发出 upsert,合作伙伴键 状态事件 仅最新位置 配送数量 配送数量 照片,来自配送小组的应用 前一天消息,附链接 Salesforce 社区 人员 身份、应用、Web 内部系统 客户渠道 集成层 从发起连接的系统开始;标签说明传输的内容 共享证书:信任,无直接调用 与第 4 章是同一家公司。重新录入和电子表格都没有了;历史和照片放在核心之外。 © 2026 Twinshark LLC · stevetecharc.com
Click to enlarge

检查点 1:如果由我来设计。与第 4 章全景图(Steve Trucking 的全景图)的位置相同,只是重复录入和电子表格都不见了。

  • 零售合作伙伴只看到自己的配送(S2)。每个工作订单都带有它的零售合作伙伴;门户用户看到他们合作伙伴的配送,配送中心经理只看到自己配送中心的。
  • 合作伙伴用户需要一个支持共享规则和角色的门户许可:Customer Community Plus 或 Partner Community。第 3 章的体育场座位:最便宜的座位得不到这个设计所需的共享。这两种许可都需要为外部用户设置角色,而 Salesforce 警告说,角色越多,org 越慢(Experience Cloud User Licenses (opens in a new tab),英文)。每个合作伙伴、每个配送中心一个角色没有问题;每家门店一个角色就不行了。

考点。掌握哪些外部许可支持标准共享:Customer Community Plus 和 Partner Community(需要为外部用户设置角色),而不是使用共享集的 Customer Community。

  • **收货人不需要许可。**跟踪页面通过一个带有会过期的令牌的链接显示一次配送(S3),而不是通过登录。
  • **留意数据倾斜。**每年几十万个工作订单指向三四个合作伙伴客户,就是客户数据倾斜。一个选项是在每个合作伙伴下为每个配送中心建一个子客户,把子记录分散到九个或更多的父记录下;在索赔期结束后归档已完成的配送,能让每个父记录下的活跃子记录保持在较低水平。第 2 部分会处理这个问题。

我不会因为收货人是个人,就为每位收货人创建一个个人客户。我不会在三个客户下面挂一百万个联系人。我不会在 Field Service 的工作订单旁边再建一个自定义的配送对象。我不会跨合作伙伴统一收货人。我不会把几百万张照片或 GPS 读数放进核心存储。而且除非我们真的做出了主控决定,否则我不会把一个统一的收货人叫作黄金记录。

第 1 部分搭好了骨架;第 2 部分来检验它。第三年的大数据量标尺(工作订单、服务预约和行项目在两三年内都会进入大数据量的范畴),针对合作伙伴范围的列表视图的查询设计,三个入站集成和三个状态回传,RouteDesk 的迁移,当天的事务,以及门户背后的安全设计。Steve Trucking 也会变化。保留这个设计;你还会回来重新审视它。

  • 哪些标准对象承载核心(这里是 Field Service 的),哪些自定义对象确实有理由存在(这里是一个)。
  • 把每个合作伙伴的订单行与一次配送匹配起来的键,以及为什么它属于合作伙伴。
  • 收货人是什么,依据的是 Steve Trucking 与他们之间实际存在的关系。
  • 是否统一身份,以及在哪里混合的代价太高而不值得冒险。
  • 什么留在 org 里,什么放在核心平台之外,以及设计依赖哪些许可。