Queueable Apex を使用したプロセスの制御
学習の目的
この単元を完了すると、次のことを理解できるようになります。
Queueableインターフェースを使用するケース。- queueable メソッドと future メソッドの違い。
- Queueable Apex の構文。
- queueable メソッドのベストプラクティス。
一緒にトレイルを進みましょう
インストラクターと一緒にこの手順を進めますか? 次の動画をご覧ください。これは Trailhead Live の「Trail Together (一緒にトレイル)」シリーズの一部です。
(この動画は 52:38 の時点から始まります。戻して手順の最初から見直す場合はご注意ください。)
Queueable Apex
Queueable Apex は、future メソッドのスーパーセットであり、次のような追加の利点があります。
- 非プリミティブ型:
Queueableクラスには、sObject 型やカスタム Apex 型など、プリミティブ以外のデータ型のメンバー変数を含めることができます。これらのオブジェクトには、ジョブの実行時にアクセスできます。 - 監視:
System.enqueueJob()メソッドを呼び出してジョブを送信すると、メソッドが AsyncApexJob レコードの ID を返します。この ID を使用して、Salesforce ユーザーインターフェースの [Apex ジョブ] ページから、またはプログラムで AsyncApexJob のレコードを照会する方法で、ジョブを識別してその進行状況を監視できます。 - ジョブのチェーニング - 実行中のジョブから 2 つ目のジョブを開始することで、2 つのジョブを連鎖的に実行することができます。ジョブのチェーニングは、連続的な処理を行う必要がある場合に役立ちます。
queueable と future の比較
queueable メソッドは機能的に future メソッドと同等であるため、ほとんどの場合は future メソッドの代わりに queueable メソッドを使用できます。ただし、すべての future メソッドに戻ってすぐにリファクタリングする必要があるわけではありません。
Queueable Apex ではなく future メソッドを使用する理由は、機能が同期実行されることもあれば、非同期実行されることもある場合です。queueable クラスに変換するよりも、この方法でメソッドをリファクタリングするほうがはるかに簡単です。既存のコードの一部を非同期実行に移す必要があることに気付いた場合は、この方法が便利です。次のように、同期メソッドをラップする同様の future メソッドを作成するだけすみます。
@future
static void myFutureMethod(List<String> params) {
// call synchronous method
mySyncMethod(params);
}キュー可能構文
Queueable Apex は、Queueable インターフェースを実装するだけで使用できます。
public class SomeClass implements Queueable {
public void execute(QueueableContext context) {
// awesome code here
}
}サンプルコード
一般的な Queueable Apex のワークフローでは、sObject レコードのセットを取得し、それらを処理したうえで、非同期的にデータベースを更新します。future メソッドはプリミティブデータ型 (またはプリミティブのコレクション) に限定されているため、Queueable 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;
// perform other processing or callout
}
update accounts;
}
}このクラスをジョブとしてキューに追加するには、次のコードを実行します。
// find all accounts in 'NY' List<Account> accounts = [SELECT Id FROM Account WHERE BillingState = 'NY']; // find a specific parent account for all records Id parentId = [SELECT Id FROM Account WHERE Name = 'ACME Corp'][0].Id; // instantiate a new instance of the Queueable class UpdateParentAccount updateJob = new UpdateParentAccount(accounts, parentId); // enqueue the job for processing ID jobID = System.enqueueJob(updateJob);
キュー可能クラスを実行のために送信すると、ジョブはキューに追加され、システムリソースが使用可能になると処理されます。
新しいジョブ ID を使用して、[Apex Job (Apex ジョブ)] ページから、またはプログラムで AsyncApexJob を照会する方法で、進行状況を監視できます。
SELECT Id, Status, NumberOfErrors FROM AsyncApexJob WHERE Id = :jobID
Queueable Apex のテスト
次のサンプルコードは、テストメソッドでキュー可能ジョブの実行をテストする方法を示しています。Apex 一括処理のテストとよく似ています。
setup() メソッドには @testSetup アノテーションが付加されます。このアノテーションを使用して、クラス内のすべてのメソッドで利用できる共通のテストレコードを作成できます。テストレコードへの更新は、各テストメソッドの実行の終了後にロールバックされます。このテストクラスには (setup() メソッド自身を除くと) 1 つのメソッドしか含まれていませんが、ここでは例として @testSetup アノテーションを使用しています。この場合、setup() メソッドは 1 件の親取引先レコードと 100 件の子取引先レコードを作成します。そして、それらのレコードをデータベースに挿入します。このデータは、Queueable クラスで使用されます。
キュー可能プロセスがテストメソッド内で実行されるようにするには、ジョブを Test.startTest から Test.stopTest のブロック内のキューに送信する必要があります。システムは、テストメソッドで開始されたすべての非同期プロセスを、Test.stopTest ステートメントの後に同期実行します。次に、テストメソッドが、ジョブで更新された取引先レコードを照会して、キュー可能ジョブの結果を検証します。
@isTest
public class UpdateParentAccountTest {
@testSetup
static void setup() {
List<Account> accounts = new List<Account>();
// add a parent account
accounts.add(new Account(name='Parent'));
// add 100 child accounts
for (Integer i = 0; i < 100; i++) {
accounts.add(new Account(
name='Test Account'+i
));
}
insert accounts;
}
@isTest
static void testQueueable() {
// query for test data to pass to queueable class
Id parentId = [SELECT Id FROM Account WHERE Name = 'Parent'][0].Id;
List<Account> accounts = [SELECT Id, Name FROM Account WHERE Name LIKE 'Test Account%'];
// Create our queueable instance
UpdateParentAccount updater = new UpdateParentAccount(accounts, parentId);
// startTest/stopTest block to force async processes to run
Test.startTest();
System.enqueueJob(updater);
Test.stopTest();
// Validate the job ran. Check if record have correct parentId now
System.assertEquals(100, [SELECT count() FROM Account WHERE parentId = :parentId]);
}
}
ジョブのチェーニング
Queueable Apex の利点の 1 つがジョブのチェーニングです。ジョブを連続して実行する必要が生じた場合、Queueable Apex を使用すると大幅に手間を省けます。ジョブを別のジョブにチェーニングするには、キュー可能クラスの execute() メソッドから 2 つ目のジョブを送信します。実行中のジョブから追加できるジョブは 1 つのみです。つまり、親ジョブごとに 1 つの子ジョブしか存在できません。たとえば、Queueable インターフェースを実装する SecondJob という 2 つ目のクラスがある場合、このクラスを execute() メソッドのキューに次のように追加できます。
public class FirstJob implements Queueable {
public void execute(QueueableContext context) {
// Awesome processing logic here
// Chain this job to next job by submitting the next job
System.enqueueJob(new SecondJob());
}
}定義済みのスタック深度を使用してキュー可能ジョブのチェーンをテストできますが、適用される Apex ガバナ制限に注意してください。「Adding a Queueable Job with a Specified Stack Depth (指定されたスタック深度でのキュー可能ジョブの追加)」を参照してください。
留意事項
Queueable Apex は優れたツールですが、いくつかの注意点があります。
- キュー内のジョブを実行すると、非同期 Apex メソッド実行の共有制限値に 1 回計数されます。
- 1 つのトランザクションで
System.enqueueJobを使用してキューに追加できるのは、最大 50 ジョブです。 - ジョブをチェーニングするとき、実行中のジョブから
System.enqueueJobで追加できるジョブは 1 つのみです。つまり、親キュー可能ジョブごとに 1 つの子ジョブしか存在できません。同じキュー可能ジョブから複数の子ジョブを開始することはできません。 - チェーニングされたジョブの深度に制限はありません。つまり、1 つのジョブから別のジョブにチェーニングし、このプロセスを新しい子ジョブごとに繰り返すことができます。ただし、Developer Edition 組織やトライアル組織の場合は、チェーニングされたジョブの最大スタック深度は 5 です。つまり、ジョブのチェーニングを 4 回行うことができ、チェーン内のジョブ数は最初の親キュー可能ジョブを含めて最大 5 個です。ただし、この制限は「チェーニングされたキュー可能ジョブのスタック深度の設定」機能を使用することで上書きできます。
リソース
