Description
Applications using @cap-js/event-broker to consume external CloudEvents can now document these dependencies in their ORD metadata. The ORD plugin generates integrationDependencies that describe which external events an application consumes.
Use Cases
- A CAP application subscribes to S/4HANA Business Partner change events via SAP Cloud Application Event Hub
- The application's ORD document declares this dependency for discoverability and governance
- External tools (like SAP Business Accelerator Hub) can display which events an application depends on
Benefits
- Complete ORD metadata for Event Broker (Event Hub) consumers
- Improved discoverability and transparency of event-driven integrations
- Alignment with the Java CAP plugin (
cds-feature-event-hub)
Suggested Solution
Architecture: Extension Registry Pattern
Instead of adding Event Broker detection logic directly to the ORD plugin (as originally proposed), we implemented an Extension Registry that allows external plugins to contribute Integration Dependency data:
┌─────────────────────────────────────────────────────────────────┐
│ Application │
├─────────────────────────────────────────────────────────────────┤
│ srv/server.js │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ messaging.subscribe("sap.s4...SalesOrder.Changed.v1", { ││
│ │ eventResourceOrdId: "sap.s4:eventResource:CE_SALES..." ││
│ │ }, handler) ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ @cap-js/event-broker │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 1. Collects eventResourceOrdId from subscribe() calls ││
│ │ 2. Groups event types by event resource ││
│ │ 3. Registers provider with ORD plugin ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ @cap-js/ord │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Extension Registry ││
│ │ - registerIntegrationDependencyProvider(fn) ││
│ │ - getProvidedIntegrationDependencies() ││
│ └─────────────────────────────────────────────────────────────┘│
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ integrationDependency.js ││
│ │ - createEventIntegrationDependency() ││
│ │ - Builds ORD subset structure from provider data ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ ORD Document │
│ { │
│ "integrationDependencies": [{ │
│ "ordId": "customer.app:integrationDependency:consumed...", │
│ "aspects": [{ │
│ "eventResources": [{ │
│ "ordId": "sap.s4:eventResource:CE_SALESORDER...", │
│ "subset": [{ "eventType": "sap.s4.beh.sales..." }] │
│ }] │
│ }] │
│ }] │
│ } │
└─────────────────────────────────────────────────────────────────┘
Using messaging.subscribe()
The subscribe() method consolidates event subscription and ORD declaration in a single place:
// srv/server.js
const cds = require("@sap/cds");
cds.on("loaded", async () => {
const messaging = await cds.connect.to("messaging");
// Subscribe to event with ORD Integration Dependency metadata
messaging.subscribe(
"sap.s4.beh.salesorder.v1.SalesOrder.Changed.v1",
{
eventResourceOrdId: "sap.s4:eventResource:CE_SALESORDEREVENTS:v1",
},
async (event) => {
console.log("Event received:", event);
},
);
// Multiple events can share the same event resource
messaging.subscribe(
"sap.s4.beh.salesorder.v1.SalesOrder.Created.v1",
{
eventResourceOrdId: "sap.s4:eventResource:CE_SALESORDEREVENTS:v1",
},
async (event) => {
console.log("Event received:", event);
},
);
});
Integration Dependency Generation
The generated ORD structure:
{
"integrationDependencies": [
{
"ordId": "customer.app:integrationDependency:consumedEvents:v1",
"title": "Consumed Events",
"version": "1.0.0",
"releaseStatus": "active",
"visibility": "public",
"partOfPackage": "customer.app:package:app-integrationDependency:v1",
"aspects": [
{
"title": "Subscribed Event Types",
"mandatory": false,
"eventResources": [
{
"ordId": "sap.s4:eventResource:CE_SALESORDEREVENTS:v1",
"subset": [
{
"eventType": "sap.s4.beh.salesorder.v1.SalesOrder.Changed.v1"
},
{
"eventType": "sap.s4.beh.salesorder.v1.SalesOrder.Created.v1"
}
]
}
]
}
]
}
]
}
Namespace Handling
integrationDependency.ordId: Uses consuming application's ORD namespace
eventResource.ordId: Uses external source namespace (from eventResourceOrdId parameter)
This matches the semantic meaning: the integration dependency belongs to the consuming app, while the event resource reference points to the external system.
Comparison: Original Proposal vs. Implementation
| Aspect |
Original Proposal |
Implementation |
| Event Detection |
cds.services.*.subscribedTopics + annotations + config |
messaging.subscribe() with eventResourceOrdId |
| Configuration |
cds.ord.consumedEventTypes in package.json |
Inline in subscribe() call |
| Event Broker Logic |
In ORD plugin (lib/eventBrokerAdapter.js) |
In Event Broker plugin (cds-plugin.js) |
| Plugin Coupling |
ORD plugin depends on Event Broker knowledge |
Loose coupling via Extension Registry |
| Developer UX |
Multiple files (CDS + config) |
Single location (code) |
Comparison with Java Plugin
| Feature |
Java (cds-feature-event-hub) |
Node.js (implemented) |
| Runtime Support |
✅ SPI-based |
✅ Extension Registry |
| Build-Time Support |
❌ |
❌ |
| Configuration |
No config (auto-detection) |
eventResourceOrdId |
| Event Detection |
Spring bean introspection |
subscribe() calls |
| Multiple Services |
Aggregated |
Aggregated |
Files Changed
@cap-js/ord
| File |
Change |
lib/extensionRegistry.js |
New: Extension Registry API |
lib/integrationDependency.js |
Modified: Added createEventIntegrationDependency() |
cds-plugin.js |
Modified: Export registerIntegrationDependencyProvider() |
__tests__/unit/extensionRegistry.test.js |
New: Unit tests |
__tests__/unit/extensionRegistry.integration.test.js |
New: Integration tests |
@cap-js/event-broker
| File |
Change |
cds-plugin.js |
Modified: Added subscribe() method with ORD support |
README.md |
Modified: Updated ORD Integration documentation |
Example Configuration
Service Implementation (srv/server.js)
const cds = require("@sap/cds");
cds.on("loaded", async () => {
const messaging = await cds.connect.to("messaging");
messaging.subscribe(
"sap.s4.beh.salesorder.v1.SalesOrder.Changed.v1",
{
eventResourceOrdId: "sap.s4:eventResource:CE_SALESORDEREVENTS:v1",
},
async (msg) => {
// Handle event
},
);
});
Event Broker Configuration (package.json)
{
"cds": {
"requires": {
"messaging": {
"kind": "event-broker"
}
}
}
}
No additional ORD configuration needed - the annotation provides the mapping.
Related Issues
Description
Applications using
@cap-js/event-brokerto consume external CloudEvents can now document these dependencies in their ORD metadata. The ORD plugin generatesintegrationDependenciesthat describe which external events an application consumes.Use Cases
Benefits
cds-feature-event-hub)Suggested Solution
Architecture: Extension Registry Pattern
Instead of adding Event Broker detection logic directly to the ORD plugin (as originally proposed), we implemented an Extension Registry that allows external plugins to contribute Integration Dependency data:
Using messaging.subscribe()
The
subscribe()method consolidates event subscription and ORD declaration in a single place:Integration Dependency Generation
The generated ORD structure:
{ "integrationDependencies": [ { "ordId": "customer.app:integrationDependency:consumedEvents:v1", "title": "Consumed Events", "version": "1.0.0", "releaseStatus": "active", "visibility": "public", "partOfPackage": "customer.app:package:app-integrationDependency:v1", "aspects": [ { "title": "Subscribed Event Types", "mandatory": false, "eventResources": [ { "ordId": "sap.s4:eventResource:CE_SALESORDEREVENTS:v1", "subset": [ { "eventType": "sap.s4.beh.salesorder.v1.SalesOrder.Changed.v1" }, { "eventType": "sap.s4.beh.salesorder.v1.SalesOrder.Created.v1" } ] } ] } ] } ] }Namespace Handling
integrationDependency.ordId: Uses consuming application's ORD namespaceeventResource.ordId: Uses external source namespace (fromeventResourceOrdIdparameter)This matches the semantic meaning: the integration dependency belongs to the consuming app, while the event resource reference points to the external system.
Comparison: Original Proposal vs. Implementation
cds.services.*.subscribedTopics+ annotations + configmessaging.subscribe()witheventResourceOrdIdcds.ord.consumedEventTypesin package.jsonsubscribe()calllib/eventBrokerAdapter.js)cds-plugin.js)Comparison with Java Plugin
cds-feature-event-hub)eventResourceOrdIdsubscribe()callsFiles Changed
@cap-js/ord
lib/extensionRegistry.jslib/integrationDependency.jscreateEventIntegrationDependency()cds-plugin.jsregisterIntegrationDependencyProvider()__tests__/unit/extensionRegistry.test.js__tests__/unit/extensionRegistry.integration.test.js@cap-js/event-broker
cds-plugin.jssubscribe()method with ORD supportREADME.mdExample Configuration
Service Implementation (srv/server.js)
Event Broker Configuration (package.json)
{ "cds": { "requires": { "messaging": { "kind": "event-broker" } } } }No additional ORD configuration needed - the annotation provides the mapping.
Related Issues