无缝集成外部数据:Salesforce 架构师外部对象深度解析
背景与应用场景
作为一名 Salesforce 架构师 (Salesforce Architect),我日常工作的核心之一便是设计可扩展、高效且安全的系统蓝图。在现代企业复杂的 IT 生态中,数据孤岛是一个普遍存在的挑战。关键业务数据往往分散在不同的系统中,例如 ERP 系统中的订单和发票、PIM 系统中的产品目录、或者专有数据库中的客户服务历史。传统的数据集成方式,如 ETL (Extract, Transform, Load),虽然强大,但其数据同步的延迟性、高昂的存储成本以及复杂的维护流程,使其在需要实时数据的场景中显得力不从心。
正是在这样的背景下,Salesforce 提供了 External Objects (外部对象) 这一强大的功能,它构建于 Salesforce Connect 框架之上。External Objects 的核心理念是 Data Virtualization (数据虚拟化)——它允许我们在 Salesforce 平台内直接查看、搜索甚至修改存储在外部系统中的数据,而无需将这些数据复制和持久化到 Salesforce 的数据库中。这就像在 Salesforce 中为外部数据源打开了一扇“窗户”,用户可以实时地“看到”窗外的数据,并与之交互,但数据本身仍然保留在原地。
这种模式带来了显著的架构优势:
- 实时性: 用户在 Salesforce 中看到的永远是外部系统的最新数据,消除了数据延迟带来的业务决策风险。
- 成本效益: 无需在 Salesforce 中为海量外部数据支付额外的存储费用,极大地降低了 TCO (Total Cost of Ownership)。
- 简化维护: 避免了复杂的 ETL 流程和数据同步逻辑的开发与维护,使集成架构更加轻量和敏捷。
典型的应用场景包括:
- 360度客户视图: 在客户 (Account) 页面上,通过外部对象实时展示来自 ERP 系统的历史订单、发货状态和应收账款,让销售和服务团队获得完整的客户画像。
- 产品信息管理: 对于拥有庞大且频繁更新的产品目录的电商企业,可以通过外部对象直接连接到 PIM (Product Information Management) 系统,确保销售人员在创建机会 (Opportunity) 时引用的总是最新的产品信息。
- 跨组织数据共享: 在大型企业中,可以使用外部对象安全地访问另一个 Salesforce 组织的数据,而无需进行复杂的 API 集成。
原理说明
从架构层面理解 External Objects,关键在于理解其背后的适配器 (Adapter) 模型。当用户通过 UI、SOQL 或 Apex 与一个外部对象交互时,Salesforce Connect 会将这个请求通过一个特定的适配器,转换为外部系统能够理解的查询语言或 API 调用。外部系统执行该请求后返回数据,Salesforce Connect 再将返回的数据格式化,最终呈现给用户。整个过程对用户是透明的,体验上与操作标准或自定义对象非常相似。
Salesforce Connect 主要提供以下几种适配器:
1. OData Adapter
这是最常用的一种适配器。OData (Open Data Protocol / 开放数据协议) 是一个基于 REST 的开放标准,用于构建和消费可查询的、可互操作的 Web API。如果您的外部系统(如 SAP, SharePoint, or a custom web service)暴露了 OData 2.0 或 4.0 的端点 (endpoint),那么集成过程会非常简单。我们只需在 Salesforce 中创建一个 External Data Source (外部数据源),指定 OData 端点的 URL 和身份验证方式(通过 Named Credentials / 指定凭据 来安全地管理),Salesforce 就能自动发现数据结构并允许我们创建相应的外部对象。
2. Cross-Org Adapter
这种适配器专门用于连接不同的 Salesforce 组织。它利用 Salesforce REST API,使得在一个组织中可以像操作本地对象一样访问另一个组织的数据。这对于需要在多个业务部门或区域子公司之间共享数据的企业来说,是一种非常高效和安全的解决方案。
3. Apex Connector Framework
当外部系统不提供 OData 接口,且不是 Salesforce 组织时,我们就需要终极武器——Apex Connector Framework。这是一个强大的框架,允许我们使用 Apex 代码编写自定义的适配器。通过实现 DataSource.Provider
和 DataSource.Connection
等接口,我们可以定义连接任何数据源的逻辑,无论是连接到一个专有的 REST/SOAP API,还是一个 SQL 数据库。这为集成提供了无限的可能性,但同时也要求更高的开发能力和对外部系统 API 的深入理解。
无论使用哪种适配器,查询的执行流程都遵循“按需调用”的原则。例如,一个 SOQL 查询 SELECT Id, Name FROM Orders__x WHERE CustomerId__c = '001...'
会被 Salesforce Connect 翻译。如果使用 OData 适配器,它可能会被翻译成一个类似 /Orders?$filter=CustomerId eq '001...'&$select=Id,Name
的 OData 查询,然后发送到外部系统。这种实时翻译和调用的机制,是 External Objects 实现数据虚拟化的核心。
示例代码
对于架构师而言,理解 Apex Connector Framework 的能力边界至关重要。以下是一个来自 Salesforce 官方文档的示例,展示了如何为假想的数据源创建一个自定义连接器。这个连接器定义了如何同步表结构、如何查询数据以及它支持哪些功能(例如排序)。
注意: 以下代码是用于演示框架结构的,实际实现需要根据目标系统的 API 进行深度定制。
DataSource.Connection Class 示例
这个类是连接逻辑的核心,它实现了数据查询 (query)、同步 (sync) 等关键方法。
// Fictitious example from Salesforce Developer Documentation // This class defines the connection and data retrieval logic for a custom connector. public class MyDataSourceConnection extends DataSource.Connection { private DataSource.ConnectionParams connectionInfo; // Constructor to initialize with connection parameters public MyDataSourceConnection(DataSource.ConnectionParams connectionInfo) { this.connectionInfo = connectionInfo; } // The sync method is called when an admin clicks "Validate and Sync" // It should return a list of tables (which become external objects) available from the source. override public List<DataSource.Table> sync() { // In a real implementation, you would make a callout to the external system // to get its schema or table definitions. List<DataSource.Table> tables = new List<DataSource.Table>(); // Define columns for a sample "Products" table List<DataSource.Column> columns = new List<DataSource.Column>(); columns.add(DataSource.Column.text('Name', 255)); columns.add(DataSource.Column.url('ImageUrl')); columns.add(DataSource.Column.number('Price', 18, 2)); columns.add(DataSource.Column.text('ExternalId', 255)); // Create the table definition // 'Products' is the name of the table in the external system. // 'Name' is the column that will be used as the display name in Salesforce. // 'ExternalId' is the column that will be used as the external ID. tables.add(DataSource.Table.create('Products', 'Name', columns, 'ExternalId')); return tables; } // The query method is called when a user performs a SOQL query, opens a list view, or views a detail page. override public DataSource.TableResult query(DataSource.QueryContext context) { // In a real implementation, you would build a query for the external system // based on the information in the DataSource.QueryContext. // This includes the table name, columns to fetch, filters, sorting, etc. System.debug('Querying table: ' + context.tableSelection.table); // This is a mock implementation. A real one would make an HTTP callout. List<Map<String, Object>> responseData = callExternalSystem(context); // The framework expects the data to be returned as a list of rows, // where each row is a Map of column names to values. return DataSource.TableResult.get(context, responseData); } // A mock method to simulate calling the external system. private List<Map<String, Object>> callExternalSystem(DataSource.QueryContext context) { // Here you would translate the context (filters, order by) into an API call. // For example: context.tableSelection.filter.toString() can give you filter conditions. List<Map<String, Object>> mockData = new List<Map<String, Object>>(); Map<String, Object> row1 = new Map<String, Object>(); row1.put('Name', 'Super Widget'); row1.put('ImageUrl', 'http://example.com/widget.png'); row1.put('Price', 100.50); row1.put('ExternalId', 'WIDGET-001'); mockData.add(row1); Map<String, Object> row2 = new Map<String, Object>(); row2.put('Name', 'Mega Gadget'); row2.put('ImageUrl', 'http://example.com/gadget.png'); row2.put('Price', 250.00); row2.put('ExternalId', 'GADGET-002'); mockData.add(row2); return mockData; } }
DataSource.Provider Class 示例
这个类是连接器的入口点,它声明了连接器支持的功能(例如,是否支持写入操作)并创建连接实例。
// Fictitious example from Salesforce Developer Documentation // This class acts as the factory for the connection. public class MyDataSourceProvider extends DataSource.Provider { // Returns the type of authentication protocol required. // In this case, it specifies using a Named Credential with Password authentication. override public List<DataSource.AuthenticationCapability> getAuthenticationCapabilities() { List<DataSource.AuthenticationCapability> capabilities = new List<DataSource.AuthenticationCapability>(); capabilities.add(DataSource.AuthenticationCapability.OAUTH); capabilities.add(DataSource.AuthenticationCapability.BASIC); // Example: Basic Auth return capabilities; } // Returns the features that this connector supports. // For example, ROW_QUERY enables basic querying. // SEARCH enables SOSL searches. override public List<DataSource.Capability> getCapabilities() { List<DataSource.Capability> capabilities = new List<DataSource.Capability>(); capabilities.add(DataSource.Capability.ROW_QUERY); capabilities.add(DataSource.Capability.SEARCH); // If the external system API supports creating, updating, or deleting records, // you would add DataSource.Capability.DML here. return capabilities; } // This method is called by Salesforce to get an instance of the connection class. override public DataSource.Connection getConnection(DataSource.ConnectionParams connectionInfo) { return new MyDataSourceConnection(connectionInfo); } }
注意事项
作为架构师,在设计包含 External Objects 的解决方案时,必须仔细评估其限制和影响,避免在未来遇到性能瓶颈或功能障碍。
- 权限与安全:
- 认证: 强烈建议使用 Named Credentials (指定凭据) 来管理外部数据源的认证信息。这不仅将认证细节(如用户名、密码、OAuth Token)与代码分离,还简化了不同环境(沙箱、生产)的部署。
- 授权: Salesforce 的配置文件 (Profile) 和权限集 (Permission Set) 可以控制用户对外部对象的 CRUD (Create, Read, Update, Delete) 权限,但这仅仅是 Salesforce 侧的控制。最终的数据访问权限仍然由外部系统决定。必须确保两边的权限模型能够协同工作。
- API 限制与性能:
- Governor Limits: Salesforce Connect 的 callout 有其独立的 Governor Limits,例如每小时的调用次数限制。高流量的场景可能会触及这些限制,需要预先规划和监控。
- 外部系统性能: 整个用户体验的瓶颈往往在外部系统。如果外部系统的 API 响应缓慢,那么在 Salesforce 中加载相关列表视图或记录详情页也会同样缓慢。必须对外部 API 进行性能测试。
- 查询优化: 确保外部系统的 API 支持过滤 (filtering) 和分页 (pagination)。否则,一个简单的查询可能会导致 Salesforce Connect 拉取大量不必要的数据,造成性能问题和超出限制的风险。
- 功能与数据模型限制:
- 不支持的功能: 传统上,外部对象不支持很多标准 Salesforce 功能,例如公式字段中的跨对象引用、触发器 (Triggers)、记录类型 (Record Types)、验证规则 (Validation Rules) 等。虽然平台在不断进化(例如,现在支持在外部对象上触发平台事件),但在设计时必须参考最新的官方文档,明确当前版本的功能边界。
- 不支持聚合查询: SOQL 中的聚合函数(如
COUNT()
,SUM()
)通常不能直接用于外部对象,因为这些计算需要在外部系统端执行。 - 关系: 外部对象可以与标准/自定义对象建立查找关系 (Lookup Relationship)、间接查找关系 (Indirect Lookup Relationship) 和外部查找关系 (External Lookup Relationship),但不支持主从关系 (Master-Detail Relationship)。正确选择关系类型对架构至关重要。
- 可写性 (Writability):
默认情况下,外部对象是只读的。要使其可写,外部数据源和适配器必须支持写入操作。对于 OData 适配器,需要 OData 服务支持相应的 CUD (Create, Update, Delete) 操作。对于 Apex 自定义适配器,则需要实现
DataSource.Updatable
,DataSource.Deletable
等接口。
总结与最佳实践
External Objects 是 Salesforce 平台上一项革命性的集成工具,它完美地诠释了“数据在需要时才获取”的现代集成理念。作为架构师,我们应该将其视为解决特定问题的利器,而不是一个万能的解决方案。
以下是我的核心建议和最佳实践:
- 明确适用场景: External Objects 最适合用于需要实时访问、数据量大但单次访问量小的场景。它非常适合作为“辅助信息”展示在核心业务对象(如客户、机会)旁边,而不是作为业务流程的核心驱动。
- 优先选择标准适配器: 如果外部系统支持 OData,请优先使用 OData 适配器。它的配置简单、性能经过优化,且维护成本最低。只有在别无选择时,才投入资源开发 Apex 自定义连接器。
- 设计时考虑性能: 从一开始就要将性能作为核心设计考量。与外部系统团队紧密合作,确保其 API 是高效和可查询的。在 Salesforce 内部,避免在常用页面上放置过多外部对象相关列表,并对用户进行关于潜在延迟的培训。
- 治理与监控: 建立对 Salesforce Connect API 调用限制的监控机制。定期审查外部数据源的健康状况和性能,确保集成方案的长期稳定。
- 混合使用集成模式: 不要试图用 External Objects 解决所有问题。对于需要进行复杂转换、聚合分析或需要离线访问的数据,传统的 ETL 或基于平台事件的集成模式可能仍然是更好的选择。一个优秀的架构师懂得如何根据业务需求,组合使用不同的集成模式,构建一个弹性的、高效的系统架构。
总之,通过深入理解 External Objects 的工作原理、能力边界和性能特征,我们可以设计出更加智能和高效的 Salesforce 解决方案,真正打破数据壁垒,为企业创造更大的价值。
评论
发表评论