この文章は Salesforce 機械翻訳システムを使用して翻訳されました。詳細はこちらをご参照ください。
英語に切り替える

Transaction Finalizers (パイロット)

Transaction Finalizers 機能により、System.Finalizer インターフェースを使用して、キュー可能フレームワークを使う非同期 Apex ジョブにアクションを関連付けることができます。具体的な使用事例として、キュー可能ジョブが失敗した場合の回復アクションの設計が挙げられます。

TransactionFinalizers 機能は、パイロットプログラムとしてスクラッチ組織でのみ使用できます。組織では作成時にこの機能を有効にしている必要があります。この機能は変更される可能性があり、パイロット期間中は本番組織で使用できません。パイロットプログラムは変更される場合があります。この機能は、Salesforce がドキュメント、プレスリリース、または公式声明で正式リリースを発表しない限り、正式リリースされません。特定期間内の正式リリースあるいはリリースの有無は保証できません。正式リリースされた製品および機能に基づいてのみ購入をご決定ください。この機能に関するフィードバックや提案は、IdeaExchange「TransactionFinalizers」グループに投稿してください。

メモ

Finalizer は、現在パイロットとして提供されており、この機能は有効化されたスクラッチ組織でしか使用できないため、パッケージ化しないでください。

メモ

非同期ジョブが成功または失敗したときに実行されるアクションを Transaction Finalizers よりも前に直接指定する方法はありません。SOQL クエリを使用して AsyncApexJob の状況をポーリングし、ジョブが失敗している場合にジョブを再度キューに追加することしかできません。Transaction Finalizers を使用すると、後処理アクションシーケンスをキュー可能ジョブに関連付け、ジョブの実行結果に基づいて関連するアクションを実行できます。

アクションをキュー可能ジョブに関連付けるには、最初に System.Finalizer インターフェースを実装するクラスを定義します。次に、キュー可能ジョブの execute メソッド内で Finalizer を関連付けます。System.Finalizer インターフェースを実装するインスタンス化されたクラスの引数として System.attachFinalizer メソッドを使用して、このメソッドを呼び出し Finalizer を関連付けます。1 つの Finalizer インスタンスのみを任意のキュー可能ジョブに関連付けることができます。execute メソッドの Finalizer の実装では、1 つの非同期 Apex ジョブ (キュー可能ジョブ、実行予定ジョブ、一括処理ジョブ) をキューに追加できます。Finalizer の実装ではコールアウトを使用できます。

execute (FinalizerContext ctx) メソッドは、指定した Finalizer インスタンスが関連付けられているキューに入れられたジョブごとにコールされます。execute メソッド内で、キュー可能ジョブの終了時に実行されるアクションを定義できます。System.FinalizerContext クラスは、Apex ランタイムエンジンによって作成され、引数として execute メソッドに挿入されます。System.FinalizerParentJobResult 列挙は、Finalizer が関連付けられている親非同期 Apex キュー可能ジョブの結果を表します。この列挙は、SUCCESSUNHANDLED_EXCEPTION の値を取ります。

予期しない、キャッチできないエラーが発生するまでにキュー可能ジョブがどこまで進むのかを���定します。この例では、エラーの詳細も取得します。
1public class AccountUpdateLoggingFinalizer implements Finalizer {
2    // Used to maintain progress
3    List<String> acctNames;
4    
5    public AccountUpdateLoggingFinalizer() {
6        acctNames = new List<String>();
7    }
8    
9    public void execute(FinalizerContext ctx) {        
10        Id parentQueueableJobId = ctx.getAsyncApexJobId();
11        System.Debug('Executing Finalizer that was attached to Queueable Job ID: ' + parentQueueableJobId);
12        if (ctx.getAsyncApexJobResult() == FinalizerParentJobResult.SUCCESS) {
13            // Queueable executed successfully
14            System.Debug('Parent Queueable (Job ID: ' + parentQueueableJobId + '): completed successfully!');
15        } else {
16            // Queueable failed
17            // Log some additional information.
18            System.Debug('Parent Queueable (Job ID: ' + parentQueueableJobId + '): FAILED!');
19            System.Debug('Parent Queueable Exception: ' + ctx.getAsyncApexJobException().getMessage());
20
21            // Show the accounts that were processed before Queueable Job encountered the exception            
22            System.Debug('Parent Queueable processed following accounts:');
23            for (String acctName : acctNames) {
24                System.Debug(acctName);
25            }
26        }        
27    }
28    
29    public void reportProgress(Account acct) {
30        acctNames.add(acct.Name);
31    }
32}
1public class FollowupActionQueueable implements Queueable {
2    public void execute(QueueableContext ctx) {
3       System.Debug('FollowupActionQueueable is executing');
4    }
5}
1public class AccountUpdateQueueable implements Queueable {
2
3    public void execute(QueueableContext ctx) {
4    
5        // Create a transaction finalizer
6        AccountUpdateLoggingFinalizer finalizer = new AccountUpdateLoggingFinalizer();
7
8        // Attach the transaction finalizer to this queueable
9        System.attachFinalizer(finalizer);
10 
11        // Do some (partial) work
12        Account acct = new Account();
13        acct.Name = '1st Account';
14        insert(acct);
15        
16        // Send some status update to the finalizer
17        finalizer.reportProgress(acct);
18        
19        // do some work that results in an unforeseen, uncatchable exception
20        someWork();
21       
22        // Attempt to do some more work
23        Account acct2 = new Account();
24        acct2.Name = '2nd Account';
25        insert(acct2);
26        
27        // Report more progress
28        finalizer.reportProgress(acct2);
29    }
30    
31    private void someWork() {
32        // regular implementation that could result in an un-catchable
33        // exception e.g. System.LimitException due to CPU usage over limits
34        
35        // for demonstration, try to enqueue 2 jobs so this method results in
36        // System.LimitException because more than one job cannot be enqueued
37        // from a Queueable
38        System.enqueueJob(new FollowupActionQueueable());
39        System.enqueueJob(new FollowupActionQueueable());
40    }
41}

ログが更新されるたびにデータベースに書き込むのではなく、ジョブの完了時に完全なログをデータベースに一括で書き込みます。

この Finalizer は成功と失敗の両方のケースで実行されるため、ジョブが成功したか失敗したかに関係なく、完全なログがデータベースに書き込まれます。
1public class LoggingFinalizer implements Finalizer {
2   private List<LogMessage__c> logRecords = new List<LogMessage__c>();
3
4   public void execute(FinalizerContext ctx){
5      Database.insert(logRecords, false);
6   }
7
8   public void addLog(String message){
9      logRecords.add(new LogMessage__c(
10                           Message__c = message,
11                           Request__c = FinalizerContext.getAsyncApexJobId(),
12                           Source__c = 'LoggingFinalizer'
13         ));
14   }
15}
1public class SomeQueueuableJob implements Queueuable, Database.AllowsCallouts {
2   public void execute(QueueuableContext ctx) {
3      LoggingFinalizer f = new LoggingFinalizer();
4      System.attachFinalizer(f);
5      DateTime start = DateTime.now();
6      f.addLog('About to callout to external system...');
7      /* do callout here */
8      f.addLog('Callout completed in, ' + DateTime.now().getTime() - start.getTime() + 'ms');
9   }
10}

現在のジョブが成功したか失敗したかに応じて、異なる種別のジョブがキューに入れられます。

1public class PathControlFinalizer implements Finalizer {
2   private List<LogMessage__c> logRecords = new List<LogMessage__c>();
3
4   public void execute(FinalizerContext ctx){
5      Database.insert(logRecords, false);
6        Id parentQueueableJobId = ctx.getAsyncApexJobId();
7        System.Debug('Executing Finalizer that was attached to Queueable Job ID: ' + parentQueueableJobId);
8        if (ctx.getAsyncApexJobResult() == FinalizerParentJobResult.SUCCESS) {
9            // Queueable executed successfully
10            System.Debug('Parent Queueable (Job ID: ' + parentQueueableJobId + '): completed successfully!');
11            // Upon successful completion of parent/previous job, enqueue type B job
12            System.enqueueJob(new QueueableTypeB()); 
13        } else {
14            // Queueable failed
15            // Log some additional information.
16            System.Debug('Parent Queueable (Job ID: ' + parentQueueableJobId + '): FAILED!');
17            System.Debug('Parent Queueable Exception: ' + ctx.getAsyncApexJobException().getMessage());
18            // Due to failure in parent/previous job, enqueue type C job
19            System.enqueueJob(new QueueableTypeC());
20        }
21   }
22
23   public void addLog(String message){
24      logRecords.add(new LogMessage__c(
25                           Message__c = message,
26                           Request__c = FinalizerContext.getRequestId(),
27                           Source__c = 'LoggingFinalizer'
28         ));
29   }
30}
1public class ParentQueueableJobA implements Queueuable, Database.AllowsCallouts {
2   public void execute(QueueuableContext ctx) {
3      PathControlFinalizer f = new PathControlFinalizer();
4      System.attachFinalizer(f);
5      DateTime start = DateTime.now();
6      f.addLog('About to callout to external system...');
7      /* do callout here */
8      f.addLog('Callout completed in, ' + DateTime.now().getTime() - start.getTime() + 'ms');
9   }
10}

キャッチ可能なエラーが発生するまでにキュー可能ジョブがどこまで進むのかを特定します。この例では、エラーの詳細も取得します。

1public class AccountUpdateLoggingFinalizer implements Finalizer {
2
3    // Used to maintain progress
4    List<String> acctNames;
5    
6    public AccountUpdateLoggingFinalizer() {
7        acctNames = new List<String>();
8    }
9    
10    public void execute(FinalizerContext ctx) {        
11        Id parentQueueableJobId = ctx.getAsyncApexJobId();
12        System.Debug('Executing Finalizer that was attached to Queueable Job ID: ' + parentQueueableJobId);
13        if (ctx.getAsyncApexJobResult() == FinalizerParentJobResult.SUCCESS) {
14            // Queueable executed successfully
15            System.Debug('Parent Queueable (Job ID: ' + parentQueueableJobId + '): completed successfully!');
16        } else {
17            // Queueable failed
18            // Log some additional information.
19            System.Debug('Parent Queueable (Job ID: ' + parentQueueableJobId + '): FAILED!');
20            System.Debug('Parent Queueable Exception: ' + ctx.getAsyncApexJobException().getMessage());
21
22            // Show the accounts that were processed before Queueable Job encountered the exception            
23            System.Debug('Parent Queueable processed following accounts:');
24            for (String acctName : acctNames) {
25                System.Debug(acctName);
26            }
27        }        
28    }
29    
30    public void reportProgress(Account acct) {
31        acctNames.add(acct.Name);
32    }
33}
1public class AccountUpdateQueueable implements Queueable {
2
3    public void execute(QueueableContext ctx) {
4    
5        // Create a transaction finalizer
6        AccountUpdateLoggingFinalizer finalizer = new AccountUpdateLoggingFinalizer();
7
8        // Attach the transaction finalizer to this queueable
9        System.attachFinalizer(finalizer);
10 
11        // Do some (partial) work
12        Account acct = new Account();
13        acct.Name = '1st Account';
14        insert(acct);
15        
16        // Send some status update to the finalizer
17        finalizer.reportProgress(acct);
18        
19        // do some work & conditionally throw a catchable/user-defined exception
20        boolean status = doSomeWork();
21        if (!status) {
22           throw new TestException('Unhandled test exception');
23        }
24        
25        // Attempt to do some more work
26        Account acct2 = new Account();
27        acct2.Name = '2nd Account';
28        insert(acct2);
29        
30        // Report more progress
31        finalizer.reportProgress(acct2);
32    }
33    
34    private class TestException extends Exception { }
35}