利用 Salesforce Composite API 简化您的集成
大家好,我是一名 Salesforce 集成工程师 (Salesforce Integration Engineer)。在我的日常工作中,我专注于连接 Salesforce 与外部系统,确保数据流畅、高效、可靠地同步。集成的性能和健壮性是我最关心的问题之一。今天,我想和大家深入探讨一个强大的工具,它极大地改变了我们构建集成的方式——Composite API。
背景与应用场景
在传统的 REST API 集成模式中,我们经常会遇到一个棘手的场景:一个完整的业务流程需要执行多个连续的操作。例如,创建一个新的销售机会,我们可能需要按顺序执行以下步骤:
1. 第一步:查询系统中是否存在同名的客户 (Account)。
2. 第二步:如果不存在,则创建一个新的客户 (Account)。
3. 第三步:使用新创建或已存在的客户 ID,创建一个关联的联系人 (Contact)。
4. 第四步:使用客户 ID 和联系人 ID,创建一个新的业务机会 (Opportunity)。
如果遵循标准的 REST API 调用方式,上述流程将需要至少 3-4 次独立的网络往返(HTTP 请求和响应)。这种模式会带来几个显而易见的问题:
1. 网络延迟 (Network Latency):每一次 API 调用都包含一次完整的网络往返。请求越多,累积的延迟就越高,导致整个业务流程响应缓慢,用户体验差。
2. API 调用限制:Salesforce 对每个组织在 24 小时内可以进行的 API 调用次数有严格的限制。频繁的、碎片化的 API 调用会迅速消耗这些宝贵的资源,尤其是在高并发或大批量数据处理的场景下,极易触及限制,导致集成中断。
3. 复杂的事务管理与错误处理:如果第四步创建 Opportunity 失败了,我们是否应该回滚前面创建的 Account 和 Contact?如何确保数据的一致性?在客户端处理这种跨多个独立请求的事务回滚逻辑是非常复杂且容易出错的。
为了解决这些痛点,Salesforce 提供了 Composite API。它允许我们将一系列独立的 REST API 请求捆绑到一个单一的 HTTP 请求中,并作为一个整体进行提交。这不仅极大地减少了网络往返次数,还提供了原子性的事务控制能力,是现代 Salesforce 集成中不可或缺的利器。
典型的应用场景包括:
- 复杂数据结构的创建:一次性创建主-从记录,例如一个客户 (Account) 及其下属的多个联系人 (Contacts) 和业务机会 (Opportunities)。
- 事务性操作:执行一系列必须“要么全部成功,要么全部失败”的 DML 操作,确保数据的完整性。
- 提升前端应用性能:对于需要在单次用户操作中与 Salesforce 进行多次交互的 Lightning Web Components (LWC) 或其他前端应用,使用 Composite API 可以显著提升响应速度和用户体验。
原理说明
Composite API 的核心思想是将多个子请求 (subrequest) 组合在一个 JSON 载荷 (payload) 中,通过向 /services/data/vXX.X/composite 端点发送一个 POST 请求来执行。
其请求体主要包含两个关键属性:
1. allOrNone (布尔值)
这个参数控制着整个 Composite 请求的事务行为。
- 当设置为
true时,整个请求将在一个单一的事务中执行。如果其中任何一个子请求失败,所有已经完成的子请求操作都将被回滚。这保证了操作的原子性 (Atomicity),是实现数据一致性的关键。 - 当设置为
false时,每个子请求会被视为一个独立的事务。即使某个子请求失败,其他成功的子请求也会被提交到数据库。这种模式适用于允许部分成功的场景。
2. compositeRequest (JSON 数组)
这是一个数组,包含了所有需要执行的子请求对象。每个子请求对象都模拟了一个标准的 REST API 调用,通常包含以下属性:
method:HTTP 方法,如 "POST", "GET", "PATCH", "DELETE"。url:子请求的目标 URL,例如/services/data/v58.0/sobjects/Account。referenceId:这是一个在 Composite 请求范围内唯一的标识符,用于引用该子请求。它的最大作用是实现子请求之间的依赖关系。body:对于 "POST" 或 "PATCH" 等需要请求体的方法,这里包含了要创建或更新的数据,格式与标准 REST API 调用中的 JSON 体完全相同。
依赖关系处理
Composite API 最强大的功能之一就是能够处理子请求之间的依赖关系。例如,在创建了一个 Account 之后,我们希望立即用这个新 Account 的 ID 去创建一个关联的 Contact。
这通过 referenceId 和特殊的绑定语法 "@{referenceId.attribute}" 来实现。在后续的子请求中,你可以通过这个语法引用前面子请求的返回结果。最常见的用法是 "@{NewAccountRef.id}",它表示引用 referenceId 为 "NewAccountRef" 的那个子请求成功后返回的记录 ID。
这种机制使得我们可以在一个请求内构建出复杂的、有先后顺序的数据创建流程,而无需等待第一个请求返回后再手动发起第二个请求。
示例代码
让我们来看一个经典的官方示例:创建一个新的客户 (Account),然后立即为该客户创建一个联系人 (Contact)。这个例子完美地展示了 referenceId 的用法。
请求 (Request)
下面是一个完整的 Composite API 请求体 JSON。我们将其发送到 /services/data/v58.0/composite 端点。
{
"allOrNone" : true,
"compositeRequest" : [{
"method" : "POST",
"url" : "/services/data/v58.0/sobjects/Account",
"referenceId" : "refAccount",
"body" : { "Name" : "Salesforce" }
},{
"method" : "POST",
"url" : "/services/data/v58.0/sobjects/Contact",
"referenceId" : "refContact",
"body" : {
"LastName" : "John Doe",
"AccountId" : "@{refAccount.id}"
}
}]
}
代码注释
- 第 2 行:
"allOrNone": true- 我们将整个操作设置为一个事务。如果创建 Contact 失败(例如,因为验证规则),那么前面创建的 Account 也会被回滚,数据库中不会留下任何数据。 - 第 3 行:
"compositeRequest": [...]- 定义了子请求数组。 - 第 4-9 行:第一个子请求。
"method": "POST"- 我们要创建一个新记录。"url": "/services/data/v58.0/sobjects/Account"- 目标是 Account 对象。"referenceId": "refAccount"- 我们给这个子请求一个唯一的引用 ID "refAccount"。这个 ID 将在后续请求中被用来引用这次操作的结果。"body": { "Name" : "Salesforce" }- 要创建的 Account 的名称。
- 第 10-17 行:第二个子请求。
"method": "POST","url": "/services/data/v58.0/sobjects/Contact"- 同样是创建一个 Contact 记录。"referenceId": "refContact"- 为这个 Contact 创建请求定义了引用 ID。"AccountId": "@{refAccount.id}"- 这是最关键的部分。我们使用@{refAccount.id}语法来引用第一个子请求 (referenceId为 "refAccount") 成功后返回的新记录的 ID。Salesforce 会在处理这个请求时,自动将第一个请求创建的 Account ID 填充到这里,从而建立起 Account 和 Contact 之间的关联关系。
响应 (Response)
如果请求成功,Salesforce 会返回一个包含各个子请求结果的响应体。
{
"compositeResponse" : [{
"body" : {
"id" : "001xx000003DHP0AAO",
"success" : true,
"errors" : [ ]
},
"httpHeaders" : {
"Location" : "/services/data/v58.0/sobjects/Account/001xx000003DHP0AAO"
},
"httpStatusCode" : 201,
"referenceId" : "refAccount"
},{
"body" : {
"id" : "003xx000004WfVfAAK",
"success" : true,
"errors" : [ ]
},
"httpHeaders" : {
"Location" : "/services/data/v58.0/sobjects/Contact/003xx000004WfVfAAK"
},
"httpStatusCode" : 201,
"referenceId" : "refContact"
}]
}
响应体中的 compositeResponse 数组与请求中的 compositeRequest 数组一一对应。每个结果对象都包含了该子请求的 HTTP 状态码、响应体和原始的 referenceId,便于客户端进行匹配和处理。
注意事项
作为一名集成工程师,我必须强调在使用 Composite API 时需要特别注意的几个方面:
1. 权限与安全 (Permissions and Security)
Composite API 的执行上下文是发起调用的用户。这意味着该用户必须拥有对所有子请求中涉及的对象和字段的相应权限(创建、读取、更新、删除)。如果用户缺少对某个对象的访问权限,相关的子请求将会失败,并且如果 allOrNone 为 true,整个事务都会失败。
2. API 限制 (API Limits)
- 调用次数:一个 Composite API 请求,无论包含多少个子请求,都只消耗一个 API 调用名额。这是它相对于独立调用的最大优势。
- 子请求数量:一个 Composite 请求中最多可以包含 25 个子请求。
- DML 限制:所有子请求中的 DML 操作(插入、更新、删除)总数受制于 Salesforce 的标准 Governor Limits,例如单个事务中 DML 语句的总数不能超过 150 条。虽然 Composite API 本身很少触及这个限制,但在有复杂触发器 (Triggers) 或流程 (Flows) 的组织中需要特别留意。
- URL 长度:GET 请求的 URL 长度也有限制。
3. 错误处理 (Error Handling)
错误处理的逻辑取决于 allOrNone 的设置。
- 当
allOrNone为true:任何一个子请求失败,整个请求都会返回一个 HTTP 400 (Bad Request) 状态码。你需要检查失败的那个子请求的响应体,其中会包含详细的错误信息。此时,你可以确定数据库状态没有被改变。 - 当
allOrNone为false:整个请求总是会返回一个 HTTP 200 (OK) 状态码,即使内部有子请求失败。你必须遍历响应中的compositeResponse数组,逐个检查每个子请求结果的httpStatusCode(成功的通常是 2xx,失败的是 4xx 或 5xx)和body中的errors数组来确定哪些成功、哪些失败。这种模式下,你需要自己设计补偿逻辑来处理部分失败的情况。
因此,设计一个健壮的响应解析器至关重要。
4. 支持的操作
Composite API 主要用于执行 sObject 相关的 REST API 资源调用,如记录的增删改查 (CRUD)。它不支持调用其他复合资源,例如不能在一个 Composite 请求中再嵌套一个 Composite 请求,或者调用 Composite Graph API。
总结与最佳实践
对于我们集成工程师来说,Composite API 不仅仅是一个便利工具,更是一种构建高性能、高可靠性集成的标准实践。它通过将多个请求捆绑为一次调用,从根本上解决了网络延迟和 API 配额消耗过快的问题,同时其内置的事务控制能力大大简化了数据一致性的保障。
最佳实践总结如下:
- 优先使用 Composite API:只要业务流程涉及多个相互关联的 DML 操作,就应该优先考虑使用 Composite API,而不是发起一连串独立的 API 调用。
- 默认使用
allOrNone = true:为了保证数据完整性和一致性,除非业务明确允许部分成功,否则应始终将allOrNone设置为true。这能让你免于编写复杂的客户端回滚和补偿逻辑。 - 精心设计
referenceId:为你的子请求分配合理且易于理解的referenceId,这不仅是实现依赖关系的基础,也能让你的代码和日志更具可读性。 - 构建通用的响应处理器:在你的集成框架中,构建一个可以处理 Composite API 响应的通用模块。这个模块应该能正确解析
compositeResponse数组,区分成功和失败的子请求,并记录详细的错误信息。 - 注意负载大小:虽然可以捆绑多达 25 个子请求,但也要注意整体请求体的大小。对于超大规模的数据批量操作,可能需要考虑使用更专业的 Bulk API。
总而言之,熟练掌握和运用 Composite API,是每一位 Salesforce 集成工程师提升集成方案质量和效率的关键一步。它让我们能够以更优雅、更高效的方式,应对日益复杂的系统集成挑战。
评论
发表评论