【Salesforce】非同期を同期のように見せる、プラットフォームイベントを使用したフロー活用術

今まではプラットフォームイベントの基本的な使い方を紹介しましたが、今回はそれらを利用して非同期処理を同期のように処理するフロー活用術について記載していきます。

プラットフォームイベントの基礎情報はこちら

* [プラットフォームイベントの定義](https://qiita.com/311052/items/be021980fc77683f4c60)
* [プラットフォームイベントの公開](https://qiita.com/311052/items/3ab92383fa27e1ac8caa)
* [プラットフォームイベントの登録](https://qiita.com/311052/items/a0879acf98efe685f04e)

# フローの起動から終了までの流れ
フローとプラットフォームイベントを絡めることで、コールアウトを伴う非同期処理を行いつつも、同期のように処理をすることが可能になります。

フローには「一時停止」ノードがあり、これを使用することでプラットフォームイベント公開のタイミングまで停止することが可能です。

実現方法としては、自動起動フローと非同期処理を行うApexのメソッドを使用します。

今回使用するプラットフォームイベントは以下のイベントを定義します。

![SavaPoint.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699607/8c6e80dd-e24c-4309-3e19-e1240410e1c6.png)

## コールアウトとプラットフォームイベントを登録するApexクラス

フローから呼び出されるApexでは、コールアウトなどの処理と共に、結果を格納するためのプラットフォームイベントを登録します。
以下、実装例です。


```Java
public without sharing class TestEventPublishAsynchronous {
    
    // フローから渡されるパラメータ
    public class TestEventPublishAsynchronousInput {
    	
        @InvocableVariable(description='フローのインタビューGUIDを設定する' required=true)
        public String flowGuid;
        
        @InvocableVariable(description='対象のレコードIDを設定する' required=true)
        public String recordId;

        public TestEventPublishAsynchronousInput(String flowGuid, String recordId) {
            this.flowGuid = flowGuid;
            this.recordId = recordId;
        }        
    }

    @InvocableMethod
    public static void run(List<TestEventPublishAsynchronousInput> inputList) {
        
        // レコードIDから商談を取得
        Opportunity opp = [Select Id, Name From Opportunity Where Id = :inputList[0].recordId];
        // コールアウト処理を非同期実行
        callout(inputList[0].flowGuid, inputList[0].recordId, opp.Name);
    }

    @future(callout=true)
    private static void callout(String flowGuid, String recordId, String oppName) {
        try {
            // ★ここでコールアウト処理を実装★
            
            // プラットフォームイベントの公開（成功）
            publishPlatformEvent(flowGuid, recordId, false, null);
        } catch (Exception e) {
            // プラットフォームイベントの公開（失敗）
            publishPlatformEvent(flowGuid, recordId, true, '商談' + oppName +'コールアウト処理に失敗しました');
        }
    }
    
    //  プラットフォームイベントの公開
    private static void publishPlatformEvent(String flowGuid, String recordId, Boolean hasError, String message) {
        TestEvent__e te = new TestEvent__e(
            FlowId__c  = flowGuid,
            RecordId__c = recordId,
            HasError__c = hasError,
            Message__c = message
        );
        // プラットフォームイベントの公開
        Database.SaveResult sr = EventBus.publish(te);
        if (sr.isSuccess()) {
            // プラットフォームイベント公開正常終了
            System.debug('プラットフォームイベント公開: ' + te);
        } else {
            // プラットフォームイベント公開エラー
            for (Database.Error err å: sr.getErrors()) {
                System.debug('プラットフォームイベント公開に失敗: ' + err.getStatusCode() + ' - ' + err.getMessage());
            }
        }
    }
}
```

## フローの実装

フローは「自動起動フロー」で作成します（レコードトリガフローでは「一時停止」ノードが対応していないので）。
![スクリーンショット 2021-01-31 18.58.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699607/9769d205-0739-dd39-22e0-a638b0a0c34f.png)

### 変数
変数には以下の2つを定義します。

* Id：プロセスから渡される商談のID（入力にチェックを入れておくこと！）
![スクリーンショット 2021-01-31 18.59.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699607/dd9f9ac2-5a57-0b02-6198-e5b0e0611034.png)

* TestEvent:コールアウト時に登録されるプラットフォームのイベント
![スクリーンショット 2021-01-31 19.03.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699607/77bd6149-213a-2e8f-627c-c20a4048c3f2.png)


### フロー
フローは以下のような構成で作成します。
![スクリーンショット 2021-01-31 19.07.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699607/6b7519ca-5000-45d7-c0d3-6182a20baeba.png)

1. Apexコール
アクションノードからApexクラスをコールします。
アクションにApexクラス名、渡すパラメータにこのフローから呼び出されたことを一意に特定するInterviewGuidとコールアウト処理対象の商談IDを渡します。
![スクリーンショット 2021-01-31 19.00.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699607/d681a843-2573-0dea-e34a-f65b8baae5af.png)

2. 一時停止
Apex側で非同期実行をしているため、フローでは待つ処理を実施します。
Apex側でテストイベントが登録された段階で停止状態から再開します。

一時停止ノード内は以下のように設定します。
![スクリーンショット 2021-01-31 19.02.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699607/5c136e2d-34d6-e8bb-da81-91f3474abfb3.png)

プラットフォームイベントは別ユーザから実行されたフローからも登録される可能性があるため、他と混在しないように、ここのフローであることを一意に特定するInterviewGuidとその時に使用した商談のレコードIDをキーとしています。
条件に一致したプラットフォームイベントはTestEvent変数に格納されます。
![スクリーンショット 2021-01-31 19.04.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699607/cba51c09-3efe-224c-3b83-04e8699ba6df.png)

3. エラー判定
イベント再開の際に取得したプラットフォームイベントの中身を精査してエラー判定をします。
テストイベントプラットフォームイベントに「エラーあり」(HasError__c)フラグを持たせており、コールアウト登録の際にエラーの有無を登録しているので今回はそれを見ます。

4. エラー処理
エラーがあった場合は受信したプラットフォームイベントをもとにエラー処理をします。
ここではChatterを投げていますが、お好みで。

フローからApexのメソッドをコールする際、フローのGUIDと処理対象となるレコードIDを渡します。
最終的にここで渡しているフローのGUIDとレコードIDをプラットフォームイベントメッセージとして公開して、後ほどフローで結果を取得するための紐付けのキーとして使用します。

フローの実装としては以上となります。
あとは、このフローをプロセスなどから呼び出すことでフロー内で非同期処理をしながら結果をリアルタイムに受信することができます。

# 使用上の注意
上記のように、フローの一時停止を使用することによって、非同期処理を同期のように扱うことができるわけですが、注意点があります。

## フローの実行数制限
一時停止によってフローの中で待ち続けることになるため、非同期処理が長ければ長いほど、フローの実行時間も長くなることになります。
**フローの実行数は最大50,000**となるため、規模の大きいシステムや非同期処理が長い処理、一度に複数件同時にトリガする場合などは上限に達する場合があります。
ビジネスロジック要件として動かす場合には、パフォーマンス面にも注視する必要があります。

## 必ずプラットフォームイベントを公開する
今回紹介した方法はプラットフォームイベントが公開されて一時停止が解除される仕組みをフローに組み込んでいます。
逆に言うと、**プラットフォームイベントが公開されないと一時停止が解除されずにフローが起動したまま**とも言えます。
プラットフォームイベントの公開をプロセス、フロー、Apex、REST APIといかなる方法で行ったとしても、必ず例外発生時でもプラットフォームイベントの公開をやりきるように実装しましょう。