Salesforce Connect(2)设置

学习目标

完成本单元后,您将能够:

  • 在您的Salesforce组织中创建外部数据源,以指定如何连接到外部系统。
  • 验证与外部系统的连接。
  • 同步以创建映射到外部系统模式的外部对象。
  • 查看外部对象。

设置概述

使用Salesforce Connect设置外部数据集成涉及到这些高级步骤。

  1. 创建外部数据源. 如果您的外部系统承载多个服务,则为每个访问数据所需的服务创建一个外部数据源。
  2. 创建外部对象及其字段。 在您的Salesforce组织中为每个要访问的外部数据表创建一个外部对象。在每个外部对象上,为要从Salesforce组织访问的每个外部表列创建一个自定义字段。

    注意

    如果外部系统允许,我们建议您同步外部数据源以自动创建相关的外部对象。您也可以选择手动定义外部对象来自定义外部对象名称并手动创建自定义字段。

  3. 定义外部对象的关系. 创建查找,外部查找和间接查找关系字段,以跨系统边界提供数据的无缝视图。
  4. 允许用户访问外部对象及其字段. 通过权限集或配置文件授予对象和字段权限。
  5. 设置用户认证. 对于使用每个用户身份验证的每个外部数据源,请执行以下两项操作。
    1. 使用户能够对外部数据源进行身份验证. 通过权限集或配置文件授予用户访问权限
    2. 设置每个用户的身份验证设置. 告诉您的用户如何设置和管理他们自己的个人设置中的外部系统的身份验证设置。或者,您可以为每个用户执行此任务。

作为此模块的一部分,您可以将示例订单数据与Salesforce Developer Edition中的现有帐户数据相集成。要完成这些步骤,您需要安装一个测试包,用于在帐户对象上配置必要的架构,创建一个Customer ID字段,并为每个帐户分配一个Customer ID值。

按照以下步骤安装软件包。

  1. 打开浏览器,点击这里 click here 开始安装。
  2. 选择 Install for Admins Only.
  3. 点击 Install.Installer for the Salesforce Connect Quickstart package
  4. 选择 External Orders 应用程序。
  5. 点击 Set Customer IDs 将客户ID号码分配到Developer Edition中的样例帐户记录。Assign Customer IDs to sample account records

您的Salesforce Developer Edition现在已经安装在本教程的主要部分。现在是整合一些数据的时候了!

连接外部数据源

作为练习,让我们连接一个可公开访问的现有OData 2.0数据源。

  1. 从安装程序中,在快速查找框中输入外部数据源,然后选择 External Data Sources.
  2. 点击 New External Data Source.
  3. 输入OrderDB作为标签。当您从标签字段中单击或远离标签时,名称字段默认为OrderDB。
  4. 选择 Salesforce Connect: OData 2.0 作为类型。
  5. 输入 https://orderdb.herokuapp.com/orders.svc/作为网址。
  6. 保留其他设置的默认值,然后单击 Save.

注意

因为这是一个示例,只读数据库,所以不需要身份验证。一个真正的外部系统可能需要一些凭证。您可以将Salesforce Connect配置为对所有数据源访问使用同一组凭据,或者为每个用户使用单独的凭据。

现在您已经配置了一个外部数据源,您可以选择您希望集成到您的Salesforce组织中的表。

创建外部对象

您可以创建或修改外部对象。

  1. 从安装程序中,在快速查找框中输入外部数据源,然后选择外部数据源,然后单击OrderDB外部数据源。
  2. 点击 Validate and Sync.

    注意

    Salesforce Connect从示例数据库中检索OData 2.0元数据并列出可用的表。点击这里查看元数据XML。

  3. 选择订单和订单明细。
    Validate External Data Source dialog
  4. 点击 Sync.

同步将创建与所选表相对应的外部对象。同步不会在Salesforce中存储任何数据。同步仅将映射定义到包含数据的外部表或存储库。这些映射使Salesforce能够访问和搜索外部数据。

注意

您可以选择手动创建外部对象。这样做使您可以自定义外部对象名称,决定为哪些表格列创建自定义字段,并自定义字段名称。从设置中,在快速查找框中输入外部对象,然后选择外部对象。

查看外部数据

连接外部数据源并定义外部对象之后,可以直接在Salesforce组织中查看外部数据。

  1. 从安装程序中,在快速查找框中输入外部数据源,然后选择 External Data Sources.
  2. 点击OrderDB外部数据源。
  3. 向下滚动到外部对象,然后单击 Orders.View external objects page
Salesforce Connect的同步进程从外部系统的模式创建此外部对象。如果您熟悉自定义对象,则会注意到外部对象看起来很相似。同步过程还创建了一组自定义字段,就像您为自定义对象创建它们一样。外部对象和自定义对象定义之间的主要区别是:

  • 外部对象API名称具有后缀__x而不是__c。
  • 外部对象具有对其外部数据源和该源内的表的引用。
  • 外部对象具有不同的标准字段。显示URL是表示外部数据库中的记录的OData 2.0 URL,而外部ID是每个记录的主键值。

External objects details page

让我们创建一个自定义选项卡,轻松访问订单记录。

  1. 从设置中,在快速查找框中输入标签,然后选择 Tabs.
  2. 单击自定义对象选项卡旁边的New 按钮。
  3. 选择Orders 作为对象
  4. 单击“选项卡样式”旁边的选择器,然后选择您喜欢的任何样式。
  5. 点击 Next.
  6. 点击 Next 以接受默认选项卡可见性设置。
  7. 点击“包含”标签旁边的复选框取消选择所有应用。
  8. 点击外部订单旁边的复选框将其选中。
  9. 点击 Save.

注意

因为还有一个名为Order的标准对象,所以现在有两个标签为Orders的选项卡。您可以通过更改对象定义中的标签来更改外部对象的选项卡名称。

您现在可以查看外部订单数据,就好像它存储在您的Salesforce组织中的自定义对象中一样。

  1. 如果应用程序菜单(右上角)未显示外部订单,请单击应用程序菜单并选择它。
  2. 点击 Orders 标签。
  3. 点击 Go! 按钮在“查看:全部”旁边。View list of external ordersSalesforce Connect从样本订单数据库中检索到前25个订单记录的订单ID。您可以像在任何其他列表视图中一样配置要显示的字段。
  4. 点击其中一个外部ID值。View details of external orderSalesforce Connect检索您选择的订单的所有字段值。
    请记住,外部数据在您的Salesforce组织中绝不会重复。 Salesforce Connect总是实时从外部系统获取当前数据。

    现在您可以在组织中看到外部数据,也可以通过创建查找关系将其链接到现有数据。你会看到如何在稍后的单位做到这一点。

设置用户认证

本机使用的示例数据库不需要认证。但是,真正的外部系统可能需要登录凭据。您有两个选项可用于设置外部数据源的用户身份验证。

  • 命名委托人—您的整个Salesforce组织在外部系统上共享一个登录帐户。
  • 每个用户—您的组织在外部系统上使用多个登录帐户。您或您的用户可以为外部系统设置个人身份验证设置。
    有关更多信息,请参阅Salesforce帮助中的外部数据源的标识类型。

Salesforce Connect(1)介绍

学习目标

完成本单元后,您将能够:

  • 解释Salesforce Connect是什么。
  • 描述Salesforce Connect的两个典型用例。
  • 说明Salesforce Connect与提取,转换和加载(ETL)工具的不同之处。
  • 解释外部对象如何与标准和自定义对象不同。

Salesforce Connect概述

Salesforce Connect是一个使您能够查看,搜索和修改存储在Salesforce组织外部的数据的框架。例如,您可能拥有存储在企业资源规划(ERP)系统中的数据。您可以使用外部对象通过Web服务标注实时访问数据,而不是将数据复制到您的组织中。

Salesforce Connect可让您的Salesforce组织从多种外部系统访问数据。您可以实时整合来自SAP®NetWeaver Gateway,MicrosoftDynamics®NAV和许多其他数据源的表格,无需编写一行代码。以前,将外部数据与Salesforce集成的唯一方法是使用提取,转换和加载(ETL)工具。该过程非常耗时,并且需要将数据复制到您的组织中,而您可能永远不会使用这些数据,或者很快就会过时。而Salesforce Connect将外部系统中的数据表映射到组织中的外部对象。

外部对象与自定义对象类似,只不过它们映射到位于Salesforce组织外部的数据。外部对象数据始终是最新的。 Salesforce Connect提供到外部数据的实时连接,而不是消耗存储空间的副本,并且必须定期同步。访问外部对象实时从外部系统获取数据。

如果大多数条件适用,我们建议您使用Salesforce Connect。

  • 您有大量不想复制到Salesforce组织中的数据。
  • 您在任何时候都需要少量的数据。
  • 您需要实时访问最新的数据。
  • 您将数据存储在云或后台系统中,但希望显示或处理Salesforce组织中的数据。

外部对象与自定义对象

外部对象与自定义对象有许多相同的功能。例如,您可以:

  • 通过列表视图,详细信息页面,记录源,自定义选项卡和页面布局访问外部对象。
  • 定义外部对象与标准或自定义对象之间的关系,以整合不同来源的数据。
  • 在外部对象页面上启用Chatter订阅源以进行协作。
  • 对外部对象启用创建,编辑和删除操作。

如果您需要频繁访问大量的外部数据,ETL可能仍然是您获得最佳性能的最佳选择。外部对象不是ETL的替代品。它们是访问提供巨大收益的外部数据的补充方法,包括与Salesforce平台的无缝集成,包括我们的API,移动,Chatter等等。例如,外部对象可用于标准Salesforce工具,如Salesforce应用程序,全局搜索,SOSL和SOQL查询,Apex,Visualforce,API,更改集和包。

以下是外部对象和自定义对象支持的功能的快速比较。

特征 自定义对象 外部对象
数据存储在您的Salesforce组织中 Yes No
Read Yes Yes
Write Yes Yes (limited)
Tabs, layouts Yes Yes
Visualforce Yes Yes
Field-level security Yes Yes
Sharing Yes No
REST and SOAP API Yes Yes
SOQL Yes Yes (limited)
Search and SOSL Yes Yes (pass-through)
Formula fields Yes Not Yet
Workflow, triggers Yes Not Yet
Reports and analytics Yes Yes (limited)
Chatter Yes Yes (no field tracking)

一个Salesforce连接示例

假设您将产品订单信息存储在外部数据库中,并且您希望将这些订单作为Salesforce中每个帐户记录上的相关列表进行查看。通过Salesforce Connect,您可以为订单设置父帐户对象与子外部对象之间的关系。然后,您可以设置父对象的页面布局,以包含显示子记录的相关列表。

下图显示了Salesforce Connect如何跨系统边界提供数据的无缝视图。 Business_Partner外部对象的记录详细信息页面包含两个相关的子对象列表。

  • 客户标准对象(1)
  • Sales_Order外部对象(2)

External objects example

在此示例中,外部查找关系和页面布局使用户能够在单个页面上查看Salesforce org内部和外部存储的相关数据。

外部连接的类型

要连接到存储在外部系统上的数据,Salesforce Connect使用这些专门设计的适配器之一。

  • OData 2.0适配器或OData 4.0适配器—连接到Internet上任何OData 2.0或4.0生产者公开的数据。 OData(开放数据协议)是用于集成数据的现代基于REST的协议。 SAP和Microsoft等供应商已经实施了OData支持,因此可以直接访问NetWeaver和SharePoint等产品。 来自Salesforce合作伙伴的集成产品将Salesforce Connect的范围扩展到更广泛的后台系统。
  • 跨组织适配器—连接到存储在另一个Salesforce组织中的数据。 跨组织适配器使用标准的Force.com REST API。 它直接连接到另一个组织,而不需要中间Web服务,就像OData一样。
  • 通过Apex创建定制适配器—如果OData和跨组织适配器不适合您的需要,请使用Apex Connector Framework开发您自己的适配器。

Apex-事件驱动(3)

学习目标

完成本单元后,您将能够:

  • 描述如何订阅平台事件消息。
  • 使用Apex触发器订阅事件。
  • 用Apex测试方法测试平台事件。
  • 通过CometD订阅平台事件。

订阅平台事件

现在您已经看到了如何发布平台事件,您如何订阅他们以获得最新消息或发货的通知?在Salesforce平台上,Apex触发器,流程和流程接收事件通知。 Visualforce和Lightning组件应用程序通过CometD接收事件。在外部应用程序中,您也可以使用CometD订阅事件。

使用Apex触发器订阅平台事件通知

您可能以前使用过Apex触发器,以基于数据库事件执行操作。通过平台事件,过程是相似的。您只需在事件对象上插入一个插入Apex触发器来订阅传入事件即可。触发器在Apex中提供自动订阅机制。不需要明确创建和收听频道。触发器接收来自各种来源的事件通知,不管它们是通过Apex还是API发布的。

平台事件仅在插入触发器后才支持。后插入触发器事件对应于发布平台事件之后的时间。事件消息发布后,触发后插入触发器。

要创建平台事件触发器,请使用开发者控制台。

  1. 单击设置图标,选择 Developer Console, 然后单击 File | New | Apex Trigger.
  2. 提供一个名称并为sObject选择事件,然后单击 Submit.

开发者控制台会自动在触发器模板中添加after插入事件。另外,您可以方便地从“触发器”相关列表中的“设置”中的事件定义页面创建触发器,但是必须指定after insert关键字。

以下示例显示了Cloud News事件的触发器。它遍历每个事件并通过Urgent__c字段检查消息是否紧急。如果消息紧急,则触发器创建一个派发新闻记者的案例,并将事件位置添加到案例主题。

// 触发Cloud_News事件。
trigger CloudNewsTrigger on Cloud_News__e (after insert) {    
    // 列举所有要创建的案例。
    List<Case> cases = new List<Case>();
    
    // 获取案例所有者的队列ID
    Group queue = [SELECT Id FROM Group WHERE Name='Regional Dispatch' LIMIT 1];
       
    // 遍历每个通知
    for (Cloud_News__e event : Trigger.New) {
        if (event.Urgent__c == true) {
            // Create Case to dispatch new team.
            Case cs = new Case();
            cs.Priority = 'High';
            cs.Subject = 'News team dispatch to ' + 
                event.Location__c;
            cs.OwnerId = queue.Id;
            cases.add(cs);
        }
   }
    
    // 插入与收到的事件相对应的所有案例。
    insert cases;
}
设置调试日志记录

与标准或自定义对象上的触发器不同,平台事件上的触发器不会在与发布事件相同的Apex事务中执行。触发器在系统用户Automated Process实体下的自己的进程中运行。因此,与触发器执行相对应的调试日志由Automated Process实体创建,并且在Developer Console中不可用。要收集平台事件触发器日志,请在“安装”中为“自动过程”实体添加跟踪标志条目。

  1. 在安装程序中,在快速查找框中输入调试日志,然后单击 Debug Logs.
  2. 点击 New.
  3. 对于跟踪的实体类型,请选择 Automated Process.
  4. 选择您要收集的日志的开始日期和到期日期。
  5. 对于调试级别,输入*然后单击 Search.
  6. 选择一个预定义的调试级别,例如SFDC_DevConsole或单击New来创建您自己的调试级别。
  7. 点击 Save.

注意

Apex测试的调试日志是一个例外。它们包括在同一个测试执行日志中记录事件触发器。

有关平台事件触发器的注意事项

事件处理的顺序
触发器按收到的顺序处理平台事件通知。事件的顺序基于事件重播ID。 Apex触发器可以一次接收一批事件。事件的顺序保存在每个批次中。批处理中的事件可以来自一个或多个发布者。
异步触发器执行
平台事件触发器异步运行在其自己的进程中,不是发布事件的事务的一部分。因此,事件发布时间和触发器处理事件之间可能存在延迟。不要期望触发器执行的结果在事件发布之后立即可用。
自动化的过程系统用户
由于平台事件触发器不会在执行它们的用户(正在运行的用户)下运行,而是在Automated Process系统用户下运行,所以我们在CloudNewsTrigger示例中明确地设置了所有者ID字段。我们使用了一个称为Regional Dispatch的示例用户队列的ID作为触发器示例。如果您在触发器(例如案例或商机)中创建带有OwnerId字段的Salesforce记录,请明确设置所有者ID。对于案例和潜在客户,您可以使用分配规则来设置所有者。
此外,系统字段(例如CreatedById和LastModifiedById)引用自动处理实体,而不是正在运行的用户。
Apex Governor 限制
与标准或自定义对象触发器一样,平台事件触发器也受到Apex控制器限制。
Apex Trigger 限制
平台事件触发器共享许多自定义和标准对象触发器的相同限制。例如,您不能从触发器中同步创建Apex标注。

订阅事件定义页面上的相关列表

您可以在安装程序的“平台事件定义详细信息”页面上查看所有事件触发器的状态。 CometD用户不在此列表中。在订阅下,每个激活的触发器都与执行信息和状态一起列出。信息包括上次发布和上次处理事件的重播ID。由于错误不可恢复或权限不足,状态会指示触发器是否正在运行或已与订阅断开连接。仅当触发器重试次数达到最大次数时,才会达到错误状态。以下屏幕截图显示了“云新闻”事件详细信息页面上的“订阅”相关列表。

Subscriptions related list shows the state of subscribed triggers

测试平台事件触发器

通过添加Apex测试,确保您的平台事件触发器正常工作。在将任何Apex代码(包括触发器)打包或部署到生产之前,您的Apex代码必须进行测试。要在Apex测试中发布平台事件,请将发布语句放在Test.startTest和Test.stopTest语句中。

// 创建测试事件
Test.startTest();
// 发布事件
Test.stopTest();
// 在这里执行验证
在测试上下文中,发布方法调用将发布操作排队。 Test.stopTest()语句导致事件发布被执行。 Test.stopTest()后,执行您的验证。

以下是我们的Cloud_News事件及其相关触发器的测试类示例。发布事件会导致关联的触发器触发。在Test.stopTest()之后,测试通过检查Database.SaveResult中的isSuccess()返回的值来验证发布是否成功。另外,测试查询触发器创建的情况。如果找到案例记录,则触发器成功执行,并通过测试。

@isTest
public class PlatformEventTest {
    @isTest static void test1() {
        // 创建测试事件实例
        Cloud_News__e newsEvent = new Cloud_News__e(
            Location__c='Mountain City', 
            Urgent__c=true, 
            News_Content__c='Test message.');
        
        Test.startTest();

        // 调用方法来发布事件
        Database.SaveResult sr = EventBus.publish(newsEvent);
        
        Test.stopTest();
        
        // 在这里执行验证

        // 验证发布是否成功
        System.assertEquals(true, sr.isSuccess());

        // 检查创建的触发器是否存在。
        List<Case> cases = [SELECT Id FROM Case];
        // 验证是否发现此案例。
        // 在测试上下文中只有一个测试用例。
        System.assertEquals(1, cases.size());
    }
}

使用点击订阅平台事件通知

要订阅没有代码的事件消息,请创建一个在发生平台事件时启动的进程。

此屏幕截图显示了一个进程在发生Cloud News事件时开始。当它开始时,该过程查找邮件城市匹配事件通知位置的联系人记录。

Process Builder matching criteria screen

同样,您可以通过使用Wait元素来订阅具有流程的平台事件消息。当发生平台事件时,不是启动流程,而是先前启动的流程等待平台事件,然后恢复。例如,以下是等待Cloud News事件消息发生的Wait元素。只有当事件的位置匹配{!MailingCity_Location.MailingCity}时才会继续。 {!MailingCity_Location}是流中的一个sObject变量。

Cloud Flow Designer Wait element

使用CometD订阅平台事件通知

外部应用程序使用CometD订阅平台事件并执行长时间轮询。平台应用程序(如Visualforce页面和Lightning组件)也可以使用CometD。 CometD是一个可扩展的基于HTTP的事件路由总线,它使用了一种名为Comet的AJAX推送技术模式。它实现了Bayeux协议。长轮询(也称为Comet编程)允许模拟从服务器到客户端的信息推送。与普通轮询类似,客户端连接并请求来自服务器的信息。但是,如果信息不可用,服务器不会发送空的响应,而是等待信息可用(发生事件)。

Salesforce提供了一个Java库EMP连接器,该连接器实现了连接到CometD和监听通道的所有细节。您可以使用EMP Connector轻松订阅平台事件。 EMP连接器隐藏了订阅事件的复杂性。有关EMP Connector的更多信息,请查看Streaming API开发人员指南中的Java客户端示例。

通过CometD订阅平台事件通知的过程与订阅PushTopic事件或通用事件类似。唯一的区别是频道名称。以下是平台事件主题(频道)名称的格式:

/event/<EventName>__e
例如,如果您有名为“云新闻”的平台事件,请在订阅时提供此频道名称。
/event/Cloud_News__e
在CometD URL末尾指定API版本,如下所示。
// 连接到CometD端点
    cometd.configure({
               url: 'https://<Salesforce_URL>/cometd/41.0/',
               requestHeaders: { Authorization: 'OAuth <Session_ID>'}
    });

JSON格式的平台事件消息

交付平台事件的消息与Cloud News事件的以下示例类似。

{
  "data": {
    "schema": "_2DBiqh-utQNAjUH78FdbQ", 
    "payload": {
      "CreatedDate": "2017-04-27T16:50:40Z", 
      "CreatedById": "005D0000001cSZs", 
      "Location__c": "San Francisco", 
      "Urgent__c": true, 
      "News_Content__c": "Large highway is closed due to asteroid collision."
    }, 
    "event": {
      "replayId": 2
    }
  }, 
  "channel": "/event/Cloud_News__e"
}

事件消息中的模式字段包含平台事件模式的标识(在本例中为“schema”:“_2DBiqh-utQNAjUH78FdbQ”)。模式是版本化的 – 当模式改变时,模式ID也改变。

要确定事件的模式是否已更改,请通过REST API检索模式。通过对此REST API资源执行GET请求来使用模式标识:/vXX.X/event/eventSchema/Schema_ID。或者,您可以通过将事件名称提供给此端点来检索事件模式:/vXX.X/sobjects/Platform_Event_Name__e/eventSchema。有关更多信息,请参阅Force.com REST API开发人员指南。

注意

与PushTopic和泛型事件不同,平台事件不支持使用过滤的订阅。例如,订阅/事件/ Cloud_News__e?Location__c =’旧金山’按位置过滤不受支持。

现在您已经看到了如何在Salesforce平台和外部应用程序中使用平台事件,这种可能性是无止境的!将平台事件用于任何数量的应用程序和集成,例如处理业务交易或参与主动客户服务。借助平台事件,您可以采用基于事件的编程模式,并享受基于事件的软件架构的优势。

Apex-事件驱动(2)

学习目标

完成本单元后,您将能够:

  • 定义一个平台事件。
  • 描述如何发布平台事件消息。
  • 使用Apex方法发布一个事件。
  • 通过插入一个sObject来发布一个使用REST API的事件。

定义和发布平台事件

现在,您已经了解了平台事件以及何时使用了平台事件,接下来我们将介绍如何定义平台事件。还记得云端通讯社吗?我们来创建一个平台事件定义来保存新闻事件的数据。

定义一个名为Cloud News的平台事件:

  1. 从安装程序中,在快速查找框中输入平台事件,然后选择 Platform Events.
  2. 在“平台事件”页面上,单击 New Platform Event.
  3. 对于标签,请输入 Cloud News.
  4. 对于多标签,请输入 Cloud News.
  5. 对于描述,输入 Cloud news events deliver news at your fingertips.
  6. 点击 Save.
  7. 在“自定义字段和关系”相关列表中,单击 New.
  8. 选择 Text, 然后单击 Next.
  9. 对于字段标签/名称,输入 Location.
  10. 对于“长度”,请键入100.保留其他字段的默认值,并将“说明”字段保留为空。点击 Save.
  11. 按照步骤7,8和9添加下两个字段:
    字段标签/名称 字段类型
    Urgent Checkbox
    News Content Text Area (Long)

对于您刚定义的Cloud News事件,您创建了各种类型的字段,如文本字段或复选框。平台事件支持的所有字段类型是:

  • Checkbox
  • Date
  • Date/Time
  • Number
  • Text
  • Text Area (Long)

事件保留和ReplayId系统字段

Salesforce将平台事件存储24小时。您可以检索API CometD客户端中存储的事件,但不能检索Apex中的事件。您可以检索所有存储的事件,也可以将事件的重播ID指定为检索事件的基线。

即使Salesforce暂时保留事件记录,也不能通过SOQL或SOSL查询它们。同样,您不能在报告,列表视图和搜索中的用户界面中使用事件记录。只有在订购CometD和使用ReplayId选项时才能检索过去的事件。我们向您展示如何订阅下一单元的活动。

每个事件消息被分配一个包含在ReplayId字段中的不透明ID。由系统填充的ReplayId字段值指的是事件流中事件的位置。重放ID值不保证连续事件连续。例如,ID为999的事件之后的事件可以具有1,025的ID。订阅者可以存储重播ID值并在重新订阅时使用它来检索保留窗口内的事件。例如,用户可以在连接失败后检索错过的事件。订阅者不得根据存储的重播ID来计算新的重播ID,以引用流中的其他事件。

API名称后缀

在创建平台事件时,系统会附加__e后缀来创建事件的API名称。例如,对于Cloud News事件,API名称是Cloud_News__e。只要以编程方式引用该事件,请使用API​​名称,例如,在Apex,REST API和Enterprise API中。

发布事件

如果您的应用程序位于Salesforce平台上,则可以使用Apex方法或声明性工具(如Process Builder或Cloud Flow Designer)发布事件。如果您的应用是外部应用,则可以使用Salesforce API发布事件。

使用Apex发布事件消息

要发布事件消息,请创建事件的实例并将其传递给EventBus.publish方法。

以下示例创建一个类型为Cloud_News__e的事件,将其发布,然后检查发布是成功还是遇到错误。 EventBus.publish()方法返回一个Database.SaveResult对象,其中包含发布的结果。如果isSuccess()返回true,则事件已在Salesforce事件总线中发布。否则,该事件遇到在Database.Error对象中返回的错误。

// 创建一个事件的实例并将其存储在newsEvent变量中
Cloud_News__e newsEvent = new Cloud_News__e(
           Location__c='Mountain City', 
           Urgent__c=true, 
           News_Content__c='Lake Road is closed due to mudslides.');

// 调用方法来发布事件
Database.SaveResult sr = EventBus.publish(newsEvent);

// 检查发布结果
if (sr.isSuccess()) {
    System.debug('Successfully published event.');
} else {
    for(Database.Error err : sr.getErrors()) {
        System.debug('Error returned: ' +
                     err.getStatusCode() +
                     ' - ' +
                     err.getMessage());
    }
}
要在同一个调用中发布多个事件,请将您的事件添加到事件列表中,然后将该列表传递给EventBus.publish()方法。此方法的输出是Database.SaveResult对象的数组:每个发布的事件一个。 EventBus.publish()可以发布一些传入事件,即使由于错误而无法发布其他事件。 EventBus.publish()方法不会因发布操作失败而引发异常。在使用部分成功选项调用时,它的行为类似于Apex Database.insert()方法。
// List来保存要发布的事件对象。
List<Cloud_News__e> newsEventList = new List<Cloud_News__e>();
// 创建事件对象
Cloud_News__e newsEvent1 = new Cloud_News__e(
           Location__c='Mountain City', 
           Urgent__c=true, 
           News_Content__c='Lake Road is closed due to mudslides.');
Cloud_News__e newsEvent2 = new Cloud_News__e(
           Location__c='Mountain City', 
           Urgent__c=false, 
           News_Content__c='Small incident on Goat Lane causing traffic.');
// 将事件对象添加到列表中。
newsEventList.add(newsEvent1);
newsEventList.add(newsEvent2);

// 调用方法来发布事件。
List<Database.SaveResult> results = EventBus.publish(newsEventList);

// 检查每个事件的发布结果
for (Database.SaveResult sr : results) {
    if (sr.isSuccess()) {
        System.debug('Successfully published event.');
    } else {
        for(Database.Error err : sr.getErrors()) {
            System.debug('Error returned: ' +
                        err.getStatusCode() +
                        ' - ' +
                        err.getMessage());
        }
    }       
}

注意

Salesforce平台对您可以在组织中定义的事件数量以及您可以在一个小时内发布的事件记录数量进行限制。此外,由于事件发布相当于DML插入操作,所以适用DML限制和其他Apex调控限制。有关平台事件限制和Apex调控器限制的更多信息,请参阅参考资料部分。

使用点击发布事件消息

要发布没有代码的事件消息,请在流程或流程中使用记录创建功能。

此屏幕截图显示了在发布Cloud News事件消息的Process Builder中创建记录操作的示例。基本上,将“记录类型”设置为要发布的平台事件(云新闻),然后设置事件的值。

The Create a Record action in Process Builder is set to the Cloud News record type. The fields of the Cloud News event are filled.

同样,您可以使用流发布平台事件消息。配置Record Create元素以创建平台事件(Cloud_News__c)的实例,然后设置事件的值。要设置布尔字段的值(如Urgent__c),请使用{!$ GlobalConstant.True}。

使用Salesforce API发布事件消息

外部应用使用API​​发布平台事件消息。您可以像插入sObjects一样创建事件记录来发布事件。您可以使用Salesforce API来创建平台事件记录,如SOAP API,REST API或Bulk API。

例如,对于Cloud News事件,您可以通过插入Cloud_News__e记录来发布事件通知。以下示例在REST API中创建一个类型为Cloud_News__e的事件。

sObject REST端点:

/services/data/v40.0/sobjects/Cloud_News__e/
POST请求的请求主体:
{
   "Location__c" : "Mountain City",
   "Urgent__c" : true,
   "News_Content__c" : "Lake Road is closed due to mudslides."
}
平台事件记录创建后,REST响应看起来像这个输出。为了简洁,标题被删除。
HTTP/1.1 201 Created 

{   
   "id" : "e00xx000000000B",
   "success" : true,
   "errors" : [ ],
   "warnings" : [ ] 
}
您可以使用任何REST API工具或HTTP客户端应用程序来进行REST API调用。例如,您可以按照以下步骤使用Workbench。
  1. 登录到您的Trailhead DE组织。
  2. 打开一个新标签页并导航到Workbench https://workbench.developerforce.com/login.php
  3. 对于环境,请选择 Production.
  4. 对于API版本,请选择可用的最高编号。
  5. 选择 I agree to the terms of service.
  6. 点击 Login with Salesforce.
  7. 在下一个屏幕上,单击 Allow.
  8. 在顶部菜单中,选择 utilities | REST Explorer.
  9. 点击 POST.
  10. 将URI替换为:
    /services/data/v40.0/sobjects/Cloud_News__e
  11. 对于请求正文,以JSON格式添加以下正文。
    {
       "Location__c" : "Mountain City",
       "Urgent__c" : true,
       "News_Content__c" : "Lake Road is closed due to mudslides."
    }
  12. 点击 Execute.

    Salesforce在发布事件后返回的响应类似于以下内容。

    {
      "id" : "e00xx0000000001AAA",
      "success" : true,
      "errors" : [ ],
      "warnings" : [ ]
    }
    
    或者,您可以通过create()调用或在Bulk API中使用批处理作业在SOAP API中发布事件。

平台事件和事务

与自定义对象不同,平台事件不在Salesforce平台的数据库事务中处理。 因此,发布的平台事件无法回滚。 请注意以下几点:

  • 当您通过API发布平台事件时,allOrNoneHeader API标头将被忽略。
  • 平台事件不支持Apex setSavepoint()和rollback()数据库方法。

当您发布平台事件时,适用DML限制和其他Apex调控限制。

现在你已经看到了如何定义和发布事件,让我们看看如何订阅它们!

Apex-事件驱动(1)

学习目标

完成本单元后,您将能够:

  • 列出基于事件的软件体系结构的组件。
  • 解释一个事件驱动的软件架构的好处。
  • 描述平台事件功能的用例。
  • 描述平台事件的特征。

了解事件驱动的软件体系结构

您的订单系统是否有包裹?打印机墨盒是否需要更换?无论您希望得到什么通知,Salesforce企业消息传递平台都可以在Salesforce内部和外部来源提供安全且可扩展的自定义通知。通过平台事件,您可以监视系统并将更改传达给其他系统。

注意

如果尚未完成,请先完成API基础模块,然后再使用该模块。熟悉Streaming API的概念是此模块的先决条件。

基于事件的沟通的范例围绕发布者 – 订阅者模型展开 – 发送者广播一个或多个接收者捕获的消息。这就像无线电传输 – 发射塔发射无线电信号,接收机获得信号,如果他们调整到正确的频率。

就像无线电传输一样,基于事件的通信从发送者流向接收者。无论接收者是否正在侦听,事件都会被发送,接收者在收到事件时不会确认。基于事件的通信可以实时进行,或者更准确地说,可以近乎实时地进行。无线电波以光速行进,但基于事件的软件和硬件系统通常有一些延迟。非必要的披露:Salesforce速度很快,但速度不及光速!

在API基础模块中,我们使用海盗船上雷达的类比来表示事件检测。这个类比适用于基于Salesforce记录更改的PushTopic事件流。该通信模型只需要一个用户。但是对于平台事件,通信有两方:发送方和接收方。它们是事件驱动架构的两个组成部分。

事件驱动系统的组件

在我们继续之前,让我们定义一些术语。

Event
在业务流程中有意义的状态变化例如,采购订单的放置是一个有意义的事件,因为订单履行中心希望在处理订单前收到通知。
Event message
包含有关事件数据的消息。也被称为事件通知。例如,事件消息可以是关于包含关于订单的信息的订单放置的通知。
Event producer
通过频道发布事件消息。例如,一个订单放置应用程序。
Event channel
事件生成器发送事件消息和事件消费者读取这些消息的事件流。在Salesforce中也称为事件总线。
Event consumer
从频道接收讯息的频道的订阅者。例如,通知新订单的订单履行应用程序。

下图说明了一个基于事件的软件体系结构。

A diagram showing components of event-based systems: event producers, which feed information into the event bus, which sends messages to the event consumers

与请求响应通信模型不同,基于事件驱动模型的软件架构将事件生成者与事件消费者分离,从而简化了连接系统中的通信模型。不需要向服务器请求获取关于某个状态的信息。相反,系统订阅事件通道,并在新状态发生时通知。任何数量的消费者都可以接收和响应相同的事件。当事件发生时,系统会获得这些信息,并可以近乎实时地对其作出反应。除了消息内容的语义之外,发送事件的系统和接收事件的系统彼此之间没有依赖关系。

Salesforce企业消息传递平台提供事件驱动软件体系结构的好处。平台事件是您的应用程序发送和接收的事件消息。它们简化了沟通变化和响应变化的过程,而无需编写复杂的逻辑。发布者和订阅者通过平台事件相互通信。一个或多个用户可以听相同的事件并执行操作。

假设一家名为“云新闻”的新闻机构向订阅的客户发送活动信息,以及有关山区撤退目的地的交通和道路状况的最新突发消息。这些事件的内容不仅仅是新闻事件本身,还包括新闻是否紧急以及事件的发生地点等相关细节。用户可以接收这些事件,并根据新闻的紧急性确定要采取的行动。

所有这一切听起来都不错,但是当你可以使用平台事件时,真实情况是什么呢?当然,平台活动的使用不限于新闻机构。以下是一些有用的应用程序。

何时使用平台事件的示例

我们来看看使用平台事件的一些业务场景。在这些情况下,Salesforce和外部系统通过平台事件消息进行通信。在第一种情况下,Salesforce中的应用程序通知产品装运订单的外部订单履行应用程序。在第二种情况下,外部产品应用程序通知Salesforce商品退货。最后一个场景显示了如何使用触发器在Salesforce中使用事件消息。

平台到外部应用程序:订单履行供应商应用程序

当Salesforce关闭一个机会时,您的公司已经赢得了与客户的交易。假设您使用供应商来运送与机会相关的产品。每个供应商都有一个处理装运订单的外部应用。外部应用程序监听平台事件。当机会关闭时,作为Salesforce产品订购应用程序一部分的触发器会触发并发布平台事件消息。每个供应商应用程序都会收到有关该事件的通知,并为特定产品创建装运订单。

In this diagram, a product order app publishes an order event to an event bus. Various vendor apps subscribe to the event bus and receive the event.

平台应用程序的外部应用程序:处理Salesforce中的商品退货

比方说,有人想把购买的商品退还给供应商。外部系统将商品退货请求发送给Salesforce进行处理。外部系统发布平台事件以提醒Salesforce返回商品。 Salesforce中的事件侦听器(触发器)接收事件并执行一些操作。例如,触发器可能会提醒销售代表退货,并向客户发送确认电子邮件。

An external vendor app publishes a platform event message for a merchandise return request. In Salesforce, a trigger subscribes to the event bus and receives the event.

平台到平台:重新分配主要记录

在Salesforce中分配销售线索时,销售线索触发器会触发并检查与销售线索所有者相关的未决商机和案例。根据相关记录,触发器发布由Salesforce应用程序接收的事件。根据事件信息,应用程序将重新分配潜在客户并创建一个Chatter帖子。

在这种情况下,您可以使用其他Salesforce功能执行相同的操作,例如Process Builder或流程。但是通过使用平台事件,您可以从基于事件的编程模型和跨应用程序的标准编程方式中受益。

In this diagram, an app in Salesforce publishes a platform event. A trigger subscribes to this event channel and receives the event.

平台事件特征

现在您已经了解了何时使用平台事件,让我们深入了解其组件和特性。

您可以定义平台事件包含的自定义数据。就像自定义对象一样,您可以在Salesforce中定义平台事件。通过给它一个名称并添加自定义字段来创建一个平台事件定义。以下是云新闻社新闻事件的自定义字段的示例定义。

字段标签/名称 字段API名称 字段类型
Location Location__c Text

Length: 100

Urgent Urgent__c Checkbox
News Content News_Content__c Text Area (Long)

平台事件和sObjects

平台事件是一种特殊的Salesforce实体,在很多方面与sObject类似。事件消息是平台事件的实例,类似于记录是自定义对象的实例。与自定义对象不同,您不能更新或删除事件记录,也不能在Salesforce用户界面中查看事件记录。

您可以设置读取和创建平台事件的权限。您向配置文件或权限集中的用户授予权限。

在本机和外部应用程序中使用平台事件

平台事件支持Salesforce内部和外部应用程序中的事件消息流。 Salesforce平台上的应用程序使用Apex方法发布事件,并使用Apex触发器来消费事件。此外,Visualforce和Lightning组件应用程序可以使用CometD订阅事件。作为代码的替代方法,您可以使用声明性工具(如Process Builder和Cloud Flow Designer)发布事件。最后,外部应用程序使用sObject API发布事件,并使用CometD客户端消费事件。如您所见,在选择使用平台事件方面有很大的灵活性!

平台事件和其他流式事件之间的差异

其他流媒体事件呢?其他事件包括PushTopic和通用事件。借助PushTopic事件,客户端将根据预定义的查询接收有关Salesforce记录更改的消息。使用通用事件,您可以发送和接收任意消息内容(有效内容),而不一定与Salesforce记录绑定。平台事件与通用事件类似,但提供更强大的自定义功能。借助平台事件,您可以发布任何自定义数据。您可以将粒度级别的事件数据模式定义为键入的字段。另外,您可以在本地Salesforce平台应用程序和外部应用程序中使用平台事件。在以下情况下使用平台事件:

  • 使用预定义模式发送和接收自定义事件数据
  • 发布或订阅Apex中的活动
  • 为了在Salesforce平台上和以外发布和处理事件的灵活性该表比较了一般事件和平台事件的特征。

功能通用事件平台事件

特征 通用事件 平台事件
将事件模式定义为类型字段 Check mark
包含用户定义的有效载荷 Check mark Check mark
通过一个或多个API发布事件 Check mark Check mark
通过Apex发布活动 Check mark
通过CometD订阅 Check mark Check mark
通过Apex触发器订阅 Check mark
使用Process Builder以声明方式发布并流动复选标记 Check mark

在下一个单元中,我们将定义一个平台事件并发布事件。

Apex 异步(6)监视异步

学习目标

完成这个单位后,你会知道:

  • 如何监控不同类型的工作。
  • 如何使用弹性队列。

监视异步作业

异步工作的好处是他们在后台默默地工作。异步工作的难点在于他们在后台默默工作。幸运的是,有几种方法可以监视你的工作内容。

您可以监视Salesforce用户界面中所有作业的状态。从安装程序中,在快速查找框中输入作业,然后选择Apex作业。

Apex Jobs页面显示所有异步Apex作业,其中包含有关每个作业执行的信息。以下屏幕截图显示了同一个Batch Apex类的一个未来方法作业和两个已完成的批处理作业。

Apex Jobs

如果您有许多批处理作业,请使用“批处理作业”页面仅查看批处理作业。要打开“Apex批处理作业”页面,请单击“Apex作业”页面顶部的链接。使用“Apex批处理作业”页面中的滑块选择特定的日期范围,并缩小显示的批处理作业的列表。您可以查看过去尚未删除的作业。 “批处理作业”页面按批处理类别对作业进行分组。

Apex Batch Jobs

点击您感兴趣的课程ID旁边的“更多信息”,查找有关为该课程执行的作业的详细信息。此图像显示单击“更多信息”后显示的弹出窗口。这个批次类有两个成功执行的作业。

information about parent jobs

您还可以监视Apex Flex队列中Apex作业的状态,并对其进行重新排序以控制首先处理哪些作业。从“设置”中,在“快速查找”框中输入作业,然后选择 Apex Flex Queue.

监测Future Jobs

Future jobs就像任何其他工作一样出现在Apex工作页面上。但是,未来的工作目前不属于灵活队列的一部分。

你可以查询AsyncApexJob找到你未来的工作,但有一个警告。由于启动未来作业不会返回ID,因此您必须过滤其他字段(如MethodName或JobType)以查找您的作业。在这个Stack Exchange文章中有几个示例SOQL查询可能会有所帮助。

使用SOQL监视Queued Jobs

要查询有关您提交的作业的信息,请通过筛选System.enqueueJob方法返回的作业ID,在AsyncApexJob上执行SOQL查询。

AsyncApexJob jobInfo = [SELECT Status, NumberOfErrors
    FROM AsyncApexJob WHERE Id = :jobID];

使用Flex队列监视Queue Jobs

Apex Flex队列使您能够提交多达100个批处理作业执行。任何提交执行的作业都处于保持状态,并放置在Apex Flex队列中。最多可以有100个批处理作业处于保持状态。

按照先进先出的顺序处理作业。您可以查看当前的队列顺序并将其排列,以便将重要的工作移到前面,或者将重要的工作移到后面。

当系统资源可用时,系统从Apex Flex队列的顶部选取下一个作业,并将其移至批处理作业队列。系统可以为每个组织同时处理最多五个排队或活动作业。这些已移动作业的状态从“保持”更改为“已排队”。排队的作业在系统准备好处理新作业时被执行。像其他作业一样,您可以在Apex Jobs页面中监控排队的作业。

监视Scheduled Jobs

在计划完Apex作业之后,您可以通过在CronTrigger上运行SOQL查询来获得更多关于它的信息。以下示例查询作业运行的次数以及计划再次运行作业的日期和时间。它使用从System.schedule方法返回的jobID变量。

CronTrigger ct = [SELECT TimesTriggered, NextFireTime FROM CronTrigger WHERE Id = :jobID];
如果您在可调度类的execute方法内部执行此查询,则可以通过调用SchedulableContext参数变量上的getTriggerId来获取当前作业的ID。
global class DoAwesomeStuff implements Schedulable {

    global void execute(SchedulableContext sc) {
        // 一些很棒的代码
        CronTrigger ct = [SELECT TimesTriggered, NextFireTime FROM CronTrigger WHERE Id = :sc.getTriggerId()];
    }
    
}
您还可以从与CronTrigger记录关联的CronJobDetail记录中获取作业的名称和作业的类型。为此,请在CronTrigger上执行查询时使用CronJobDetail关系。本示例使用CronJobDetail中的作业名称和类型来检索最新的CronTrigger记录。
CronTrigger job = [SELECT Id, CronJobDetail.Id, CronJobDetail.Name, CronJobDetail.JobType FROM CronTrigger ORDER BY CreatedDate DESC LIMIT 1];
或者,您可以直接查询CronJobDetail以获取作业的名称和类型。 以下示例获取上例中查询的CronTrigger记录的作业名称和类型。 相应的CronJobDetail记录标识由CronTrigger记录上的CronJobDetail.Id表达式获取。
CronJobDetail ctd = [SELECT Id, Name, JobType FROM CronJobDetail WHERE Id = :job.CronJobDetail.Id];
最后,要获取所有Apex计划作业的总计数(不包括所有其他计划作业类型),请执行以下查询。 请注意,值“7”是为作业类型指定的,该作业类型对应于预定的Apex作业类型。 请参阅参考资料部分中的CronJobDetail,了解所有类型的列表。
SELECT COUNT() FROM CronTrigger WHERE CronJobDetail.JobType = '7

Apex 异步(5)schedule

学习目标

完成这个单位后,你会知道:

  • 何时使用预定的Apex。
  • 如何监控计划的工作。
  • 计划的Apex语法。
  • 计划的方法最佳实践。

计划的Apex

Apex Scheduler让您延迟执行,以便您可以在指定的时间运行Apex类。对于使用Batch Apex的每日或每周维护任务来说,这是理想之选。要利用调度程序,编写一个实现Schedulable接口的Apex类,然后按照特定的调度安排它执行。

计划的Apex 语法

为了调用Apex类在特定时间运行,首先实现类的Schedulable接口。然后,使用System.schedule方法安排类的实例在特定时间运行。

global class SomeClass implements Schedulable {
    global void execute(SchedulableContext ctx) {
        // 真棒代码在这里
    }
}

该类实现了Schedulable接口,并且必须实现此接口包含的唯一方法,即execute方法。

这个方法的参数是一个SchedulableContext对象。在课程安排好之后,将创建一个CronTrigger对象来表示计划的作业。它提供了一个getTriggerId方法,该方法返回一个CronTrigger API对象的ID。

示例代码

这个类查询当前应该关闭的开放机会,并创建一个任务来提醒所有者更新机会。

global class RemindOpptyOwners implements Schedulable {

    global void execute(SchedulableContext ctx) {
        List<Opportunity> opptys = [SELECT Id, Name, OwnerId, CloseDate 
            FROM Opportunity 
            WHERE IsClosed = False AND 
            CloseDate < TODAY];
        // 为列表中的每个机会创建一个任务
        TaskUtils.remindOwners(opptys);
    }
    
}
您可以安排您的课程以编程方式或从Apex调度程序用户界面运行。

使用System.Schedule方法

在使用Schedulable接口实现类之后,使用System.Schedule方法执行它。 System.Schedule方法使用用户的时区作为所有调度的基础,但是以系统模式运行 – 所有类都被执行,无论用户是否有执行类的权限。

注意

如果您计划通过触发器安排课程,请特别小心。您必须能够保证触发器不会添加比限制更多的计划作业分类。特别是,考虑API批量更新,导入向导,通过用户界面进行的大容量记录更改以及一次可以更新多个记录的所有情况。

System.Schedule方法有三个参数:作业的名称,用于表示作业计划运行的时间和日期的CRON表达式以及类的名称。

RemindOpptyOwners reminder = new RemindOpptyOwners();
// 秒分钟小时数Day_of_month Month Day_of_week optional_year
String sch = '20 30 8 10 2 ?';
String jobID = System.schedule('Remind Opp Owners', sch, reminder);
有关用于计划的CRON表达式的更多信息,请参阅Apex Scheduler中的“使用System.Schedule方法”部分。


从UI调度作业

您也可以使用用户界面安排课程。

  1. 从安装程序中,在快速查找框中输入Apex,然后选择 Apex Classes.
  2. 点击 Schedule Apex.
  3. 对于工作名称,请输入类似Daily Oppty Reminder的内容。
  4. 单击Apex类旁边的查找按钮,然后为搜索项输入*以获取所有可以调度的类的列表。在搜索结果中,单击您的计划课程的名称。
  5. 为频率选择每周或每月,并设置所需的频率。
  6. 选择开始和结束日期,以及首选开始时间。
  7. 点击 Save.

测试计划的 Apex

就像我们到目前为止所介绍的其他异步方法一样,对于Scheduled Apex,您还必须确保计划的作业在测试结果之前完成。为此,请在System.schedule方法周围再次使用startTest和stopTest,以确保在继续测试之前完成处理。

@isTest
private class RemindOppyOwnersTest {

    // CRON表情:3月15日午夜。
    // 因为这是一个测试,所以在Test.stopTest()之后立即执行作业。
    public static String CRON_EXP = '0 0 0 15 3 ? 2022';

    static testmethod void testScheduledJob() {

        // 创建一些过时的机会记录
        List<Opportunity> opptys = new List<Opportunity>();
        Date closeDate = Date.today().addDays(-7);
        for (Integer i=0; i<10; i++) {
            Opportunity o = new Opportunity(
                Name = 'Opportunity ' + i,
                CloseDate = closeDate,
                StageName = 'Prospecting'
            );
            opptys.add(o);
        }
        insert opptys;
        
        // 获取我们刚刚插入的机会的ID
        Map<Id, Opportunity> opptyMap = new Map<Id, Opportunity>(opptys);
        List<Id> opptyIds = new List<Id>(opptyMap.keySet());

        Test.startTest();
        // 安排测试工作
        String jobId = System.schedule('ScheduledApexTest',
            CRON_EXP, 
            new RemindOpptyOwners());         
        // 验证计划作业尚未运行。
        List<Task> lt = [SELECT Id 
            FROM Task 
            WHERE WhatId IN :opptyIds];
        System.assertEquals(0, lt.size(), 'Tasks exist before job has run');
        // 停止测试将同步运行作业
        Test.stopTest();
        
        // 现在计划的作业已经执行,
        // 检查我们的任务是否被创建
        lt = [SELECT Id 
            FROM Task 
            WHERE WhatId IN :opptyIds];
        System.assertEquals(opptyIds.size(), 
            lt.size(), 
            'Tasks were not created');

    }
}

要记住的事情

计划的Apex包含许多需要注意的项目(有关详细信息,请参阅参考资料部分中的Apex Scheduler),但一般情况下:
  • 一次只能有100个预定的Apex作业,每24小时有最多的预定Apex执行次数。有关详细信息,请参阅参考资料部分中的执行调控器和限制。
  • 如果您计划通过触发器安排课程,请特别小心。您必须能够保证触发器不会添加比限制更多的计划作业。
  • 计划的Apex不支持同步Web服务标注。为了能够进行标注,通过将标注放在用@future(callout = true)注释的方法中进行异步标注,并从预定的Apex中调用此方法。但是,如果预定的Apex执行批处理作业,则批处理类将支持标注。

Apex 异步(4)enqueue

学习目标

完成这个单位后,你会知道:

  • 何时使用Queueable接口。
  • 排队和未来方法之间的区别。
  • 可排队的Apex语法。
  • 可排队方法的最佳实践。

Queueable Apex

Queueable Apex在15年冬季发布,基本上是未来方法的一个超集,并带有一些额外的优势。我们把未来方法的简单性和Batch Apex的力量混合在一起形成了Queueable Apex!它为您提供了平台为您序列化的类结构,简化的界面,无需启动和结束方法,甚至允许您使用不仅仅是原始参数!它由一个简单的System.enqueueJob()方法调用,该方法返回一个可以监视的作业ID。它击败切片面包手!

Queueable Apex允许您提交与未来方法类似的异步处理作业,并具有以下附加优势:

  • 非原始类型:您的Queueable类可以包含非原始数据类型的成员变量,如sObjects或自定义Apex类型。这些对象可以在作业执行时被访问。
  • 监视:通过调用System.enqueueJob方法提交作业时,该方法将返回AsyncApexJob记录的ID。您可以使用此ID来标识您的作业并监视其进度,可以通过Apex Jobs页面中的Salesforce用户界面,也可以通过从AsyncApexJob查询记录来以编程方式进行监视。
  • 链接工作:通过从正在运行的工作中启动第二份工作,可以将一份工作链接到另一份工作上。链接作业是有用的,如果你需要做一些顺序处理。

 

Queueable与Future

因为可排队的方法在功能上等同于未来的方法,所以大多数情况下你可能希望使用可排队方法而不是将来的方法。但是,这并不意味着您现在应该回头重构所有未来的方法。如果您未来的方法超出了总督限制,或者如果您认为未来的方法需要更高的限制,则可以使用“未来方法更高限制”试验增加未来方法的限制。

使用未来的方法而不是可排队的另一个原因是当你的功能有时被同步执行,有时是异步执行的。以这种方式重构一个方法要比转换成一个可排队的类容易得多。当您发现现有代码的一部分需要移动到异步执行时,这很方便。你可以简单地创建一个相似的将来的方法来包装你的同步方法,就像这样:

@future
static void myFutureMethod(List<String> params) {
    // 调用同步方法
    mySyncMethod(params);
}
Queueable 语法

To use Queueable Apex, simply implement the Queueable interface.

public class SomeClass implements Queueable { 
    public void execute(QueueableContext context) {
        // 真棒代码在这里
    }
}

示例代码

一个常见的情况是采用一些sObject记录集,执行一些处理,例如向外部REST端点发出调用,或执行一些计算,然后异步地在数据库中更新它们。由于@future方法仅限于原始数据类型(或数组或基元集合),所以可排队的Apex是理想的选择。下面的代码采取帐户记录的集合,为每个记录设置parentId,然后更新数据库中的记录。

public class UpdateParentAccount implements Queueable {
    
    private List<Account> accounts;
    private ID parent;
    
    public UpdateParentAccount(List<Account> records, ID id) {
        this.accounts = records;
        this.parent = id;
    }

    public void execute(QueueableContext context) {
        for (Account account : accounts) {
          account.parentId = parent;
          // 执行其他处理或标注
        }
        update accounts;
    }
    
}
要将此类添加为队列中的作业,请执行以下代码:
// 找到'NY'的所有帐户
List<Account> accounts = [select id from account where billingstate = ‘NY’];
// 为所有记录找到一个特定的父帐户
Id parentId = [select id from account where name = 'ACME Corp'][0].Id;

// 实例化一个Queueable类的新实例
UpdateParentAccount updateJob = new UpdateParentAccount(accounts, parentId);

// 排队处理作业
ID jobID = System.enqueueJob(updateJob);

提交可执行的队列类之后,作业将被添加到队列中,并在系统资源可用时进行处理。

您可以使用新的作业ID来监视进度,无论是通过Apex Jobs页面还是以编程方式查询AsyncApexJob:

SELECT Id, Status, NumberOfErrors FROM AsyncApexJob WHERE Id = :jobID

测试Queueable Apex

以下代码示例演示如何在测试方法中测试可排队作业的执行情况。它看起来非常类似于Batch Apex测试。为了确保可排队的进程在测试方法内运行,作业被提交到Test.startTest和Test.stopTest块之间的队列中。在Test.stopTest语句之后,系统同步执行在测试方法中启动的所有异步进程。接下来,测试方法通过查询作业更新的帐户记录来验证可排队作业的结果。

@isTest
public class UpdateParentAccountTest {

    @testSetup 
    static void setup() {
        List<Account> accounts = new List<Account>();
        // 添加一个父帐户
        accounts.add(new Account(name='Parent'));
        // 添加100个子帐户
        for (Integer i = 0; i < 100; i++) {
            accounts.add(new Account(
                name='Test Account'+i
            ));
        }
        insert accounts;
    }
    
    static testmethod void testQueueable() {
        // 查询测试数据传递给队列类
        Id parentId = [select id from account where name = 'Parent'][0].Id;
        List<Account> accounts = [select id, name from account where name like 'Test Account%'];
        // 创建我们的Queueable实例
        UpdateParentAccount updater = new UpdateParentAccount(accounts, parentId);
        // startTest / stopTest block强制异步进程运行
        Test.startTest();        
        System.enqueueJob(updater);
        Test.stopTest();        
        // 验证作业运行。检查记录现在是否有正确的parentId
        System.assertEquals(100, [select count() from account where parentId = :parentId]);
    }
    
}

 

Chaining Jobs

Queueable Apex的最大特点之一就是职位链。如果您需要连续运行作业,Queueable Apex可以让您的生活更轻松。要将作业链接到其他作业,请从可排队类的execute()方法提交第二个作业。您可以从正在执行的作业中仅添加一个作业,这意味着每个父作业只能存在一个子作业。例如,如果您有另一个名为SecondJob的类实现Queueable接口,则可以将此类添加到execute()方法中的队列中,如下所示:

public class FirstJob implements Queueable { 
    public void execute(QueueableContext context) { 
        //真棒处理逻辑在这里通过提交下一份工作把这份工作链接到下一份工作
        System.enqueueJob(new SecondJob());
    }
}

再次,测试有一个稍微不同的模式。您不能在Apex测试中链接可排队的作业,这样做会导致错误。为了避免令人讨厌的错误,您可以通过在链接作业之前调用Test.isRunningTest()来检查Apex是否在测试环境中运行。

 

要记住的事情

Queueable Apex是一个伟大的新工具,但有几件事值得注意:

  • 针对异步Apex方法执行的共享限制,排队作业的执行计数一次。
  • 在一个事务中,您可以使用System.enqueueJob将最多50个作业添加到队列中。
  • 在链接作业时,只能使用System.enqueueJob从正在执行的作业中添加一个作业,这意味着每个父排队作业只能存在一个子作业。从同一个可排队的作业开始多个子作业是一个禁忌。
  • 链式作业的深度没有限制,这意味着你可以链接一个作业到另一个作业,并重复这个过程,每个新的子作业链接到一个新的子作业。但是,对于Developer Edition和Trial orgs,链接作业的最大堆栈深度为5,这意味着您可以链接作业四次,链中的最大作业数为5,包括初始父排队作业。

Apex 异步(3)批处理

学习目标

完成这个单位后,你会知道:

  • 在哪里使用Batch Apex。
  • 使用批次时Apex限制较高。
  • Batch Apex语法。
  • 批量Apex最佳实践。

Batch Apex

Batch Apex用于运行大型工作(认为数千或数百万条记录!),这将超出正常的处理限制。使用Batch Apex,您可以批量异步处理记录(因此名称为“Batch Apex”)以保持平台限制。如果您有很多记录要处理,例如数据清理或归档,Batch Apex可能是您最好的解决方案。

Batch Apex的工作原理如下。假设您想使用Batch Apex处理100万条记录。对于您正在处理的每批记录,批处理类的执行逻辑被调用一次。每次调用批次类时,作业将被放置在Apex作业队列中,并作为离散事务执行。这个功能有两个好处:

  • 每笔交易都以一组新的限额开始,更容易确保您的代码保持在限额执行范围内。
  • 如果一个批处理失败,则所有其他成功的批处理事务都不会回滚。

Batch Apex语法

要编写批量Apex类,您的类必须实现Database.Batchable接口并包含以下三种方法:

start
用于收集要传递给接口方法的记录或对象执行处理。此方法在Batch Apex作业开始时调用一次,并返回一个Database.QueryLocator对象或一个包含传递给作业的记录或对象的Iterable。

大多数情况下,QueryLocator通过简单的SOQL查询来生成批量作业中的对象范围。但是,如果您需要做一些疯狂的事情,比如在传递给execute方法之前循环调用API的结果或预处理记录,则可能需要查看参考资料部分中的自定义迭代器链接。

使用QueryLocator对象,SOQL查询检索的记录总数限制被绕过,并且您可以查询多达5000万条记录。但是,对于Iterable,由SOQL查询检索的记录总数的限制仍然是强制执行的。

execute

对传递给方法的每个块或“批量”数据执行实际处理。默认的批量大小是200条记录。不保证记录批按从启动方法收到的顺序执行。

该方法采取以下措施:

  • 对Database.BatchableContext对象的引用。
  • sObject的列表,例如List <sObject>或者参数化类型列表。如果您正在使用Database.QueryLocator,请使用返回的列表。
finish
用于执行后处理操作(例如,发送电子邮件)并在所有批处理完成后调用一次。

以下是Batch Apex类的框架:

global class MyBatchClass implements Database.Batchable<sObject> {

    global (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {
        // 收集要传递的记录或对象的批处理执行
    }

    global void execute(Database.BatchableContext bc, List<P> records){
        // 处理每批记录
    }    

    global void finish(Database.BatchableContext bc){
        // 执行任何后期处理操作
    }    

}

调用批处理类

要调用批处理类,只需实例化它,然后调用Database.executeBatch与实例:

MyBatchClass myBatchObject = new MyBatchClass(); 
Id batchId = Database.executeBatch(myBatchObject);
您还可以选择传递第二个作用域参数,以指定应该传递到每个批处理的执行方法的记录数。专家提示:如果您遇到管理员限制,您可能需要限制此批量。
Id batchId = Database.executeBatch(myBatchObject, 100);
每个批次的Apex调用都将创建一个AsyncApexJob记录,以便您可以跟踪作业的进度。您可以通过SOQL查看进度或在Apex作业队列中管理作业。我们马上谈谈Job Queue。
AsyncApexJob job = [SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors FROM AsyncApexJob WHERE ID = :batchId ];
Batch Apex使用状态

Batch Apex通常是无状态的。批量Apex作业的每次执行都被视为离散事务。例如,包含1,000条记录并使用默认批量大小的批量Apex作业被视为每个200条记录的五个事务。

如果在类定义中指定Database.Stateful,则可以在所有事务中维护状态。当使用Database.Stateful时,只有实例成员变量在事务之间保留它们的值。维护状态对于计算或汇总处理记录非常有用。在下一个示例中,我们将更新批处理作业中的联系人记录,并要跟踪受影响的总记录,以便我们可以将其包含在通知电子邮件中。

Batch Apex例子代码

现在你已经知道如何编写一个Batch Apex类了,我们来看一个实际的例子。假设您有一项业务要求,即美国公司的所有联系人都必须将其母公司的帐单邮寄地址作为邮寄地址。不幸的是,用户输入的新联系人没有正确的地址!用户不会学习?!编写一个批处理Apex类,确保这个要求被强制执行。

以下示例类查找使用QueryLocator由start()方法传入的所有帐户记录,并使用其帐户的邮寄地址更新关联的联系人。最后,它发送一个包含批量作业结果的电子邮件,因为我们使用Database.Stateful来跟踪状态,所以更新记录的数量。

global class UpdateContactAddresses implements 
    Database.Batchable<sObject>, Database.Stateful {
    
    // 实例成员在事务中保持状态
    global Integer recordsProcessed = 0;

    global Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(
            'SELECT ID, BillingStreet, BillingCity, BillingState, ' +
            'BillingPostalCode, (SELECT ID, MailingStreet, MailingCity, ' +
            'MailingState, MailingPostalCode FROM Contacts) FROM Account ' + 
            'Where BillingCountry = \'USA\''
        );
    }

    global void execute(Database.BatchableContext bc, List<Account> scope){
        // 处理每批记录
        List<Contact> contacts = new List<Contact>();
        for (Account account : scope) {
            for (Contact contact : account.contacts) {
                contact.MailingStreet = account.BillingStreet;
                contact.MailingCity = account.BillingCity;
                contact.MailingState = account.BillingState;
                contact.MailingPostalCode = account.BillingPostalCode;
                // 添加联系人列表进行更新
                contacts.add(contact);
                // 增加实例成员计数器
                recordsProcessed = recordsProcessed + 1;
            }
        }
        update contacts;
    }    

    global void finish(Database.BatchableContext bc){
        System.debug(recordsProcessed + ' records processed. Shazam!');
        AsyncApexJob job = [SELECT Id, Status, NumberOfErrors, 
            JobItemsProcessed,
            TotalJobItems, CreatedBy.Email
            FROM AsyncApexJob
            WHERE Id = :bc.getJobId()];
        // 调用一些实用程序发送电子邮件
        EmailUtils.sendMessage(a, recordsProcessed);
    }    

}
代码应该是相当直接的,但在现实中可能会有点抽象。以下是更详细的内容:
  • start方法提供了执行方法将在单个批处理中处理的所有记录的集合。它通过使用SOQL查询调用Database.getQueryLocator来返回要处理的记录列表。在这种情况下,我们只需查询“美国”的帐单国家/地区的所有帐户记录。
  • 每个批次的200条记录都在执行方法的第二个参数中传递。 execute方法将每个联系人的邮寄地址设置为帐户的帐单地址,并增加recordsProcessed以跟踪处理的记录数。
  • 作业完成后,完成方法将在AsyncApexJob对象(列出有关批处理作业的信息的表)上执行查询,以获取作业的状态,提交者的电子邮件地址以及其他信息。然后它会发送一封通知邮件给作业提交者,其中包括工作信息和联系人数量的更新。

测试Batch Apex

由于Apex开发和测试齐头并进,所以我们如何测试上述批次类。简而言之,我们插入一些记录,调用Batch Apex类,然后用正确的地址声明记录已经正确更新。

@isTest
private class UpdateContactAddressesTest {

    @testSetup 
    static void setup() {
        List<Account> accounts = new List<Account>();
        List<Contact> contacts = new List<Contact>();
        // 插入10个帐户
        for (Integer i=0;i<10;i++) {
            accounts.add(new Account(name='Account '+i, 
                billingcity='New York', billingcountry='USA'));
        }
        insert accounts;
        // 找到刚插入的帐号。为每个添加联系人
        for (Account account : [select id from account]) {
            contacts.add(new Contact(firstname='first', 
                lastname='last', accountId=account.id));
        }
        insert contacts;
    }

    static testmethod void test() {        
        Test.startTest();
        UpdateContactAddresses uca = new UpdateContactAddresses();
        Id batchId = Database.executeBatch(uca);
        Test.stopTest();

        // 在测试停止之后,声明记录被正确更新
        System.assertEquals(10, [select count() from contact where MailingCity = 'New York']);
    }
    
}
设置方法插入10个帐户记录,开票城市为“纽约”,开票国家为“美国”。然后为每个帐户创建一个关联的联系人记录。该数据由批次类使用。

注意

确保插入的记录数小于批量大小200,因为测试方法只能执行一个批次总数。

在测试方法中,UpdateContactAddresses批次类被实例化,通过调用Database.executeBatch并将其传递给批次类的实例来调用。

对Database.executeBatch的调用包含在Test.startTest和Test.stopTest块中。这就是所有魔法发生的地方。在调用Test.stopTest之后执行作业。包含在Test.startTest和Test.stopTest中的任何异步代码都是在Test.stopTest之后同步执行的。

最后,测试通过检查结算城市“纽约”的联系人记录数量与插入的记录数量(即10)相匹配来验证所有联系人记录已被正确更新。

最佳实践

与未来的方法一样,在使用Batch Apex时,您还是要记住一些事情。为确保快速执行批处理作业,请尽量减少Web服务调出时间并调整批处理Apex代码中使用的查询。批处理作业执行的时间越长,当许多作业在队列中时,其他排队的作业就越有可能被延迟。最佳实践包括:

  • 如果您有多个批次的记录,请仅使用Batch Apex。如果您没有足够的记录来运行多个批次,则最好使用Queueable Apex。
  • 调整任何SOQL查询以尽可能快地收集要执行的记录。
  • 尽量减少创建的异步请求的数量,以尽量减少延迟的机会。
  • 如果您打算从触发器调用批处理作业,请特别小心。您必须能够保证触发器不会添加比限制更多的批处理作业。

Apex 异步(2)使用future

学习目标

完成这个单位后,你会知道:

  • 何时使用未来的方法。
  • 使用未来方法的局限性。
  • 如何使用未来的标注方法。
  • 未来方法的最佳实践。

Future Apex

未来的Apex用于在单独的线程中运行进程,稍后系统资源可用。

注意:从技术上讲,您使用@future注释来标识异步运行的方法。但是,由于“使用@future注解标识的方法”很费力,通常被称为“将来的方法”,所以我们将在本模块的其余部分中引用它们。

在使用同步处理时,所有的方法调用都是由执行Apex代码的同一个线程来完成的,在这个过程完成之前不会进行额外的处理。你可以使用未来的方法进行任何你想在自己的线程中异步运行的操作。这提供了不阻止用户执行其他操作并为该过程提供更高调控器和执行限制的好处。每个人都是异步处理的赢家。

未来的方法通常用于:

  • 标注到外部Web服务。如果从触发器或执行DML操作之后进行标注,则必须使用将来的或可排队的方法。触发器中的标注将保持数据库连接在标注的生命周期中处于打开状态,这是多租户环境中的“否定”。
  • 如果时间允许,例如某种资源密集型计算或记录处理,您希望在自己的线程中运行的操作。
  • 隔离不同sObject类型的DML操作以防止混合的DML错误。这是一个边缘情况,但你偶尔会遇到这个问题。有关更多详细信息,请参阅无法在DML操作中一起使用的sObjects。

Future方法语法

Future的方法必须是静态方法,并且只能返回一个void类型。指定的参数必须是原始数据类型,原始数据类型数组或原始数据类型的集合。值得注意的是,未来的方法不能将标准或自定义对象作为参数。一个常见的模式是传递一个你想异步处理的记录ID列表。

global class SomeClass {
  @future
  public static void someFutureMethod(List<Id> recordIds) {
    List<Account> accounts = [Select Id, Name from Account Where Id IN :recordIds];
    // 处理帐户记录做真棒的东西
  }
}

注意

对象不能作为参数传递给未来方法的原因是因为对象可以在调用方法的时间和实际执行的时间之间改变。请记住,未来的方法在系统资源可用时执行。在这种情况下,未来的方法在实际执行时可能会有一个旧的对象值,这会导致各种不好的事情发生。

重要的是要注意,未来的方法不能保证按照所调用的顺序执行。同样,未来的方法也不能保证按照它们被调用的顺序执行。如果您需要这种类型的功能,那么Queueable Apex可能是更好的解决方案。当使用未来的方法时,也可能会同时运行两个未来的方法,如果两个方法更新相同的记录,可能会导致记录锁定和令人讨厌的运行时错误。

示例标注代码

要为外部服务或API创建Web服务标注,可以使用标记为(callout = true)的未来方法创建Apex类。下面的类具有在不允许标注的情况下同步和异步调用标注的方法。我们在自定义日志对象中插入一条记录来跟踪标注的状态,仅仅是因为日志记录总是很有趣!

public class SMSUtils {

    // 从触发器中调用异步,等等,不允许标注。
    @future(callout=true)
    public static void sendSMSAsync(String fromNbr, String toNbr, String m) {
        String results = sendSMS(fromNbr, toNbr, m);
        System.debug(results);
    }

    // 从控制器等呼叫立即处理
    public static String sendSMS(String fromNbr, String toNbr, String m) {
        // 调用“发送”将会产生一个标注
        String results = SmsMessage.send(fromNbr, toNbr, m);
        insert new SMS_Log__c(to__c=toNbr, from__c=fromNbr, msg__c=results);
        return results;
    }

}

测试类

测试未来的方法与典型的Apex测试有点不同。要测试将来的方法,请将测试代码放在startTest和stopTest测试方法之间。系统收集startTest之后所做的所有异步调用。当执行stopTest时,所有这些收集的异步进程将同步运行。然后您可以断言异步调用正常运行。

注意

测试代码实际上无法将标注发送到外部系统,因此您必须“嘲笑”测试覆盖的标注。查看Apex集成服务模块,获取有关模拟测试标注的完整详细信息。

这是我们用于测试的模拟标注类。 Apex测试框架利用这个“模拟”响应,而不是实际调出REST API端点。

@isTest
global class SMSCalloutMock implements HttpCalloutMock {
    global HttpResponse respond(HttpRequest req) {
        // 创建一个假的回应
        HttpResponse res = new HttpResponse();
        res.setHeader('Content-Type', 'application/json');
        res.setBody('{"status":"success"}');
        res.setStatusCode(200);
        return res; 
    }
}
测试类包含一个测试方法,它测试异步和同步方法,因为前者调用后者。
@IsTest
private class Test_SMSUtils {

  @IsTest
  private static void testSendSms() {
    Test.setMock(HttpCalloutMock.class, new SMSCalloutMock());
    Test.startTest();
      SMSUtils.sendSMSAsync('111', '222', 'Greetings!');
    Test.stopTest();
    // 运行标注并检查结果
    List<SMS_Log__c> logs = [select msg__c from SMS_Log__c];
    System.assertEquals(1, logs.size());
    System.assertEquals('success', logs[0].msg__c);
  }

}

最佳实践

由于每个将来的方法调用都会向异步队列添加一个请求,因此避免在短时间内添加大量未来请求的设计模式。如果您的设计有可能一次添加2000个或更多请求,则由于流量控制,请求可能会延迟。以下是您想要记住的一些最佳做法:

  • 确保将来的方法尽可能快地执行。
  • 如果使用Web服务标注,请尝试将所有标注捆绑在一起,而不是为每个标注使用单独的未来方法。
  • 大规模进行彻底的测试。测试触发排队@future调用的触发器能够处理200条记录的触发器集合。这有助于确定在目前和未来的设计中是否会出现延迟。
  • 考虑使用Batch Apex而不是将来的方法来异步处理大量的记录。这比为每条记录创建未来请求更有效率。

要记住的事情

未来的方法是一个伟大的工具,但拥有巨大的权力是很大的责任。在使用它们时,请注意以下几点:

  • 使用未来注解的方法必须是静态方法,并且只能返回一个void类型。
  • 指定的参数必须是原始数据类型,原始数据类型数组或原始数据类型的集合;未来的方法不能把对象当作论据。
  • 未来的方法将不一定按照它们被调用的顺序执行。另外,有可能两个未来的方法可能同时运行,如果这两个方法正在更新相同的记录,则可能导致记录锁定。
  • 未来的方法不能用于getMethodName(),setMethodName()中的Visualforce控制器中,也不能用于构造器中。
  • 你不能从将来的方法调用未来的方法。在运行未来的方法时,你也不能调用一个调用未来方法的触发器。请参阅参考资料中的链接以防止递归未来方法调用。
  • getContent()和getContentAsPDF()方法不能用于将来注释的方法。
  • 您每个Apex调用将被限制为50个未来呼叫,并且在24小时内对呼叫数量有额外的限制。有关限制的更多信息,请参阅下面的链接。