Salesforce Apex Callout 完全ガイド:開発者向け外部システム連携の実践

背景と応用シナリオ

こんにちは、Salesforce 開発者の皆さん。今回は、Salesforce プラットフォームの能力を最大限に引き出すための重要な機能、Apex Callouts について深掘りしていきます。Salesforce は強力な CRM プラットフォームですが、その真価は他のシステムと連携することでさらに高まります。Apex Callout は、その連携を実現するための中心的な技術です。

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

具体的な応用シナリオ

開発者として、私たちは日々さまざまなビジネス要件に直面します。Apex Callout が活躍するシナリオは多岐にわたります。

  • データエンリッチメント: 取引先レコードが作成された際に、外部の企業情報データベース API を呼び出し、業種や従業員数などの詳細情報を自動的に補完する。
  • リアルタイム在庫確認: 商談に商品を追加する際、外部の ERP (Enterprise Resource Planning / 企業資源計画) システムの API を叩き、リアルタイムの在庫状況を確認・表示する。
  • 外部サービスへのデータ同期: 商談が「成立」になったタイミングで、契約管理システムや請求システムの API を呼び出し、新しい契約情報や請求書を作成する。
  • 物流追跡: カスタムオブジェクトに保存された配送伝票番号を使い、配送業者の追跡 API にリクエストを送信し、最新の配送状況を取得して画面に表示する。
  • 金融サービス連携: 顧客の与信審査プロセスで、外部の信用情報機関の API を呼び出してスコアを取得する。

これらのシナリオが示すように、Apex Callout は Salesforce を単なる情報のサイロではなく、ビジネスプロセス全体のハブとして機能させるための鍵となります。


原理說明

Apex Callout の基本的な仕組みは、Apex が HTTP リクエストを作成し、それを外部のエンドポイントに送信し、レスポンスを受け取って処理するという、標準的な Web API コールの流れに基づいています。Salesforce では、このプロセスを安全かつ効率的に行うために、いくつかの組み込みクラスが提供されています。

中心となるのは以下の3つのクラスです。

  • HttpRequest: 送信する HTTP リクエストを表現するクラスです。エンドポイント URL、HTTP メソッド (GET, POST, PUT, DELETE など)、ヘッダー、リクエストボディなどを設定します。
  • HttpResponse: 外部サービスから返された HTTP レスポンスを表現するクラスです。ステータスコード、レスポンスボディ、ヘッダーなどが含まれます。
  • Http: 実際にリクエストを送信するためのクラスです。このクラスの send() メソッドに HttpRequest オブジェクトを渡すことで、コールアウトが実行されます。

同期コールアウトと非同期コールアウト

Apex Callout を理解する上で最も重要な概念の一つが、同期 (Synchronous)非同期 (Asynchronous) の違いです。

データベースのトランザクションをブロックしないようにするため、Salesforce ではトリガなどの同期的な Apex コンテキストから直接コールアウトを実行することはできません。なぜなら、外部システムの応答が遅れた場合、データベースのロック時間が長くなり、プラットフォーム全体のパフォーマンスに影響を与える可能性があるからです。

この制約を回避するため、コールアウトは通常、非同期コンテキストで実行されます。

  • Future Methods (@future(callout=true)): 最も簡単な非同期処理の方法です。メソッドに @future(callout=true) アノテーションを付与することで、そのメソッドは別のトランザクションとしてバックグラウンドで実行され、コールアウトが可能になります。
  • Queueable Apex: Future Methods よりも高度な非同期処理です。ジョブ ID を取得して処理状況を追跡したり、ジョブを連結したりすることができます。コールアウトを行う場合は、Queueable インターフェースと共に Database.AllowsCallouts インターフェースを実装する必要があります。
  • Batch Apex: 大量のレコードを処理するための非同期処理です。execute メソッド内でコールアウトを行う場合、Database.Batchable インターフェースと共に Database.AllowsCallouts インターフェースを実装します。
  • Visualforce Controllers / Lightning Component Apex Controllers: ユーザー操作を起点とする場合、これらのコントローラメソッドから直接コールアウトを実行できます。これは、トランザクションが UI スレッドで開始され、データベース操作とは切り離されているためです。

開発者としては、どのコンテキストでコールアウトが必要になるかを正確に把握し、適切な非同期処理パターンを選択することが求められます。


示例コード

ここでは、Salesforce の公式ドキュメントでよく紹介される、外部の動物検索サービス API を呼び出す例を見ていきましょう。この例は、GET リクエストを送信し、返ってきた JSON データを解析する基本的なフローを示しています。

Apex Callout クラス

まず、コールアウトを実行する Apex クラスを作成します。

public class AnimalLocator {
    // ID を指定して動物の名前を取得するメソッド
    public static String getAnimalNameById(Integer id) {
        // 1. Http オブジェクトのインスタンスを作成
        Http http = new Http();

        // 2. HttpRequest オブジェクトのインスタンスを作成
        HttpRequest request = new HttpRequest();
        // 外部サービスの URL をエンドポイントとして設定
        request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals/' + id);
        // HTTP メソッドを 'GET' に設定
        request.setMethod('GET');

        // 3. send() メソッドを呼び出してリクエストを送信し、HttpResponse を受け取る
        HttpResponse response = http.send(request);

        // レスポンスのステータスコードを確認
        // 200 は成功を意味する
        if (response.getStatusCode() == 200) {
            // 4. レスポンスボディ (JSON) を解析する
            // JSON.deserializeUntyped を使用して、Map<String, Object> に変換
            Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
            
            // 'animal' キーでネストされた動物の情報を取得
            Map<String, Object> animal = (Map<String, Object>) results.get('animal');
            
            // 'name' キーの値(動物の名前)を文字列として返す
            return (String) animal.get('name');
        }
        
        // 成功しなかった場合は null を返す
        return null;
    }
}

Apex Callout のテストクラス

Apex Callout を含むコードをデプロイするには、テストカバレッジが必須です。しかし、テスト実行中に実際に外部 API を呼び出すことはできません。そこで、Salesforce は HttpCalloutMock インターフェースを提供しています。これを使用して、擬似的なレスポンスを返すモッククラスを作成します。

@isTest
global class AnimalLocatorMock implements HttpCalloutMock {
    // respond メソッドを実装し、擬似レスポンスを返す
    global HttpResponse respond(HttpRequest request) {
        // 1. HttpResponse オブジェクトを作成
        HttpResponse response = new HttpResponse();
        
        // ヘッダーを設定
        response.setHeader('Content-Type', 'application/json');
        
        // 擬似的なレスポンスボディ (JSON) を設定
        response.setBody('{"animal":{"id":1,"name":"chicken"}}');
        
        // 擬似的なステータスコードを設定
        response.setStatusCode(200);
        
        // 2. HttpResponse を返す
        return response;
    }
}

そして、このモッククラスを使ってテストメソッドを記述します。

@isTest
private class AnimalLocatorTest {
    @isTest
    static void testGetAnimalNameById() {
        // 1. モックを設定
        // Test.setMock を使用して、HttpCalloutMock.class に AnimalLocatorMock を割り当てる
        Test.setMock(HttpCalloutMock.class, new AnimalLocatorMock());

        // Test.startTest() と Test.stopTest() の間でテスト対象のコードを実行する
        Test.startTest();
        
        // 2. テスト対象のメソッドを呼び出す
        String result = AnimalLocator.getAnimalNameById(1);
        
        Test.stopTest();

        // 3. 結果を検証
        // モックが返した 'chicken' という名前と一致するかどうかをアサーションで確認
        System.assertEquals('chicken', result, 'The animal name should be "chicken"');
    }
}

注意事項

Apex Callout を実装する際には、プラットフォームの制約やセキュリティに関するいくつかの重要な点に注意する必要があります。

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

Apex コードが外部サイトにコールアウトを行うには、そのエンドポイント URL を Salesforce の Remote Site Settings (リモートサイト設定) に登録する必要があります。これはセキュリティ上の措置であり、登録されていない URL へのコールアウトはブロックされます。
設定 > セキュリティ > リモートサイトの設定 から、コールアウト先のドメイン (例: `https://th-apex-http-callout.herokuapp.com`) を登録してください。

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

より推奨される方法は、Named Credentials (指定ログイン情報) を使用することです。Named Credentials は、エンドポイント URL と認証情報をコードから分離し、宣言的に管理できるようにする機能です。これにより、コードを変更することなく本番環境と Sandbox 環境でエンドポイントを切り替えたり、認証情報を安全に管理したりできます。コード内でエンドポイント URL をハードコーディングする代わりに、指定ログイン情報の URL を参照します (例: `callout:My_Named_Credential`)。セキュリティと保守性の観点から、可能な限り Named Credentials を使用すべきです。

// Named Credential を使用した場合の HttpRequest の設定例
HttpRequest request = new HttpRequest();
// URL をハードコーディングする代わりに、指定ログイン情報を参照する
// これにより、実際の URL と認証情報は Salesforce の設定画面で管理される
request.setEndpoint('callout:My_Animal_Service/animals/1'); 
request.setMethod('GET');

3. Governor Limits (ガバナ制限)

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

  • 1 トランザクションあたりのコールアウト数: 100 回まで
  • 1 トランザクションあたりのコールアウトの累積タイムアウト: 120 秒まで
  • コールアウトの最大タイムアウト時間: 120 秒まで (req.setTimeout(120000); で設定可能)

特にループ内でコールアウトを行うと、簡単に制限に達してしまう可能性があります。外部 API が一括処理をサポートしている場合は、複数のリクエストを1つにまとめる(バルク化する)設計を検討してください。

4. エラー処理

外部システムは常に正常に応答するとは限りません。ネットワークの問題、サーバーダウン、API の仕様変更など、さまざまな理由でコールアウトは失敗する可能性があります。堅牢なアプリケーションを構築するためには、徹底したエラー処理が不可欠です。

  • ステータスコードの確認: response.getStatusCode() の値を確認し、2xx 系の成功コード以外の場合の処理を実装します (例: 404 Not Found, 500 Internal Server Error)。
  • try-catch ブロック: コールアウト処理全体を try-catch ブロックで囲み、System.CalloutException などの予期せぬ例外を捕捉します。タイムアウトや DNS 解決の失敗などがこの例外を引き起こします。
  • リトライ処理: 一時的なネットワークエラーなどを考慮し、Queueable Apex などを使って一定時間後に再試行するロジックを組み込むことも有効です。

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

Apex Callout は、Salesforce を外部の世界と繋ぎ、その価値を飛躍的に高めるための強力なツールです。開発者として、その仕組みと制約を深く理解し、ベストプラクティスに従って実装することが重要です。

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

  1. Named Credentials を最優先で利用する:

    コードからエンドポイントと認証情報を分離し、セキュリティと管理性を向上させます。Remote Site Settings は、認証が不要なシンプルな GET リクエストなどに限定して利用を検討します。

  2. コールアウトロジックを分離する:

    トリガハンドラやビジネスクラスからコールアウトのロジックを分離し、専用のサービスクラスに実装します。これにより、コードの再利用性が高まり、テストも容易になります。

  3. 非同期処理を適切に選択する:

    要件に応じて @future, Queueable Apex, Batch Apex を使い分けます。単純な非同期化であれば @future、処理の追跡や連鎖が必要な場合は Queueable Apex が適しています。

  4. バルク化を常に意識する:

    複数のレコードに対してコールアウトが必要な場合、1レコードごとにコールアウトするのではなく、可能な限りリクエストをまとめてガバナ制限を回避します。

  5. 堅牢なエラーハンドリングとログ記録を実装する:

    失敗は起こるものと想定し、すべてのコールアウトに try-catch ブロックとステータスコードのチェックを実装します。失敗した際には、カスタムオブジェクトやプラットフォームイベントにログを記録し、後から追跡できるようにします。

  6. HttpCalloutMock を活用した網羅的なテストを行う:

    成功ケースだけでなく、さまざまなエラーステータスコード(404, 500 など)やタイムアウトをシミュレートするモックを作成し、コードが予期せぬレスポンスに正しく対処できることを確認します。

これらの原則を守ることで、あなたは信頼性が高く、スケーラブルで、保守しやすいインテグレーションを構築できる Salesforce 開発者となることができるでしょう。

コメント