Salesforce 外部对象:架构师的实时数据访问蓝图

背景与应用场景

在当今的企业 IT 生态中,数据孤岛是一个普遍存在的挑战。关键业务数据往往分散在多个系统中,例如 ERP 系统(如 SAP、Oracle)、专有数据库、大型机或其他的云服务中。作为一名 Salesforce 架构师,我们的核心职责之一是设计能够提供统一、全面的客户视图的解决方案。然而,传统的 ETL (Extract, Transform, Load) 数据同步方案虽然成熟,却面临着数据延迟、存储成本高昂、维护复杂等问题,尤其是在处理海量或需要实时更新的数据时,其弊端愈发明显。

正是在这样的背景下,Salesforce 推出了 External Objects (外部对象),这是一种基于 Salesforce Connect 技术的强大功能。从架构师的视角来看,External Objects 并非一种数据复制工具,而是一种数据虚拟化 (Data Virtualization) 的实现。它允许我们在 Salesforce 平台内直接查看、搜索甚至修改存储在外部系统中的数据,而无需将这些数据实际复制到 Salesforce 的数据库中。这种“按需访问”的模式为我们提供了极大的灵活性和效率。

典型的应用场景包括:

  • 360 度客户视图集成:在客户(Account)页面上,直接展示来自 ERP 系统的实时订单历史、发货状态或账单信息。销售人员无需切换系统即可获得完整的客户交互背景,极大地提升了工作效率。
  • 大型数据卷 (Large Data Volumes, LDV) 处理:对于物联网 (IoT) 设备产生的海量时间序列数据、网站点击流日志或金融交易记录,将其全部存入 Salesforce 不仅成本高昂,也可能触及存储限制。通过 External Objects,我们可以将这些数据保留在专门的大数据平台(如 Heroku Postgres 或 AWS RDS)中,同时在 Salesforce 中进行有针对性的查询和展示。
  • - 数据合规性与驻留:某些行业(如金融、医疗)对数据的存储位置有严格的法律法规要求。External Objects 允许我们将敏感数据保留在符合规定的本地数据中心或私有云中,同时通过 Salesforce 提供安全的访问接口,做到“数据不动,服务动”。 - 遗留系统现代化:对于那些功能依然稳定但用户界面陈旧的遗留系统,我们可以利用 External Objects 将其后端数据暴露给 Salesforce,通过 Lightning Experience 强大的 UI/UX 能力为其赋予现代化的“新面孔”,延长其服务生命周期,降低重构风险。

原理说明

External Objects 的核心是 Salesforce Connect,它充当了 Salesforce 平台与外部数据源之间的桥梁。当用户在 Salesforce 中与一个外部对象交互时(例如,打开一个列表视图或查看一条记录),整个过程是实时发生的,其基本工作流程如下:

1. 请求发起:用户在 Salesforce UI 或通过 API 发起一个数据请求,例如一个 SOQL (Salesforce Object Query Language) 查询。

2. 请求翻译:Salesforce Connect 捕获这个请求,并将其翻译成外部数据源能够理解的格式。这个翻译过程由所谓的适配器 (Adapter) 完成。

3. 外部调用:适配器通过网络向外部系统发送一个经过翻译的查询请求。例如,一个 SOQL 查询可能会被翻译成一个 OData URL 或一个自定义的 REST API 调用。

4. 数据返回:外部系统处理该请求,并将结果集返回给 Salesforce Connect。

5. 结果呈现:Salesforce Connect 将返回的数据格式化,使其看起来和标准的 Salesforce 对象数据完全一样,并最终呈现给用户。

Salesforce Connect 提供了多种适配器类型,以满足不同的集成需求,这是我们作为架构师在设计方案时必须仔细评估和选择的:

OData 2.0 / 4.0 适配器

OData (Open Data Protocol) 是一个基于 REST 的开放标准,用于构建和消费可查询、可互操作的 Web API。如果外部系统已经提供了 OData 端点,这是连接的首选方式。它具有标准化、配置简单、功能全面(支持 CRUD 操作,即创建、读取、更新、删除)的优点。我们只需在 Salesforce 中创建一个外部数据源 (External Data Source),并提供 OData 服务的根 URL 即可。

跨组织 (Cross-Org) 适配器

此适配器专门用于连接两个不同的 Salesforce 组织。它利用 Salesforce REST API,可以方便地将一个组织中的数据实时暴露给另一个组织,非常适用于企业内部不同业务部门或子公司之间的 Salesforce 实例集成。

自定义 Apex 适配器 (Custom Apex Adapter)

当外部系统不提供标准的 OData 接口,或者我们需要实现复杂的业务逻辑(如数据转换、调用多个 API 端点、处理特殊的认证协议)时,自定义 Apex 适配器就成了我们的终极武器。通过实现 Apex Connector Framework,开发人员可以编写自己的 Apex 代码来定义 Salesforce 如何连接、查询和操作任何外部数据源。这为我们提供了最大的灵活性和控制力,但同时也带来了更高的开发和维护成本。


示例代码

作为架构师,理解自定义 Apex 适配器的实现机制至关重要,因为它代表了 Salesforce Connect 的最高灵活性。以下是一个基于 Salesforce 官方文档的简化示例,用于演示如何创建一个 Apex Connector 来连接一个虚构的数据源。这个连接器只返回一些硬编码的数据,但完整地展示了框架的结构。

我们需要创建两个主要的 Apex 类:一个 `Provider` 类和一个 `Connection` 类。

1. DataSourceProvider 类

这个类用于声明我们的连接器支持哪些功能(例如,搜索、行内编辑)并建立连接。

/*
 * 此 Provider 类是 Apex Connector Framework 的入口点。
 * Salesforce 通过它来获取连接器的能力和建立连接实例。
 */
global class MyDataSourceProvider extends DataSource.Provider {
    // 声明连接器支持的功能。
    // 在本例中,我们声明支持基本查询 (ROW_QUERY) 和搜索 (SEARCH)。
    override global 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;
    }

    // 当 Salesforce 需要与外部系统通信时,会调用此方法来获取一个连接实例。
    // a `DataSource.Connection` 类的实例负责处理实际的数据操作。
    override global DataSource.Connection getConnection(
        DataSource.ConnectionParams connectionParams) {
        return new MyDataSourceConnection(connectionParams);
    }
}

2. DataSourceConnection 类

这个类是连接器的核心,负责处理实际的数据同步、查询和搜索逻辑。

/*
 * 此 Connection 类实现了与外部数据源的实际交互逻辑。
 * 它包含同步 (sync)、查询 (query) 和搜索 (search) 等核心方法。
 */
global class MyDataSourceConnection extends DataSource.Connection {

    private DataSource.ConnectionParams connectionInfo;

    // 构造函数,保存连接参数
    global MyDataSourceConnection(
        DataSource.ConnectionParams connectionParams) {
        this.connectionInfo = connectionParams;
    }

    // sync() 方法用于发现外部系统的数据结构(表和列),
    // 并在 Salesforce 中创建或更新相应的外部对象元数据。
    override global List<DataSource.Table> sync() {
        // 创建一个名为 "simple" 的表的元数据定义。
        // 这将对应 Salesforce 中的一个名为 "simple__x" 的外部对象。
        DataSource.Table simpleTable = DataSource.Table.get('simple', 'Name');

        // 为该表定义列。
        // 'Name' 是外部显示 URL 的标准字段。
        // 'Id' 是外部 ID 的标准字段。
        simpleTable.columns.add(DataSource.Column.get('Id', 'string'));
        simpleTable.columns.add(DataSource.Column.get('Name', 'string'));
        simpleTable.columns.add(DataSource.Column.get('Data', 'string'));
        
        List<DataSource.Table> tables = new List<DataSource.Table>();
        tables.add(simpleTable);
        return tables;
    }
    
    // query() 方法是响应 SOQL 查询的核心。
    // Salesforce 将 SOQL 查询转换为 DataSource.QueryContext 对象传递给此方法。
    override global DataSource.TableResult query(DataSource.QueryContext context) {
        // 在实际场景中,这里会发起对外部系统的 API 调用。
        // 为简化示例,我们返回硬编码的静态数据。
        List<Map<String, Object>> responseData = new List<Map<String, Object>>();
        
        // 创建第一行数据
        Map<String, Object> row1 = new Map<String, Object>();
        row1.put('Id', '1');
        row1.put('Name', 'Row 1');
        row1.put('Data', 'This is the first row.');
        responseData.add(row1);
        
        // 创建第二行数据
        Map<String, Object> row2 = new Map<String, Object>();
        row2.put('Id', '2');
        row2.put('Name', 'Row 2');
        row2.put('Data', 'This is the second row.');
        responseData.add(row2);
        
        // 使用 DataSource.TableResult.get() 静态方法来构建并返回结果。
        // context.tableSelection.columns 确保我们只返回查询请求的列。
        return DataSource.TableResult.get(context, responseData);
    }

    // search() 方法响应 SOSL 查询。
    override global List<DataSource.TableResult> search(DataSource.SearchContext context) {
        // 此处应实现对外部系统的搜索逻辑。
        // 为简化,我们直接复用 query() 的逻辑来返回所有结果。
        List<DataSource.TableResult> searchResults =
            new List<DataSource.TableResult>();
        for (DataSource.TableSelection tableSelection : context.tableSelections) {
            DataSource.QueryContext queryContext = DataSource.QueryContext.get(tableSelection);
            searchResults.add(this.query(queryContext));
        }
        return searchResults;
    }
}

注意事项

在设计和实施 External Objects 解决方案时,架构师必须仔细权衡其优势与局限性,并考虑以下关键点:

权限与安全

  • 认证:强烈建议使用 Named Credentials (命名凭证) 来管理外部系统的认证信息。它将端点 URL 和认证细节(如密码、OAuth 令牌)从代码中分离出来,便于管理和维护,并且更加安全。支持两种认证模式:共享(所有用户使用同一个凭证)和每用户(每个用户提供自己的凭证)。
  • 权限集:对外部对象的访问权限(CRUD)同样通过简档 (Profile) 和权限集 (Permission Set) 来控制,与标准对象类似。字段级别安全性 (FLS) 也同样适用。

性能与 API 限制

  • 延迟是关键:每一次对外部对象数据的访问都会触发一次到外部系统的实时调用 (Callout)。因此,外部系统的响应延迟会直接影响 Salesforce 的用户体验。架构设计时必须评估外部系统的性能,并与业务方明确性能预期。
  • 专属限制:Salesforce Connect 的调用有其独立的、更宽松的限制。例如,Apex Connector 的最长超时时间可达 120 秒,远高于常规 Apex Callout 的限制。但同时,对外部数据源的调用总数也受限于组织的 API 调用配额。
  • SOQL/SOSL 局限性:并非所有的 SOQL 和 SOSL 功能都支持外部对象。最显著的限制是不支持聚合查询(如 `COUNT()`、`SUM()`、`GROUP BY`)。这意味着你无法直接在外部对象上构建复杂的聚合报告。此类需求通常需要通过其他方式(如将聚合结果同步到 Salesforce 的一个标准对象中)来实现。
  • 报表和仪表板:包含外部对象的报表存在一些限制。例如,外部对象不能作为联合报表 (Joined Report) 中的主对象。在设计报表方案时必须考虑到这些限制。

数据关系与管理

  • 关系类型:External Objects 引入了新的关系类型:
    • 外部查找关系 (External Lookup):用于将 Salesforce 中的一个对象(标准或自定义)关联到另一个外部对象。这是最常见的父子关系。
    • 间接查找关系 (Indirect Lookup):用于将 Salesforce 中的一个子对象关联到一个外部父对象。它依赖于子对象上的一个自定义字段,该字段的值与父外部对象上的一个唯一外部 ID 匹配。
    正确选择关系类型是数据模型设计的关键。
  • 可写性:External Objects 默认是只读的。要使其可写,外部数据源本身必须支持创建、更新和删除操作,并且在外部数据源的配置中需要勾选“可写外部对象”选项。对于 OData 适配器,这通常是开箱即用的;对于 Apex 适配器,则需要开发者在 `DataSource.Connection` 类中实现 `upsertRows` 和 `deleteRows` 方法。

总结与最佳实践

作为 Salesforce 架构师,External Objects 是我们工具箱中一件应对复杂集成场景的利器。它完美诠释了“数据虚拟化”的理念,让我们能够在正确的时间、正确的地点,以正确的方式访问数据,而不是盲目地进行数据复制。

以下是一些关键的最佳实践:

  1. 明确适用场景:优先将 External Objects 用于实时性要求高、数据量大、不常用于复杂聚合分析的场景。对于需要深度参与 Salesforce 内部业务流程(如自动化、复杂报表)或需要高性能聚合的数据,传统的 ETL 或数据复制方案可能仍然是更优选择。
  2. 优先选择标准适配器:如果外部系统支持 OData,应始终优先使用 OData 适配器。这能最大限度地减少开发和维护工作量,并能利用 Salesforce 平台对该标准的持续改进。
  3. 为性能而设计:在页面布局中谨慎使用外部对象字段,避免在单一页面上触发过多的外部调用。利用 SOQL `WHERE` 子句尽可能地在源头筛选数据,减少网络传输的数据量。
  4. 安全第一:始终使用 Named Credentials 来管理认证,避免硬编码凭证。根据最小权限原则,精细地为用户分配对外部对象的访问权限。
  5. 管理用户期望:向业务用户和利益相关者清晰地沟通,外部数据源的性能和可用性会直接影响 Salesforce 的体验。建立监控和告警机制,以便在外部系统出现问题时能够快速响应。

总而言之,Salesforce External Objects 提供了一种优雅而强大的方式来打破数据孤岛,构建无缝集成的用户体验。通过深刻理解其工作原理、优势和局限性,我们可以设计出既灵活又可扩展的解决方案,真正实现以客户为中心的数据驱动型架构。

评论

此博客中的热门博文

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

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

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