Salesforce Apex Calloutをマスターする:外部システム連携のための開発者ガイド

背景と応用シナリオ

Salesforce 開発者として、私たちは Salesforce Platform が単独で機能する島ではないことを理解しています。現代のエンタープライズアーキテクチャでは、データは複数のシステムに分散しており、それらをシームレスに連携させることがビジネスの成功に不可欠です。ここで強力なツールとなるのが Apex Callouts (Apex コールアウト) です。

Apex Callout とは、Apex コード内から外部の Web サービスを呼び出し、データの送受信を行う機能です。これにより、Salesforce を中心とした強力な統合ソリューションを構築することが可能になります。

具体的な応用シナリオ

  • データエンリッチメント: 外部のデータプロバイダーAPIを呼び出し、取引先企業の情報(業種、従業員数など)を自動で補完する。
  • リアルタイム在庫確認: Eコマースサイトで注文が入った際に、外部のERPシステムのAPIを呼び出して在庫を確認し、引き当てを行う。
  • 住所情報の正規化: 顧客が入力した住所を、外部の住所検証サービスのAPIに送信し、正確な形式にクレンジングする。
  • 決済処理: クレジットカード決済ゲートウェイのAPIを呼び出し、支払い処理を実行する。
  • 通知連携: Salesforce で重要なイベント(例:大規模商談の成立)が発生した際に、Slack や Microsoft Teams のAPIを呼び出してチームに通知する。

このように、Apex Callout を使いこなすことで、Salesforce の機能をプラットフォームの枠を超えて拡張し、ビジネスプロセス全体を自動化・効率化することができます。この記事では、私たち開発者が Apex Callout を実装する上での基本原理、具体的なコード例、そして注意すべき点について詳しく解説していきます。


原理説明

Apex Callout は、大きく分けて2つの主要なプロトコルをサポートしています。それは REST (Representational State Transfer)SOAP (Simple Object Access Protocol) です。

REST Callout

REST は、現代の Web API の主流となっているアーキテクチャスタイルです。HTTP プロトコルの標準メソッド(GET, POST, PUT, DELETEなど)を利用して、リソースを操作します。データ形式には主に JSON (JavaScript Object Notation) が使われ、その軽量さと柔軟性から広く採用されています。

Salesforce で REST Callout を行う際には、主に以下の組み込み Apex クラスを使用します。

  • HttpRequest: エンドポイントURL、HTTPメソッド、ヘッダー、ボディなど、送信するHTTPリクエストのすべてをカプセル化します。
  • HttpResponse: 外部サービスからのレスポンス(ステータスコード、ヘッダー、ボディ)をカプセル化します。
  • Http: HttpRequest オブジェクトを実際に送信し、HttpResponse オブジェクトを受け取るためのメソッド(send() など)を提供します。

SOAP Callout

SOAP は、より厳格で構造化されたプロトコルです。WSDL (Web Services Description Language) という XML ベースの言語でサービスの仕様が定義されており、すべての通信は XML 形式で行われます。

Salesforce は、WSDL ファイルから Apex クラスを自動生成する強力な機能を提供しています。この「WSDL-to-Apex」機能を使うと、外部の SOAP サービスをあたかもローカルの Apex メソッドのように簡単に呼び出すことができます。開発者は複雑な XML の構築や解析を意識する必要がなく、生成されたクラスのメソッドを呼び出すだけで済みます。

実行コンテキストと非同期処理

Apex Callout を理解する上で最も重要な概念の一つが、実行コンテキストです。セキュリティとデータ整合性を保つため、Salesforce は未完了の DML 操作(insert, update, delete など)があるトランザクション内から直接同期的な Callout を実行することを許可していません。なぜなら、もし Callout が失敗した場合に、すでに行われた DML 操作をロールバックすべきかどうかの判断が困難になるためです。

この制約を回避するため、Callout は非同期処理で行うのが一般的です。

  • Future メソッド: @future(callout=true) アノテーションを付与したメソッド内で Callout を実行します。これにより、Callout は現在のトランザクションとは別の、独立したトランザクションで実行されます。
  • Queueable Apex: Future メソッドよりも柔軟性が高く、ジョブIDの取得やジョブの連鎖が可能です。Queueable インターフェースを実装したクラスから Callout を実行します。


サンプルコード

ここでは、Salesforce の公式ドキュメントに基づいたコード例をいくつか紹介します。

REST GET Callout の例

これは、外部の動物名を提供するダミーサービスから情報を取得する簡単な GET リクエストの例です。

// 外部サービスを呼び出すクラス
public class AnimalLocator {
    public static String getAnimalNameById(Integer id) {
        // 1. HttpRequestインスタンスを作成
        HttpRequest req = new HttpRequest();
        
        // 2. エンドポイントURLを設定
        // 通常、このURLは指定ログイン情報(Named Credential)に格納するのがベストプラクティスです
        req.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals/' + id);
        
        // 3. HTTPメソッドを 'GET' に設定
        req.setMethod('GET');
        
        // 4. Httpインスタンスを作成し、リクエストを送信
        Http http = new Http();
        HttpResponse res = http.send(req);
        
        // 5. レスポンスを処理
        // ステータスコードが200 (成功) の場合
        if (res.getStatusCode() == 200) {
            // レスポンスボディ (JSON) をMapにデシリアライズ
            Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
            // 'animal' キーに対応するMapを取得
            Map<String, Object> animal = (Map<String, Object>) results.get('animal');
            // 'name' キーの値 (動物名) を返す
            return (String) animal.get('name');
        }
        return null;
    }
}

非同期 Callout の例 (Future メソッド)

DML 操作の後に Callout を行う必要がある場合の典型的なパターンです。

public class FutureMethodExample {

    // @future(callout=true) アノテーションにより、このメソッドが
    // 外部サービスへのコールアウトを行うことをプラットフォームに伝えます。
    @future(callout=true)
    public static void sendNotification(String message) {
        // 外部の通知サービスのエンドポイントを想定
        String endpoint = 'https://api.example.com/notifications';

        HttpRequest req = new HttpRequest();
        req.setEndpoint(endpoint);
        req.setMethod('POST');
        // ヘッダーでコンテントタイプをJSONに指定
        req.setHeader('Content-Type', 'application/json;charset=UTF-8');
        // リクエストボディを設定
        req.setBody('{"text":"' + message + '"}');

        Http http = new Http();
        // 実際にリクエストを送信
        HttpResponse res = http.send(req);

        // レスポンスのステータスコードを確認し、必要に応じてログ出力などの処理を行う
        if (res.getStatusCode() != 201) {
            System.debug('The status code returned was not expected: ' +
                res.getStatusCode() + ' ' + res.getStatus());
        }
    }
}

この Future メソッドは、例えばトリガーから以下のように呼び出します。

trigger AccountTrigger on Account (after update) {
    for (Account a : Trigger.new) {
        // 特定の条件を満たした場合に非同期で通知を送る
        if (a.AnnualRevenue > 1000000 && Trigger.oldMap.get(a.Id).AnnualRevenue <= 1000000) {
            String message = a.Name + ' is now a high-value account!';
            FutureMethodExample.sendNotification(message);
        }
    }
}

注意事項

Apex Callout を実装する際には、プラットフォームの制約とセキュリティを考慮する必要があります。

リモートサイトの設定 (Remote Site Settings)

Apex コードから外部の URL にアクセスするには、そのエンドポイントを事前に Remote Site Settings (リモートサイトの設定) に登録する必要があります。これは、悪意のあるコードが Salesforce 組織から任意の外部サイトに情報を送信するのを防ぐためのセキュリティ機能です。登録されていないエンドポイントに Callout しようとすると、System.CalloutException: Unauthorized endpoint というエラーが発生します。

指定ログイン情報 (Named Credentials)

ハードコーディングされた URL や認証情報をコード内に記述するのは、セキュリティ上およびメンテナンス上の観点から非常に悪いプラクティスです。代わりに Named Credentials (指定ログイン情報) を使用することを強く推奨します。Named Credentials は、エンドポイント URL と認証情報を一元管理する仕組みです。コード内では Named Credential の名前を指定するだけでよく、Salesforce が認証処理を自動的にハンドルしてくれます。これにより、本番環境と Sandbox 環境でエンドポイントが異なる場合でも、コードを変更することなく設定だけで対応できます。

ガバナ制限 (Governor Limits)

Salesforce はマルチテナント環境であるため、リソースの公平な利用を保証するために Governor Limits (ガバナ制限) が存在します。Callout にも以下のような制限があります。

  • 1トランザクションあたりのコールアウト数: 100回
  • 1トランザクションあたりの累積タイムアウト時間: 120秒
  • コールアウトの最大ペイロードサイズ: (Heap サイズに依存)

これらの制限は、API バージョンによって変更される可能性があるため、常に最新の公式ドキュメントを確認することが重要です。特に、ループ内で Callout を行うような設計は絶対に避けるべきです。

エラー処理 (Error Handling)

外部システムは常に正常に稼働しているとは限りません。ネットワークの問題、サーバーダウン、API仕様の変更など、予期せぬエラーが発生する可能性があります。堅牢な実装のためには、try-catch ブロックを使用して例外を捕捉し、HttpResponse のステータスコードを必ず確認する習慣が不可欠です。エラー発生時には、ログを記録したり、管理者に通知したり、再試行ロジック(リトライ処理)を組み込むなどの対策を検討しましょう。

テスト (Testing)

Apex のテストクラスは、実際の外部サービスへの Callout を実行できません。テストの実行が外部システムの状態に依存するのを防ぐためです。そのため、Salesforce は HttpCalloutMock インターフェースを提供しています。このインターフェースを実装したクラスを作成することで、Callout に対する擬似的なレスポンス(モックレスポンス)を定義できます。テスト実行時には、Test.setMock(HttpCalloutMock.class, new YourMockResponseGenerator()); を呼び出すことで、実際の Callout の代わりにモックが使用され、コードのロジックを安全かつ確実にテストできます。


まとめとベストプラクティス

Apex Callout は、Salesforce を外部システムと連携させ、その価値を飛躍的に高めるための強力な機能です。私たち開発者は、その仕組みと制約を正しく理解し、ベストプラクティスに沿って実装することが求められます。

以下に、Apex Callout を実装する際のベストプラクティスをまとめます。

  • 指定ログイン情報 (Named Credentials) を常に使用する

    URL や認証情報をコードから分離し、セキュリティとメンテナンス性を向上させます。

  • 非同期処理を積極的に活用する

    DML 操作を伴う場合や、処理に時間がかかる可能性がある場合は、@futureQueueable Apex を使用して、ユーザーエクスペリエンスやガバナ制限への影響を最小限に抑えます。

  • 堅牢なエラーハンドリングを実装する

    try-catch ブロックとステータスコードのチェックを徹底し、失敗した場合のシナリオを考慮した設計を行います。

  • 処理をバルク化する

    複数のレコードに対して同じような Callout を行う必要がある場合、一度のリクエストで複数のデータを扱えるように API を設計・利用し、Callout の回数を最小限に抑えます。

  • HttpCalloutMock を用いて網羅的なテストを行う

    成功ケースだけでなく、様々なエラーステータスコードが返ってくるケースもモックでシミュレートし、コードカバレッジと品質を確保します。

  • ガバナ制限を常に意識する

    設計段階から、トランザクションあたりの Callout 回数や実行時間を考慮に入れます。

これらのプラクティスを遵守することで、私たちは安定的で、スケーラブルかつセキュアな統合ソリューションを構築し、ビジネスに貢献することができます。

コメント