精通 Salesforce Connect:架构师视角下的外部对象与数据虚拟化指南

背景与应用场景

作为一名 Salesforce 架构师 (Salesforce Architect),我们日常工作的核心是设计可扩展、高性能且安全的解决方案,以满足复杂的业务需求。其中,数据集成是几乎所有大型项目都无法回避的挑战。传统的集成模式,如 ETL (Extract, Transform, Load - 提取、转换、加载),虽然成熟,但其固有的数据延迟、存储成本和维护复杂性,使其在某些场景下显得力不从心。

想象以下几个常见的业务场景:

  • 实时订单查询:一家制造企业的销售代表希望在 Salesforce 的客户(Account)页面上,直接看到该客户在后端 ERP 系统(如 SAP 或 Oracle)中的实时订单状态和历史记录,而无需切换系统。
  • 海量数据访问:一家金融服务公司需要分析存储在外部数据仓库中的数亿条交易记录,但将这些数据全部复制到 Salesforce 不仅会超出存储限制,而且成本高昂。
  • 遗留系统现代化:一个组织仍在使用一个功能强大但界面陈旧的内部系统。他们希望利用 Salesforce 平台强大的 UI 和自动化能力来操作这些数据,但短期内无法对后端系统进行重构或迁移。
  • 数据合规性要求:出于隐私或法规要求(如 GDPR),某些敏感的客户数据必须保留在企业本地的数据库中,不能存储在云端。

在这些场景下,将数据“搬运”到 Salesforce 并不是最佳选择。我们需要一种更敏捷、更高效的方式来“访问”而非“复制”数据。这正是 Salesforce Connect 发挥其核心价值的地方。Salesforce Connect 是一种强大的数据虚拟化 (Data Virtualization) 框架,它允许您将存储在 Salesforce 外部系统中的数据,以外部对象 (External Objects) 的形式无缝地呈现在 Salesforce UI 中。用户可以像操作标准或自定义对象一样查看、搜索、甚至修改这些外部数据,而数据本身始终保留在其源系统中。


原理说明

Salesforce Connect 的核心理念是“按需访问”。它并不是一个数据同步工具,而是一个实时的数据查询代理。当用户在 Salesforce 中与一个外部对象交互时,其工作流程大致如下:

1. 用户请求:用户点击一个与外部对象关联的选项卡、相关列表或报表。

2. 查询转换:Salesforce 平台将用户的操作(如视图加载、记录查看)转换为对外部系统的查询。例如,一个 SOQL 查询会被转换成一个 OData 查询。

3. 外部连接:Salesforce 通过预先配置的外部数据源 (External Data Source),使用特定的适配器 (Adapter) 将这个查询发送到外部系统。

4. 外部系统响应:外部系统接收并处理该查询,然后将结果集返回给 Salesforce。

5. 结果呈现:Salesforce 接收到数据后,将其呈现在用户界面上,整个过程对用户来说是透明的,感觉就像在访问 Salesforce 内部的数据一样。

为了实现这一过程,Salesforce Connect 提供了多种适配器:

  • OData 2.0/4.0 Adapter: 这是最常用的适配器。OData (Open Data Protocol - 开放数据协议) 是一种基于 REST 的 Web 服务标准,专门用于查询和操作数据。如果您的外部系统(如 SAP, SharePoint, 或自定义服务)暴露了 OData 端点,集成过程会非常简单,几乎完全是声明式的。
  • Cross-Org Adapter: 用于连接另一个 Salesforce 组织。这在公司有多个 Salesforce 实例并希望共享数据时非常有用。
  • Custom Adapter (via Apex Connector Framework): 当外部系统不提供标准 OData 接口时,这就成了架构师的利器。我们可以使用 Apex 编写自定义适配器,来连接任何能够通过 Apex 代码访问的数据源,无论是专有的 REST/SOAP API、SQL 数据库(通过中间件),还是其他任何系统。

从数据模型的角度看,外部对象与标准/自定义对象在关系上通过三种特殊的查找关系字段进行连接:

  • 外部查找关系 (External Lookup Relationship): 用于将 Salesforce 的子对象(标准或自定义)链接到外部对象父对象。
  • 间接查找关系 (Indirect Lookup Relationship): 用于将 Salesforce 的父对象链接到外部对象子对象。它依赖于外部对象上的一个具有 `External ID` 和 `Unique` 属性的字段来匹配 Salesforce 对象上的字段值。
  • 查找关系 (Lookup Relationship): 可以从外部对象链接到标准或自定义对象,但这通常需要外部数据源支持这种关联。

示例代码

对于架构师而言,最能体现技术深度的是使用 Apex Connector Framework 创建自定义适配器。当 OData 不可用时,这为我们打开了连接万物的可能性。以下示例代码片段来自 Salesforce 官方文档,展示了如何实现一个自定义连接器来从外部数据源查询数据。

一个完整的自定义适配器通常包含两个核心类:一个 `DataSource.Provider` 类和一个 `DataSource.Connection` 类。

1. Provider 类 (DataSource.Provider)

Provider 类负责声明连接器的能力和验证连接设置。

global class MyCustomDataSourceProvider extends DataSource.Provider {
    // 声明此连接器支持哪些能力
    // 例如,是否支持查询、搜索或写入操作
    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);     // 支持搜索
        capabilities.add(DataSource.Capability.SYNC);       // 支持 Validate and Sync
        return capabilities;
    }

    // 声明身份验证类型
    // 例如,是匿名访问、基本认证还是 OAuth
    override global List<DataSource.AuthenticationCapability> getAuthenticationCapabilities() {
        List<DataSource.AuthenticationCapability> authCapabilities = new List<DataSource.AuthenticationCapability>();
        authCapabilities.add(DataSource.AuthenticationCapability.ANONYMOUS);
        return authCapabilities;
    }

    // 返回一个 DataSource.Connection 类的实例
    // Salesforce 平台将使用这个连接实例来执行实际的数据操作
    override global DataSource.Connection getConnection(DataSource.ConnectionParams connectionParams) {
        return new MyCustomDataSourceConnection(connectionParams);
    }
}

2. Connection 类 (DataSource.Connection)

Connection 类是执行数据操作的核心,它实现了 `sync()` 和 `query()` 等关键方法。

global class MyCustomDataSourceConnection extends DataSource.Connection {
    private DataSource.ConnectionParams connectionInfo;

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

    // sync() 方法在管理员点击 "Validate and Sync" 时被调用
    // 它的作用是发现外部系统中的 "表" (Tables) 和 "列" (Columns),
    // 以便在 Salesforce 中创建对应的外部对象和字段
    override global List<DataSource.Table> sync() {
        // 在真实场景中,这里会发起一个 HTTP Callout 到外部系统的元数据端点
        // 以下为示意代码
        List<DataSource.Table> tables = new List<DataSource.Table>();

        // 定义外部表的列
        List<DataSource.Column> columns = new List<DataSource.Column>();
        columns.add(DataSource.Column.text('id', 255));
        columns.add(DataSource.Column.text('name', 255));
        columns.add(DataSource.Column.text('orderStatus', 50));
        columns.add(DataSource.Column.url('displayUrl')); // 用于在 Salesforce 中直接链接到外部记录

        // 创建一个名为 "Orders" 的外部表定义
        tables.add(DataSource.Table.create('Orders', 'name', columns));
        return tables;
    }

    // query() 方法在用户通过 UI、SOQL 或 API 查询外部对象时被调用
    // 这是获取实际数据的核心逻辑
    override global DataSource.Results query(DataSource.QueryContext context) {
        // context 对象包含了查询的所有信息,如查询的表名、列、过滤条件、排序等
        System.debug('Querying table: ' + context.tableSelection.table);
        
        // 构造一个对外部系统的 HTTP 请求
        // HttpRequest req = new HttpRequest();
        // req.setEndpoint('https://api.my-erp.com/orders?' + buildQueryString(context));
        // req.setMethod('GET');
        // ... 设置认证头等

        // Http http = new Http();
        // HttpResponse res = http.send(req);

        // 解析返回的 JSON/XML 数据
        // List<Map<String, Object>> externalData = (List<Map<String, Object>>) JSON.deserializeUntyped(res.getBody());

        // **以下为硬编码的示例数据,用于演示目的**
        List<Map<String, Object>> externalData = new List<Map<String, Object>>();
        Map<String, Object> row1 = new Map<String, Object>();
        row1.put('id', 'ORD-001');
        row1.put('name', 'Order 001');
        row1.put('orderStatus', 'Shipped');
        row1.put('displayUrl', 'https://example.com/orders/ORD-001');
        externalData.add(row1);

        // 将外部数据映射到 DataSource.Row
        List<DataSource.Row> resultRows = new List<DataSource.Row>();
        for (Map<String, Object> item : externalData) {
            resultRows.add(DataSource.Row.create(context.tableSelection, item));
        }

        return DataSource.Results.create(resultRows);
    }
}

注意事项

作为架构师,在设计 Salesforce Connect 方案时,我们必须充分考虑其限制和潜在风险,并向业务方明确这些权衡。

  1. 性能与延迟:外部对象的性能完全取决于外部系统的响应速度和网络延迟。一个缓慢的后端 API 会直接导致 Salesforce 页面加载缓慢,用户体验下降。在方案设计阶段,必须对外部系统的 API 进行严格的性能测试。
  2. API 调用限制:每一次对外部对象数据的访问(无论是加载列表视图、打开记录详情,还是运行报表)都会消耗 Salesforce 的 API Callout 限制。在高频访问的场景下,这可能很快耗尽组织的每日限额。需要仔细评估使用模式和频率。
  3. SOQL 和报表限制:外部对象不支持所有的 SOQL 功能。例如,它们不支持聚合函数(如 `COUNT()`, `SUM()`, `GROUP BY`),这使得在 Salesforce 中对外部数据进行复杂的聚合报表变得非常困难甚至不可能。大部分分析需求仍需在外部系统或专门的数据仓库中完成。
  4. 安全性与身份验证:连接外部系统必须安全可靠。最佳实践是使用命名凭据 (Named Credentials)。它将端点 URL 和身份验证信息(如密码、API 密钥或 OAuth 令牌)从代码中分离出来,由 Salesforce 安全地管理,简化了认证流程并提高了安全性。
  5. 数据可写性:并非所有外部数据源都支持写入操作。即使支持,也需要确保外部系统 API 具备事务处理和错误回滚能力,以避免数据不一致。OData 4.0 规范比 2.0 提供了更强大的写入和批处理支持。
  6. 搜索行为:虽然外部对象支持全局搜索 (SOSL),但其工作方式与标准对象不同。Salesforce 不会为外部数据创建索引。搜索请求会被实时发送到外部系统,依赖于外部系统的搜索能力。

总结与最佳实践

Salesforce Connect 是一个强大的架构工具,但它不是万能的。它不是 ETL 的替代品,而是其重要的补充。成功的关键在于理解何时使用它,以及如何规避其局限性。

何时应该优先考虑 Salesforce Connect?

  • 当业务需要实时或接近实时地访问外部数据时。
  • 当外部数据量非常庞大,不适合或无法经济地存储在 Salesforce 中时。
  • 当数据由于合规性或主权原因必须保留在源系统时。
  • 当您希望为用户提供一个统一的界面来处理分散在多个系统中的数据,而无需进行复杂的数据迁移时。

何时应该考虑其他方案(如 ETL)?

  • 当您需要对数据进行复杂的转换、清洗和聚合操作时。
  • 当您需要在 Salesforce 中运行复杂的分析报表和仪表板时。
  • 当外部系统性能不稳定,或无法提供可靠的实时 API 时。
  • 当用户需要对数据进行离线访问时(因为 Salesforce Connect 需要实时连接)。

架构师最佳实践:

  1. 明确数据所有权:始终将外部系统视为数据的“真实来源 (Source of Truth)”。避免在 Salesforce 和外部系统之间创建复杂双向同步逻辑,除非业务场景明确要求并且经过了审慎设计。
  2. API 优先设计:与后端团队紧密合作,确保外部系统提供的 API 是为 Salesforce Connect 的使用场景而优化的。这包括支持服务端的分页 (Server-Side Paging)、过滤 (Filtering) 和排序 (Sorting),以最大限度地减少传输的数据量。
  3. 使用命名凭据:这是管理认证和端点的唯一推荐方式,可以实现安全、可维护的连接。
  4. 从小处着手,逐步扩展:从一两个关键的、只读的外部对象开始,验证方案的可行性和性能。在获得成功并收集用户反馈后,再逐步引入更复杂的功能,如可写操作和更多的外部对象。
  5. 用户培训与预期管理:向最终用户清楚地解释外部对象可能存在的加载延迟,让他们了解这是在访问一个外部系统,从而管理好他们的使用预期。

总之,Salesforce Connect 为我们提供了一种优雅的方式来打破数据孤岛,实现“无边界”的 CRM 体验。作为架构师,深刻理解其原理、能力和局限性,将使我们能够设计出既能满足当前业务需求,又具备未来扩展性的高效集成解决方案。

评论

此博客中的热门博文

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

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

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