future メソッドの使用
学習の目的
この単元を完了すると、次のことを理解できるようになります。
- future メソッドを使用する場合。
- future メソッド使用の制限。
- コールアウトに future メソッドを使用する方法。
- future メソッドのベストプラクティス。
一緒にトレイルを進みましょう
インストラクターと一緒にこの手順を進めますか? 次の動画をご覧ください。これは Trailhead Live の「Trail Together」(一緒にトレイル) シリーズの一部です。
(この動画は 9:39 の時点から始まります。戻して手順の最初から見直す場合はご注意ください。)
future Apex
future Apex は、後でシステムリソースが使用可能になったときに複数の処理を別個のスレッドで実行するために使用します。
注意: 厳密に言えば、@future
アノテーションを使用して非同期に実行するメソッドを識別します。ただし、「@future
アノテーションで識別されるメソッド」と呼ぶのは煩雑なので、一般的に「future メソッド」と呼ばれており、このモジュールでもこれ以降その呼称を使用します。
同期処理を使用する場合、すべてのメソッドコールは Apex コードを実行している同じスレッドから実行され、処理が完了するまで他の処理は実行されません。future メソッドは、非同期に独自のスレッド内で実行する任意の操作に使用できます。そのため、ユーザーが他の操作を実行できるようになり、また処理のガバナ制限や実行制限を引き上げられるという利点があります。非同期処理により、さまざまな問題が解消されます。
future メソッドは通常、次のような場合に使用されます。
- 外部 Web サービスへのコールアウト。トリガーから、または DML 操作の実行後にコールアウトを実行する場合、future メソッドまたはキュー可能メソッドを使用する必要があります。トリガー内のコールアウトでは、コールアウトの存続期間中データベース接続が開いたままになりますが、これはマルチテナント環境では避ける必要があります。
- 時間が許せば、独自のスレッド内で実行する操作 (リソースを大量に消費する計算やレコードの処理など)。
- DML 操作を別個の sObject 型に隔離して混合 DML エラーを回避する場合。特殊な状況とはいえ、この問題が発生することもあります。詳細は、「DML 操作で同時に使用できない sObject」を参照してください。
future メソッドの構文
future メソッドは静的メソッドである必要があり、void 型のみを返します。指定するパラメーターはプリミティブデータ型、プリミティブデータ型の配列、プリミティブデータ型のコレクションである必要があります。特に、future メソッドは引数として標準またはカスタムオブジェクトを取ることができません。一般的なパターンでは、メソッドに、非同期で処理するレコード ID のリスト
を渡します。
public class SomeClass { @future public static void someFutureMethod(List<Id> recordIds) { List<Account> accounts = [Select Id, Name from Account Where Id IN :recordIds]; // process account records to do awesome stuff } }
future メソッドがコールされた順序で実行される保証はありません。重要なことなので繰り返します。future メソッドがコールされた順序で実行される保証はありません。future メソッドを使用する場合、2 つの future メソッドが並行して実行される可能性もあります。その結果、2 つのメソッドが同じレコードを更新してレコードがロックされ、重大なランタイムエラーが発生することもあり得ます。
サンプルコールアウトコード
外部サービスまたは API への Web サービスコールアウトを実行するには、(callout=true)
でマークされた future メソッドを使用して Apex クラスを作成します。下記のクラスには、コールアウトが許可されない場合にコールアウトを同期または非同期の両方で実行するためのメソッドがあります。念のためにレコードをカスタムログオブジェクトに挿入して、コールアウトの状況を追跡します。
public class SMSUtils { // Call async from triggers, etc, where callouts are not permitted. @future(callout=true) public static void sendSMSAsync(String fromNbr, String toNbr, String m) { String results = sendSMS(fromNbr, toNbr, m); System.debug(results); } // Call from controllers, etc, for immediate processing public static String sendSMS(String fromNbr, String toNbr, String m) { // Calling 'send' will result in a callout String results = SmsMessage.send(fromNbr, toNbr, m); insert new SMS_Log__c(to__c=toNbr, from__c=fromNbr, msg__c=results); return results; } }
テストクラス
future メソッドのテストは、通常の Apex テストは若干異なります。future メソッドをテストするには、テストコードを startTest()
および stopTest()
テストメソッドで囲みます。システムが startTest()
の後に実行されたすべての非同期コールを収集します。stopTest()
が実行されると、この収集された非同期プロセスすべてが同期して実行されます。実行後、非同期コールが適切に動作したことを確認できます。
次に、テストで使用する疑似コールアウトクラスを示します。Apex テストフレームワークでは、REST API エンドポイントへのコールアウトを実際に行うのではなく、この「疑似」応答を利用します。
@isTest public class SMSCalloutMock implements HttpCalloutMock { public HttpResponse respond(HttpRequest req) { // Create a fake response HttpResponse res = new HttpResponse(); res.setHeader('Content-Type', 'application/json'); res.setBody('{"status":"success"}'); res.setStatusCode(200); return res; } }
テストクラスには、非同期メソッドと同期メソッドの両方をテストする 1 つのテストメソッド (この例では testSendSms()
) が含まれており、非同期メソッドが同期メソッドをコールします。
@IsTest private class Test_SMSUtils { @IsTest private static void testSendSms() { Test.setMock(HttpCalloutMock.class, new SMSCalloutMock()); Test.startTest(); SMSUtils.sendSMSAsync('111', '222', 'Greetings!'); Test.stopTest(); // runs callout and check results List<SMS_Log__c> logs = [select msg__c from SMS_Log__c]; System.assertEquals(1, logs.size()); System.assertEquals('success', logs[0].msg__c); } }
ベストプラクティス
future メソッドを呼び出すたびに 1 つの要求が非同期キューに追加されるため、短時間に多くの future 要求を追加する設計パターンは避けてください。設計で一度に 2000 以上の要求を追加する可能性がある場合、フロー制御により要求が遅延することがあります。次に、留意すべきベストプラクティスを示します。
- future メソッドができるだけ速く実行されるようにします。
- Web サービスコールアウトを使用する場合、コールアウトごとに別個の future メソッドを使用するのではなく、同じ future メソッドからのすべてのコールアウトを一緒にまとめるようにします。
- 大規模に徹底したテストを実施します。
@future
コールをキューに入れるトリガーが、200 件のレコードからなるトリガーコレクションを処理できることをテストします。これにより、特定の設計において現在と今後の量で遅延が発生する可能性があるかどうかを判断しやすくなります。 - 大量のレコードを非同期に処理するには、future メソッドではなく Apex 一括処理を使用することを検討してください。レコードごとに future 要求を作成するよりも効率的です。
留意事項
future メソッドは便利なツールですが、強力な機能には多大な責任を伴います。次に、future メソッドを使用する際の留意事項をいくつか示します。
@future
アノテーションのあるメソッドは静的メソッドである必要があり、void 型のみを返します。- 指定するパラメーターはプリミティブデータ型、プリミティブデータ型の配列、プリミティブデータ型のコレクションである必要があります。future メソッドは引数としてオブジェクトを取ることができません。
- future メソッドはコールされた順序で実行されるとは限りません。さらに、2 つの future メソッドが並行して実行される可能性があります。その結果、2 つのメソッドが同じレコードを更新してレコードがロックされることもあり得ます。
- future メソッドは Visualforce コントローラーの
getMethodName()
、setMethodName()
、コンストラクターでは使用できません。 - future メソッドから future メソッドをコールすることはできません。future メソッドを実行中に、future メソッドをコールするトリガーを呼び出すこともできません。再帰的な future メソッドコールを回避する方法については、[リソース] のリンクを参照してください。
@future
アノテーションのあるメソッド内でgetContent()
メソッドとgetContentAsPDF()
メソッドは使用できません。- Apex の呼び出しごとの future コール数は 50 に制限されており、さらに 24 時間内のコール数の制限があります。制限についての詳細は、下記のリンクを参照してください。
リソース
- Apex 開発者ガイド: future メソッド
- Apex リファレンスガイド: System.isFuture() メソッド
- Apex 開発者ガイド: DML 操作で同時に使用できない sObject
- Apex 開発者ガイド: 実行ガバナと制限
- Apex 開発者ガイド: 単体テストの組織データとテストデータの分離