Queueable Apex を使用したプロセスの制御
学習の目的
この単元を完了すると、次のことを理解できるようになります。
Queueable
インターフェースを使用するケース。- queueable メソッドと future メソッドの違い。
- Queueable Apex の構文。
- queueable メソッドのベストプラクティス。
一緒にトレイルを進みましょう
インストラクターと一緒にこの手順を進めますか? 次の動画をご覧ください。これは Trailhead Live の「Trail Together」(一緒にトレイル) シリーズの一部です。
(この動画は 52:38 の時点から始まります。戻して手順の最初から見直す場合はご注意ください。)
Queueable Apex
Winter '15 でリリースされた Queueable Apex は、future メソッドのスーパーセットでちょっとした趣向が凝らされています。future メソッドの簡便性と Apex 一括処理の機能性に着目して、この 2 つを合体させた Queueable Apex を編み出しました。この Apex は、プラットフォームによって逐次化されるクラス構造で、start と finish メソッドを使用しない簡潔なユーザーインターフェースを備え、プリミティブ引数以外も利用できます。シンプルな System.enqueueJob()
メソッドでコールされ、監視可能なジョブ ID を返します。これに勝るものはありません。
Queueable Apex を使用した場合、future メソッドと同様に非同期処理のジョブを送信できますが、次のようなさらなる利点もあります。
- 非プリミティブ型:
Queueable
クラスには、sObject 型やカスタム Apex 型など、プリミティブ以外のデータ型のメンバー変数を含めることができます。これらのオブジェクトには、ジョブの実行時にアクセスできます。 - 監視:
System.enqueueJob()
メソッドを呼び出してジョブを送信すると、メソッドが AsyncApexJob レコードの ID を返します。この ID を使用して、Salesforce ユーザーインターフェースの [Apex ジョブ] ページから、またはプログラムで AsyncApexJob のレコードを照会する方法で、ジョブを識別してその進行状況を監視できます。 - ジョブのチェーニング: 実行中のジョブから 2 つ目のジョブを開始することで、2 つのジョブを連鎖的に実行することができます。ジョブのチェーニングは、連続的な処理を行う必要がある場合に役立ちます。
queueable と future の比較
queueable メソッドは機能的に future メソッドと同等であるため、ほとんどの場合は future メソッドの代わりに queueable メソッドを使用できます。ただし、すべての future メソッドに戻ってすぐにリファクタリングする必要があるわけではありません。
queueable メソッドではなく 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 } }
サンプルコード
一般的なシナリオは、いくつかの sObject レコードを取り、外部の REST エンドポイントへのコールアウトの実行など一定の処理を実行するか、何らかの計算を実行してからデータベースで非同期で更新します。@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 一括処理のテストとよく似ています。キュー可能プロセスがテストメソッド内で実行されるようにするには、ジョブを 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; } static testmethod 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 テストでキュー可能ジョブをチェーニングすることはできません。実行するとエラーが発生します。厄介なエラーを回避するために、ジョブをチェーニングする前に Test.isRunningTest()
をコールすると、Apex がテストコンテキストで実行されているかどうかを確認できます。
留意事項
Queueable Apex は最新の優れたツールですが、いくつかの注意点があります。
- キュー内のジョブを実行すると、非同期 Apex メソッド実行の共有制限値に 1 回計数されます。
- 1 つのトランザクションで
System.enqueueJob
を使用してキューに追加できるのは、最大 50 ジョブです。 - ジョブをチェーニングするとき、実行中のジョブから
System.enqueueJob
で追加できるジョブは 1 つのみです。つまり、親キュー可能ジョブごとに 1 つの子ジョブしか存在できません。同じキュー可能ジョブからの複数の子ジョブを開始することはできません。 - チェーニングされたジョブの深度に制限はありません。つまり、1 つのジョブから別のジョブにチェーニングし、このプロセスを新しい子ジョブごとに繰り返して新しい子ジョブにリンクできます。ただし、Developer Edition 組織やトライアル組織の場合は、チェーニングされたジョブの最大スタック深度は 5 です。つまり、ジョブのチェーニングを 4 回行うことができ、チェーン内のジョブ数は最初の親キュー可能ジョブを含めて最大 5 個です。ただし、この制限は「チェーニングされたキュー可能ジョブのスタック深度の設定」機能を使用することで上書きできます。
リソース