Salesforce Connectと外部オブジェクト:リアルタイムデータ連携のためのアーキテクトガイド

背景と適用シナリオ

Salesforce アーキテクトとして、私たちは常にデータ戦略の中心にいます。顧客データを一元管理し、360度のビューを提供することは多くのプロジェクトで共通の目標です。しかし、すべてのデータを Salesforce プラットフォームにコピー&ペーストすることが常に最善の解決策とは限りません。特に、基幹システム(ERP)にある請求情報や、物流システムの最新の在庫データ、あるいはデータレジデンシー要件により物理的に特定の国に保持する必要があるデータなど、大容量で、頻繁に更新され、かつ Salesforce 外に「System of Record(信頼できる唯一の情報源)」を持つデータがそれに該当します。

従来、このようなデータ連携には夜間バッチ処理による ETL (Extract, Transform, Load) が用いられてきました。しかし、このアプローチにはデータの鮮度が失われる、ストレージコストが増大する、二重管理による複雑性が増すといった課題が伴います。ここで登場するのが Salesforce Connect と、それが提供する External Objects (外部オブジェクト) という強力なソリューションです。

External Objects は、Salesforce 内に物理的なデータを格納することなく、外部システムのデータにリアルタイムでアクセスし、あたかも Salesforce の標準オブジェクトやカスタムオブジェクトのように表示・操作できるようにする機能です。これは「データの仮想化(Data Virtualization)」とも呼ばれ、以下のようなシナリオで絶大な効果を発揮します。

  • 大量データのリアルタイム参照: ERPシステムに存在する数百万件の注文履歴や請求明細を、Salesforce の取引先責任者ページから直接参照したい場合。データをコピーすることなく、必要な時に必要な分だけ表示できます。
  • 頻繁に更新されるデータの活用: 倉庫管理システム(WMS)の在庫状況を、営業担当者が見積作成時にリアルタイムで確認したい場合。
  • データコンプライアンス要件への対応: 特定の個人情報や財務データを、規制により社外のクラウドに保存できないが、Salesforce の CRM プロセスからは参照する必要がある場合。
  • システム移行の段階的アプローチ: レガシーシステムから Salesforce への完全移行プロジェクトにおいて、移行期間中もレガシーシステムのデータを参照しながら、新しい業務プロセスを Salesforce 上で稼働させる場合。

アーキテクトの視点から見れば、External Objects は「いつデータをコピーし、いつデータを参照するか」という、データ連携における根源的な問いに対する洗練された答えの一つなのです。


原理説明

External Objects の魔法の裏側には Salesforce Connect というフレームワークが存在します。Salesforce Connect は、外部データソースへの接続を抽象化し、Salesforce 内のデータアクセスレイヤーに統合する役割を担います。ユーザーが外部オブジェクトのリストビューを開いたり、SOQL クエリを発行したりすると、Salesforce Connect はそのリクエストをリアルタイムで外部システムが理解できる形式のクエリ(例:OData URL)に変換し、外部システムに送信します。外部システムからのレスポンスは、再び Salesforce Connect によって解析され、ユーザーインターフェースや Apex コードに返されます。重要なのは、この一連のプロセスにおいて、データが Salesforce のデータベースに永続的に保存されることはないという点です。

Salesforce Connect は、主に3種類のアダプタを提供しています。

1. OData アダプタ

OData (Open Data Protocol) は、REST API を標準化するための ISO/IEC 承認の OASIS 標準規格です。多くのエンタープライズシステム(SAP、Microsoft Dynamics など)が OData をサポートしており、標準的な設定で比較的容易に Salesforce との連携を実現できます。OData 2.0 と OData 4.0 のバージョンがサポートされています。これは、最も一般的に使用されるアダプタです。

2. 組織間アダプタ (Cross-Org Adapter)

これは、別の Salesforce 組織のデータにアクセスするための専用アダプタです。複数の Salesforce 組織を運用している企業が、組織間でデータを同期することなく、一方の組織からもう一方の組織のデータをリアルタイムに参照したい場合に非常に有効です。

3. カスタムアダプタ (Apex Connector Framework)

OData をサポートしていない、あるいは独自の認証方式やデータ構造を持つ外部システムと連携したい場合、Apex Connector Framework を使用して Apex で独自のアダプタを開発することができます。これにより、理論上は API を持つあらゆるシステムとの連携が可能になります。アーキテクトとしては、このフレームワークの存在が Salesforce Connect の適用範囲を劇的に広げることを理解しておく必要があります。開発者は DataSource.ProviderDataSource.Connection という2つの Apex クラスを実装することで、外部システムのメタデータ(テーブル定義)の取得、データのクエリ、さらにはデータの作成・更新・削除(DML)処理を定義できます。

これらのアダプタを通じて設定された外部データソースと外部オブジェクトは、Salesforce 内でタブ、ページレイアウト、リレーション(間接参照関係、外部参照関係)、SOQL、Apex、さらには一部のレポート機能など、多くの標準機能とシームレスに統合されます。


サンプルコード

カスタムアダプタ(Apex Connector Framework)は、最も柔軟性が高い選択肢です。ここでは、アーキテクトが開発チームに実装を依頼する際に理解しておくべき、基本的な構造のサンプルコードを Salesforce 公式ドキュメントから引用して解説します。この例では、単純なデータソースへの接続を想定しています。

まず、外部データソースのプロバイダを定義する DataSource.Provider クラスを実装します。

/**
 * データソースのタイプ(この例では 'Simple')と、
 * 認証や接続の能力を宣言します。
 */
global class SimpleDataSourceProvider extends DataSource.Provider {
    // データソースのタイプを返す
    override global DataSource.Connection getConnection(DataSource.ConnectionParams connectionParams) {
        return new SimpleDataSourceConnection(connectionParams);
    }

    // このデータソースがサポートする認証方式を定義
    override global List<DataSource.AuthenticationCapability> getAuthenticationCapabilities() {
        List<DataSource.AuthenticationCapability> capabilities = new List<DataSource.AuthenticationCapability>();
        capabilities.add(DataSource.AuthenticationCapability.ANONYMOUS);
        return capabilities;
    }

    // このデータソースがサポートする機能(例:検索)を定義
    override global List<DataSource.Capability> getCapabilities() {
        List<DataSource.Capability> capabilities = new List<DataSource.Capability>();
        capabilities.add(DataSource.Capability.ROW_QUERY);
        capabilities.add(DataSource.Capability.SEARCH);
        return capabilities;
    }
}

次に、実際のデータアクセスロジックを実装する DataSource.Connection クラスです。このクラスが、Salesforce からのリクエストを外部システムへのリクエストに変換し、結果を返す心臓部となります。

global class SimpleDataSourceConnection extends DataSource.Connection {

    private DataSource.ConnectionParams connectionInfo;

    // コンストラクタ
    global SimpleDataSourceConnection(DataSource.ConnectionParams connectionParams) {
        this.connectionInfo = connectionParams;
    }

    /**
     * Salesforce が外部システムのテーブル定義を同期(sync)するために呼び出すメソッド。
     * ここで外部オブジェクトとその項目を定義します。
     * @param syncContext 同期操作のコンテキスト情報
     * @return 外部システムのテーブル定義のリスト
     */
    override global List<DataSource.Table> sync() {
        List<DataSource.Table> tables = new List<DataSource.Table>();
        List<DataSource.Column> columns = new List<DataSource.Column>();
        columns.add(DataSource.Column.text('ID', 255));
        columns.add(DataSource.Column.text('Name', 255));
        columns.add(DataSource.Column.url('URL'));
        columns.add(DataSource.Column.text('Description', 500));
        tables.add(DataSource.Table.get('SimpleTable', 'Name', columns));
        return tables;
    }
    
    /**
     * データのクエリ要求を処理するメソッド。
     * Salesforce からの SOQL は、このメソッドに渡される queryContext に変換されます。
     * @param queryContext クエリのコンテキスト(対象テーブル、フィルタ条件、順序など)
     * @return クエリ結果のデータ行のリスト
     */
    override global DataSource.TableResult query(DataSource.QueryContext queryContext) {
        // ここで外部APIを呼び出し、データを取得するロジックを実装する
        // 例として、静的なデータを生成して返す
        
        List<Map<String, Object>> sampleData = new List<Map<String, Object>>();
        
        Map<String, Object> row1 = new Map<String, Object>();
        row1.put('ID', '101');
        row1.put('Name', 'Sample Record 1');
        row1.put('URL', 'http://www.salesforce.com');
        row1.put('Description', 'This is the first sample record from the external system.');
        sampleData.add(row1);
        
        Map<String, Object> row2 = new Map<String, Object>();
        row2.put('ID', '102');
        row2.put('Name', 'Sample Record 2');
        row2.put('URL', 'http://developer.salesforce.com');
        row2.put('Description', 'This is the second sample record.');
        sampleData.add(row2);
        
        return DataSource.TableResult.get(true, null, queryContext.tableSelection, sampleData);
    }
}

上記の query メソッド内では、実際には Http.send() などを使って外部 REST API をコールし、取得した JSON データをパースして List<Map<String, Object>> 形式に変換する処理を実装します。このコードが、Salesforce と任意の外部システムとを繋ぐ架け橋となるのです。


注意事項

External Objects は強力なツールですが、銀の弾丸ではありません。アーキテクトとして、その制約とトレードオフを深く理解し、顧客に適切なガイダンスを提供する必要があります。

権限とセキュリティ

外部システムへの接続には、Named Credentials (指定ログイン情報) の使用が強く推奨されます。これにより、認証情報をコードから分離し、安全に管理できます。認証方式として、組織単位で単一の認証情報を共有する「匿名」や「指定ユーザ」と、Salesforce ユーザーごとに外部システムの認証情報をマッピングする「ユーザごと」が選択できます。後者は、外部システム側のアクセス権限を Salesforce ユーザーに反映させたい場合に重要です。

API 制限とパフォーマンス

外部オブジェクトへのアクセスは、Salesforce のコールアウト制限(Apex Callouts)と、外部システムの API レート制限の両方を消費します。特に、多くのユーザーが頻繁にアクセスするリストビューやレポートに外部オブジェクトを含めると、あっという間に制限に達する可能性があります。また、外部システムの応答速度がそのまま Salesforce の UI パフォーマンスに直結します。外部システムの API が低速な場合、ユーザー体験は著しく低下します。ページング処理(Server Driven Paging)の実装など、パフォーマンスを考慮した設計が不可欠です。

データモデルとリレーション

External Objects は、Salesforce の標準・カスタムオブジェクトとリレーションを組むことができますが、いくつか特殊な参照関係が使われます。

  • 間接参照関係 (Indirect Lookup Relationship): Salesforce 側の子オブジェクトから、外部オブジェクトの親レコードを参照します。外部オブジェクト側には、Salesforce のレコード ID 以外のユニークな外部 ID が必要です。
  • 外部参照関係 (External Lookup Relationship): 外部オブジェクト側の子オブジェクトから、Salesforce の標準・カスタムオブジェクトの親レコードを参照します。親オブジェクトの Salesforce ID が外部システム側に保存されている必要があります。

主従関係(Master-Detail Relationship)はサポートされていないため、積み上げ集計項目の作成やカスケード削除は利用できません。

クエリの制約

SOQL のすべての機能が外部オブジェクトで利用できるわけではありません。COUNT()SUM() といった集計関数、GROUP BYHAVING 句などのサポートは、使用するアダプタと外部システムの能力に依存します。また、SOSL は外部オブジェクトの検索には使用できません。


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

Salesforce Connect と External Objects は、データを物理的に移動させることなく、Salesforce を真のエンゲージメントプラットフォームへと昇華させるための重要なアーキテクチャパターンです。これにより、ユーザーは使い慣れた Salesforce のインターフェースから、社内に散在するあらゆる情報へシームレスにアクセスできるようになります。

アーキテクトとしてのベストプラクティスは以下の通りです。

  1. 適切な連携パターンの選択: すべての連携に Salesforce Connect が最適とは限りません。「リアルタイム性」が絶対条件か、「データの鮮度」はどの程度許容できるか、「データの所有権」はどこにあるべきかを問いかけましょう。分析や集計が主目的であれば、従来のETLツールや Salesforce Platform Events を使ったニアリアルタイム同期の方が適している場合もあります。「Copy vs. Reference」のトレードオフを常に意識してください。
  2. 外部システムの評価: 連携対象の外部システムの API が、Salesforce からのクエリ負荷に耐えられるか、十分なパフォーマンスを提供できるか、ページングやフィルタリングをサポートしているかなどを事前に評価することがプロジェクトの成否を分けます。
  3. ユーザー体験の設計: 外部オブジェクトのデータは、読み込みに時間がかかる可能性があることをユーザーに周知し、ページレイアウトの設計を工夫する(例:デフォルトで表示せず、別タブや関連リストに配置する)などの配慮が必要です。
  4. ガバナンスと監視: API コールアウトの使用状況を定期的に監視し、予期せぬパフォーマンス劣化やエラーが発生していないかをチェックする仕組みを導入します。Apex Connector Framework を使う場合は、堅牢なエラーハンドリングとロギングを実装することが不可欠です。
  5. 小さく始めてスケールさせる: まずは一つの読み取り専用の外部オブジェクトから始め、その価値とパフォーマンスを検証した上で、書き込み可能なオブジェクトや、より複雑な連携へと段階的に拡張していくアプローチを推奨します。

External Objects を使いこなすことで、私たちはデータのサイロ化を打破し、より俊敏で、コスト効率が高く、そして何よりユーザーにとって価値のある統合ソリューションを設計することができるのです。

コメント