Salesforce External Services: ローコード連携を強化するアーキテクチャ設計

背景と応用シーン

現代のビジネス環境において、企業は様々なシステム間でデータを連携し、効率的な業務プロセスを構築する必要があります。Salesforceは強力なCRMプラットフォームですが、多くの場合、外部の基幹システム、ERP(Enterprise Resource Planning)システム、マーケティングオートメーションツール、金融サービスなどとの連携が不可欠です。従来のSalesforceにおける外部システム連携は、Apexコードによる開発が主流でした。しかし、これは開発コスト、メンテナンス性、そして開発者のスキルセットに対する依存度が高いという課題を抱えていました。

ここでSalesforceのExternal Services(外部サービス)が登場します。External Servicesは、OpenAPI(旧Swagger)仕様書をインポートすることで、外部のRESTful APIをSalesforce Flow(フロー)やApexからローコードまたはノーコードで呼び出せるようにする機能です。Salesforceアーキテクトの視点から見ると、この機能は連携戦略に革命をもたらします。

External Servicesは、以下のような多様な応用シーンでその真価を発揮します。

  • 信用調査サービスとの連携:営業担当者が顧客の信用情報をリアルタイムで取得し、商談プロセス中に迅速な意思決定を行う。
  • ERPシステムとの在庫確認・受注ステータス更新:顧客からの注文があった際に、Flowを通じてERPの在庫状況を確認し、必要に応じて注文を送信したり、ステータスを更新したりする。
  • レガシーシステムからのデータ取得:既存のレガシーシステムが公開しているREST APIを介して、Salesforce内で必要な情報を参照する。
  • サードパーティマーケティングプラットフォームとのデータ同期:Salesforceで更新されたリードや顧客情報を、Flowを使って自動的にマーケティングプラットフォームに同期する。
  • 複雑なビジネスプロセスのオーケストレーション:複数の外部サービスとSalesforceの内部処理を組み合わせ、一連の複雑なビジネスプロセスをFlowで構築する。

アーキテクトとして、External Servicesを活用することで、開発のアジリティ(俊敏性)を高め、メンテナンス性(保守性)を向上させ、そしてSalesforce管理者やビジネスアナリストがより多くの連携タスクを宣言的に処理できるよう、ローコード開発の可能性を広げることができます。これにより、Apex開発リソースへの依存を減らし、より戦略的な開発に注力することが可能になります。

原理説明

External Servicesの核となる原理は、外部APIの「契約」をOpenAPI仕様書(OpenAPI Specification)としてSalesforceに登録し、その契約に基づいてSalesforce内で呼び出し可能なアクションを自動生成することにあります。このプロセスにより、外部APIの詳細な実装を知らなくても、その機能をFlowやApexから利用できるようになります。

主要コンポーネントと動作原理

External Servicesの動作は、以下の主要コンポーネントによって支えられています。

  • OpenAPI仕様書:外部APIのエンドポイント、HTTPメソッド(GET、POSTなど)、要求パラメータ、応答形式、認証方法などをJSONまたはYAML形式で記述したドキュメントです。External Servicesはこの仕様書を「設計図」として利用します。SalesforceはOpenAPI 2.0および3.0をサポートしています。
  • Named Credentials(名前付き資格情報):外部サービスのエンドポイントURLと認証情報を安全に保存する機能です。これにより、開発者は本番環境やサンドボックス環境ごとに異なる認証情報をコードにハードコーディングすることなく、一元的に管理できます。セキュリティと環境間の移行の容易性において不可欠なコンポーネントです。アーキテクトは、組織のセキュリティポリシーと連携先の認証方式(OAuth 2.0、APIキーなど)に基づいて、適切なNamed Credentialの設定を設計します。
  • External Services Registration(外部サービス登録):OpenAPI仕様書とNamed CredentialをSalesforceに登録するプロセスです。この登録が完了すると、Salesforceは仕様書に基づいて、Flow Builderで使用可能なInvocable Actions(呼び出し可能アクション)を自動的に生成します。また、Apex開発者向けには、対応するApexクラスが自動生成され、型安全な方法で外部サービスを呼び出すことが可能になります。
  • Invocable Actions(呼び出し可能アクション):Flow Builderの要素として利用できる、自動生成されたアクションです。これにより、管理者はドラッグ&ドロップ操作で外部サービスをFlowに組み込むことができます。入力パラメータはFlow変数から提供され、応答データはFlow変数に格納され、後続の処理で利用されます。

アーキテクトの視点から見ると、External Servicesは以下の点で大きなメリットを提供します。

  • 保守性:外部APIのインターフェースに変更があった場合、OpenAPI仕様書を更新し再登録するだけで、FlowやApexコードの変更を最小限に抑えることができます(APIの根本的な変更を除く)。これにより、システム全体の保守コストを削減できます。
  • セキュリティ:Named Credentialsによって認証情報がセキュアに管理され、機密情報がコード内に露出するリスクを低減します。また、Salesforceの権限セットやプロファイルを通じて、External Servicesの利用を制御することも可能です。
  • 拡張性:Flowを活用することで、複雑なビジネスロジックを宣言的にオーケストレーションできます。これにより、特定のビジネスニーズに合わせて迅速に連携プロセスを拡張できます。ガバナ制限についても、外部サービス呼び出しはHTTPコールアウト制限の範囲内で管理され、通常のApexロジックとは異なる考慮が必要です。
  • 俊敏性:ローコードのアプローチは、ビジネス要件の変化に対する開発サイクルの短縮を可能にし、市場投入までの時間を短縮します。


示例コード

External Servicesの登録自体は、SalesforceのUIからOpenAPI仕様書をアップロードする宣言的なプロセスですが、その根幹にはOpenAPI仕様書の定義と、必要に応じてApexからの呼び出しが可能です。ここでは、架空の製品情報取得サービスを想定したOpenAPI 2.0仕様書と、それをApexから呼び出す例を示します。

OpenAPI 2.0 仕様書の例

以下のJSONは、製品IDに基づいて製品の詳細情報を取得するシンプルなREST APIの仕様書です。

{
    "swagger": "2.0",
    "info": {
        "title": "ProductService",
        "version": "1.0.0",
        "description": "製品情報を取得するためのAPIサービスです。"
    },
    "host": "api.example.com",
    "basePath": "/products/v1",
    "schemes": [
        "https"
    ],
    "paths": {
        "/items/{productId}": {
            "get": {
                "summary": "製品IDで製品詳細を取得",
                "operationId": "getProductDetails",
                "parameters": [
                    {
                        "name": "productId",
                        "in": "path",
                        "required": true,
                        "type": "string",
                        "description": "取得したい製品のID"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "製品詳細データ",
                        "schema": {
                            "$ref": "#/definitions/Product"
                        }
                    },
                    "404": {
                        "description": "指定された製品が見つかりません"
                    },
                    "500": {
                        "description": "サーバー内部エラー"
                    }
                }
            }
        },
        "/items": {
            "post": {
                "summary": "新しい製品を作成",
                "operationId": "createProduct",
                "parameters": [
                    {
                        "name": "product",
                        "in": "body",
                        "required": true,
                        "schema": {
                            "$ref": "#/definitions/ProductInput"
                        }
                    }
                ],
                "responses": {
                    "201": {
                        "description": "製品が正常に作成されました",
                        "schema": {
                            "$ref": "#/definitions/Product"
                        }
                    },
                    "400": {
                        "description": "無効なリクエストデータ"
                    }
                }
            }
        }
    },
    "definitions": {
        "Product": {
            "type": "object",
            "properties": {
                "id": {
                    "type": "string",
                    "description": "製品の一意なID"
                },
                "name": {
                    "type": "string",
                    "description": "製品名"
                },
                "price": {
                    "type": "number",
                    "format": "float",
                    "description": "製品の価格"
                },
                "description": {
                    "type": "string",
                    "description": "製品の詳細な説明"
                }
            },
            "required": ["id", "name", "price"]
        },
        "ProductInput": {
            "type": "object",
            "properties": {
                "name": {
                    "type": "string",
                    "description": "新しい製品名"
                },
                "price": {
                    "type": "number",
                    "format": "float",
                    "description": "新しい製品の価格"
                },
                "description": {
                    "type": "string",
                    "description": "新しい製品の詳細な説明"
                }
            },
            "required": ["name", "price"]
        }
    }
}

この仕様書をExternal Servicesに登録する際、Named Credentialapi.example.comを指すように設定します。登録が完了すると、ProductServiceという外部サービス名で、getProductDetailscreateProductというオペレーションがFlowやApexから利用可能になります。

ApexからExternal Serviceを呼び出す例

External Servicesは主にFlowでの利用を想定していますが、Apexから直接呼び出すことも可能です。これは、より複雑なロジックが必要な場合や、テストコードの記述などで役立ちます。External Servicesを登録すると、Salesforceによって自動的にApexのラッパークラスが生成されます。

ここでは、上記のProductServicegetProductDetailsオペレーションをApexから呼び出す例を示します。External Service登録時に、例えば「製品サービス」という名前で登録した場合、内部的なApexクラス名はSystem.ExternalService.製品サービスのような形式になります。OpenAPIのoperationIdがApexのメソッド名に対応します。

public class ProductServiceCaller {

    /**
     * 指定された製品IDで外部の製品サービスから製品詳細を取得します。
     * @param productId 取得する製品のID
     * @return 製品詳細オブジェクト、またはエラーが発生した場合はnull
     */
    public static ProductService.Product getProductDetails(String productId) {
        // External Serviceのラッパークラスをインスタンス化します。
        // ここで 'ProductService' は、External Services登録時に指定したAPI名に置き換えてください。
        // 例: 'ProductService'
        System.ExternalService.ProductService.getProductDetails request =
            new System.ExternalService.ProductService.getProductDetails();

        // サービス呼び出しに必要な入力パラメータを設定します。
        // OpenAPI仕様書で定義されたパスパラメータ 'productId' に対応します。
        request.productId = productId;

        ProductService.Product productDetails = null;

        try {
            // 外部サービスを呼び出します。
            System.ExternalService.ProductService.getProductDetails.Response response =
                request.invoke();

            // エラーが発生したかどうかを確認します。
            if (response.getErrors().isEmpty()) {
                // 応答が成功した場合、出力パラメータから製品詳細を取得します。
                // 出力パラメータはOpenAPIの定義に基づいて型付けされます。
                productDetails = response.getProduct(); // 'Product'はOpenAPI定義のdefinitionsから生成
                System.debug('製品詳細取得成功: ' + productDetails.name + ' (価格: ' + productDetails.price + ')');
            } else {
                // エラー処理
                for (System.ExternalService.Error err : response.getErrors()) {
                    System.debug('製品詳細取得エラー: ' + err.getMessage());
                }
                // エラーを再スローするか、適切なエラーハンドリングを行う
                throw new CalloutException('外部製品サービス呼び出しエラー: ' + response.getErrors()[0].getMessage());
            }
        } catch (Exception e) {
            System.debug('例外発生: ' + e.getMessage());
            throw new CalloutException('製品サービス呼び出し中に予期せぬエラーが発生しました: ' + e.getMessage());
        }
        return productDetails;
    }

    /**
     * 新しい製品を外部の製品サービスに作成します。
     * @param name 製品名
     * @param price 製品価格
     * @return 作成された製品詳細オブジェクト、またはエラーが発生した場合はnull
     */
    public static ProductService.Product createNewProduct(String name, Decimal price) {
        System.ExternalService.ProductService.createProduct request =
            new System.ExternalService.ProductService.createProduct();

        // リクエストボディとして送信するProductInputオブジェクトを生成します。
        System.ExternalService.ProductService.ProductInput productInput =
            new System.ExternalService.ProductService.ProductInput();
        productInput.name = name;
        productInput.price = price;
        // productInput.descriptionはオプションなので、ここでは設定しない

        request.product = productInput; // OpenAPI定義の'product'パラメータに対応

        ProductService.Product createdProduct = null;

        try {
            System.ExternalService.ProductService.createProduct.Response response =
                request.invoke();

            if (response.getErrors().isEmpty()) {
                createdProduct = response.getProduct();
                System.debug('製品作成成功: ' + createdProduct.id + ', ' + createdProduct.name);
            } else {
                for (System.ExternalService.Error err : response.getErrors()) {
                    System.debug('製品作成エラー: ' + err.getMessage());
                }
                throw new CalloutException('外部製品サービス作成呼び出しエラー: ' + response.getErrors()[0].getMessage());
            }
        } catch (Exception e) {
            System.debug('例外発生: ' + e.getMessage());
            throw new CalloutException('製品作成サービス呼び出し中に予期せぬエラーが発生しました: ' + e.getMessage());
        }
        return createdProduct;
    }
}

注意点: 上記コード例では、System.ExternalService.ProductService.getProductDetailsのように、登録したExternal Servicesの名前とオペレーションIDに基づいたApexクラス名を使用しています。実際の環境では、External Services登録時に指定した「API名」に基づいてSalesforceが生成するクラス名を正確に記述する必要があります。また、レスポンスの処理方法やエラーオブジェクトの構造も、OpenAPIの定義とSalesforceの生成ルールに従います。

注意事項

SalesforceアーキテクトとしてExternal Servicesを設計・導入する際には、いくつかの重要な考慮事項があります。これらを適切に管理することで、堅牢でスケーラブルな統合ソリューションを構築できます。

  • API制限(API Limits):

    External Servicesを通じた外部コールアウトは、SalesforceのHTTPコールアウトに関するガバナ制限(Governor Limits)に計上されます。これには、トランザクションあたりのコールアウト数(通常100)、累積コールアウト時間(通常120秒)、および応答ペイロードのサイズが含まれます。高頻度で呼び出されるサービスの場合、これらの制限に抵触しないよう、アーキテクトは呼び出しパターン、データ量、トランザクション設計を慎重に検討する必要があります。

  • エラー処理(Error Handling):

    外部サービスはネットワークの不安定性、外部システムのダウンタイム、不正なリクエストデータなど、さまざまな理由で失敗する可能性があります。FlowでExternal Servicesを使用する場合、フォールトパス(Fault Path)を利用してエラーを捕捉し、適切な処理(リトライ、ユーザーへの通知、ログ記録など)を行う設計が不可欠です。Apexからの呼び出しの場合も、try-catchブロックと、System.ExternalService.Errorオブジェクトの利用を通じて、堅牢なエラー処理を実装する必要があります。冪等性(Idempotency)を考慮したAPI設計も、リトライ戦略においては重要です。

  • セキュリティ(Security):

    Named Credentialsの適切な利用は、外部サービス連携におけるセキュリティの要です。認証情報をコードに埋め込むことを避け、Named Credentialsで一元管理することで、セキュリティリスクを低減します。また、外部サービスへのアクセスは常にHTTPSを使用し、転送中のデータを保護する必要があります。さらに、誰がどのExternal Servicesを呼び出せるかについて、プロファイルや権限セットを通じて細かく制御することもアーキテクトの責任です。

  • スキーマ進化とバージョン管理(Schema Evolution and Versioning):

    外部APIは時間の経過とともに変更されることがあります。External Servicesに登録されたOpenAPIスキーマは、外部APIのインターフェース変更に合わせて更新する必要があります。破壊的変更(Breaking Changes)が発生した場合、既存のFlowやApexコードに影響が出る可能性があるため、外部API提供者との連携を密にし、バージョン管理戦略を策定することが重要です。一般的には、APIのメジャーバージョンアップ時には新しいExternal Serviceとして登録することを検討します。

  • パフォーマンス(Performance):

    外部サービスへのネットワークコールは、Salesforceのトランザクションに遅延をもたらす可能性があります。特に同期呼び出しの場合、Flowの実行時間やユーザーエクスペリエンスに直接影響します。レイテンシ(Latency)を最小限に抑えるための外部APIの選定、または非同期処理(例:Platform EventsやQueueable Apexと組み合わせる)の検討が求められます。大量データのバッチ処理には、External Servicesが最適ではない場合もあります。

  • データ量の制限:

    External Servicesのペイロードサイズには制限があります。特に大きな応答データを受け取る場合や、大量のデータを外部サービスに送信する場合には、そのデータがSalesforceのガバナ制限(例えば、JSONのシリアライズ/デシリアライズのサイズ制限)に適合するかを確認する必要があります。必要に応じて、ページネーションの実装や、より適した統合パターン(例:ETLツール)の採用を検討します。

  • テスト容易性(Testability):

    FlowやApexでExternal Servicesを利用する機能のテストは重要です。Apexのテストコードでは、Test.setMock()を使用して、外部サービスからの応答を模擬(モック)することで、実際のコールアウトなしにロジックをテストできます。Flowのテストにおいては、Flow Debuggerを活用し、外部サービス呼び出しが正しく設定されているかを確認します。開発環境やサンドボックス環境での十分なテストを通じて、本番環境での問題を未然に防ぎます。

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

Salesforce External Servicesは、Salesforceプラットフォームの統合能力を大きく拡張し、特にローコード・ノーコード戦略を推進する上で不可欠なツールです。Salesforceアーキテクトとして、この機能を適切に活用することで、開発のアジリティを向上させ、保守コストを削減し、堅牢でセキュアな統合ソリューションを設計することが可能になります。

以下に、External Servicesを活用するためのベストプラクティスをまとめます。

  • Named Credentialsの一貫した使用:

    すべての外部サービス連携においてNamed Credentialsを使用し、認証情報とエンドポイントURLを一元管理します。これにより、環境間の移行を容易にし、セキュリティを強化します。

  • OpenAPI仕様書の厳密な定義とバージョン管理:

    外部APIの契約を正確に反映したOpenAPI仕様書を作成し、変更があった場合は速やかに更新します。また、外部APIのメジャーバージョンアップには新しいExternal Servicesの登録を検討し、既存のシステムへの影響を最小限に抑えます。

  • 包括的なエラーハンドリングの設計:

    FlowのフォールトパスやApexのtry-catchブロックを積極的に利用し、外部サービスからのエラーを適切に処理します。リトライロジック、代替経路、通知メカニズムを組み込み、ユーザーエクスペリエンスの低下を防ぎます。

  • 非同期処理の検討:

    パフォーマンスがクリティカルな場合や、外部サービスの応答時間が長い場合は、Platform Events、Queueable Apex、またはFutureメソッドと組み合わせて非同期処理を検討します。これにより、ユーザーインターフェースの応答性を保ちながら、バックグラウンドで外部サービスを呼び出すことが可能になります。

  • セキュリティとアクセス制御の徹底:

    最小権限の原則に基づき、External Servicesへのアクセス権限をプロファイルや権限セットで厳密に管理します。また、常にHTTPS通信を使用し、データの安全な転送を保証します。

  • 詳細なドキュメント作成:

    External Servicesの登録情報、使用しているNamed Credentials、OpenAPI仕様書、FlowやApexでの利用方法、エラー処理ロジックなど、統合に関するすべての情報を詳細に文書化します。これにより、将来的なメンテナンスやトラブルシューティングが容易になります。

  • テスト戦略の確立:

    外部サービス連携を含むFlowやApexのテストコードを記述し、Salesforceのテストカバレッジ要件を満たすだけでなく、実際のビジネスロジックが期待通りに動作することを確認します。Apexのモック(Mock)機能は、外部コールアウトのテストにおいて非常に有効です。

  • スケーラビリティとパフォーマンスの監視:

    External Servicesの利用状況、APIコール数、応答時間などを継続的に監視し、ガバナ制限への抵触やパフォーマンスボトルネックが発生しないかを確認します。必要に応じて、統合アーキテクチャの見直しや外部APIの最適化を検討します。

External Servicesは、Salesforceエコシステム内で革新的な統合ソリューションを構築するための強力な基盤を提供します。アーキテクトは、これらの機能を最大限に活用し、ビジネスの成長を加速させるための最適な統合戦略を策定する上で中心的な役割を果たすことができます。

コメント