cap-add-remote-service について
cap-add-remote-serviceは、Calesiパターンに沿って既存のCAP Node.jsプロジェクトへのリモートサービス統合を案内します。APIの取り込み、コンシュンプションビュー、ドメインモデルの変更、ハンドラー、検証に加え、複製と委譲読み取りの選択も扱います。
主な機能
- 01CAPプロジェクト構成とサービスの確認
- 02npmパッケージまたはEDMXからAPIを取り込み
- 03コンシュンプションビューとドメインマッピングの定義
- 04リモートエンティティをドメインモデルに接続
- 05委譲、複製、Outboxイベントの実装
- 06CAPツールによる統合の検証
活用シーン
- 01CAPプロジェクトへのOData API追加
- 02アプリケーションサービス経由でのリモートエンティティ公開
- 03ローカルデータとの結合に使うデータ方式の選択
- 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.jsonevals/fixture/package.jsonevals/fixture/apis/xflights.cdsevals/fixture/data/API_BUSINESS_PARTNER.edmxevals/fixture/db/schema.cdsevals/fixture/srv/travel-service.cdstravel-service.js詳細
対応内容
既存の CAP Node.js プロジェクトにリモートサービス連携を追加する手順を、CAP-Level Service Integration(Calesi)パターンに沿って開発者に順を追って案内します。API のインポート、コンシュームビューの定義、モデルへの統合、必要な Node.js ハンドラーの接続を行います。
事前に必要なコンテキスト
作業を始める前に、プロジェクトの構成を把握するために確認します。
package.jsonを読み、プロジェクト名と既存の依存関係を確認します。cds-mcp_search_modelを使い、プロジェクトで定義済みのサービスとエンティティの概要を取得します。- ユーザーに次の点を尋ねます。
- どのリモートサービスを連携したいか(別の 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'を注釈している場合は、コンシュームビューのファイルで上書きする必要があります。
そうしないと、射影に要素がなくなり、"Expecting view to have at least one non-virtual element" というエラーでデプロイに失敗します。annotate S4.A_BusinessPartner with @cds.minify:false;
ステップ 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アノテーションを無視すること。プロジェクションからすべての要素が削除され、デプロイに失敗することがあります。