Salesforce の非同期実行アプローチ - ローコード、プロコード、ハイブリッド

[まとめページに戻る](https://qiita.com/Keiji_otsubo/items/60afc2a8d211b6f7d89f)
[まとめA～M](https://qiita.com/Keiji_otsubo/items/f3ddedee6793bf1576f8)

-------------------------

+ [Salesforce Asynchronous Execution Approaches - Low-Code, Pro- Code and Hybrid](https://meera-r-nair.blogspot.com/2022/03/salesforce-asynchronous-execution.html)


# Salesforceのトランザクションとは何ですか

トランザクションは、Salesforce でレコードの変更が手動または自動プロセスによって発生したときに、順次実行される一連の関連する操作またはアクションです。たとえば、ユーザーがユーザー インターフェイスから Salesforce で新しい商談を作成しようとすると、バックエンドで一連のアクションがトリガーされます。これには、商談に対する検証ルールの実行、フロー/トリガーなどを介して実装されたカスタマイズ ロジックの実行などが含まれます。 Salesforce ブログでは、トランザクションの一部として発生する可能性のあるさまざまな操作とその実行順序について詳しく説明しています。

# 同期および非同期実行モードとは何ですか

同期実行

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/79eb5fe1-7142-2f11-e1cd-71423ee65c0c.png)

上の図は、同期トランザクションがどのように機能するかを示しています。ここで、トランザクションは 3 つのアクションを順番に実行します。意味 アクション 2 は、アクション 1 が完了した後にのみ呼び出され、実行されます。同様に、アクション 3 はアクション 2 が完了した後にのみ実行されます。3 つのアクションが実行されると、トランザクションも完了としてマークされます。


非同期実行

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/5da03ea8-a164-7f34-2644-3d17b0e360e9.png)

上の図では、アクション 1 が完了すると、アクション 2 が非同期で呼び出されることがわかります。ただし、アクション 2 の完了を待たずに、アクション 3 が呼び出され、実行されます。したがって、ここではアクション 2 がバックエンドで完全に別個のトランザクションとして実行されており、トランザクション 1 はアクション 2 の応答を待ってアクション 3 を実行することはありません。この実行モードは非同期実行と呼ばれます。

# 非同期実行モードを使用できる場所

非同期実行モードは、トランザクション関連の多くの問題を解決するのに役立ちます

1. パフォーマンス- 長時間実行されているプロセスがあり、現在のトランザクションのパフォーマンスに影響を与える場合、そのプロセスを非同期モードに移行して、現在のトランザクションのパフォーマンスを向上させることができます。
2. 統合- トリガーの実行から外部システムを呼び出す必要がある場合は、非同期モードが必要です
3. 混合 DML 例外- セットアップ オブジェクトと非セットアップ オブジェクトの両方を一緒に更新する必要がある場4. 合、混合 DML 例外を回避するために、それらの更新の 1 つを非同期モードに移動できます。
再帰的実行 -親レコード更新トランザクションで子レコードを更新し、次に親を再度更新すると、再帰的実行が発生する可能性があります。したがって、この種の動作は、子レコードの更新を非同期モードに移行することで制御できます。


# Salesforce におけるさまざまな非同期実行アプローチと考慮事項

以下の表は、Salesforce で使用できるさまざまな非同期実行オプションを示しています。

|  | アプローチ | 実装の複雑さ|考慮事項 |
|:-----------|------------:|:------------:|:------------:|
|1|フロースケジューラ	|ローコード|1. 実装とメンテナンスの労力が少ない2. スケジュールは 1 回、毎日または毎週のみ可能ですが、1 日に 2 回実行するような柔軟なスケジュール設定はできません3. 一括レコード処理に使用されます4. 複雑なロジックの実装には適していません|
|1|非同期パスを使用したフロー|	ローコード|1. 実装とメンテナンスの労力が少ない2. Future または Queueable を使用したトリガーに似ている3. 操作によって画面上の値が更新される場合、更新はほぼリアルタイムで表示される3. 複雑なロジックの実装には非効率|
|3|Flow サブスクリプションを使用したプラットフォーム イベント|	ローコード|1. 実装とメンテナンスの労力が少なくて済みます。 2.ここで説明されているプラ​​ットフォーム イベント関連の制限を考慮する必要があります。|
|4|非同期 Apex アクションによるフロー|	ハイブリッド|1. 実装と保守の労力は平均的2. 複雑なロジックを頂点アクションに移動できる3.ここで言及されている将来の制限を考慮する必要がある|
|5|非同期トリガーによる変更データキャプチャ	|ハイブリッド|1. 実装とメンテナンスの労力は平均的です。 2.ここで説明されている変更データ キャプチャ関連の制限を参照してください。3. CDC アドオン ライセンスが必要です。4. 変更イベント レコードには、あらゆる種類の挿入、更新、削除、削除の取り消しの詳細が保持されます。基準に基づいてレコードをフィルタリングする必要があります。5. Update Change イベントは、変更されたフィールドのみを保持します。|
|6|トリガーサブスクリプションを使用したプラットフォームイベント|	ハイブリッド|1. 実装とメンテナンスの労力は平均的です。 2. 常に必要な場合にのみ起動でき、必要な情報をすべて含めることができるため、CDC と比較して柔軟性が高くなります。 3.ここで説明されているプラ​​ットフォーム イベント関連の制限を考慮する必要があります。|
|7|Future メソッドによるトリガー|	プロコード|1. 実装とメンテナンスの労力がかかる2. 複雑な要件の実装に使用できる3. 将来の異なるメソッドを連鎖させることができない4.ここで言及されている将来の関連制限を考慮する必要がある|
|8|Queueableメソッドによるトリガー|	プロコード|1. 実装とメンテナンスの労力がかかる2. 将来のメソッドと比較して柔軟性が高い3. トランザクション ファイナライザーによるトランザクション制御の向上4. 異なるキュー可能メソッドの実行を連鎖させることができる5.ここで説明されているキュー可能関連の制限を考慮する必要がある|
|9|バッチApex	|プロコード|1. 実装とメンテナンスの労力がかかる2. 大量のレコードの処理に適している3. 同じジョブの異なるバッチ間で状態を維持できる4. トリガーまたはスケジューラから実行できる5.ここで
説明されているバッチ頂点関連の制限を考慮する必要がある|
|10|Apex スケジューラ|	プロコード|1. 実装とメンテナンスの労力がかかる2. 必要な頻度に基づいて Batch Apex をスケジュールするために使用できる3. フロー スケジューラよりも柔軟性が高い4.ここで説明されている Apex スケジューラ関連の制限を考慮する必要がある|

# 実装のユースケース

さまざまな非同期アプローチを理解するために、小さなシナリオを考えてみましょう。取引先と取引先担当者には、 「 Yes」と「No 」の値を含むアクティブな選択リスト項目があります。親取引先の Active フィールド値が「No」になるたびに、子取引先責任者の Active 値も No になる必要があります。つまり、取引先企業が非アクティブ化されるたびに、子取引先担当者も非アクティブ化される必要があります。

# 実装の詳細

子連絡先をすぐに非アクティブ化したくない場合は、いつでもこれを非同期実行に移行できます。さまざまな非同期実行アプローチを使用してこれを実装する方法を考えてみましょう。


アプローチ 1: フロー スケジューラ
これはスケジュールされたフローであるため、処理する必要があるレコードを識別するための特定の基準が必要です。そこで、「 Update Contacts 」というチェックボックスを作成しています。これは、Account、Active の値が「No」になると true に設定されます。フローは次のとおりです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/0f9bcdba-ba5d-9e2a-b0fc-98b8413312ce.png)

次に、レコードの更新 = はいでこれらのアカウント レコードを取得するには、以下に示すようにスケジュールされたフローを作成する必要があります。処理後、レコードの更新値を false に切り替える必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/ffd641cd-a659-1d5a-737e-b140e10985ea.png)

ここでは、このフローが毎日実行されることがわかります。そして、以下に示すように、[スケジュールされたジョブ] でスケジュールされた詳細を確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/c7bc6778-9484-c77c-8758-8b2c4b8e024c.png)

前述したように、利用可能な実行頻度はOnce、Daily、および Weeklyのみです。これでは柔軟なスケジュールを立てることができません。


アプローチ 2 - 非同期パスを使用したフロー

これは、フローの一部として同期実行パスと非同期実行パスの両方を言及できる場合のフローのもう 1 つの優れた機能です。実装は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/36bc3e7a-bedc-373f-8ea6-8053ea30b693.png)


非同期実行パスが表示されます。したがって、これはシステム リソースが利用可能なときに実行され、デバッグ ログでは別のトランザクションとして確認できます。


アプローチ 3 - フロー サブスクリプションを使用したプラットフォーム イベント
プラットフォームイベントは、特に同じイベントメッセージを複数のシステムに送信する場合に、Salesforce を外部システムと統合する強力なモードです。しかし、これに加えて、これを使用して内部的に非同期実行モードを導入することもできます。

このシナリオでは、必須フィールドを含む新しいプラットフォーム イベントを定義できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/7b5aded7-4d39-352d-7c3d-f5b2b1d29609.png)


次に、[アカウントがアクティブ] チェックボックスの値が false になったときに、このプラットフォーム イベントを公開する新しいフローを作成できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/a17e2d7d-c0a0-333f-8ac0-9bbab04738ef.png)

別のフローを使用して、このプラットフォーム イベントを再度サブスクライブできます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/972f89d8-f07d-2b1b-f2aa-3f88a7887b24.png)


フローは以下のようになります。プラットフォーム イベント サブスクリプションの詳細をデバッグする必要がある場合は、自動プロセス

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/d8559854-479a-1ab0-df48-ef4f3c8a6b82.png)


のデバッグ ログを有効にする必要があります。


アプローチ 4 - 非同期 Apex アクションによるフロー
このアプローチでは、条件をチェックして非アクティブ化されたアカウントの ID を取得する、レコードによってトリガーされるフローを設定できます。その後、フローは非同期で実行される Apex アクションを呼び出すことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/9e976ab1-40e9-6708-23bf-e35a802bc7d7.png)


呼び出された Apex メソッドには、実際には将来のメソッドである別の関数呼び出しが含まれています。コードは以下のようなものです。

```
@InvocableMethod(label='連絡先の非アクティブ化' description='連絡先の非アクティブ化') 
     public static void deativateContact(List<id> accids){ 
         //将来のメソッドを呼び出します   
         deactivatecontactfuture(accids); 
     } 
    
    @future 
    public static void deactivatecontactfuture(List<id> accids){ 
        try{ 
            //このアカウントのアクティブな連絡先を取得
             List<Contact> conList = new List<Contact>(); 
             for(Contact con:[アカウント ID IN :accids の連絡先から ID を選択]) { 
                 con.active__c = 'No'; 
                 conList。追加(コン); 
             } 
            //アクティブな連絡先が存在する場合は、それらを非アクティブ化します
             if(conList.size()>0){ 
                 conList を更新します。
             } 
            
        }catch(Exception ex){ 
            System.debug('例外が発生しました:'+ex.getMessage()); 
        } 
        
    }
```

future メソッドの代わりに Queueable メソッドを実行することもできます。



アプローチ 5 - 非同期トリガーによる変更データ キャプチャ
変更データ キャプチャはプラットフォーム イベントに似ていますが、一部の標準オブジェクトとすべてのカスタム オブジェクトで有効にできる点が異なります。有効にすると、自動的に変更が新しいレコードとして <SObject>ChangeEvent オブジェクトに記録されます。そのため、プラットフォーム イベントと比較して、ログに記録される詳細に対する制御が少なくなります。

まず、アカウント オブジェクトで変更データ キャプチャを有効にする必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/74af04e2-df87-a2eb-62a7-4e172e5513ef.png)


これを有効にすると、取引先レコードが作成、更新、削除、または削除取り消しされるたびに、新しいエントリが AccountChangeEvent オブジェクトに追加されます。このオブジェクトにトリガーを記述すると、これは別のトランザクションとして実行されます。

```
/************************************************ ********************** * * アカウントの無効化 * *
に子の連絡先を無効にするアカウント変更イベントをトリガーします* ************ ************************************************* ******/ AccountChangeEvent で AccountChangeEventTrigger をトリガーします (挿入後) {     set<id> accids = new set<id>();     try{         for(AccountChangeEvent ace : Trigger.new){             System.debug(ace);             System.debug(ace.id);             //ヘッダーの詳細を確認して、操作タイプと変更されたフィールドの詳細を取得します            。 EventBus.ChangeEventHeader header = ace.ChangeEventHeader;











            if(header.changetype == 'UPDATE' && header.changedfields.contains('Active__c')){             
                accIds.add(ace.Id);            
            } 
        } 
        //基準を満たすアカウント変更がある場合は、アクティブな連絡先を更新するメソッドを呼び出します
        if(accids.size()>0){ 
            ContactDeactivation.deativateContactsync(accids); 
        } 
    }catch(Exception ex){ 
        System.debug('例外が発生しました:'+ex.getMessage()); 
    }     
}
```

アプローチ 6 - トリガー サブスクリプションを使用したプラットフォーム イベント
アプローチ 3 (フロー サブスクリプションを使用したプラットフォーム イベント) と似ていますが、より複雑なロジックの処理に使用できます。

プラットフォームイベントをサブスクライブするトリガーは以下のようになります。

```
/************************************************ *************** 
* アカウントを非アクティブ化するためのアカウント プラットフォーム イベントをトリガーします
* 連絡先の非アクティブ化時に
* ********************* ******************************************/ 

Account_ev__e で AccountEventTrigger をトリガーします (挿入後) { 
    //アカウントが非アクティブかどうかを確認します
    List<id> accids = new List<id>(); 
    try{ 
        for(Account_ev__e accev : Trigger.new){ 
            if(accev.isActive__c == 'No'){ 
                accids.add(accev.AccountID__c); 
            if(accids.size (         )>0){ 
        ContactDeactivation.deativateContact             (accids);         }     }catch(例外例){




        System.debug('例外が発生しました:'+ex.getMessage()); 
    }     
}
```

これは、トリガーを使用して変更データ キャプチャをサブスクライブするのと似ています。


アプローチ 7 - Future メソッドによるトリガー

アカウントトリガーでは、非アクティブ化されたアカウントを見つけるための基準を適用できます。基準を満たす記録がある場合は、将来の連絡先非アクティブ化メソッドを呼び出すことができます。

```
/**************************************** 
* アカウントトリガー
* 
* **** ************************************/
アカウントで AccountTrigger をトリガー (更新後) { 
    
    List<id> accIds = 新しい List<id>(); 
    //更新後かどうかを確認する
    if(Trigger.isupdate && Trigger.isafter){ 
        for(Account acc:Trigger.new){ 
            if( acc.Active__c != Trigger.oldMap.get(acc.Id).Active__c && acc.Active__c == 'いいえ'){ 
                accIds.add(acc.id); 
            } 
        } 
               
        //future メソッドを呼び出します
        if(accIds.size()>0){ 
            ContactDeactivation.deactivatecontactfuture(accids); 
        }   } 
        
        }
```

@future は別のトランザクションとして実行され、デバッグ ログで future メソッドとして追跡できます。


アプローチ 8 - キュー可能メソッドを使用したトリガー
これはアプローチ 7 に似ていますが、Queueable には、サポートするパラメーター、非同期プロセス チェーンのサポートなどに関して、より多くの利点があります。これはアカウント トリガーから呼び出され、ロジックはアプローチ 7 と同じです。

```
/**************************************** 
* アカウントトリガー
* 
* **** ************************************/
アカウントで AccountTrigger をトリガー (更新後) { 
    
    List<id> accIds = 新しい List<id>(); 
    //更新後かどうかを確認する
    if(Trigger.isupdate && Trigger.isafter){ 
        for(Account acc:Trigger.new){ 
            if( acc.Active__c != Trigger.oldMap.get(acc.Id).Active__c && acc.Active__c == 'いいえ'){ 
                accIds.add(acc.id); 
            } 
        } 
               
        //キュー可能メソッドを呼び出す
        if(accIds.size()>0){ 
            ContactDeactivationQueueable myq = new ContactDeactivationQueueable(accIds);
            ID ジョブ ID = System.enqueueJob(myq); 
        } 
        
  }
```

そして、Queueable インターフェースを実装する Queueable メソッドは以下のようになります。

```
/*********************************************** 
* キュー可能連絡先を無効にするクラス
* 
* ******************************************** */ 

public class ContactDeactivationQueueable は Queueable{ 
     private List<id> accids ;を実装します。
     public ContactDeactivationQueueable(List<id> accids) { 
        //変数を設定します
         this.accids = accids;            public voidexecute(QueueableContext context) {         //このアカウントのアクティブな連絡先を取得
    List          <Contact> conList = new List<Contact>();         try{             for(Contact con:[アカウント ID IN :accids の連絡先から ID を選択]) {                  con.
    






                 conList.add(con); 
             } 
            //アクティブな連絡先が存在する場合は更新 if 
             (conList.size()>0){ 
                 update conList; 
             } 
        }catch(Exception ex){ 
            System.debug('例外が発生しました:'+ex.getMessage()); 
        } } 
         
    }
```

アプローチ 9 - バッチ Apex
Database.Batchable インターフェイスを実装します。バッチ ジョブを実行するには複数の方法があります。

1. 匿名で実行する
2. トリガーから実行
3. スケジューラを使用して実行する
Batch apex の利点は、各バッチ (バッチのスコープ) で実行する必要があるレコードの数を定義できることと、大量のデータを複数のバッチで処理できることです。


```
/************************************************ *************************************** 
* Apex クラスをバッチ処理して取引先責任者を無効化します
* 
* *** ************************************************* ********************************/ 
public と共有クラス Batch_ContactDeactivation は Database.Batchable<sObject>{ 
    
    String active = 'Yesを実装します'; 
    文字列非アクティブ = 'いいえ'; 
    文字列クエリ = '連絡先 WHERE account.Active__c=:inactive AND Active=:active から ID を選択'; 
    
    //バッチ開始関数
    public Database.QueryLocator start(Database.BatchableContext BC){ 
      
      return Database.getQueryLocator(query);      
     } 
     
      //関数を実行  
    public voidexecute(Database.BatchableContext BC, List<sObject>scope){ 
     try{ 
          for(sobject s :scope){ 
            s.put('Active__c',inactive); 
          スコープを更新します
          。
     }catch(Exception exc){ 
       System.debug('例外が発生しました:'+exc.getMessage()); 
     }     
    } 
    
   //finish メソッド   
   public voidfinish(Database.BatchableContext BC){ 
        // このバッチ ジョブを表す AsyncApexJob の ID を
        // Database.BatchableContext から取得します。
        // AsyncApexJob オブジェクトをクエリして、現在のジョブの情報を取得します。
        AsyncApexJob a = [SELECT Id、ステータス、NumberOfErrors、JobItemsProcessed、
            TotalJobItems、CreatedBy.Email 
            FROM AsyncApexJob WHERE Id = 
            :BC.getJobId()]; 
            System.debug('完了したジョブの詳細:'+a); 
        // Apex ジョブの送信者にジョブの完了を通知する電子メールを送信します。
        Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage(); 
        String[] toAddresses = new String[] {a.CreatedBy.Email}; 
        mail.setToAddresses(toAddresses); 
        mail.setSubject('連絡先の非アクティブ化プロセス: ' + a.Status); 
        mail.setPlainTextBody 
        ('バッチ Apex ジョブは ' + a.TotalJobItems + 
        ' 個のバッチを処理しました。'+ a.NumberOfErrors + ' 失敗しました。');
        Messaging.sendEmail(new Messaging.SingleEmailMessage[] { メール }); 
   } 
}
```

アプローチ 10 - Apex スケジューラ


Apex スケジューラを使用すると、特定の機能を特定の時間に実行できる柔軟性が得られます。通常の Apex クラス機能または Batch Apex をスケジューラからスケジュールできます。フロー スケジューラと比較して、いつ実行する必要があるかについてより柔軟です。たとえば、連絡先の非アクティブ化ロジックを 1 日に 2 回実行する必要がある場合、Apex スケジューラを使用して実行できます。

```
/****************************************** 
* 連絡先非アクティブ化のスケジューラ クラス
* 
* ****************************************/ 
global class ContactDeactivationScheduler 実装 Schedulable { 
    global voidexecute( SchedulableContext sc) { 
      //バッチ Apex を呼び出す
      Batch_ContactDeactivation バッチ = new Batch_ContactDeactivation(); 
      データベース.executebatch(バッチ); 
   } 
}
```


上記のクラスは 2 つの方法でスケジュールできます。

UI から直接 - Apex クラスリストの一部として利用可能な Schedule apex オプションを使用

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328136/ad2ebd65-bbd4-3d12-9c5a-a40ee7f386a9.png)


または、以下のような CRON 式を使用します。
ContactDeactivationScheduler sch = new ContactDeactivationScheduler(); 
Stringfrequency = '0 0 8,20 * * ?';//毎日午前 8 時と午後 8 時に実行
String jobID = system.schedule('Merge Job',frequency, sch);

-------------------------

+ [UNABLE_TO_LOCK_ROW: unable to obtain exclusive access to this record](https://trailhead.salesforce.com/trailblazer-community/feed/0D54V000076DfFY)