mcpField
C
cap-add-remote-service

cap-add-remote-service

github.com/capire開発17161 回インストール4 日前に更新
リポジトリ

cap-add-remote-service について

cap-add-remote-serviceは、Calesiパターンに沿って既存のCAP Node.jsプロジェクトへのリモートサービス統合を案内します。APIの取り込み、コンシュンプションビュー、ドメインモデルの変更、ハンドラー、検証に加え、複製と委譲読み取りの選択も扱います。

主な機能

  1. 01CAPプロジェクト構成とサービスの確認
  2. 02npmパッケージまたはEDMXからAPIを取り込み
  3. 03コンシュンプションビューとドメインマッピングの定義
  4. 04リモートエンティティをドメインモデルに接続
  5. 05委譲、複製、Outboxイベントの実装
  6. 06CAPツールによる統合の検証

活用シーン

  1. 01CAPプロジェクトへのOData API追加
  2. 02アプリケーションサービス経由でのリモートエンティティ公開
  3. 03ローカルデータとの結合に使うデータ方式の選択
  4. 04ローカル開発中のサービス統合テスト

MCP 対応

このスキルは `cds-mcp_search_model` を使って、プロジェクトに定義済みのサービスとエンティティを確認します。入力には、MCPサーバーやMCPクライアントに公開するツールについての説明はありません。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/capire/skills --skill cap-add-remote-service

インストール後、Claude Code で/cap-add-remote-serviceを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

evals/
evals.json
evals/fixture/
package.json
evals/fixture/apis/
xflights.cds
evals/fixture/data/
API_BUSINESS_PARTNER.edmx
evals/fixture/db/
schema.cds
evals/fixture/srv/
travel-service.cds
travel-service.js

詳細

対応内容

既存の CAP Node.js プロジェクトにリモートサービス連携を追加する手順を、CAP-Level Service Integration(Calesi)パターンに沿って開発者に順を追って案内します。API のインポート、コンシュームビューの定義、モデルへの統合、必要な Node.js ハンドラーの接続を行います。

事前に必要なコンテキスト

作業を始める前に、プロジェクトの構成を把握するために確認します。

  1. package.json を読み、プロジェクト名と既存の依存関係を確認します。
  2. cds-mcp_search_model を使い、プロジェクトで定義済みのサービスとエンティティの概要を取得します。
  3. ユーザーに次の点を尋ねます。
    • どのリモートサービスを連携したいか(別の CAP アプリ、S/4HANA Business Partner など)
    • API が npm パッケージ、.edmx ファイル、または URL のみのどの形式で用意されているか

ステップ 1 — API をインポートする

オプション A:npm の CAP パッケージ

リモートサービスが CAP パッケージ(例:@capire/xflights-data)として公開されている場合:

npm add <package-name>

追加したパッケージの index.cds と package.json を確認し、公開されているサービス名とエンティティを把握します。

重要:パッケージのモックデータ CSV ファイル(通常は srv/external/data/ 内)も確認し、実際にどの列にデータがあるかを確かめます。コンシュームビューでは、モックデータに存在する列のみを射影してください。そうしないと、ローカル開発中に UI に空の値が表示されます。(不明な場合はユーザーに確認してください)

オプション B:OData EDMX ファイル

開発者から .edmx ファイル(例:SAP Business Accelerator Hub からダウンロードしたファイル)が提供された場合:

cds import path/to/API_NAME.edmx

これにより .edmx が srv/external/ にコピーされ、.csn ファイルが生成されます。人が読みやすい .cds ファイルが必要な場合は、--as cds を使用します。

インポート後、srv/external/ に生成されたファイルを確認します。

ls srv/external/

まだ設定されていない場合は、cds.requires の下にサービスを package.json へ追加します。

"cds": {
  "requires": {
    "API_NAME": {
      "kind": "odata-v2"
    }
  }
}

OData v4 サービスには "kind": "odata" を使用します。

ステップ 2 — コンシュームビューを作成する

新しい .cds ファイル(例:apis/<source-system>.cds または srv/external/<name>.cds)を作成し、インポートした定義をドメインモデルにマッピングする コンシュームビュー(必要に応じて名前を変更したり、フラット化したりする射影)を定義します。

// Example: mapping S/4 A_BusinessPartner to a domain-friendly Customers entity
using { API_BUSINESS_PARTNER as S4 } from '@capire/s4';
namespace sap.capire.s4;

@federated entity Customers as projection on S4.A_BusinessPartner {
  BusinessPartner as ID,
  PersonFullName  as Name,
  LastChangeDate  as modifiedAt,
} where BusinessPartnerCategory == 1;
// Example: mapping xflights data to a domain-friendly Flights entity
using { sap.capire.flights.data as x } from '@capire/xflights-data';
namespace sap.capire.xflights;

@federated entity Flights as projection on x.Flights {
  ID, date, departure, arrival, modifiedAt,
  airline.icon     as icon,
  airline.name     as airline,
  origin.name      as origin,
  destination.name as destination,
}

主な規約:

  • ローカルでのデータ複製が必要なエンティティには、@federated を使用します(ステップ 5 を参照)。
  • リモートの名前をドメイン名にマッピングします(例:BusinessPartner → ID)。
  • 有用な場合は、関連を列にフラット化します(HCQL では機能しますが、プレーンな OData では機能しません)。
  • 必要に応じて、データを事前に絞り込むための where 句を追加します。
  • パッケージがソースエンティティに @cds.minify:'unused-elements' を注釈している場合は、コンシュームビューのファイルで上書きする必要があります。
    annotate S4.A_BusinessPartner with @cds.minify:false;
    
    そうしないと、射影に要素がなくなり、"Expecting view to have at least one non-virtual element" というエラーでデプロイに失敗します。

ステップ 3 — ドメインモデルからリモートエンティティを参照する

db/schema.cds(または該当するドメインモデルファイル)で、コンシュームビューをインポートして関連を追加します。

using { sap.capire.xflights as x } from '../apis/capire/xflights';

entity Bookings {
  // ... existing elements ...
  Flight : Association to x.Flights;
}

リモートエンティティがローカルエンティティを置き換える場合(例:ローカルの Customers を S/4 Business Partner に置き換える場合)は、ローカルエンティティの定義を完全に削除し、代わりにコンシュームビューをインポートします。その後、既存のテストデータ CSV を更新し、リモートのモックデータに一致する ID(例:1004155 ではなく 000001)を使用します。

また、新しいリモート連携エンティティに存在しなくなったフィールドを参照している UI 注釈(値リスト、テキストの配置など)も更新します。

リモートエンティティからローカルエンティティへの逆方向の関連も必要な場合は、非管理対象の関連を使用します(リモート側に外部キーはありません)。

extend x.Flights with columns {
  Bookings : Association to many Bookings on Bookings.Flight = $self;
}

非管理対象の関連と仮想/計算フィールドのみが、リモートエンティティに安全に追加できる拡張です。通常の要素や管理対象の関連は避けてください。

ステップ 4 — リモートエンティティをサービスで公開する(UI 向け)

リモートエンティティを Fiori UI または OData サービスから利用できるようにする必要がある場合は、アプリケーションサービス内で読み取り専用のプロジェクションとして公開します。

// In srv/my-service.cds
using { sap.capire.xflights as x } from '../apis/capire/xflights';

service MyService {
  // ... existing entities ...
  @readonly entity Flights as projection on x.Flights;
}

これを行わないと、公開されていないエンティティへの関連は切り離されます。

ステップ 5 — 統合ハンドラーを接続する(Node.js)

サービスハンドラーファイルを開く(または作成する)例:srv/my-service.js。

5a — リモートサービスに接続する

cds.connect.to() に渡すサービス名は、リモートパッケージの index.cds で定義されたサービス名(または EDMX インポートで cds.requires に使用されたキー)と完全に一致している必要があります。必ず確認してください。別名を推測で設定しないでください。

module.exports = class MyService extends cds.ApplicationService {
  async init() {
    const remote = await cds.connect.to('sap.capire.flights.data')
    // or: cds.connect.to('API_BUSINESS_PARTNER')
    const { Flights, Customers } = this.entities

    // ... handlers below ...

    return super.init()
  }
}
5b — 委譲(値ヘルプ/オンデマンド読み取り向け)

リモートエンティティに対する受信 READ リクエストを、上流サービスに直接委譲します。CAP はコンシュームビューを通じてクエリを自動的に変換します。

this.on('READ', Customers, req => s4.run(req.query))
5c — データフェデレーション(ローカルデータとの結合向け)

リモートデータをローカルデータと合わせて使用する必要がある場合(例:予約と合わせてフライトの詳細を表示する場合)、方法は 2 つあります。トレードオフを説明し、どちらの方法を希望するかユーザーに確認してください。

オプション 1:データをローカルに複製する

リモートデータをローカルの永続化テーブルにコピーし、SQL JOIN をネイティブに実行できるようにします。

メリット:

  • SQL JOIN を全面的にサポートします(ローカルデータとリモートデータをまたいだフィルタリング、ソート、ページング)
  • 読み取りが高速です。クエリ時のネットワーク往復がありません
  • オフライン時やリモートサービスが利用できない場合でも動作します

デメリット:

  • データが古くなる可能性があります(最後の複製処理時点のデータになります)
  • 複製ロジックとスケジュール設定が必要です(初回ロードと差分同期)
  • ローカルストレージを消費します
  • 保守・監視する要素が増えます

実装:

コンシュームビューにアノテーションを付け、複製データを永続化します。

// In db/schema.cds
annotate x.Flights with @cds.persistence.table;

サービスハンドラーに複製ロジックを追加します(例:起動時またはタイマーで実行)。

const { Flights } = cds.entities('sap.capire.xflights')
let { latest } = await SELECT.one`max(modifiedAt) as latest`.from(Flights)
let touched = await xflights.read(Flights).where`modifiedAt > ${latest || 0}`
if (touched.length) await UPSERT(touched).into(Flights)

または、コンシュームビューにすでに付与されている @federated と CAP Data Federation プラグインを併用して、自動複製を行うこともできます。

オプション 2:読み取りをリモートサービスに委譲する(複製なし)

リモートエンティティへの READ リクエストを処理する際、リクエスト時にクエリを上流サービスに転送し、常に最新のデータを保持します。

メリット:

  • 常に最新のデータを取得できます
  • ローカルストレージや複製ロジックが不要です
  • 読み取り専用のシナリオでは、より簡単に設定できます

デメリット:

  • ローカルデータとリモートデータをまたいだネイティブな SQL JOIN はできません
  • 読み取りのたびにネットワーク往復が発生します(遅延、可用性のリスク)
  • 追加のアプリケーションレベルのロジックがないと、ローカルフィールドとリモートフィールドをまたいだフィルタリングやソートはサポートされません
  • リモートサービスが停止すると、読み取りに失敗します

実装:

サービスハンドラーで READ を委譲します。

this.on('READ', Flights, req => xflights.run(req.query))

@cds.persistence.table アノテーションは必要ありません。ローカル DB の観点では、エンティティは仮想のままです。

推奨

リモートデータがローカルデータとの JOIN、フィルタリング、ソートに必要な場合や、リモートサービスの停止に対する耐障害性が重要な場合は、複製を使用してください。データの鮮度が重要な場合、データセットが大規模または変動しやすい場合、あるいはエンティティが単独でのみアクセスされる場合(例:値ヘルプ、詳細ビュー)は、委譲を使用してください。

重要: プロジェクトスキーマにリモートエンティティへの Association to を持つローカルエンティティがあり、そのローカルエンティティをリストビューで公開する場合(つまり SQL JOIN が必要な場合)、複製は必須です。委譲ではサービス境界をまたいだ SQL JOIN をサポートできません。この関連は実行時にエラーなく null を返します。この場合、ユーザーに確認する必要はありません。複製を選択してください。

5d — Outboxed イベント(信頼性の高い結果を待たない書き込み)

同期的な結果を必要としない、リモートサービスへの書き込み操作の場合:

const xflights_ = cds.outboxed(xflights)

this.after('SAVE', Travels, ({ Bookings = [] }) => {
  return Promise.all(Bookings.map(b => {
    let { Flight_ID: flight, Flight_date: date } = b
    return xflights_.send('POST', 'BookingCreated', { flight, date })
  }))
})

イベントは同じ DB トランザクション内でローカルの outbox テーブルに保存され、その後、再試行しながら非同期で転送されます。

ステップ 6 — Cloud SDK の前提条件をインストールする

リモート HTTP 通信には SAP Cloud SDK パッケージが必要です。すでにインストールされているか確認し、未インストールの場合は追加してください。

npm add @sap-cloud-sdk/connectivity @sap-cloud-sdk/http-client @sap-cloud-sdk/resilience

ステップ 7 — cds watch で確認する

アプリを起動し、ログの出力を確認してください。

cds watch

接続行を注意深く確認してください。 次のいずれかが表示されていることを確認します。

  • mocking <service-name> — CAP がプロセス内で自動的にモックしています(開発に適しています)
  • connect to <service-name> > odata { url: ... } — CAP が外部のモックプロセスに接続しています

URL を指す connect to ... > odata が表示されているのに、その URL でモックが実行されていない場合、READ リクエストはエラーが表面化しないまま失敗し、空の結果を返します。これは、以前の cds mock の実行による古いエントリーが ~/.cds-services.json に残っている場合に発生します。次のいずれかで修正してください。

  • モックを起動する:cds mock <service-name>
  • 古いエントリーを削除し、CAP がプロセス内のモックにフォールバックするようにする

期待される出力例:

[cds] - mocking sap.capire.flights.data { at: [...], ... }

これにより、CAP がローカル開発用にリモートサービスを自動的にモックしていることが確認できます。開発中の反復作業では、上流サービスは必要ありません。

実際の委譲をテストする場合(別プロセスのモックを使用):

# Terminal 1
cds mock apis/capire/xflights.cds

# Terminal 2
cds watch

利用側のサービスのログに mocking ではなく、connect to ... > hcql(または odata)が表示されていることを確認してください。

ステップ 8 — cds repl でクエリをテストする

UI を起動せずに、連携を手早く検証できます。

cds repl ./
// Connect to the service
const svc = await cds.connect.to('sap.capire.flights.data')
const { Flights } = cds.entities('sap.capire.xflights')

// Read through the consumption view
await svc.read(Flights).limit(3)

// Check that column mapping worked (should see domain names, not remote names)

主要なパターンのまとめ

シナリオパターン
必要に応じてリモートデータを読み取る(例:値ヘルプ)委譲:this.on('READ', Entity, req => remote.run(req.query))
ローカルの SQL JOIN にリモートデータが必要データフェデレーション:@federated + @cds.persistence.table + レプリケーションロジック
リモートへの書き込みを結果を待たずに送信Outboxed emit:cds.outboxed(remote).send(...)
上流サービスを起動せずにローカル開発する自動:CAP がプロセス内でモックするため、設定は不要
ドメインに合わせてリモート API の名前変更や形状変更を行うコンシュームビュー(列エイリアスと where 句を使用したプロジェクション)

避けるべきよくある間違い

  • 避けてください: インポートしたエンティティをそのままサービスで公開すること。必ずコンシュームビューまたはプロジェクションを経由してください。
  • 避けてください: コンシュームビューに view キーワードを使うこと。必ず entity ... as projection on を使ってください(view は別の CDS の概念です)。
  • 避けてください: コンシュームビューに @cds.persistence.skip を使うこと。これは別のアノテーションです。代わりに @federated(データフェデレーション対象のエンティティとしてマーク)を使い、ローカルレプリケーションを行う場合は別途 @cds.persistence.table を追加してください。
  • 避けてください: ハンドラーの登録にエンティティ名の文字列を使うこと。必ず this.entities から分割代入してください:const { Flights } = this.entities の後に this.on('READ', Flights, ...) を使い、this.on('READ', 'Flights', ...) は使わないでください。
  • 避けてください: cds.connect.to() でサービス名を推測すること。パッケージの index.cds、または EDMX インポートの場合は cds.requires で確認してください。
  • 避けてください: CQN オブジェクトを複製せずに変更すること:const q2 = cds.ql.clone(q1)。
  • 避けてください: リモートエンティティの拡張に通常の要素や管理対象アソシエーションを追加すること。追加できるのは非管理アソシエーション、仮想フィールド、計算フィールドのみです。
  • 避けてください: データフェデレーションを設定せずに、サービス間のライブナビゲーションによるフィルタリングやソートに依存すること。実行時に失敗します。
  • 避けてください: Cloud SDK パッケージを忘れること。これがないと、リモート HTTP 呼び出しは依存関係の欠落を示すエラーになります。
  • 避けてください: リモートのモックデータに存在しない列をプロジェクションすること。UI に空の値が表示されます。モック CSV のヘッダーとコンシュームビューの列を必ず照合してください。
  • 避けてください: ローカルエンティティをリモートエンティティに置き換える際、テストデータの外部キーの更新を忘れること。古い ID はリモートのモックデータと一致しません。
  • 避けてください: インポートしたエンティティの @cds.minify アノテーションを無視すること。プロジェクションからすべての要素が削除され、デプロイに失敗することがあります。

コメント