Salesforce Apex Callout 完全ガイド:連携エンジニアのための外部システム連携実践術

執筆者:Salesforce 連携エンジニア


背景と応用シーン

現代のビジネス環境において、Salesforce はもはや単独の CRM プラットフォームとして機能するだけではありません。ERP、マーケティングオートメーション、データウェアハウス、独自開発のマイクロサービスなど、多種多様な外部システムと連携することで、その価値を最大限に発揮します。私たち Salesforce 連携エンジニア (Integration Engineer) の重要な使命の一つは、これらのシステム間のデータフローをシームレスかつ堅牢に設計・実装することです。

その連携を実現するための強力なツールが Apex Callouts です。Apex Callouts とは、Salesforce の Apex コードから外部のウェブサービス(Web Service)や API を呼び出すための仕組みです。これにより、Salesforce プラットフォーム内から直接、外部システムに対してデータの要求(Request)や送信を行うことが可能になります。

具体的な応用シーン:

  • リアルタイムデータ取得:外部の金融APIにアクセスし、商談レコードに最新の株価情報を表示する。
  • データ同期:Salesforce で新規注文が作成された際、リアルタイムで外部の在庫管理・ERPシステムに注文情報を送信する。
  • データ検証・補強:住所クレンジングサービスを呼び出し、取引先責任者の住所情報の正確性を検証し、郵便番号などを自動補完する。
  • 外部サービスのビジネスロジック実行:複雑な計算や独自のビジネスロジックを持つ外部のマイクロサービスを呼び出し、その結果を Salesforce のレコード更新に利用する。

このように、Apex Callouts は Salesforce の機能を拡張し、ビジネスプロセスを自動化するための根幹をなす技術です。本記事では、連携エンジニアの視点から、Apex Callouts の原理、実践的なコード例、そして本番環境で安定稼働させるための注意事項やベストプラクティスを詳細に解説します。


原理説明

Apex Callout の中核は、Apex が HTTP プロトコルを使用して外部のエンドポイントと通信する点にあります。このプロセスは、主に Salesforce が提供する組み込みの Apex HTTP クラス (`Http`、`HttpRequest`、`HttpResponse`) を使用して実現されます。

基本的な処理フローは以下の通りです。

  1. HttpRequest の作成:まず、`HttpRequest` オブジェクトをインスタンス化します。これは、送信するリクエストそのものを表します。
  2. リクエストパラメータの設定:作成した `HttpRequest` オブジェクトに、通信に必要な情報を設定します。
    • `setEndpoint(url)`: 呼び出す外部 API の URL を指定します。
    • `setMethod(method)`: HTTP メソッド('GET', 'POST', 'PUT', 'DELETE' など)を指定します。
    • `setHeader(key, value)`: HTTP ヘッダー(認証情報や Content-Type など)を設定します。
    • `setBody(body)`: POST や PUT リクエストの場合、送信するデータ(通常は JSON や XML 形式)を設定します。
    • `setTimeout(milliseconds)`: タイムアウト時間を設定します。
  3. Http オブジェクトによるリクエスト送信:`Http` オブジェクトをインスタンス化し、その `send(request)` メソッドを実行して、設定済みの `HttpRequest` を送信します。
  4. HttpResponse の受信と処理:`send()` メソッドは `HttpResponse` オブジェクトを返します。このオブジェクトには、外部サーバーからのレスポンス情報が含まれています。
    • `getStatusCode()`: HTTP ステータスコード(200 は成功、404 は Not Found など)を取得します。
    • `getBody()`: レスポンスボディ(通常は JSON や XML 形式のデータ)を取得します。
    • `getHeader(key)`: 特定のレスポンスヘッダーを取得します。
  5. レスポンスの解析:取得したレスポンスボディ(多くは JSON 文字列)を `JSON.deserialize()` などのメソッドを使って Apex オブジェクトに変換し、ビジネスロジックで利用します。

重要な点として、トリガーなどの同期的なコンテキストから直接 Apex Callout を実行することはできません。これは、外部システムの応答遅延がユーザーの操作性を損なうことを防ぐための Salesforce の制約です。同期コンテキストから Callout を行う必要がある場合は、処理を非同期にする必要があります。そのための代表的な方法が、メソッドに `@future` アノテーションを付与することです。これにより、Callout 処理はバックグラウンドで非同期に実行され、ユーザーのトランザクションをブロックしません。


サンプルコード

ここでは、Salesforce の公式ドキュメントでよく紹介される、動物の名前を返す外部サービスを呼び出す簡単な GET リクエストの例を見ていきましょう。このコードは Callout の基本構造を理解するのに非常に役立ちます。

Apex クラス: AnimalLocator

このクラスは、外部 API へ実際に HTTP GET リクエストを送信し、レスポンスを解析するロジックを含みます。

public class AnimalLocator {
    public static String getAnimalNameById(Integer id) {
        // 1. HttpRequest オブジェクトを作成
        HttpRequest req = new HttpRequest();

        // 2. エンドポイントURLを設定。ここでは、サンプル用の外部サービスを指定
        req.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals/' + id);

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

        // 4. Http オブジェクトを作成
        Http http = new Http();

        // 5. リクエストを送信し、HttpResponse を受け取る
        // send() メソッドが実際のコールアウトを実行する
        HttpResponse res = http.send(req);

        // 6. レスポンスのステータスコードをチェック
        // 200 は成功(OK)を示す
        if (res.getStatusCode() == 200) {
            // 7. レスポンスボディ(JSON)を Map にデシリアライズ
            Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
            
            // 8. animal キーでネストされた Map を取得
            Map<String, Object> animal = (Map<String, Object>) results.get('animal');
            
            // 9. name キーの値(動物の名前)を文字列として返す
            return (String) animal.get('name');
        }
        
        // 成功しなかった場合は null を返す
        return null;
    }
}

Apex テストクラス: AnimalLocatorTest

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

@IsTest
private class AnimalLocatorTest {
    @IsTest
    static void testGetAnimalNameById() {
        // 1. 擬似的なレスポンスを定義するモッククラスのインスタンスを作成
        AnimalLocatorMock mock = new AnimalLocatorMock();
        
        // 2. Salesforce に、このテスト実行中は実際のコールアウトの代わりに
        //    指定したモッククラスを使用するように指示
        Test.setMock(HttpCalloutMock.class, mock);
        
        // 3. テスト対象のメソッドを呼び出す
        String result = AnimalLocator.getAnimalNameById(1);
        
        // 4. モックが返すように設定した期待値と実際の結果を比較検証
        System.assertEquals('chicken', result);
    }
}

HttpCalloutMock 実装クラス: AnimalLocatorMock

これが `HttpCalloutMock` を実装したクラスです。`respond` メソッド内で、テスト用の擬似的な `HttpResponse` を作成して返します。

@IsTest
global class AnimalLocatorMock implements HttpCalloutMock {
    // HttpCalloutMock インターフェースの respond メソッドを実装
    global HTTPResponse respond(HTTPRequest req) {
        // 1. 新しい HttpResponse オブジェクトを作成
        HttpResponse res = new HttpResponse();
        
        // 2. 擬似的なレスポンスヘッダーを設定
        res.setHeader('Content-Type', 'application/json');
        
        // 3. 擬似的なレスポンスボディ(JSON文字列)を設定
        // この JSON は、実際の API が返す形式を模倣している
        res.setBody('{"animal":{"id":1,"name":"chicken","eats":"insects","says":"cluck"}}');
        
        // 4. 擬似的なステータスコードを設定 (200 = 成功)
        res.setStatusCode(200);
        
        // 5. 作成した擬似レスポンスを返す
        return res;
    }
}

注意事項

連携エンジニアとして Apex Callout を実装する際には、機能要件を満たすだけでなく、プラットフォームの制約、セキュリティ、エラー耐性を考慮することが極めて重要です。

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

Apex コードから外部 URL へ Callout を行うには、その URL を事前に Salesforce の「リモートサイトの設定」に登録する必要があります。これを怠ると、`System.CalloutException: Unauthorized endpoint` というエラーが発生します。これは Salesforce のセキュリティ機能の一つで、意図しない外部サイトへの通信を防ぐためのものです。

2. ガバナ制限 (Governor Limits)

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

  • トランザクションあたりのコールアウト数:同期・非同期ともに、1 トランザクション内で実行できるコールアウトは最大 100 回です。
  • コールアウトのタイムアウト:1回のコールアウトの最大タイムアウトは 120 秒です。
  • 合計リクエストサイズとレスポンスサイズ:ヒープサイズなどの制限も間接的に影響します。

ループ内でコールアウトを実行すると、容易にこの制限に抵触します。複数のレコードに対して処理が必要な場合は、外部 API が一括処理をサポートしているかを確認し、可能な限りリクエストをまとめてコールアウト回数を削減する「バルク化」の設計が不可欠です。

3. 堅牢なエラー処理 (Error Handling)

外部システムとの連携は、ネットワークの問題、相手先サーバーのダウン、API仕様の変更など、様々な不確定要素を伴います。

  • 例外処理:Callout のコードは必ず `try-catch` ブロックで囲み、`System.CalloutException` などの例外を捕捉できるようにします。
  • ステータスコードの確認:`HttpResponse` を受け取ったら、必ず `getStatusCode()` を確認します。`200` 番台以外のコード(`4xx` クライアントエラー、`5xx` サーバーエラー)が返された場合の処理分岐を明確に実装する必要があります。安易に `200` の場合のみを想定した実装は、本番環境での障害に繋がります。
  • 再試行ロジック:一時的なネットワークエラー(`503 Service Unavailable` など)に備え、Queueable Apex を使用して一定間隔で再試行するロジックを組み込むことも有効な戦略です。
  • ロギング:Callout の失敗時には、リクエスト内容、レスポンスコード、レスポンスボディなどをカスタムオブジェクトやプラットフォームイベントに記録し、後から追跡・デバッグできるように設計することが重要です。

4. 認証とセキュリティ (Authentication and Security)

API キーやパスワードなどの認証情報を Apex コード内に直接ハードコーディングすることは、絶対に避けるべきです。代わりに Salesforce の 指定ログイン情報 (Named Credentials) を使用することを強く推奨します。指定ログイン情報を使うことで、以下のメリットが得られます。

  • エンドポイント URL と認証情報をコードから分離できるため、環境間(Sandbox、本番)での設定変更が容易になります。
  • パスワードなどの機密情報がセキュアに管理されます。
  • リモートサイト設定が不要になります。
  • 認証処理(OAuth 2.0 など)を Salesforce プラットフォームに任せることができ、コードが簡潔になります。


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

Apex Callouts は、Salesforce を企業の IT エコシステムのハブとして機能させるための不可欠な技術です。連携エンジニアは、単に動くコードを書くだけでなく、パフォーマンス、スケーラビリティ、セキュリティ、そして保守性を考慮した実装を心がける必要があります。

ベストプラクティス一覧:

  1. 指定ログイン情報を常に利用する:セキュリティと保守性の観点から、エンドポイントと認証情報の管理には指定ログイン情報(Named Credentials)を第一選択とします。
  2. コールアウトは非同期に実行する:ユーザーの操作をブロックせず、ガバナ制限を緩和するために、`@future`, Queueable Apex, Batch Apex を活用してコールアウトを非同期処理とします。特に、ユーザーインターフェースからのアクションを起点とする場合は必須です。
  3. バルク化を意識した設計を:複数のレコードに対するコールアウトが必要な場合は、1レコードごとにリクエストを送信するのではなく、可能な限り1回のリクエストにまとめて処理効率を高めます。
  4. 網羅的なエラーハンドリングとロギングを実装する:`try-catch`、HTTP ステータスコードのチェック、失敗時の詳細なログ記録は、安定した連携を維持するための生命線です。
  5. `HttpCalloutMock` でテストを徹底する:外部システムの状態に依存しない、再現性の高いテストを `HttpCalloutMock` を用いて実装し、正常系・異常系両方のシナリオをカバーします。これにより、コードの信頼性が大幅に向上します。
  6. タイムアウト値を適切に設定する:外部システムの応答性能を考慮し、`HttpRequest.setTimeout()` を使用して適切なタイムアウト値を設定します。これにより、長時間にわたる応答待ちでサーバーリソースを無駄に消費することを防ぎます。

これらの原則に従うことで、私たちは単なる「繋がる」連携ではなく、ビジネスの変化に強く、障害発生時にも迅速に対応できる「信頼性の高い」連携システムを構築することができるのです。

コメント