こんにちは、管理人の@Salesforce.Zです。
Salesforceにはfutureアノテーションがあります。メソッドに@futrueをつけて、非同期メソッドになります。 そして、非同期にすることによってメリットもあります。future メソッドは、バッググラウンドで非同期で実行されます。外部 Web サービスへのコールアウト、独自のスレッドを独自の時間に実行する処理など、長時間にわたる処理を実行する場合に future メソッドをコールできます。
目次
@futureアノテーションメソッドの書き方
この方法によって、長時間にわたる処理の完了を待たずにコードを実行できます。future メソッドを使用する利点は、SOQL クエリの制限やヒープサイズ制限など、一部のガバナ制限値が高くなる点にあります。
基本の記載方法
future メソッドを定義するには、単に次のように future アノテーションを使用してアノテーションを付加するだけ
global class FutureClass{ @future public static void myFutureMethod(){ // Perform some operations } }
非同期メソッド:よく使用するパターン
長時間を要するメソッドがあり、Apex トランザクションの遅延を防止する必要がある場合
外部 Web サービスへのコールアウトを実行する場合
引数を受け取る応用記載方法
記載注意点
Staticのメソッドである必要がある(静的メソッド)
引数はプリミティブデータ型である つまりプリミティブ、配列、List、Mapのみ
戻り値はVoidのみ
sObject またはオブジェクトを引数とすることがダメ
呼び出した順に処理されるかは保証されない
1 つの Apex トランザクションで、HTTP 要求または API コールに対するコールアウトを最大 100 回実行できます
コールアウト(外部接続)するときはcallout=trueを指定する
- @future(callout=true)メソッドから@futureメソッドを呼出することはできない
- テストメソッドでcalloutを実行するとスキップしてしまうので、フラグまたはコールアウト用クラスを作ってテスト処理はモックで実行する
- コールアウトする前後にDML処理(insert, update, upsert, delete)を行うとCalloutExceptionが発生する >>> System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling
コール順番で検証した結果
# | 順番 | 結果 |
---|---|---|
1 | DML ⇒ callout | ○ |
2 | callout ⇒ DML | ○ |
3 | DML ⇒ callout ⇒ DML | ✖ |
4 | callout ⇒ DML ⇒ callout | ✖ |
future メソッドに sObject を引数として渡せない理由は、メソッドをコールしてからそのメソッドを実行するまでの間に sObject が変更されてしまう可能性があるためです。この場合、future メソッドが以前の sObject 値を取得して新しい値を上書きしてしまう可能性があります。データベースにすでに存在する sObject を使用するには、代わりに sObject ID (または ID のコレクション) を渡し、ID を使用して最新のレコードに対してクエリを実行します。次の例では、ID のリストを使用してこれを実行する方法を示します。
応用例A
global class FutureMethodRecordProcessing{ @future public static void processRecords(List<ID> recordIds){ // Get those records based on the IDs List<Account> accts = [SELECT Name FROM Account WHERE Id IN :recordIds]; // Process records } }
応用例B
外部サービスへのコールアウトを実行するには、コールアウトが許可されることを示す追加パラメータ (callout=true) で対応する
global class FutureMethodExample{ @future(callout=true) public static void getStockQuotes(String acctName){ // Perform a callout to an external service } }
Webサービスコールアウト
例えば、どこで、Webサービスのコールアウトをしたい場合があります。大体下記のような場合かと考えられる。
Webサービスのコールアウト例
/** * Webサービスのコールアウト:トリガやクラスの中の非同期メソッドからWebサービスのコールアウト * * @param idList 処理したいsObjectのIDリスト */ private static void callWebService(Set<Id> idList){ YourApexClass.httpCalloutStaticClass(idList); }
public with sharing class YourApexClass { public static void httpCalloutStaticClass(Set<Id> idList) { List<sObject> dataList = YourDao.getRecsByIds(idList); if(dataList != null && !dataList.isEmpty()){ // ....処理 } callOutWebService(your want to post message); } /** * Webサービスのコールアウト * * @param jsonMsg */ @future(callout=true) private static Integer callOutWebService(String jsonMsg){ try{ Http http = new Http(); HttpRequest req = new HttpRequest(); //your end point req.setEndpoint('https://yourendpoint.com'); req.setMethod('POST'); req.setHeader('Content-Type', 'application/json'); req.setBody(jsonMsg); HttpResponse res = http.send(req); System.debug('Post result:' + res.getStatus()); return res.getStatusCode(); }catch(System.CalloutException e) { System.debug(LoggingLevel.INFO, 'ERROR:' + e.getMessage()); return 404; } } }
Webサービスのコールアウトのテストクラス
WebサービスのコールアウトをテストするにはHttpCalloutMock インターフェースを実装して respond メソッドで送信される応答を指定する必要があります。
そして、この応答の指定は疑似です。本当のコールアウトではありません。
擬似応答の値を指定したら、テストメソッドで Test.setMock をコールし、この擬似応答を送信するように Apex ランタイムに指示できます。次のように、第 1 引数では HttpCalloutMock.class を渡し、第 2 引数では HttpCalloutMock のインターフェース実装の新しいインスタンスを渡します。
下記はHttpCalloutMock インターフェースを実装したYourHttpCalloutMockImplクラスの使用例
Test.setMock(HttpCalloutMock.class, new YourHttpCalloutMockImpl());
/**下記のようなコールアウトクラスがあるとします*/ public class CalloutClass { @future(callout=true) public static HttpResponse getInfoFromExternalService() { HttpRequest req = new HttpRequest(); req.setEndpoint('http://example.com/example/test'); req.setMethod('GET'); Http h = new Http(); HttpResponse res = h.send(req); return res; } }
/**上記のコールアウトクラス()のテストクラス*/ @isTest private class CalloutClassTest { @isTest static void testCallout() { // Set mock callout class Test.setMock(HttpCalloutMock.class, new MockHttpResponseGenerator()); // Call method to test. // This causes a fake response to be sent // from the class that implements HttpCalloutMock. HttpResponse res = CalloutClass.getInfoFromExternalService(); // Verify response received contains fake values String contentType = res.getHeader('Content-Type'); System.assert(contentType == 'application/json'); String actualValue = res.getBody(); String expectedValue = '{"example":"test"}'; System.assertEquals(actualValue, expectedValue); System.assertEquals(200, res.getStatusCode()); } }
/**CalloutClassTest で使う疑似コールアウトクラスの定義*/ @isTest global class MockHttpResponseGenerator implements HttpCalloutMock { // Implement this interface method global HTTPResponse respond(HTTPRequest req) { // Optionally, only send a mock response for a specific endpoint // and method. System.assertEquals('http://example.com/example/test', req.getEndpoint()); System.assertEquals('GET', req.getMethod()); // Create a fake response HttpResponse res = new HttpResponse(); res.setHeader('Content-Type', 'application/json'); res.setBody('{"example":"test"}');//なんでもよい res.setStatusCode(200); return res; } }
最後に
基本、Webサービスのコールアウトなら、非同期メソッドを使用すると覚えたら、いいじゃないかなと思います。