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

Transaction Finalizers

Transaction Finalizers 機能により、System.Finalizer インターフェースを使用して、キュー可能フレームワークを使う非同期 Apex ジョブにアクションを関連付けることができます。具体的な使用事例として、キュー可能ジョブが失敗した場合の回復アクションの設計が挙げられます。
Transaction Finalizers の機能により、非同期ジョブが成功または失敗したときに実行されるアクションを直接指定できます。Transaction Finalizers よりも前は、非同期ジョブの失敗に対して、次の 2 つのアクションしか取ることができませんでした。
  • SOQL クエリを使用して AsyncApexJob の状況をポーリングし、ジョブが失敗している場合にジョブを再度キューに追加する
  • Apex の一括処理メソッドで未対応の例外が発生した場合に、BatchApexErrorEvents を起動する
Transaction Finalizers を使用すると、後処理アクションシーケンスをキュー可能ジョブに関連付け、ジョブの実行結果に基づいて関連するアクションを実行できます。

未対応の例外が原因で失敗したキュー可能ジョブは、Transaction Finalizer によって連続して 5 回再度キューに追加できます。この制限は、キュー可能ジョブの一連の連続した失敗に適用されます。未対応の例外が発生せずにキュー可能ジョブが完了すると、カウンターはリセットされます。

Finalizers は内部クラスとして実装できます。また、同じクラスで Queueable インターフェースと Finalizer インターフェースの両方を実装できます。

キュー可能ジョブと Finalizer は、別個の Apex およびデータベーストランザクションで実行されます。たとえば、キュー可能ジョブには DML を含めることができ、Finalizer には REST コールアウトを含めることができます。Finalizer を使用しても、追加の実行として日次非同期 Apex 制限に反映されません。同期ガバナ制限は Finalizer トランザクションに適用されます。ただし、非同期制限が適用される場合は除きます。
  • ヒープの合計サイズ
  • System.enqueueJob によってキューに追加される Apex ジョブの最大数
  • Apex 呼び出し 1 回につき許可される future アノテーションを持つメソッドの最大数
ガバナ制限についての詳細は、「実行ガバナと制限」を参照してください。

System.Finalizer インターフェース

System.Finalizer インターフェースには execute メソッドがあります。
1global void execute(System.FinalizerContext ctx) {}
このメソッドは、指定した FinalizerContext インスタンスが関連付けられているキューに入れられたジョブごとにコールされます。execute メソッド内で、キュー可能ジョブの終了時に実行されるアクションを定義できます。System.FinalizerContext のインスタンスは、Apex ランタイムエンジンによって引数として execute メソッドに挿入されます。

System.FinalizerContext インターフェース

System.FinalizerContext インターフェースには 4 つのメソッドがあります。
  • getAsyncApexJobId メソッド
    1global Id getAsyncApexJobId {}
    この Finalizer が定義されているキュー可能ジョブの ID を返します。
  • getRequestId メソッド
    1global String getRequestId {}
    要求を一意に識別する文字列である要求 ID を返し、イベント監視ログと関連付けることができます。AsyncApexJob テーブルと関連付けるには、代わりに getAsyncApexJobId メソッドを使用します。キュー可能ジョブと Finalizer の実行の両方が (同じ) 要求 ID を使用します。
  • getResult メソッド
    1global System.ParentJobResult getResult {}
    Finalizer が関連付けられている親非同期 Apex キュー可能ジョブの結果を表す System.ParentJobResult 列挙を返します。この列挙は、SUCCESSUNHANDLED_EXCEPTION の値を取ります。
  • getException メソッド
    1global System.Exception getException {}
    getResultUNHANDLED_EXCEPTION の場合はキュー可能ジョブが失敗し、それ以外の場合は null になる例外を返します。
System.attachFinalizer メソッドを使用して Finalizer をキュー可能ジョブに関連付けます。
  1. System.Finalizer インターフェースを実装するクラスを定義します。
  2. キュー可能ジョブの execute メソッド内で Finalizer を関連付けます。Finalizer を関連付けるには、System.Finalizer インターフェースを実装するインスタンス化されたクラスの引数として System.attachFinalizer メソッド使用して、このメソッドを呼び出します。
    1global void attachFinalizer(Finalizer finalizer) {}

実装の詳細

  • 1 つの Finalizer インスタンスのみを任意のキュー可能ジョブに関連付けることができます。
  • execute メソッドの Finalizer の実装では、1 つの非同期 Apex ジョブ (キュー可能ジョブ、実行予定ジョブ、一括処理ジョブ) をキューに追加できます。
  • Finalizer の実装ではコールアウトを使用できます。
  • Finalizer フレームワークは、キュー可能実行の終了時に Finalizer オブジェクト (関連付けられている場合) の状��を使用します。このため、Finalizer が関連付けられた後の状態の変更がサポートされます。
  • transient として宣言された変数は、シリアライゼーションとデシリアライゼーションで無視されるため、Transaction Finalizer で保持されません。

Finalizer のログ記録の例

次の例では、Transaction Finalizers を使用して、ジョブが成功したかどうかに関係なくキュー可能ジョブからメッセージをログ記録する方法を示します。この LoggingFinalizer クラスでは、Queueable インターフェースと Finalizer インターフェースの両方を実装します。キュー可能実装では、Finalizer がインスタンス化され、Finalizer が関連付けられた後、ログメッセージをバッファリングするための addLog() メソッドが呼び出されます。LoggingFinalizer の Finalizer 実装には、キュー可能ジョブからのメッセージを Fnalizer の状態にバッファリングできる addLog(message, source) メソッドが含まれます。キュー可能ジョブが完了すると、Finalizer インスタンスは、バッファリングされたログをコミットします。Finalizer の状態は、キュー可能ジョブが失敗しても保持され、Finalizer の実装または実行内の DML で使用できます。

1public class LoggingFinalizer implements Finalizer, Queueable {
2
3  // Queueable implementation
4  // A queueable job that uses LoggingFinalizer to buffer the log
5  // and commit upon exit, even if the queueable execution fails
6
7    public void execute(QueueableContext ctx) {
8        String jobId = '' + ctx.getJobId();
9        System.debug('Begin: executing queueable job: ' + jobId);
10        try {
11            // Create an instance of LoggingFinalizer and attach it
12            // Alternatively, System.attachFinalizer(this) can be used instead of instantiating LoggingFinalizer
13            LoggingFinalizer f = new LoggingFinalizer();
14            System.attachFinalizer(f);
15
16            // While executing the job, log using LoggingFinalizer.addLog()
17            // Note that addlog() modifies the Finalizer's state after it is attached 
18            DateTime start = DateTime.now();
19            f.addLog('About to do some work...', jobId);
20
21            while (true) {
22              // Results in limit error
23            }
24        } catch (Exception e) {
25            System.debug('Error executing the job [' + jobId + ']: ' + e.getMessage());
26        } finally {
27            System.debug('Completed: execution of queueable job: ' + jobId);
28        }
29    }
30
31  // Finalizer implementation
32  // Logging finalizer provides a public method addLog(message,source) that allows buffering log lines from the Queueable job.
33  // When the Queueable job completes, regardless of success or failure, the LoggingFinalizer instance commits this buffered log.
34  // Custom object LogMessage__c has four custom fields-see addLog() method.
35
36    // internal log buffer
37    private List<LogMessage__c> logRecords = new List<LogMessage__c>();
38
39    public void execute(FinalizerContext ctx) {
40        String parentJobId = ctx.getAsyncApexJobId();
41        System.debug('Begin: executing finalizer attached to queueable job: ' + parentJobId);
42
43        // Update the log records with the parent queueable job id
44        System.Debug('Updating job id on ' + logRecords.size() + ' log records');
45        for (LogMessage__c log : logRecords) {
46            log.Request__c = parentJobId; // or could be ctx.getRequestId()
47        }
48        // Commit the buffer
49        System.Debug('committing log records to database');
50        Database.insert(logRecords, false);
51
52        if (ctx.getResult() == ParentJobResult.SUCCESS) {
53            System.debug('Parent queueable job [' + parentJobId + '] completed successfully.');
54        } else {
55            System.debug('Parent queueable job [' + parentJobId + '] failed due to unhandled exception: ' + ctx.getException().getMessage());
56            System.debug('Enqueueing another instance of the queueable...');
57        }
58        System.debug('Completed: execution of finalizer attached to queueable job: ' + parentJobId);
59    }
60
61    public void addLog(String message, String source) {
62        // append the log message to the buffer
63        logRecords.add(new LogMessage__c(
64            DateTime__c = DateTime.now(),
65            Message__c = message,
66            Request__c = 'setbeforecommit',
67            Source__c = source
68        ));
69    }
70}

キュー可能ジョブの再試行の例

次の例では、Finalizer で失敗したキュー可能ジョブを再度キューに追加する方法を示します。また、キュー可能チェーニング制限の 5 回までジョブを再度キューに追加できることを示します。

1public class RetryLimitDemo implements Finalizer, Queueable {
2
3  // Queueable implementation
4  public void execute(QueueableContext ctx) {
5    String jobId = '' + ctx.getJobId();
6    System.debug('Begin: executing queueable job: ' + jobId);
7    try {
8        Finalizer finalizer = new RetryLimitDemo();
9        System.attachFinalizer(finalizer);
10        System.debug('Attached finalizer');
11        Integer accountNumber = 1;
12        while (true) { // results in limit error
13          Account a = new Account();
14          a.Name = 'Account-Number-' + accountNumber;
15          insert a;
16          accountNumber++;
17        }
18    } catch (Exception e) {
19        System.debug('Error executing the job [' + jobId + ']: ' + e.getMessage());
20    } finally {
21        System.debug('Completed: execution of queueable job: ' + jobId);
22    }
23  }
24
25  // Finalizer implementation
26  public void execute(FinalizerContext ctx) {
27    String parentJobId = '' + ctx.getAsyncApexJobId();
28    System.debug('Begin: executing finalizer attached to queueable job: ' + parentJobId);
29    if (ctx.getResult() == ParentJobResult.SUCCESS) {
30        System.debug('Parent queueable job [' + parentJobId + '] completed successfully.');
31    } else {
32        System.debug('Parent queueable job [' + parentJobId + '] failed due to unhandled exception: ' + ctx.getException().getMessage());
33        System.debug('Enqueueing another instance of the queueable...');
34        String newJobId = '' + System.enqueueJob(new RetryLimitDemo()); // This call fails after 5 times when it hits the chaining limit
35        System.debug('Enqueued new job: ' + newJobId);
36    }
37    System.debug('Completed: execution of finalizer attached to queueable job: ' + parentJobId);
38  }
39}

ベストプラクティス

ISV では、Finalizer をグローバルで使用している際に、パッケージで状態を変更するメソッドを使う場合には注意が必要です。登録者組織の実装から、グローバル Finalizer のこのようなメソッドを呼び出すと、予期しない動作が発生する可能性があります。状態を変更するメソッドすべて調べて、Finalizer の状態や全体の動作にどのような影響を与えるかを確認してください。