精通 Salesforce 外部对象:深入解析 Salesforce Connect 与 OData 集成


背景与应用场景

在现代企业架构中,数据通常分散在多个异构系统中。例如,订单数据可能存放在 ERP 系统中,客户支持工单在专门的帮助台软件里,而产品目录则由另一个 PIM (Product Information Management) 系统管理。对于 Salesforce 用户来说,他们希望在 CRM 平台中能够 360 度无死角地查看客户信息,但将所有这些外部数据同步或复制到 Salesforce 内部会带来一系列挑战:

  • 数据冗余与存储成本:复制数据会占用宝贵的 Salesforce 存储空间,对于海量数据而言,成本会急剧上升。
  • 数据一致性问题:同步过程可能存在延迟或失败,导致 Salesforce 内的数据与源系统不一致,影响业务决策的准确性。
  • 维护复杂性:需要开发和维护复杂的数据集成(ETL)流程,增加了技术债务和运维成本。

为了解决这些问题,Salesforce 推出了 Salesforce Connect,这是一个强大的集成框架,其核心就是 External Objects (外部对象)。External Objects 是一种特殊的 Salesforce 对象,其数据存储在外部系统中,而不是 Salesforce 数据库中。通过 External Objects,用户可以在 Salesforce 界面(如列表视图、页面布局、报表)和 API 中实时地查看、搜索和(在某些情况下)修改外部数据,就好像这些数据是 Salesforce 的标准或自定义对象一样。

典型的应用场景包括:

  • 订单管理:销售人员在客户页面上,可以直接查看来自 SAP 或 Oracle ERP 系统的实时订单历史和发货状态,而无需切换系统。
  • 客户支持:服务座席在处理 Case 时,能够看到来自外部 Jira 或 Zendesk 系统的关联工单信息,提供更全面的支持。
  • 产品信息:在创建 Opportunity 或 Quote 时,能够直接查询和关联来自外部产品信息管理系统的最新产品目录和库存。
  • 数据归档:对于不常访问的历史数据(如旧的联系人或已关闭的案例),可以将其归档到成本较低的外部数据库中,但仍然通过 External Object 在需要时进行查询。

External Objects 的核心价值在于“数据虚拟化”——它提供了一个访问外部数据的窗口,而不是数据的物理副本,实现了“原地访问”,极大地简化了集成架构。


原理说明

Salesforce Connect 的工作原理可以概括为“元数据在内,数据在外”。它通过一个适配器(Adapter)将 Salesforce 的请求(如 SOQL 查询)实时翻译成外部系统能够理解的查询语言,然后将返回的结果呈现给用户。

1. External Data Source (外部数据源)

这是 Salesforce Connect 的起点。External Data Source (外部数据源) 定义了 Salesforce 如何连接到外部系统。在创建外部数据源时,您需要指定以下关键信息:

  • 类型(Type):连接适配器的类型。Salesforce 提供了多种标准适配器,最常用的是 OData 2.0/4.0。OData (Open Data Protocol) 是一个基于 RESTful API 的开放标准,用于构建和消费可查询、可互操作的数据服务。许多现代系统(如 SAP、SharePoint)都原生支持 OData。此外,还有跨组织适配器(用于连接其他 Salesforce Org)和自定义的 Apex Custom Adapter
  • URL:外部数据服务的端点地址。
  • 身份验证(Authentication):配置 Salesforce 如何安全地连接到外部系统。选项包括匿名访问、密码认证(Per User 或 Named Principal)以及 OAuth 2.0。

2. External Objects (外部对象)

在配置好 External Data Source 后,您可以点击“Validate and Sync”按钮。Salesforce 会连接到外部数据源,发现其暴露的数据表(在 OData 中称为实体集),并允许您选择要同步为 External Objects 的表。这个同步过程只创建元数据——即对象的字段、数据类型等定义,并不会拉取任何数据。

创建好的 External Object 在 Salesforce 中拥有一个 API 名称(以 `__x` 结尾),并具备许多标准对象的特性,例如:

  • 可以设置字段级安全性(Field-Level Security)。
  • 可以添加到页面布局和列表视图中。
  • 可以在全局搜索中被找到。
  • 可以用于报表和仪表盘。
  • 可以通过 SOQL 和 SOSL进行查询。

3. 查询执行流程

当用户在 Salesforce 中执行一个涉及 External Object 的操作时(例如,打开一个列表视图),后台会发生以下事情:

  1. Salesforce 平台接收到用户的请求,并将其转换为一个 SOQL (Salesforce Object Query Language) 查询。
  2. Salesforce Connect 框架捕获这个 SOQL 查询。
  3. 根据 External Data Source 中定义的适配器(如 OData Adapter),框架将 SOQL 查询“翻译”成外部系统支持的格式。例如,一个 SOQL 查询 `SELECT Id, Name FROM Orders__x WHERE CustomerId__c = '001...'` 可能会被翻译成一个 OData GET 请求,如 `https://my-erp.com/odata/Orders?$filter=CustomerId eq '001...'&$select=Id,Name`。
  4. 外部系统执行该查询并返回结果(通常是 JSON 或 XML 格式)。
  5. 适配器接收外部系统的响应,并将其解析为 Salesforce 用户界面或 API 能够理解的格式。
  6. 最终,数据呈现在用户面前。

整个过程是实时的,确保了用户看到的数据永远是最新的。如果需要处理 OData 标准无法满足的复杂逻辑或连接非标准数据源,则可以开发 Apex Custom Adapter,通过 Apex 代码完全自定义数据的连接、查询和处理逻辑。


示例代码

虽然 External Objects 的主要配置是声明式的,但在需要连接到不提供 OData 接口的专有 API 或实现复杂数据转换逻辑时,就需要使用 Apex Connector Framework 创建自定义适配器。以下代码示例展示了一个简化的自定义适配器,它由一个 `DataSource.Provider` 类和一个 `DataSource.Connection` 类组成。

注意:此代码结构源自 Salesforce 官方文档,用于演示 Apex Connector Framework 的基本实现。

DataSource.Provider 实现

Provider 类负责声明适配器的能力(如是否支持排序、过滤等)并建立连接。

// MyCustomDataSourceProvider.cls
// 这个 Provider 类是 Apex Connector Framework 的入口点。
// Salesforce 在配置外部数据源时会调用这个类。
public class MyCustomDataSourceProvider extends DataSource.Provider {

    // getAuthenticationCapabilities() 方法定义了此数据源支持的身份验证类型。
    // 返回 DataSource.AuthenticationCapability.ANONYMOUS 表示允许匿名访问。
    // 其他选项包括 BASIC (用户名密码) 和 OAUTH。
    public override List<DataSource.AuthenticationCapability> getAuthenticationCapabilities() {
        List<DataSource.AuthenticationCapability> capabilities = new List<DataSource.AuthenticationCapability>();
        capabilities.add(DataSource.AuthenticationCapability.ANONYMOUS);
        return capabilities;
    }

    // getCapabilities() 方法声明了此连接器支持的操作类型。
    // 例如,ROW_QUERY 表示支持基本的行查询 (SELECT ... FROM ... WHERE ...)。
    // SEARCH 表示支持搜索。
    public override List<DataSource.Capability> getCapabilities() {
        List<DataSource.Capability> capabilities = new List<DataSource.Capability>();
        capabilities.add(DataSource.Capability.ROW_QUERY);
        capabilities.add(DataSource.Capability.SEARCH);
        return capabilities;
    }

    // getConnection() 是核心方法,当 Salesforce 需要与外部系统交互时调用此方法。
    // 它负责实例化并返回一个 Connection 对象,实际的数据操作在 Connection 类中完成。
    // connectionInfo 参数包含了在设置外部数据源时输入的信息。
    public override DataSource.Connection getConnection(DataSource.ConnectionParams connectionInfo) {
        return new MyCustomDataSourceConnection(connectionInfo);
    }
}

DataSource.Connection 实现

Connection 类是实际处理数据请求的地方,它实现了查询、搜索等具体逻辑。

// MyCustomDataSourceConnection.cls
// 这个 Connection 类处理所有实际的数据交互,如查询和搜索。
public class MyCustomDataSourceConnection extends DataSource.Connection {
    
    private DataSource.ConnectionParams connectionInfo;

    // 构造函数,保存外部数据源的配置信息。
    public MyCustomDataSourceConnection(DataSource.ConnectionParams connectionInfo) {
        this.connectionInfo = connectionInfo;
    }

    // sync() 方法在管理员点击 "Validate and Sync" 时被调用。
    // 它的职责是连接到外部系统,发现可用的数据表和列,并以 DataSource.Table 和 DataSource.Column 的形式返回给 Salesforce。
    // Salesforce 基于这些返回的元数据来创建或更新 External Objects。
    public override List<DataSource.Table> sync() {
        // 示例:手动定义一个名为 'Orders' 的表及其列。
        // 在真实场景中,这里应该是一个 HTTP Callout,去调用外部系统的元数据 API。
        List<DataSource.Table> tables = new List<DataSource.Table>();
        List<DataSource.Column> columns = new List<DataSource.Column>();
        
        columns.add(DataSource.Column.text('OrderID', 255)); // 订单ID,文本类型
        columns.add(DataSource.Column.text('CustomerName', 255)); // 客户名称
        columns.add(DataSource.Column.number('Amount', 18, 2)); // 订单金额
        columns.add(DataSource.Column.url('DisplayUrl')); // 用于在 Salesforce 中显示的记录链接
        
        // isName="true" 和 isExternalId="true" 表示 'OrderID' 字段是记录名和外部ID。
        columns[0].isName = true; 
        columns[0].isExternalId = true;

        tables.add(DataSource.Table.get('Orders', 'OrderID', columns));
        return tables;
    }
    
    // query() 方法是执行 SOQL 查询的核心。
    // 当用户或代码对这个外部对象执行 SOQL 查询时,Salesforce 会调用此方法。
    // queryContext 参数包含了查询的详细信息,如查询的表、要选择的列、过滤条件、排序规则等。
    public override DataSource.Results query(DataSource.QueryContext queryContext) {
        // 在真实场景中,这里会将 queryContext 转换为对外部系统的 API 调用。
        // 例如,构建一个 HTTP 请求,将过滤条件作为查询参数。
        // Http.send(request);

        // 示例:返回硬编码的静态数据以作演示。
        List<Map<String, Object>> data = new List<Map<String, Object>>();
        Map<String, Object> row1 = new Map<String, Object>();
        row1.put('OrderID', 'ORD-001');
        row1.put('CustomerName', 'Tech Corp');
        row1.put('Amount', 1500.50);
        row1.put('DisplayUrl', 'https://example.com/orders/ORD-001');
        data.add(row1);

        Map<String, Object> row2 = new Map<String, Object>();
        row2.put('OrderID', 'ORD-002');
        row2.put('CustomerName', 'Global Solutions');
        row2.put('Amount', 2750.00);
        row2.put('DisplayUrl', 'https://example.com/orders/ORD-002');
        data.add(row2);
        
        return DataSource.Results.builder().result(data).build();
    }
    
    // search() 方法用于处理 SOSL 搜索请求。
    public override List<DataSource.SearchResult> search(DataSource.SearchContext searchContext) {
        // 实现搜索逻辑,调用外部系统的搜索 API,并返回结果。
        // 此处省略实现细节。
        return new List<DataSource.SearchResult>();
    }
}

注意事项

作为一名技术架构师,在使用 External Objects 时必须充分考虑其限制和影响,以确保方案的可行性和高性能。

1. 权限与安全

  • 对象和字段级权限:External Objects 像标准对象一样,遵循 Profile 和 Permission Set 定义的对象级和字段级安全设置。
  • 外部系统身份验证:在 External Data Source 中配置的身份验证至关重要。Named Principal 模式下,所有 Salesforce 用户都使用同一个凭证访问外部系统;而 Per User 模式下,每个 Salesforce 用户需要用自己的凭证进行身份验证,这可以实现更精细的外部系统权限控制。

2. API 限制与性能

  • Callout 限制:Salesforce Connect 有其独立的 Callout 限制。例如,每小时从 Salesforce 对外部系统的请求次数是有限的。在设计高流量应用时,必须参考 Salesforce 的最新限制文档,避免超出配额。
  • 查询延迟:由于每次访问数据都需要进行一次网络调用(Callout),其性能严重依赖于外部系统的响应速度和网络延迟。复杂的报表或包含大量外部对象的页面加载速度可能会很慢。
  • 分页(Paging):分页处理方式会影响性能。Client-driven paging(客户端分页)会一次性获取大量数据到 Salesforce 端再进行分页,消耗更多资源;而 Server-driven paging(服务器端分页)则由外部系统处理分页,效率更高。OData V4 通常支持服务器端分页。

3. 功能与数据模型限制

  • 不支持的 Salesforce 功能:External Objects 不支持所有标准功能。例如,它们不能用于触发 Apex Triggers、大多数工作流规则和批准流程(尽管 Flow 的支持正在增强)。它们也不能被记录在 Activities 中或拥有 Notes & Attachments。
  • 关系限制:
    • Lookup Relationships:支持标准/自定义对象到外部对象的查找(Lookup)、外部对象到外部对象的查找(在同一数据源内)。
    • Indirect & External Lookups:通过 Indirect Lookup (间接查找) 可以实现子级标准/自定义对象关联到父级外部对象。通过 External Lookup (外部查找) 可以实现子级外部对象关联到父级标准/自定义对象。这两种关系类型是构建混合数据模型的关键。
    • Master-Detail Relationships:完全不支持。
  • SOQL 限制:并非所有 SOQL 子句都受支持。例如,对于 External Objects 的查询不支持 `AGGREGATE` 函数(如 `COUNT()`、`SUM()`),并且 `ORDER BY` 的字段也有限制。
  • 公式字段:涉及跨对象引用的公式字段,如果引用了外部对象,可能会导致额外的 Callout,影响性能。

总结与最佳实践

External Objects 和 Salesforce Connect 是一个强大的集成工具,它遵循“系统记录(System of Record)”的最佳实践,让数据保留在其源头,同时在 Salesforce 中实现无缝访问。它并非要取代所有 ETL 场景,而是为特定用例提供了更轻量、更实时的解决方案。

何时选择 External Objects?

  • 当您需要实时访问外部数据,且数据变化频繁时。
  • 当外部数据量非常大,不适合或不希望将其全部复制到 Salesforce 时。
  • 当您需要快速集成,提供对外部数据的基本查看和搜索功能时。
  • 当外部系统是该数据的唯一真实来源 (Single Source of Truth) 时。

何时应避免使用 External Objects?

  • 当您需要在数据上执行复杂的报表、分析和聚合操作时(ETL 到 Tableau CRM 或核心 Salesforce 报表对象中可能更合适)。
  • 当您需要在数据变更时触发复杂的 Salesforce 自动化(如 Apex Triggers)时。
  • 当外部系统响应缓慢或不稳定,会严重影响 Salesforce 用户体验时。
  • 当您需要对数据进行频繁的批量更新或删除操作时。

最佳实践:

  1. 优先选择标准适配器:如果外部系统支持 OData,请优先使用 OData 2.0/4.0 适配器。这能最大限度地减少开发和维护工作。
  2. 谨慎设计查询:在列表视图、报表和自定义代码中,始终添加选择性强的过滤条件,避免查询返回大量数据,以提高性能。
  3. 理解性能影响:在页面布局中添加外部对象相关列表或字段之前,请充分测试其对页面加载时间的影响。
  4. 合理规划关系:善用 Indirect 和 External Lookups 来连接 Salesforce 内部和外部的数据,构建统一的数据视图。
  5. 监控限制:密切关注 Salesforce Connect 的 Callout 使用情况,确保您的应用不会因为超出限制而中断服务。

总而言之,Salesforce External Objects 是技术架构师工具箱中一件优雅而强大的工具。通过深刻理解其工作原理、适用场景和 inherent limitations (固有局限性),您可以设计出既高效又可扩展的集成解决方案,真正实现客户信息的 360 度视图。

评论

此博客中的热门博文

Salesforce 登录取证:深入解析用户访问监控与安全

Salesforce Experience Cloud 技术深度解析:构建社区站点 (Community Sites)

Salesforce Data Loader 全方位指南:数据迁移与管理的最佳实践