Salesforce Apex コールアウト完全ガイド:開発者向け外部 API 連携の実践

背景と応用シーン

Salesforce 開発者の皆さん、こんにちは!日々の業務で、Salesforce プラットフォームの強力な機能を活用して、ビジネスプロセスの自動化やカスタムアプリケーションの構築に取り組んでいることと思います。しかし、今日のビジネス環境では、単一のシステムで完結することは稀です。Salesforce はエコシステムの中心として機能することが多く、外部の様々なシステムと連携する必要があります。ここで登場するのが Apex Callouts (Apex コールアウト) です。

Apex コールアウトとは、Apex コード内から外部の Web サービスや API を呼び出すためのメカニズムです。これにより、Salesforce のデータを外部システムに送信したり、外部システムからデータを取得して Salesforce 内で活用したりすることが可能になります。

具体的な応用シーン

  • データエンリッチメント: 住所クレンジング API を呼び出して、取引先責任者の住所データを正規化・補完する。
  • リアルタイム情報取得: 金融情報サービスの API を叩き、商談に関連する最新の株価情報を取得して表示する。
  • 基幹システム連携: 商談が「成立」になったタイミングで、外部の ERP (Enterprise Resource Planning) システムに注文情報を作成する。
  • 物流連携: 発送指示を外部の物流システム (WMS) に送信し、追跡番号を取得して Salesforce のカスタムオブジェクトに保存する。
  • 本人確認サービス: オンライン申請プロセスで、外部の KYC (Know Your Customer) サービス API を呼び出して本人確認を行う。

このように、Apex コールアウトは Salesforce の可能性をプラットフォームの垣根を越えて拡張するための、開発者にとって不可欠なツールです。この記事では、Apex コールアウトの基本的な仕組みから、実践的なコード例、そして開発者として押さえておくべき注意事項やベストプラクティスまでを詳しく解説していきます。


原理説明

Apex コールアウトの核心は、HTTP プロトコルに基づいたリクエストとレスポンスの交換です。Apex は、このプロセスを簡単に行うための組み込みクラスを提供しています。主要な登場人物は以下の3つです。

1. HttpRequest Class

これは、外部サービスに送信する HTTP リクエストを表現するオブジェクトです。開発者はこのオブジェクトをインスタンス化し、リクエストの内容を組み立てます。主要なメソッドには以下のようなものがあります。

  • setEndpoint(url): 呼び出す API の Endpoint (エンドポイント、つまり URL) を指定します。
  • setMethod(method): HTTP Method (メソッド) を指定します。一般的には 'GET' (データ取得)、'POST' (データ作成)、'PUT' (データ更新)、'DELETE' (データ削除) などが使用されます。
  • setHeader(key, value): HTTP Header (ヘッダー) を設定します。認証情報 (例: 'Authorization') やコンテンツタイプ (例: 'Content-Type') などを指定するために使用します。
  • setBody(body): 'POST' や 'PUT' リクエストで送信するリクエストの Body (ボディ) を設定します。通常は JSON や XML 形式の文字列です。
  • setTimeout(milliseconds): タイムアウトまでの時間をミリ秒単位で指定します。

2. Http Class

このクラスは、実際に HTTP リクエストを送信する役割を担います。最も重要なメソッドは send(request) です。このメソッドは HttpRequest オブジェクトを引数として受け取り、外部サーバーにリクエストを送信し、その結果を HttpResponse オブジェクトとして返します。

3. HttpResponse Class

これは、外部サービスからの HTTP レスポンスを表現するオブジェクトです。Http.send() メソッドの戻り値として取得し、開発者はこのオブジェクトから結果を解析します。

  • getStatusCode(): HTTP Status Code (ステータスコード) を数値で返します。200 は成功、404 は Not Found、500 は Internal Server Error などを意味します。
  • getStatus(): ステータスコードに対応する説明文字列 (例: 'OK', 'Not Found') を返します。
  • getBody(): レスポンスのボディを文字列で返します。通常、ここに API から返された JSON や XML データが含まれています。
  • getHeader(key): 指定したキーに対応するレスポンスヘッダーの値を返します。

これらのクラスを組み合わせることで、「リクエストを作成 (HttpRequest) → 送信 (Http) → レスポンスを処理 (HttpResponse)」という一連の流れを Apex コードで実装することができます。

また、重要な前提条件として、Salesforce はセキュリティ上の理由から、未登録のエンドポイントへのコールアウトを許可していません。そのため、コールアウトを行う前に対象の URL を Remote Site Settings (リモートサイトの設定) に登録しておく必要があります。


示例代码

ここでは、Salesforce の公式ドキュメントでよく使用される、公開されている動物の名前を生成する API を呼び出す簡単な GET リクエストの例を見てみましょう。このコードは、API を呼び出し、返ってきた動物の名前をデバッグログに出力します。

事前準備: このコードを実行する前に、Salesforce の [設定] > [セキュリティ] > [リモートサイトの設定] に移動し、新しいリモートサイトとして URL https://th-apex-http-callout.herokuapp.com を登録してください。

// AnimalLocator クラス: 動物の名前を取得するコールアウトを実行します。
public class AnimalLocator {
    // コールアウトを実行し、動物の名前を返すメソッド
    public static String getAnimalNameById(Integer id) {
        // 1. HttpRequest オブジェクトをインスタンス化
        HttpRequest req = new HttpRequest();

        // 2. エンドポイントURLを設定。クエリパラメータとして id を渡します。
        // 文字列内の '{0}' は、String.format メソッドによって id の値に置き換えられます。
        String endpoint = String.format('https://th-apex-http-callout.herokuapp.com/animals/{0}', new List<Object>{id});
        req.setEndpoint(endpoint);

        // 3. HTTP メソッドを 'GET' に設定
        req.setMethod('GET');

        // 4. Http オブジェクトをインスタンス化
        Http http = new Http();
        HttpResponse res = null;
        
        String responseBody = '';

        try {
            // 5. リクエストを送信し、レスポンスを取得
            res = http.send(req);

            // 6. レスポンスのステータスコードを確認
            // 200 は成功を意味します
            if (res.getStatusCode() == 200) {
                // 7. レスポンスボディを解析
                // レスポンス例: {"animal":{"id":1,"name":"chicken"}}
                Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
                
                // animal というキーでネストされたマップを取得
                Map<String, Object> animal = (Map<String, Object>) results.get('animal');
                
                // animal マップから name を取得し、文字列にキャスト
                responseBody = (String) animal.get('name');
            } else {
                // 失敗した場合、エラーログを記録
                System.debug('Error from callout. Status code: ' + res.getStatusCode());
                responseBody = 'Error: ' + res.getStatus();
            }
        } catch(System.CalloutException e) {
            // コールアウト固有の例外(例:リモートサイト設定がない、タイムアウトなど)をキャッチ
            System.debug('Callout error: '+ e);
            responseBody = 'Callout Error: ' + e.getMessage();
        }
        
        return responseBody;
    }
}

このコードを匿名実行ウィンドウで System.debug(AnimalLocator.getAnimalNameById(1)); のように実行すると、デバッグログに動物の名前が出力されることが確認できます。


注意事項

Apex コールアウトを実装する際には、Salesforce プラットフォーム特有の制約や考慮事項がいくつかあります。これらを無視すると、予期せぬエラーやパフォーマンス問題につながる可能性があります。

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

前述の通り、これは最も基本的かつ重要な設定です。コールアウト先のドメイン (例: `https://api.example.com`) を事前に登録しない限り、Apex はそのドメインへのリクエストをブロックし、`System.CalloutException: Unauthorized endpoint` というエラーをスローします。

Governor Limits (ガバナ制限)

Salesforce はマルチテナント環境であるため、リソースの公平な利用を保証するためにガバナ制限が設けられています。コールアウトに関しても、以下のような主要な制限があります。

  • 1トランザクションあたりのコールアウト数: 100回まで。
  • コールアウトの累積タイムアウト: 1トランザクション内で最大120秒まで。

ループ内でコールアウトを呼び出すような設計は、容易にこの制限に抵触するため避けるべきです。複数のデータを処理する必要がある場合は、外部 API が一括処理用のエンドポイントを提供しているか確認し、一度のコールアウトで済むように設計することが推奨されます。

Asynchronous Execution (非同期実行)

Apex トリガーなどの同期的なコンテキストから、直接コールアウトを実行することはできません。これは、コールアウトが完了するまでデータベーストランザクションをロックし続け、パフォーマンスに悪影響を与える可能性があるためです。
同期コンテキストからコールアウトを呼び出す必要がある場合は、処理を非同期にする必要があります。そのための主な方法は以下の通りです。

  • @future(callout=true) アノテーション: メソッドにこのアノテーションを付与することで、そのメソッドは別のトランザクションで非同期に実行されます。最も手軽な方法ですが、パラメータとして渡せるのはプリミティブ型、プリミティブ型のコレクション、または sObject のみです。
  • Queueable Apex: Queueable インターフェースを実装したクラスです。@future よりも柔軟で、複雑なオブジェクトを渡したり、ジョブIDを取得して監視したり、ジョブをチェーン(連結)させたりすることが可能です。コールアウトを伴う非同期処理には、現在最も推奨される方法です。
  • Batch Apex: 大量のレコードを処理する際に使用します。バッチの execute メソッドからコールアウトを行うことができます。

Error Handling (エラーハンドリング)

外部システムとの通信は、ネットワークの問題や相手先サーバーの都合など、様々な要因で失敗する可能性があります。そのため、堅牢なエラーハンドリングは必須です。

  • try-catch ブロック: System.CalloutException などの例外を必ずキャッチして、処理が中断しないようにします。
  • ステータスコードの確認: レスポンスが返ってきても、それが成功を意味するとは限りません。res.getStatusCode() が 200 番台であることを確認し、4xx (クライアントエラー) や 5xx (サーバーエラー) の場合は適切に処理を分岐させ、エラーログを記録するべきです。
  • リトライ処理: 一時的なネットワークエラーなど、再試行すれば成功する可能性のあるエラーについては、Queueable Apex などを使ってリトライロジックを実装することも検討します。

Security and Named Credentials (セキュリティと指定ログイン情報)

コード内にエンドポイント URL や API キーなどの認証情報を直接書き込む(ハードコーディングする)のは、セキュリティ上およびメンテナンス上の観点から非常に悪いプラクティスです。
Salesforce では、このような情報を安全に管理するために Named Credentials (指定ログイン情報) を使用することが強く推奨されています。指定ログイン情報を使うと、エンドポイント URL と認証情報をコードから分離できます。Apex コード内では、指定ログイン情報の名前を参照するだけで、Salesforce が認証処理を自動的にハンドルしてくれます。これにより、認証情報が変更された場合でも、コードを修正・デプロイする必要がなくなります。


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

Apex コールアウトは、Salesforce を外部システムと連携させるための強力な機能です。開発者としてこの機能を使いこなすことで、より価値の高いソリューションを提供できるようになります。最後に、コールアウトを実装する際のベストプラクティスをまとめます。

  1. 指定ログイン情報 (Named Credentials) を常に使用する:
    ハードコーディングは避けましょう。URL、APIキー、パスワードなどの機密情報は指定ログイン情報で一元管理することで、コードのセキュリティと保守性が劇的に向上します。
  2. 非同期処理を積極的に活用する:
    トリガーからのコールアウトや、ユーザーの操作に直接関連しない処理は、Queueable Apex を使って非同期に実行しましょう。これにより、ガバナ制限の回避とユーザーエクスペリエンスの向上が期待できます。
  3. コールアウトのバルク化を検討する:
    複数のレコードに対して同様のコールアウトが必要な場合、1レコードずつループで呼び出すのではなく、一度に複数のデータを処理できるような設計を心がけましょう。外部 API がバルクエンドポイントを提供している場合は、それを最大限に活用します。
  4. 堅牢なエラーハンドリングとロギングを実装する:
    「コールアウトは失敗するもの」という前提でコードを書きましょう。try-catch、ステータスコードのチェックは必須です。また、失敗した際には、後から調査できるように、リクエスト内容やレスポンス、エラーメッセージをカスタムオブジェクトなどにログとして記録する仕組みを構築することが重要です。
  5. HttpCalloutMock でテストを記述する:
    Apex のテストクラスは、実際の外部サービスへのコールアウトを実行できません。テストコードカバレッジを満たし、ロジックを検証するためには、HttpCalloutMock インターフェースを実装したモッククラスを作成する必要があります。これにより、テスト実行時に擬似的なレスポンスを返し、コールアウト後の処理が正しく動作するかを検証できます。

これらの原則を守り、Salesforce プラットフォームの特性を理解した上で Apex コールアウトを実装することで、安定的でスケーラブルなインテグレーションを実現できるはずです。

コメント