future メソッドの使用

学習の目的

この単元を完了すると、次のことを理解できるようになります。

  • future メソッドを使用する場合。
  • future メソッド使用の制限。
  • コールアウトに future メソッドを使用する方法。
  • future メソッドのベストプラクティス。

future Apex

future Apex は、後でシステムリソースが使用可能になったときに複数の処理を別個のスレッドで実行するために使用します。

注意: 厳密に言えば、@future アノテーションを使用して非同期に実行するメソッドを識別します。ただし、「@future アノテーションで識別されるメソッド」と呼ぶのは煩雑なので、一般的に「future メソッド」と呼ばれており、このモジュールでもこれ以降その呼称を使用します。

同期処理を使用する場合、すべてのメソッドコールは Apex コードを実行している同じスレッドから実行され、処理が完了するまで他の処理は実行されません。future メソッドは、非同期に独自のスレッド内で実行する任意の操作に使用できます。そのため、ユーザが他の操作を実行できるようになり、また処理のガバナ制限や実行制限を引き上げられるという利点があります。非同期処理により、さまざまな問題が解消されます。

future メソッドは通常、次のような場合に使用されます。
  • 外部 Web サービスへのコールアウト。トリガから、または DML 操作の実行後にコールアウトを実行する場合、future メソッドまたはキュー可能メソッドを使用する必要があります。トリガ内のコールアウトでは、コールアウトの存続期間中データベース接続が開いたままになりますが、これはマルチテナント環境では避ける必要があります。
  • 時間が許せば、独自のスレッド内で実行する操作 (リソースを大量に消費する計算やレコードの処理など)。
  • DML 操作を別個の sObject 型に隔離して混合 DML エラーを回避する場合。特殊な状況とはいえ、この問題が発生することもあります。詳細は、「DML 操作で同時に使用できない sObject」を参照してください。

future メソッドの構文

future メソッドは静的メソッドである必要があり、void 型のみを返します。指定するパラメータはプリミティブデータ型、プリミティブデータ型の配列、プリミティブデータ型のコレクションである必要があります。特に、future メソッドは引数として標準またはカスタムオブジェクトを取ることができません。一般的なパターンでは、メソッドに、非同期で処理するレコード ID のリストを渡します。

global 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 メソッドのオブジェクト値が古くなっていると、悪影響を及ぼすおそれがあります。

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 インテグレーションサービス」モジュールを参照してください。

次に、テストで使用する疑似コールアウトクラスを示します。Apex テストフレームワークでは、REST API エンドポイントへのコールアウトを実際に行うのではなく、この「疑似」応答を利用します。

@isTest
global class SMSCalloutMock implements HttpCalloutMock {
    global 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 つのテストメソッドが含まれており、非同期メソッドが同期メソッドをコールします。

@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 時間内のコール数の制限があります。制限についての詳細は、下記のリンクを参照してください。

リソース

メモ

メモ

このモジュールは Salesforce Classic 向けです。ハンズオン組織を起動するときには、Salesforce Classic に切り替えてから、この Challenge を実行してください。