Apexの@future
メソッドは、Salesforceで非同期処理を実行するための機能です。これを使うことで、時間のかかる処理や特定の制約に関連する操作をバックグラウンドで実行し、メインのトランザクションを効率化することができます。@future
メソッドの使用例とその目的を、同期処理と非同期処理の観点から説明します。
同期処理と非同期処理
-
同期処理:
同期処理では、Apexコードが順番に実行され、各ステップが完了するまで次のステップに進みません。これにより、すべての処理が一度に完了しますが、長時間かかる処理や複雑なトランザクションの場合、パフォーマンスが低下したり、タイムアウトエラーが発生する可能性があります。 -
非同期処理:
非同期処理では、長時間かかる処理やトリガー後の処理を別のスレッドで実行するため、ユーザーにとってのパフォーマンスが向上します。非同期に処理されるため、メインのトランザクションがすぐに終了し、長時間の待ち時間がなくなります。
@future
メソッドの使用ケース
1. 外部Webサービスへのコールアウト
Salesforceから外部のWebサービスへリクエストを送信する際に、コールアウトと呼ばれるプロセスが行われます。このとき、外部サービスからの応答を待つ必要があり、その間トランザクションがブロックされる可能性があります。@future
メソッドを使うと、このコールアウトを非同期で実行できるため、トリガーやDML操作がすぐに完了し、他の操作が遅延しません。
例:
public class CalloutExample {
@future(callout=true)
public static void makeCallout() {
HttpRequest req = new HttpRequest();
req.setEndpoint('https://example.com/api');
req.setMethod('GET');
Http http = new Http();
HttpResponse res = http.send(req);
// 応答の処理
}
}
2. トリガー内でのコールアウト
トリガーから直接外部サービスにコールアウトを行うと、コールアウトの応答を待つ間、データベース接続が開いたままになります。これはSalesforceのマルチテナント環境に悪影響を及ぼす可能性があるため、避けるべきです。@future
メソッドを使ってトリガー後にコールアウトを非同期で実行することで、この問題を解決できます。
例:
trigger AccountTrigger on Account (after insert) {
for (Account acc : Trigger.new) {
CalloutExample.makeCallout();
}
}
3. リソースを大量に消費する計算やレコードの処理
大量のデータを処理する必要がある場合や計算量が多い操作を行う場合、同期処理では時間がかかりすぎるため、@future
メソッドで非同期に実行するのが適しています。これにより、メインのトランザクションはすぐに完了し、時間のかかる処理はバックグラウンドで行われます。
例:
public class HeavyProcessingExample {
@future
public static void processHeavyData() {
// 大量のデータを処理する
}
}
4. 混合DMLエラーの回避
混合DMLエラーは、特定のsObject(例えば、Userオブジェクト)と他の標準オブジェクトを同じトランザクション内で操作しようとすると発生します。この問題を避けるために、@future
メソッドを使用して、DML操作を異なるトランザクションで実行します。
例:
public class MixedDMLExample {
@future
public static void performMixedDML(Id userId) {
User u = [SELECT Id, LastName FROM User WHERE Id = :userId];
u.LastName = 'Updated';
update u;
}
}
// トリガー内や別のメソッドから呼び出し
MixedDMLExample.performMixedDML(someUserId);
まとめ
- 同期処理は、一連の操作を順番に実行するため、長時間かかる操作ではパフォーマンスの低下が懸念されます。
- 非同期処理は、時間のかかる操作をバックグラウンドで実行し、ユーザーエクスペリエンスの向上やガバナ制限の回避に役立ちます。
-
@future
メソッドは、非同期処理を実現するための便利な手段であり、外部Webサービスのコールアウト、リソース集約的な処理、混合DMLエラーの回避など、特定の状況で有効です。
このように、@future
メソッドを適切に利用することで、Apexコードのパフォーマンスを向上させ、Salesforceのガバナ制限を順守しながら効果的な処理を実行できます。
具体的な例
1. 外部APIコールアウトの非同期処理
外部システムへのAPIコールアウトは、応答を待つ間、Salesforceのトランザクションがブロックされる可能性があるため、非同期で実行するのが一般的です。以下は、@future(callout=true)
アノテーションを使用して、非同期でAPIコールアウトを行う例です。
例: 外部APIから為替レートを取得し、レコードに更新する
public class CurrencyUpdateService {
@future(callout=true)
public static void updateCurrencyRates(Set<Id> accountIds) {
try {
// 外部APIコールアウト
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.exchangeratesapi.io/latest?base=USD');
req.setMethod('GET');
Http http = new Http();
HttpResponse res = http.send(req);
if (res.getStatusCode() == 200) {
// レスポンスの処理
Map<String, Object> responseMap = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
Map<String, Decimal> rates = (Map<String, Decimal>) responseMap.get('rates');
// 為替レートを更新
List<Account> accountsToUpdate = new List<Account>();
for (Id accountId : accountIds) {
Account acc = new Account(Id = accountId);
acc.Exchange_Rate__c = rates.get('JPY'); // 為替レート(例:JPY)を更新
accountsToUpdate.add(acc);
}
update accountsToUpdate;
} else {
System.debug('Error: ' + res.getStatus());
}
} catch (Exception e) {
System.debug('Exception: ' + e.getMessage());
}
}
}
この例では、外部APIから為替レートを取得し、取得したレートを基に複数のAccount
レコードのExchange_Rate__c
フィールドを更新しています。この処理は非同期に行われるため、APIコールアウト中に他のトランザクションがブロックされることはありません。
2. 大量データの処理
大量のデータを処理する場合、同期的に実行するとガバナ制限に引っかかる可能性があります。そこで、バッチApexを使用してデータを分割し、非同期に処理することが推奨されます。
例: 顧客レコードの更新をバッチ処理する
public class AccountBatchUpdate implements Database.Batchable<SObject>, Database.AllowsCallouts {
public Database.QueryLocator start(Database.BatchableContext bc) {
// バッチ処理で取得するレコードのクエリ
return Database.getQueryLocator('SELECT Id, Name FROM Account WHERE Industry = null');
}
public void execute(Database.BatchableContext bc, List<SObject> scope) {
List<Account> accountsToUpdate = new List<Account>();
for (SObject sObj : scope) {
Account acc = (Account) sObj;
acc.Industry = 'Technology'; // 業界フィールドを更新
accountsToUpdate.add(acc);
}
update accountsToUpdate;
}
public void finish(Database.BatchableContext bc) {
// バッチ処理完了後の処理(例:完了通知の送信)
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setSubject('Batch Process Completed');
mail.setPlainTextBody('The account update batch process has completed.');
mail.setToAddresses(new String[] { 'admin@example.com' });
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
}
このバッチクラスは、業界フィールドが空のAccount
レコードを対象に一括で更新します。バッチ処理は非同期で行われるため、大量のデータを効率的に処理できます。finish
メソッドでは、バッチ処理の完了を通知するメールが送信されます。
3. キューアブルApexを使用した非同期処理
キューアブルApexは、非同期処理をシンプルに書くためのもう一つの手段であり、他の非同期処理と比べてより柔軟に利用できます。例えば、非同期処理の完了後にさらに別の非同期処理を実行することもできます。
例: 顧客データのバックグラウンド処理
public class AccountProcessingJob implements Queueable, Database.AllowsCallouts {
public void execute(QueueableContext context) {
List<Account> accountsToUpdate = [SELECT Id, Name FROM Account WHERE Industry = 'Technology'];
for (Account acc : accountsToUpdate) {
acc.Description = 'Processed by Queueable Apex';
}
update accountsToUpdate;
// 次のキューアブルジョブをチェーン
System.enqueueJob(new NotifyAdminJob());
}
}
public class NotifyAdminJob implements Queueable {
public void execute(QueueableContext context) {
// 管理者に処理完了を通知
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setSubject('Processing Complete');
mail.setPlainTextBody('The account processing job has completed.');
mail.setToAddresses(new String[] { 'admin@example.com' });
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
}
この例では、AccountProcessingJob
クラスがAccount
レコードを更新し、その後、NotifyAdminJob
がチェーンされて管理者にメール通知を送信します。これにより、複数の非同期処理を連携させたワークフローを構築できます。
まとめ
非同期処理は、Salesforceで長時間実行される処理やリソースを大量に消費するタスクをバックグラウンドで処理するのに役立ちます。Apexでは、@future
メソッド、バッチApex、キューアブルApexなどを利用して非同期処理を実装できます。それぞれの手法には特定の利点があり、具体的なニーズに応じて適切な方法を選択することが重要です。