Salesforce Composite API 徹底解説:複数リクエストをまとめて効率化するインテグレーション手法

背景と適用シナリオ

Salesforce 統合エンジニアとして、私は日々、Salesforce と外部システムを連携させるための最適なソリューションを模索しています。多くのインテグレーションシナリオで直面する共通の課題の一つに、「API コールの非効率性」があります。例えば、取引先(Account)を作成し、その直後にその取引先に関連する複数の取引先責任者(Contact)を登録する、といった一連の処理を考えてみましょう。

従来のアプローチでは、まず取引先を作成するために REST API を1回コールし、成功レスポンスから新しい取引先の ID を取得します。次に、その ID を使って取引先責任者を作成するための API コールを、人数分だけ繰り返し実行する必要がありました。この方法は、以下のような問題点を抱えています。

  • ネットワーク遅延 (Network Latency): サーバーとクライアント間の通信(ラウンドトリップ)が何度も発生するため、全体の処理時間が長くなります。
  • API 制限の消費: Salesforce には組織ごとに24時間あたりの API コール数に上限があります。個別のコールを多用すると、この貴重なリソースを急速に消費してしまいます。
  • トランザクション管理の複雑さ: もし途中の処理でエラーが発生した場合(例えば、3人目の取引先責任者の作成に失敗した場合)、それまでに作成したレコードをすべて手動でロールバックするような複雑なエラー処理ロジックをクライアント側で実装する必要がありました。

こうした課題を解決するために Salesforce が提供しているのが、Composite API (コンポジット API) です。Composite API は、複数の独立した REST API リクエストを一つの HTTP リクエストにまとめ、単一のコールとして実行することを可能にします。これにより、ネットワークのオーバーヘッドを大幅に削減し、API 制限の消費を抑え、さらに一連の処理を単一のトランザクションとして扱うことができるようになります。

具体的な適用シナリオとしては、以下のようなケースが考えられます。

  • 親子関係レコードの同時作成: 新規の商談(Opportunity)と、それに関連する商品(OpportunityLineItem)を一度に作成する。
  • 複雑なデータ更新: あるレコードを更新し、その結果に基づいて別のレコードを更新、さらに別のレコードを削除する、といった一連の依存関係のある処理をまとめて実行する。
  • UI のパフォーマンス向上: ユーザーが画面上で複数の関連情報を一度に保存する操作を行った際に、バックエンドで複数の SObject を更新する必要がある場合、Composite API を使うことでレスポンスを高速化し、ユーザーエクスペリエンスを向上させる。

原理説明

Composite API の中心的な概念は、「サブリクエスト (subrequest)」と「単一トランザクション」です。クライアントは、実行したい一連の API コールを JSON 形式の配列として定義し、それを単一の POST リクエストのボディに含めて /services/data/vXX.X/composite エンドポイントに送信します。

Salesforce サーバーはこのリクエストを受け取ると、JSON 配列内のサブリクエストを定義された順序で一つずつ実行します。この一連の処理は、デフォルトで単一のトランザクションとして扱われます。

主要な構成要素

Composite API のリクエストボディは、主に2つの重要なプロパティで構成されます。

1. allOrNone (オールオアナン)
これは、トランザクションの挙動を制御する boolean 型のフラグです。

  • true (デフォルト): すべてのサブリクエストが成功した場合にのみ、全体のトランザクションがコミットされます。もし一つでもサブリクエストが失敗すると、それまでに行われたすべてのデータベース操作がロールバックされます。これにより、データの一貫性と原子性 (atomicity) が保証されます。
  • false: 各サブリクエストは独立した処理単位と見なされます。一部のサブリクエストが失敗しても、成功した他のサブリクエストの結果はコミットされます。エラーが発生したサブリクエストについては、レスポンス内で個別にエラー情報が返されます。

2. compositeRequest
これは、実行したい個々の API リクエスト(サブリクエスト)をオブジェクトの配列として格納する場所です。各サブリクエストオブジェクトには、以下のプロパティが含まれます。

  • method: HTTP メソッド(例: "POST", "PATCH", "GET", "DELETE")。
  • url: Salesforce REST API のエンドポイント URL(例: /services/data/v60.0/sobjects/Account)。バージョン指定子を含む必要があります。
  • referenceId (参照 ID): このサブリクエストを一意に識別するための ID です。非常に重要なプロパティで、後続のサブリクエストが、このリクエストの結果(例えば、新規作成されたレコードの ID)を参照するために使用します。
  • body: POST や PATCH リクエストの場合に、送信するデータ(レコード情報など)を JSON 形式で指定します。

サブリクエスト間のデータ参照

Composite API の最も強力な機能の一つが、referenceId を利用したサブリクエスト間の動的なデータ参照です。例えば、最初のサブリクエストで新しい取引先を作成し、その取引先の ID を、二番目のサブリクエストで作成する取引先責任者の AccountId 項目に設定したい場合を考えます。

最初のサブリクエストに "referenceId": "NewAccount" と設定しておけば、後続のサブリクエストの body 内で "AccountId": "@{NewAccount.id}" という構文を使用できます。@{} という記法により、Salesforce は "NewAccount" という referenceId を持つサブリクエストのレスポンスボディから id プロパティの値を取得し、動的に埋め込んでくれます。これにより、クライアント側で ID を取得して次のリクエストを作成するという手間が不要になります。


示例代码

ここでは、Salesforce の公式ドキュメントに記載されている典型的な例として、「新しい取引先 (Account) を作成し、続けてその取引先に関連付けられた新しい取引先責任者 (Contact) を作成する」処理を Composite API で実行するコードを示します。

リクエスト (Request)

以下は、クライアントが Salesforce に送信する HTTP POST リクエストのボディです。エンドポイントは /services/data/v60.0/composite となります。

{
  "allOrNone" : true,
  "compositeRequest" : [{
    "method" : "POST",
    "url" : "/services/data/v60.0/sobjects/Account",
    "referenceId" : "NewAccount",
    "body" : {
      "Name" : "Salesforce"
    }
  },{
    "method" : "POST",
    "url" : "/services/data/v60.0/sobjects/Contact",
    "referenceId" : "NewContact",
    "body" : {
      "LastName" : "Vader",
      "AccountId" : "@{NewAccount.id}"
    }
  }]
}

コードの解説:

  • "allOrNone": true: このリクエストはアトミックなトランザクションとして扱われます。取引先の作成か取引先責任者の作成のどちらかが失敗した場合、両方の操作が取り消されます。
  • 最初のサブリクエスト:
    • "method": "POST", "url": ".../sobjects/Account": 新しい取引先レコードを作成することを指定しています。
    • "referenceId": "NewAccount": このサブリクエストに「NewAccount」という一意の参照IDを割り当てています。
    • "body": { "Name": "Salesforce" }: 作成する取引先の名前を「Salesforce」に設定しています。
  • 二番目のサブリクエスト:
    • "method": "POST", "url": ".../sobjects/Contact": 新しい取引先責任者レコードを作成することを指定しています。
    • "referenceId": "NewContact": このサブリクエストに「NewContact」という参照IDを割り当てています。
    • "body": { ... }: 作成する取引先責任者の詳細です。
      • "LastName": "Vader": 姓を設定しています。
      • "AccountId": "@{NewAccount.id}": ここが最も重要な部分です。@{} 構文を使い、referenceId が "NewAccount" である最初のサブリクエストの結果から、作成されたレコードの id を取得し、この取引先責任者の AccountId 項目に設定しています。

レスポンス (Response)

上記のリクエストが成功した場合、Salesforce からは以下のような JSON レスポンスが返されます。

{
  "compositeResponse" : [{
      "body" : {
        "id" : "001xx000003DHP0AAO",
        "success" : true,
        "errors" : [ ]
      },
      "httpHeaders" : {
        "Location" : "/services/data/v60.0/sobjects/Account/001xx000003DHP0AAO"
      },
      "httpStatusCode" : 201,
      "referenceId" : "NewAccount"
    },{
      "body" : {
        "id" : "003xx000004Wpr0AAC",
        "success" : true,
        "errors" : [ ]
      },
      "httpHeaders" : {
        "Location" : "/services/data/v60.0/sobjects/Contact/003xx000004Wpr0AAC"
      },
      "httpStatusCode" : 201,
      "referenceId" : "NewContact"
  }]
}

レスポンスの解説:

  • compositeResponse: レスポンスも、リクエストと同様にサブリクエストごとの結果が配列で返されます。配列の順序はリクエスト時の順序と一致します。
  • 各要素:
    • body: 個々のサブリクエストのレスポンスボディです。成功時には作成されたレコードの ID などが含まれます。
    • httpStatusCode: HTTP ステータスコードです。成功した場合、作成(POST)なら 201、更新(PATCH)なら 204 などが返されます。
    • referenceId: 対応するリクエストの referenceId がそのまま返されるため、どのリクエストに対するレスポンスなのかを簡単に特定できます。

注意事項

Composite API は非常に強力ですが、利用する際にはいくつかの重要な点に注意する必要があります。

権限 (Permissions)

API を実行するユーザーは、サブリクエストに含まれるすべての操作(オブジェクトの作成、参照、更新、削除)を実行するために必要な権限(プロファイルや権限セットによるオブジェクト権限、項目レベルセキュリティなど)を持っている必要があります。権限が不足しているサブリクエストは失敗し、allOrNonetrue の場合はトランザクション全体がロールバックされます。

API 制限 (API Limits)

Composite API を使うと HTTP リクエストの数は1回に集約できますが、各サブリクエストは個別の API コールとしてカウントされます。例えば、5つのサブリクエストを含む Composite API コールは、Salesforce の24時間あたりの API コール制限に対して5回分として消費されます。この点は誤解されやすいため、注意が必要です。

また、単一の Composite API リクエストに含めることができるサブリクエストの数には上限があります。現在の制限では、最大25個のサブリクエストを含めることができます。これを超える数の操作を一度に行いたい場合は、リクエストを分割するか、Bulk API などの別の手段を検討する必要があります。

ガバナ制限 (Governor Limits)

Composite API のリクエスト全体は、単一の Apex トランザクションとして処理されます。これは、一連のサブリクエスト全体で、DML ステートメントの総数、発行される SOQL クエリの総数、CPU 時間などの Apex ガバナ制限を共有することを意味します。例えば、各サブリクエストがトリガーを起動し、そのトリガーが複雑な処理を行う場合、ガバナ制限に抵触する可能性があります。設計時には、トランザクション全体で発生する処理の総量を考慮することが重要です。

エラー処理 (Error Handling)

エラーハンドリングは、インテグレーションの堅牢性を確保する上で不可欠です。

  • allOrNone: true の場合: 一つでもエラーが発生すると、HTTP ステータスコード 400 Bad Request が返され、レスポンスボディには失敗したサブリクエストのエラー詳細が含まれます。トランザクション全体がロールバックされるため、データは不整合な状態にはなりません。
  • allOrNone: false の場合: HTTP ステータスコードは 200 OK が返されます。ただし、これはリクエスト全体が正常に処理されたことを意味するわけではありません。クライアントは、レスポンスボディの compositeResponse 配列をループ処理し、各要素の httpStatusCode を確認して、個々のサブリクエストの成否を判断する必要があります。失敗したサブリクエストの body には、エラーコードとメッセージが含まれます。


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

Salesforce Composite API は、関連する複数の API 操作を単一のコールにまとめることで、インテグレーションのパフォーマンスと効率を劇的に向上させる強力なツールです。ネットワーク遅延の削減、API 制限の効率的な利用、そしてトランザクション管理の簡素化といった多くのメリットを提供します。

ベストプラクティス

  1. 適切なユースケースを選択する:

    相互に依存関係がある一連の操作(例: 親レコードを作成し、その ID を子レコードで使用する)や、論理的に一つのまとまりとして扱うべき操作に Composite API を使用します。大量の独立したレコード(例: 10,000件の取引先責任者のインポート)を処理する場合は、Bulk API の方が適しています。

  2. allOrNone を賢く使う:

    データの一貫性が最優先される場合は、必ず allOrNone: true を使用して、アトミックなトランザクションを保証します。一部の失敗を許容し、成功した処理はそのままコミットしたい場合にのみ allOrNone: false を検討します。

  3. 堅牢なエラー処理を実装する:

    レスポンスを正しく解析し、各サブリクエストの成否を確認するロジックをクライアント側に必ず実装してください。特に allOrNone: false の場合は、個々の結果を慎重に処理する必要があります。

  4. 制限を意識した設計を行う:

    サブリクエストの最大数(25個)と、各サブリクエストが API コールとしてカウントされることを念頭に置いて設計します。また、ガバナ制限に抵触しないよう、トランザクション全体の処理負荷を考慮してください。

  5. バージョン管理を徹底する:

    サブリクエストの URL には必ず API バージョン(例: v60.0)を含めます。これにより、将来の Salesforce のバージョンアップによる予期せぬ動作の変更からインテグレーションを保護できます。

Salesforce 統合エンジニアとして、Composite API を適切に活用することで、より効率的で、信頼性が高く、スケーラブルな連携ソリューションを構築することが可能になります。

コメント