精通 Salesforce 异步 Apex:开发者 Future、Queueable 与批处理作业指南
背景与应用场景
作为一名 Salesforce 开发人员,我们日常工作中不可避免地会遇到平台对我们代码执行的各种限制,即所谓的 Governor Limits (总督限制)。这些限制是为了保证多租户环境的公平性和稳定性,例如单次事务中 DML 语句的数量、CPU 执行时间、SOQL 查询行数等。当我们需要处理复杂的业务逻辑、操作大量数据,或是调用外部系统的 API 时,很容易就会触碰到这些天花板,导致事务失败,用户体验下降。
想象以下几个场景:
1. 外部系统集成
当一个 Account 记录被创建或更新后,需要立刻调用一个外部的 ERP 系统 API,将客户数据同步过去。这个 API 调用可能会因为网络延迟而耗时较长。如果将其放在同步的触发器(Trigger)中,用户在保存记录时就必须等待 API 调用完成,这会导致页面长时间无响应,严重影响用户体验。如果 API 调用失败,整个 DML 操作可能都会回滚。
2. 大批量数据处理
业务部门需要对系统中所有符合特定条件的 Contact 记录执行一次复杂的数据清洗和更新操作。这些记录可能有数十万甚至数百万条。在单次同步事务中处理这么多数据是完全不可能的,会立刻超出 DML 限制和 CPU 时间限制。
3. 复杂的计算任务
在生成一份复杂的报价单(Quote)后,系统需要根据其所有报价单行项目(Quote Line Items)进行复杂的利润计算、折扣分配和佣金核算。这个过程可能涉及多次查询和大量的计算,如果同步执行,同样会面临超时风险。
为了优雅地解决这些问题,Salesforce 平台提供了强大的异步 Apex (Asynchronous Apex) 框架。异步 Apex 允许我们将耗时较长、资源密集型的操作放到后台线程中执行,从而将它们与用户直接交互的主线程分离开。这样做不仅可以避免触碰同步 Governor Limits,还能显著提升应用的响应速度和用户体验。主线程可以立即完成它的任务(例如保存记录),而后台任务则在 Salesforce 平台的资源调度下排队等待执行。
原理说明
异步 Apex 的核心原理是将任务提交到一个队列中,由 Salesforce 平台在资源可用时分配一个独立的线程来执行。这个新线程拥有自己的一套、通常更宽松的 Governor Limits。例如,同步 Apex 的 CPU 执行时间限制是 10,000 毫秒,而异步 Apex 则是 60,000 毫秒。
Salesforce 平台主要提供了四种异步 Apex 的实现方式,每种方式都有其特定的适用场景:
1. Future Methods
这是最简单的一种异步执行方式。通过在方法上添加 @future
注解,我们可以告诉平台这个方法应该在后台异步执行。Future 方法非常适合执行一些独立的、"即发即忘" (fire-and-forget) 的任务,例如在触发器上下文中发起一个对外部系统的 API 调用。
特点:
- 必须是静态 (static) 方法。
- 返回值必须是 void。
- 参数类型必须是基本数据类型 (primitive data types)、基本数据类型的数组或集合。不能直接传递 sObject 对象作为参数,需要先将其 ID 传递过去,然后在方法内部重新查询。
- 无法直接监控其执行状态,也没有办法将多个 Future 方法链接起来形成一个执行链。
2. Queueable Apex
Queueable Apex 是对 Future 方法的增强和现代化替代方案。它通过实现 Queueable
接口来定义一个可以被添加到作业队列中的任务。相比 Future 方法,它提供了更强大的功能和灵活性。
特点:
- 可以接受非基本数据类型作为参数,例如 sObject 对象或自定义的 Apex 类对象。
- 调用
System.enqueueJob(new MyQueueableClass())
会返回一个作业 ID (Job ID),我们可以使用这个 ID 通过 SOQL 查询AsyncApexJob
对象来监控作业的状态。 - 支持作业链 (Job Chaining)。一个 Queueable 作业可以在其
execute
方法内部启动另一个 Queueable 作业,从而实现复杂的、分步骤的业务逻辑。
3. Batch Apex
当需要处理海量数据(数千到数百万条记录)时,Batch Apex 是不二之选。它通过实现 Database.Batchable
接口,将一个大的数据处理任务分割成多个小的批次 (batches) 来执行,每个批次都在一个独立的事务中处理。
特点:
- 包含三个核心方法:
start
(用于收集需要处理的记录)、execute
(对每个批次的数据进行处理)、finish
(所有批次处理完成后执行,常用于发送总结邮件等)。 - 每个
execute
方法都有自己独立的 Governor Limits,极大地提高了数据处理能力。 - 可以处理高达 5000 万条记录。
4. Schedulable Apex
如果需要让一个任务在未来的特定时间点或按固定的周期(例如每天凌晨2点)执行,就需要使用 Schedulable Apex。它通过实现 Schedulable
接口,并使用 System.schedule
方法或在 Salesforce UI 上进行调度来工作。
示例代码
以下代码示例严格来自 Salesforce 官方文档,并附有详细注释,以帮助您更好地理解。
示例 1: 使用 Future Method 进行 API Callout
这是一个经典的场景:在某个 DML 操作后,需要调用外部服务。为了不阻塞用户界面,我们使用 @future(callout=true)
将其异步化。
// FutureMethodExample.apxc public class FutureMethodExample { @future(callout=true) public static void makeHttpCallout() { // 准备一个 HTTP 请求 HttpRequest request = new HttpRequest(); // 设置请求端点,这里使用一个示例地址 request.setEndpoint('http://api.example.com/animals'); // 设置请求方法为 GET request.setMethod('GET'); // 发送请求 Http http = new Http(); HttpResponse response = http.send(request); // 检查响应状态码,200表示成功 if (response.getStatusCode() == 200) { // 解析响应体,这里假设返回的是JSON格式的数据 // 在实际项目中,这里会执行更复杂的反序列化和业务逻辑处理 System.debug('Response body: ' + response.getBody()); } else { // 如果请求失败,记录错误信息 System.debug('Request failed with status code: ' + response.getStatusCode()); } } }
如何调用: 在任何 Apex 代码中(例如一个 Trigger),只需调用这个静态方法即可。
// 从匿名窗口或另一个类中调用 FutureMethodExample.makeHttpCallout();
示例 2: 使用 Queueable Apex 实现作业链
这个例子展示了 Queueable Apex 的强大之处。第一个作业 (AddPrimaryContact
) 在处理完它的任务后,会启动第二个作业 (SendEmailToSalesTeam
),形成一个作业链。
// AddPrimaryContact.apxc // 第一个作业:为指定的客户创建一个主要联系人 public class AddPrimaryContact implements Queueable { private Contact contact; private String state; // 构造函数,用于接收需要处理的数据 public AddPrimaryContact(Contact contact, String state) { this.contact = contact; this.state = state; } // execute 方法是 Queueable 作业的入口点 public void execute(QueueableContext context) { // 查询指定州(state)的所有客户 List<Account> accounts = [SELECT Id, Name, (SELECT Id, FirstName, LastName FROM Contacts) FROM Account WHERE BillingState = :state LIMIT 200]; List<Contact> contactsToInsert = new List<Contact>(); for (Account acc : accounts) { // 为每个客户创建一个新的联系人副本 Contact newContact = contact.clone(false, false, false, false); newContact.AccountId = acc.Id; contactsToInsert.add(newContact); } // 插入新的联系人记录 if (!contactsToInsert.isEmpty()) { insert contactsToInsert; } // **作业链的关键** // 检查当前作业链的深度,防止无限递归 // System.Limits.getQueueableJobs() 返回已添加的作业数,包含当前执行的作业 // System.Limits.getLimitQueueableJobs() 返回队列中允许的最大作业数(通常是50) if (System.Limits.getQueueableJobs() < System.Limits.getLimitQueueableJobs()) { // 启动下一个作业,通知销售团队 System.enqueueJob(new SendEmailToSalesTeam()); } } } // SendEmailToSalesTeam.apxc // 第二个作业:发送邮件 public class SendEmailToSalesTeam implements Queueable { public void execute(QueueableContext context) { // 实际应用中,这里会构建并发送一封邮件 System.debug('Executing SendEmailToSalesTeam job. Imagine an email is sent here.'); // 这个作业不启动任何其他作业,作业链在此结束 } }
如何调用: 创建 AddPrimaryContact
的实例并将其入队。
// 创建一个联系人模板 Contact templateContact = new Contact(FirstName='Marc', LastName='Benioff'); // 创建第一个作业的实例,并指定目标州为'CA' AddPrimaryContact job = new AddPrimaryContact(templateContact, 'CA'); // 将作业添加到队列中并获取 Job ID ID jobId = System.enqueueJob(job); // 可以使用 jobId 查询作业状态 AsyncApexJob a = [SELECT Id, Status, NumberOfErrors FROM AsyncApexJob WHERE Id = :jobId];
注意事项
1. Governor Limits
虽然异步 Apex 的限制比同步的要宽松,但它仍然有自己的限制。开发者需要特别关注:
- 总异步 Apex 执行数: 24小时内异步 Apex 方法(Future, Queueable, Batch)执行的总数有限制,通常是 250,000 或 Salesforce 用户许可证数量乘以 200,取较大者。
- 并发执行: 在一个同步事务中,通过
System.enqueueJob
最多只能添加 50 个作业到队列中。 - 调用深度: Queueable 作业链的深度有限制。在一个同步事务中启动的链,其深度不能超过1。
2. 权限与数据可见性
异步 Apex 默认在系统模式 (System Mode) 下运行,这意味着它会忽略当前用户的字段级安全 (Field-Level Security) 和对象权限,但会遵守记录共享规则 (Sharing Rules)。如果你希望它遵循当前用户的权限,需要使用 with sharing
或 inherited sharing
关键字声明类。
3. 错误处理
由于异步任务在后台运行,错误不会直接反馈给用户。因此,稳健的错误处理机制至关重要。你必须在代码中使用 try-catch
块来捕获和处理潜在的异常。对于捕获到的异常,最佳实践是将其记录到一个自定义的日志对象中,或者通过平台事件 (Platform Events) / 邮件通知系统管理员。
4. 测试注意事项
测试异步 Apex 是强制性的,并且有特定的模式。Salesforce 提供 Test.startTest()
和 Test.stopTest()
方法来帮助我们测试。将异步方法的调用放在这两个方法之间,当 Test.stopTest()
执行时,Salesforce 会强制执行所有已入队的异步作业,这样你就可以在测试方法中断言(assert)其结果。
@isTest private class FutureMethodExampleTest { @isTest static void testMakeHttpCallout() { // 设置一个模拟的HTTP响应,避免实际的callout Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator()); // 开始测试上下文 Test.startTest(); // 调用 future 方法,此时它会被放入队列,但不会立即执行 FutureMethodExample.makeHttpCallout(); // 停止测试上下文,这会强制执行队列中的所有异步作业 Test.stopTest(); // 在这里添加断言来验证异步作业的结果 // 例如,查询一个记录是否被正确更新,或者验证模拟callout是否被调用 } }
5. 幂等性 (Idempotency)
由于网络问题或平台重试机制,异步作业有可能被执行多次。因此,在设计异步逻辑时,应尽量保证其幂等性,即多次执行同一个任务应该产生与单次执行相同的结果。例如,在插入数据前先检查数据是否已存在。
总结与最佳实践
异步 Apex 是 Salesforce 平台开发工具箱中一把锋利的瑞士军刀,它使得我们能够构建可扩展、高性能且用户体验良好的复杂应用。作为开发者,我们需要根据具体的业务场景选择最合适的异步工具。
选择指南:
- 当你需要一个简单、独立的后台任务,并且不关心它的执行状态时(例如,一个简单的 API 调用),Future Method 是一个快速的选择。
- 当你需要更强的控制力,例如传递 sObject、监控作业状态、或者将多个任务链接在一起时,Queueable Apex 是现代开发中的首选。对于所有新的开发,应优先考虑 Queueable Apex 而非 Future Methods。
- 当你的任务是处理成千上万条记录时,例如数据迁移、批量更新或数据清洗,Batch Apex 是唯一正确的选择。
- 当你需要任务在特定时间或按周期性计划运行时,Schedulable Apex 是你需要的工具。
最佳实践:
- 优先选择 Queueable Apex:除非有特别简单的理由,否则新的异步功能开发应首选 Queueable Apex,因为它提供了更好的灵活性和可追溯性。
- 设计健壮的错误处理:永远不要假设你的代码会完美运行。实现日志记录和通知机制,以便在出现问题时能快速定位和解决。
- 代码要批量化 (Bulkify):即使是在异步上下文中,你的代码也应该遵循 Salesforce 的最佳实践,即批量处理数据,避免在循环中执行 SOQL 或 DML 操作。
- 编写全面的测试:使用
Test.startTest()
和Test.stopTest()
来确保你的异步逻辑被完全覆盖,并断言其行为符合预期。 - 监控异步作业:定期检查 Salesforce 设置菜单下的“Apex 作业”页面,监控异步任务的执行情况,及时发现失败的作业。
通过深入理解并熟练运用这些异步处理模式,你将能够构建出能够应对复杂业务挑战的、真正企业级的 Salesforce 应用程序。
评论
发表评论