Skip to content

[FEATURE] Generate ORD Integration Dependencies for consumed Event Broker events #372

@f-nie

Description

@f-nie

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions