Salesforce 外部对象:实时数据集成综合指南
背景与应用场景
在现代企业架构中,数据往往分散在多个系统中。例如,客户的订单历史可能存储在 ERP 系统中,产品信息存放在 PIM(产品信息管理)系统中,而客户关系管理则在 Salesforce 中进行。传统的数据集成方法,如 ETL (Extract, Transform, Load),通常涉及定期复制和同步数据,这会导致数据延迟、增加存储成本,并引发数据一致性的挑战。
为了解决这些问题,Salesforce 引入了 External Objects (外部对象) 的概念。External Objects 是一种特殊的 Salesforce 对象,其数据实时存储在外部系统中,而不是 Salesforce 数据库中。通过 Salesforce Connect 框架,用户可以在 Salesforce 界面中无缝地查看、搜索和操作这些外部数据,就如同它们是原生的 Salesforce 对象一样。
核心应用场景包括:
- 单一客户视图:在 Salesforce 的客户或客户资产页面上,直接展示来自 ERP 系统的实时订单历史或发货状态,为销售和客服团队提供360度客户视图,而无需在系统间切换。
- 减少数据存储:对于数据量巨大但不需要在 Salesforce 中进行复杂处理的数据(如日志、历史档案),使用外部对象可以有效避免占用昂贵的 Salesforce 数据存储空间。
- 实时数据报表:将外部系统的关键数据(如财务数据、库存水平)以外部对象的形式引入 Salesforce,并利用 Salesforce 强大的报表和仪表板功能进行实时分析。
- 简化集成:对于支持 OData (Open Data Protocol, 开放数据协议) 的系统,可以快速、低代码地完成集成,大幅缩短开发周期。
原理说明
External Objects 的核心技术是 Salesforce Connect。它充当一个数据代理,将 Salesforce 中的数据请求实时地翻译成外部系统能够理解的查询语言,然后将返回的结果呈现给用户。整个过程对终端用户是透明的。
实现这一点的关键组件包括:
1. External Data Source (外部数据源)
这是集成的起点。您需要在 Salesforce 中创建一个 External Data Source,用于定义如何连接到外部系统。这包括指定外部系统的 URL (Endpoint)、认证方式(如密码认证、OAuth 2.0)以及连接协议。
Salesforce Connect 支持多种适配器 (Adapter):
- OData 2.0/4.0 Adapter: 这是最常用的标准协议,它是一种基于 HTTP 和 RESTful API 的数据访问协议。如果您的外部系统暴露了 OData 端点,集成将非常简单。
- Cross-Org Adapter: 用于连接另一个 Salesforce Org,实现跨组织的实时数据访问。
- Custom Adapter (自定义适配器): 如果外部系统不支持 OData,您可以使用 Apex Connector Framework 编写自定义的 Apex 代码来处理与任何 RESTful API 的连接、数据转换和查询逻辑。
2. External Object (外部对象)
定义了 External Data Source 后,您可以点击 "Validate and Sync" 按钮。Salesforce Connect 会连接到外部系统,读取其数据表的元数据(如表名、字段名、数据类型),并允许您选择要“同步”为 External Objects 的表。这个“同步”过程只创建元数据定义(即对象的 API 名称、字段等),并不会复制任何数据记录。
一旦创建,External Object 在 Salesforce 中就拥有了 API 名称(以 `__x` 结尾),可以像标准或自定义对象一样被访问,例如通过 SOQL 查询、API 调用、页面布局、列表视图和报表。
3. 数据关系
External Objects 支持与 Salesforce 中的其他对象建立关系,主要有三种类型:
- External Lookups (外部查找): 用于连接两个属于同一个 External Data Source 的外部对象。例如,外部的“订单”对象查找到外部的“订单详情”对象。
- Indirect Lookups (间接查找): 用于将 Salesforce 的标准或自定义对象(父)连接到一个外部对象(子)。它通过匹配 Salesforce 对象上的一个具有 `External ID` 和 `Unique` 属性的字段与外部对象上的一个字段来实现。这是最常见的跨系统关联方式。
- Lookup (标准查找): 用于将一个外部对象(子)连接到 Salesforce 的标准或自定义对象(父)。这要求外部系统的数据表中必须有一个字段专门存储所关联的 Salesforce 记录的 18 位 ID。
示例代码
当外部系统不提供 OData 服务时,我们可以使用 Apex Connector Framework 创建一个自定义适配器。以下代码示例展示了如何实现一个简单的自定义连接器,该连接器从外部数据源查询数据。此代码改编自 Salesforce 官方文档。
首先,我们需要一个 `DataSource.Provider` 类来声明我们的自定义适配器并定义其功能。
DataSource.Provider 实现
/* * 此类声明了自定义适配器的提供者。 * 它指定了适配器支持的认证类型和功能。 */ public class MyCustomConnectorProvider extends DataSource.Provider { // 返回适配器支持的认证能力 override public List<DataSource.AuthenticationCapability> getAuthenticationCapabilities() { List<DataSource.AuthenticationCapability> capabilities = new List<DataSource.AuthenticationCapability>(); capabilities.add(DataSource.AuthenticationCapability.BASIC); // 支持基本认证 (用户名/密码) return capabilities; } // 返回适配器支持的功能,如查询、行级创建/更新/删除 override public List<DataSource.Capability> getCapabilities() { List<DataSource.Capability> capabilities = new List<DataSource.Capability>(); capabilities.add(DataSource.Capability.QUERY); // capabilities.add(DataSource.Capability.ROW_UPDATE); // 如需支持更新,取消注释 // capabilities.add(DataSource.Capability.ROW_CREATE); // 如需支持创建,取消注释 return capabilities; } // 返回与此 Provider 关联的 Connection 类 override public DataSource.Connection getConnection(DataSource.ConnectionParams connectionParams) { return new MyCustomConnectorConnection(connectionParams); } }
接下来,是核心的 `DataSource.Connection` 类,它实现了数据查询的逻辑。
DataSource.Connection 实现
/* * 此类处理与外部数据源的实际连接和数据交互。 */ public class MyCustomConnectorConnection extends DataSource.Connection { private DataSource.ConnectionParams connectionInfo; // 构造函数,保存连接参数 public MyCustomConnectorConnection(DataSource.ConnectionParams connectionParams) { this.connectionInfo = connectionParams; } /* * sync() 方法用于发现外部系统的数据表并将其定义为外部对象。 * 在 "Validate and Sync" 时被调用。 */ override public List<DataSource.Table> sync() { List<DataSource.Table> tables = new List<DataSource.Table>(); // 假设我们从外部API获取了表结构 // 创建一个名为 "Orders" 的表 List<DataSource.Column> columns = new List<DataSource.Column>(); columns.add(DataSource.Column.text('DisplayUrl', 255)); // Salesforce 必需的URL列 columns.add(DataSource.Column.text('ExternalId', 255)); // 用于唯一标识记录的外部ID columns.add(DataSource.Column.text('OrderNumber', 50)); columns.add(DataSource.Column.text('Status', 20)); columns.add(DataSource.Column.datetime('OrderDate')); tables.add(DataSource.Table.get('Orders', 'ExternalId', columns)); return tables; } /* * query() 方法在用户查询外部对象时被调用。 * 它将 Salesforce 的查询请求转换为对外部系统的API调用。 */ override public DataSource.TableResult query(DataSource.QueryContext context) { // 伪代码: 构建对外部系统的HTTP请求 HttpRequest req = new HttpRequest(); // context.tableSelection.columns 包含被查询的字段 // context.tableSelection.filter 包含 WHERE 子句 String endpoint = 'https://api.example.com/orders?' + buildQueryString(context); req.setEndpoint(endpoint); req.setMethod('GET'); Http http = new Http(); HttpResponse res = http.send(req); // 解析返回的 JSON 数据 List<Map<String, Object>> responseData = (List<Map<String, Object>>) JSON.deserializeUntyped(res.getBody()); // 将解析后的数据构造成 Salesforce 需要的格式 List<Map<String, Object>> resultRows = new List<Map<String, Object>>(); for (Map<String, Object> externalRow : responseData) { Map<String, Object> sfRow = new Map<String, Object>(); sfRow.put('ExternalId', externalRow.get('id')); sfRow.put('OrderNumber', externalRow.get('order_num')); sfRow.put('Status', externalRow.get('status')); sfRow.put('OrderDate', externalRow.get('created_at')); // DisplayUrl 是必需的,指向记录的唯一URL sfRow.put('DisplayUrl', 'https://example.com/orders/' + externalRow.get('id')); resultRows.add(sfRow); } return DataSource.TableResult.get(context, resultRows); } // 辅助方法,用于将QueryContext转换为API查询参数 private String buildQueryString(DataSource.QueryContext context){ // 实际开发中需要解析 context.tableSelection.filter 来构建过滤条件 // 例如:WHERE Status = 'Shipped' return 'limit=' + context.maxResults; } }
注意事项
权限与安全
安全性是双层的。首先,用户需要拥有对 Salesforce 中该外部对象的 CRUD (Create, Read, Update, Delete) 权限(通过 Profile 或 Permission Set)。其次,在 External Data Source 中配置的认证凭证决定了 Salesforce 连接到外部系统时拥有的权限。这意味着,即使用户在 Salesforce 中有编辑权限,如果外部数据源的凭证是只读的,那么任何编辑操作都会失败。
API 限制与性能
每一次对外部对象数据的访问(无论是打开记录、运行报表还是执行 SOQL)都会产生一个到外部系统的 API Callout。这些 Callout 会计入 Salesforce 的 Governor Limits。请密切关注 Salesforce Connect 的使用限制,不同 Salesforce 版本的限制不同。
性能的瓶颈通常在外部系统。如果外部系统的 API 响应缓慢,那么在 Salesforce 中访问外部对象也会同样缓慢。因此,必须确保外部 API 经过优化,能够快速处理查询并返回数据。
功能局限性
External Objects 并非万能,它们不支持 Salesforce 对象的全部功能,主要限制包括:
- 不支持 Apex Triggers、Flows 和大多数自动化工具。
- 不能位于 Master-Detail 关系的主控端。
- 不支持记录级别的共享规则(Sharing Rules)。
- 公式字段中可用的函数有限。
- 不支持所有标准字段(如 `CreatedDate` 是可用的,但 `LastModifiedDate` 可能需要手动映射)。
总结与最佳实践
Salesforce External Objects 是一个强大的工具,它为“虚拟化”集成提供了一种优雅的解决方案,实现了对外部数据的实时、无缝访问,而无需进行数据复制。
最佳实践:
- 明确使用场景:当数据量大、变化频繁且需要实时访问时,优先考虑使用 External Objects。如果数据相对静态,或需要在 Salesforce 中进行复杂的业务逻辑处理,传统的 ETL 集成可能更合适。
- 优先使用标准适配器:如果外部系统支持 OData,请务必使用 OData 适配器。这能极大地简化配置和维护工作。
- 优化外部 API:确保外部系统的 API 是高效和可扩展的。实现服务端的分页、过滤和排序功能,以减少返回到 Salesforce 的数据量,从而提升性能并减少 Callout 消耗。
- 谨慎规划数据关系:仔细设计 Indirect Lookup 和 External Lookup 关系,确保外部 ID 的唯一性和索引,这是实现数据关联的关键。
- 监控和治理:定期监控 API Callout 的使用情况,并为外部系统的潜在故障或延迟制定应对策略。健壮的错误处理和日志记录机制至关重要。
通过遵循这些原则,技术架构师可以有效地利用 Salesforce Connect 和 External Objects 来构建灵活、可扩展且高效的集成解决方案,真正打破企业内部的数据孤岛。
评论
发表评论