From 69ac1c2634a092a20a07aecb2139b79968096124 Mon Sep 17 00:00:00 2001 From: D051920 Date: Wed, 10 Dec 2025 17:01:16 +0100 Subject: [PATCH 01/17] wip: Improve messaging docs --- node.js/messaging.md | 95 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index 74a18b65b..a0d300176 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -14,11 +14,102 @@ status: released +## Overview + +In SAP Cloud Application Programming Model (CAP), messaging enables decoupled communication between services using events. +CAP distinguishes between the logical and technical messaging layers, separating business concerns from technical infrastructure and enabling both flexibility and scalability. + +The **logical layer** consists of three primary components: + +**Modeled Events**: Events are defined in CDS models with typed schemas, providing compile-time validation and IDE support. These events represent business occurrences like `'orderProcessed'`, or `'stockUpdated'`. + +**Event Topics**: Topics organize events into logical channels, allowing for structured event categorization and routing. Topics can be explicitly defined or derived from service and event names. + +**CAP Services**: Services act as event producers and consumers, using simple APIs like `srv.emit('reviewed', data)` and `srv.on('orderProcessed', handler)`. Services communicate using friendly event names without needing to know the underlying infrastructure details. + +The **technical layer** handles the actual message transport and delivery: + +**CAP Messaging Service**: Acts as the translation layer between logical events and technical infrastructure. It converts service-level event names to fully qualified names (e.g., `'OrderSrv.reviewed'`), manages topic resolution, message serialization, and routing logic. + +**Message Brokers**: Form the core of the technical infrastructure, handling message persistence, delivery guarantees, and cross-service communication. Examples include SAP Event Mesh, Apache Kafka, or Redis Streams. + +The message flow follows a clear path through both layers: + +**Outbound Flow (Publisher)**: A service calls `srv.emit('reviewed', data)` → CAP Messaging Service resolves the event name to a fully qualified topic (e.g., `OrderSrv.reviewed`) → Message is serialized and sent to the Event Broker → Broker stores and distributes the message to all subscribers. + +**Inbound Flow (Subscriber)**: Event Broker delivers message from subscribed topic → CAP Messaging Service receives the message → Service name and event name are resolved from the topic → Message is routed to the appropriate service handler via `srv.on('reviewed', handler)`. + +**Alternatively** custom handlers can bypass the service layer and work directly with the messaging service: + +```javascript +// Direct access to messaging service +const messaging = await cds.connect.to('messaging'); + +// Send messages directly with full topic control +await messaging.emit('custom.topic', data); + +// Receive messages directly from any topic +messaging.on('external.system.events', handler); +``` + +### Topic Resolution + +Topic resolution is an important part of the integration between logical and technical messaging layers. + +Every technical message requires a topic. If a CDS event is declared without an @topic annotation, CAP uses the event’s fully qualified name (FQN) as the topic (e.g., OrderSrv.reviewed). If an event is declared with an @topic: 'foo.bar' annotation, the specified string is used as the topic (e.g., foo.bar). + +### Emitting and Receiving Events + +#### Scenario 1: No @topic Annotation + +When a CDS event is declared without an @topic annotation in a service (for example, an event called 'reviewed' in the OrderSrv service), the messaging behavior follows this pattern: + +**Emitting Events:** +• Using the logical service API, you emit the event with just the event name ('reviewed') +• Using the technical messaging API, you emit using the fully qualified name ('OrderSrv.reviewed') + +**Broker Handling:** +• The message broker receives and routes the message using the topic 'OrderSrv.reviewed' + +**Receiving Events:** +• Using the logical service API, you listen for the event with just the event name ('reviewed') +• Using the technical messaging API, you listen using the fully qualified name ('OrderSrv.reviewed') + +#### Scenario 2: With @topic Annotation + +When a CDS event is declared with a custom @topic annotation (for example, an event called 'reviewed' with @topic: 'foo.bar' in the OrderSrv service), the messaging behavior follows this pattern: + +**Emitting Events:** +• Using the logical service API, you still emit the event with the event name ('reviewed') +• Using the technical messaging API, you emit using the custom topic name ('foo.bar') + +**Broker Handling:** +• The message broker receives and routes the message using the custom topic 'foo.bar' + +**Receiving Events:** +• Using the logical service API, you listen for the event with the event name ('reviewed') +• Using the technical messaging API, you listen using the custom topic name ('foo.bar') + +### Summary Table + + +| CDS Event Declaration | Emitting via `srv.emit` | Emitting via `messaging.emit` | Broker Topic | Receiving via `srv.on` | Receiving via `messaging.on` | +|------------------------------|-------------------------|-------------------------------|----------------------|------------------------|------------------------------| +| No `@topic` | `'reviewed'` | `'OrderSrv.reviewed'` | `OrderSrv.reviewed` | `'reviewed'` | `'OrderSrv.reviewed'` | +| With `@topic: 'foo.bar'` | `'reviewed'` | `'foo.bar'` | `foo.bar` | `'reviewed'` | `'foo.bar'` | + + +### Key Points + +- Logical service API (srv.emit, srv.on) uses the event name as declared in CDS. +- Technical messaging API (messaging.emit, messaging.on) uses the resolved topic name. +- If no @topic is specified, the topic defaults to the event’s fully qualified name. +- If @topic is specified, it overrides the default topic name. ## cds.**MessagingService** class - Class `cds.MessagingService` and subclasses thereof are technical services representing asynchronous messaging channels. - They can be used directly/low-level, or behind the scenes on higher-level service-to-service eventing. +Class `cds.MessagingService` and subclasses thereof are technical services representing asynchronous messaging channels. +They can be used directly/low-level, or behind the scenes on higher-level service-to-service eventing. ### class cds.**MessagingService** extends cds.Service From 63a1b15c6e5cd967a89d532047bc7e857661cfac Mon Sep 17 00:00:00 2001 From: D051920 Date: Thu, 11 Dec 2025 08:50:09 +0100 Subject: [PATCH 02/17] small changes --- node.js/messaging.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index a0d300176..3656b2a80 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -146,7 +146,7 @@ In _srv/external/external.cds_: service ExternalService { event ExternalEvent { ID: UUID; - name: String; + rating: Decimal; } } ``` @@ -157,7 +157,7 @@ In _srv/own.cds_: service OwnService { event OwnEvent { ID: UUID; - name: String; + rating: Decimal; } } ``` @@ -185,7 +185,7 @@ Example: ```cds service OwnService { @topic: 'my.custom.topic' - event OwnEvent { ID: UUID; name: String; } + event OwnEvent { ID: UUID; rating: Decimal; } } ``` @@ -200,19 +200,19 @@ Example: const messaging = await cds.connect.to('messaging') this.after(['CREATE', 'UPDATE', 'DELETE'], 'Reviews', async (_, req) => { - const { subject } = req.data + const { ID } = req.data const { rating } = await cds.run( SELECT.one(['round(avg(rating),2) as rating']) .from(Reviews) - .where({ subject })) + .where({ ID })) // send to a topic - await messaging.emit('cap/msg/system/review/reviewed', - { subject, rating }) + await messaging.emit('cap/msg/system/my/custom/topic', + { ID, rating }) // alternative if you want to send custom headers - await messaging.emit({ event: 'cap/msg/system/review/reviewed', - data: { subject, rating }, + await messaging.emit({ event: 'cap/msg/system/my/custom/topic', + data: { ID, rating }, headers: { 'X-Correlation-ID': req.headers['X-Correlation-ID'] }}) }) ``` @@ -232,9 +232,9 @@ Example: const messaging = await cds.connect.to('messaging') // listen to a topic -messaging.on('cap/msg/system/review/reviewed', msg => { - const { subject, rating } = msg.data - return cds.run(UPDATE(Books, subject).with({ rating })) +messaging.on('cap/msg/system/my/custom/topic', msg => { + const { ID, rating } = msg.data + return cds.run(UPDATE(Books, ID).with({ rating })) }) ``` From f10625641e35fa48c557e31451810089d6a9306c Mon Sep 17 00:00:00 2001 From: Vitaly Kozyura <58591662+vkozyura@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:18:11 +0100 Subject: [PATCH 03/17] Update node.js/messaging.md Co-authored-by: Dr. David A. Kunz --- node.js/messaging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index 3656b2a80..fbad7abe0 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -16,7 +16,7 @@ status: released ## Overview -In SAP Cloud Application Programming Model (CAP), messaging enables decoupled communication between services using events. +Messaging enables decoupled communication between services using events. CAP distinguishes between the logical and technical messaging layers, separating business concerns from technical infrastructure and enabling both flexibility and scalability. The **logical layer** consists of three primary components: From 275d192b13206c88668d460c810f610a66fd8eda Mon Sep 17 00:00:00 2001 From: Vitaly Kozyura <58591662+vkozyura@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:18:51 +0100 Subject: [PATCH 04/17] Update node.js/messaging.md Co-authored-by: Dr. David A. Kunz --- node.js/messaging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index fbad7abe0..0c53b7d7e 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -17,7 +17,7 @@ status: released ## Overview Messaging enables decoupled communication between services using events. -CAP distinguishes between the logical and technical messaging layers, separating business concerns from technical infrastructure and enabling both flexibility and scalability. +CAP distinguishes between the logical and technical messaging layers, separating business concerns from technical infrastructure. The **logical layer** consists of three primary components: From eaa7b364f3b585ca7d6f8f899a97716e517d47a5 Mon Sep 17 00:00:00 2001 From: Vitaly Kozyura <58591662+vkozyura@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:28:08 +0100 Subject: [PATCH 05/17] Update messaging.md --- node.js/messaging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index 0c53b7d7e..31a39934e 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -23,7 +23,7 @@ The **logical layer** consists of three primary components: **Modeled Events**: Events are defined in CDS models with typed schemas, providing compile-time validation and IDE support. These events represent business occurrences like `'orderProcessed'`, or `'stockUpdated'`. -**Event Topics**: Topics organize events into logical channels, allowing for structured event categorization and routing. Topics can be explicitly defined or derived from service and event names. +**Event Topics**: Topics organize events into logical channels and are responsible for routing. Topics can be explicitly defined as anootation or derived from service and event names. **CAP Services**: Services act as event producers and consumers, using simple APIs like `srv.emit('reviewed', data)` and `srv.on('orderProcessed', handler)`. Services communicate using friendly event names without needing to know the underlying infrastructure details. From b5a67866d4b910c1656eb8c567767d27e08a0070 Mon Sep 17 00:00:00 2001 From: Vitaly Kozyura <58591662+vkozyura@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:28:35 +0100 Subject: [PATCH 06/17] Update messaging.md --- node.js/messaging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index 31a39934e..f9bf2fc1e 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -23,7 +23,7 @@ The **logical layer** consists of three primary components: **Modeled Events**: Events are defined in CDS models with typed schemas, providing compile-time validation and IDE support. These events represent business occurrences like `'orderProcessed'`, or `'stockUpdated'`. -**Event Topics**: Topics organize events into logical channels and are responsible for routing. Topics can be explicitly defined as anootation or derived from service and event names. +**Event Topics**: Topics organize events into logical channels and are responsible for event routing. Topics can be explicitly defined as anootation or derived from service and event names. **CAP Services**: Services act as event producers and consumers, using simple APIs like `srv.emit('reviewed', data)` and `srv.on('orderProcessed', handler)`. Services communicate using friendly event names without needing to know the underlying infrastructure details. From b618d1de6c333f7b09ea7f99e4b3d54ff70152ba Mon Sep 17 00:00:00 2001 From: Vitaly Kozyura <58591662+vkozyura@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:30:46 +0100 Subject: [PATCH 07/17] Update node.js/messaging.md Co-authored-by: Dr. David A. Kunz --- node.js/messaging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index f9bf2fc1e..c0dc61609 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -25,7 +25,7 @@ The **logical layer** consists of three primary components: **Event Topics**: Topics organize events into logical channels and are responsible for event routing. Topics can be explicitly defined as anootation or derived from service and event names. -**CAP Services**: Services act as event producers and consumers, using simple APIs like `srv.emit('reviewed', data)` and `srv.on('orderProcessed', handler)`. Services communicate using friendly event names without needing to know the underlying infrastructure details. +**CAP Services**: Services act as event producers or consumers, using simple APIs like `srv.emit('reviewed', data)` or `srv.on('orderProcessed', handler)`. Services communicate using friendly event names without needing to know the underlying infrastructure details. The **technical layer** handles the actual message transport and delivery: From 07d3f303d3d73e388131aed0df45c259655c1f1e Mon Sep 17 00:00:00 2001 From: Vitaly Kozyura <58591662+vkozyura@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:31:11 +0100 Subject: [PATCH 08/17] Update messaging.md --- node.js/messaging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index c0dc61609..d2ab5aa25 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -25,7 +25,7 @@ The **logical layer** consists of three primary components: **Event Topics**: Topics organize events into logical channels and are responsible for event routing. Topics can be explicitly defined as anootation or derived from service and event names. -**CAP Services**: Services act as event producers or consumers, using simple APIs like `srv.emit('reviewed', data)` or `srv.on('orderProcessed', handler)`. Services communicate using friendly event names without needing to know the underlying infrastructure details. +**CAP Services**: Services act as event producers or consumers, using simple APIs like `srv.emit('reviewed', data)` or `srv.on('orderProcessed', handler)`. Services communicate using logical event names without needing to know the underlying infrastructure details. The **technical layer** handles the actual message transport and delivery: From 7cd76f95a8987f2fd89d681fa5abad127c07f28e Mon Sep 17 00:00:00 2001 From: Vitaly Kozyura <58591662+vkozyura@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:39:01 +0100 Subject: [PATCH 09/17] Update messaging.md --- node.js/messaging.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index d2ab5aa25..1364c3ddf 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -29,7 +29,8 @@ The **logical layer** consists of three primary components: The **technical layer** handles the actual message transport and delivery: -**CAP Messaging Service**: Acts as the translation layer between logical events and technical infrastructure. It converts service-level event names to fully qualified names (e.g., `'OrderSrv.reviewed'`), manages topic resolution, message serialization, and routing logic. +**CAP Messaging Service**: Acts as the translation layer between logical events and technical infrastructure. It manages topic resolution, message serialization, and routing logic. +For topic resolution the logical events are delegated to the messaging service, the corresponding event name on the technical service is either the fully qualified event name or the value of the @topic annotation if given. **Message Brokers**: Form the core of the technical infrastructure, handling message persistence, delivery guarantees, and cross-service communication. Examples include SAP Event Mesh, Apache Kafka, or Redis Streams. From f2d792c5feb983fc709a70b7396cbbc3c0f545c0 Mon Sep 17 00:00:00 2001 From: Vitaly Kozyura <58591662+vkozyura@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:44:23 +0100 Subject: [PATCH 10/17] Update messaging.md --- node.js/messaging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index 1364c3ddf..be22e8ef1 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -36,7 +36,7 @@ For topic resolution the logical events are delegated to the messaging service, The message flow follows a clear path through both layers: -**Outbound Flow (Publisher)**: A service calls `srv.emit('reviewed', data)` → CAP Messaging Service resolves the event name to a fully qualified topic (e.g., `OrderSrv.reviewed`) → Message is serialized and sent to the Event Broker → Broker stores and distributes the message to all subscribers. +**Outbound Flow (Publisher)**: A CAP service calls `srv.emit('reviewed', data)` → CAP Messaging Service resolves the event name to a fully qualified topic (e.g., `OrderSrv.reviewed`) → Message is serialized and sent to the Event Broker → Broker stores and distributes the message to all subscribers. **Inbound Flow (Subscriber)**: Event Broker delivers message from subscribed topic → CAP Messaging Service receives the message → Service name and event name are resolved from the topic → Message is routed to the appropriate service handler via `srv.on('reviewed', handler)`. From f2e0389f3d446ed57a97b21ba8a0b8debe6901bc Mon Sep 17 00:00:00 2001 From: Vitaly Kozyura <58591662+vkozyura@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:56:07 +0100 Subject: [PATCH 11/17] Update messaging.md --- node.js/messaging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index be22e8ef1..5f5449c37 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -38,7 +38,7 @@ The message flow follows a clear path through both layers: **Outbound Flow (Publisher)**: A CAP service calls `srv.emit('reviewed', data)` → CAP Messaging Service resolves the event name to a fully qualified topic (e.g., `OrderSrv.reviewed`) → Message is serialized and sent to the Event Broker → Broker stores and distributes the message to all subscribers. -**Inbound Flow (Subscriber)**: Event Broker delivers message from subscribed topic → CAP Messaging Service receives the message → Service name and event name are resolved from the topic → Message is routed to the appropriate service handler via `srv.on('reviewed', handler)`. +**Inbound Flow (Subscriber)**: Event Broker delivers message from subscribed topic → CAP Messaging Service receives the message → Service name and event name are resolved from the topic → Message is routed to the appropriate CAP service handler via `srv.on('reviewed', handler)`. Registering a modeled `srv.on(...)` event handler causes the broker to listen to those events, e.g. creates a subscription for Event Mesh. **Alternatively** custom handlers can bypass the service layer and work directly with the messaging service: From a02abbfd894db4d32d78f4f674456322aec7f533 Mon Sep 17 00:00:00 2001 From: Vitaly Kozyura <58591662+vkozyura@users.noreply.github.com> Date: Thu, 8 Jan 2026 10:59:12 +0100 Subject: [PATCH 12/17] Update messaging.md --- node.js/messaging.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index 5f5449c37..74d5f7944 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -40,18 +40,7 @@ The message flow follows a clear path through both layers: **Inbound Flow (Subscriber)**: Event Broker delivers message from subscribed topic → CAP Messaging Service receives the message → Service name and event name are resolved from the topic → Message is routed to the appropriate CAP service handler via `srv.on('reviewed', handler)`. Registering a modeled `srv.on(...)` event handler causes the broker to listen to those events, e.g. creates a subscription for Event Mesh. -**Alternatively** custom handlers can bypass the service layer and work directly with the messaging service: - -```javascript -// Direct access to messaging service -const messaging = await cds.connect.to('messaging'); - -// Send messages directly with full topic control -await messaging.emit('custom.topic', data); - -// Receive messages directly from any topic -messaging.on('external.system.events', handler); -``` +**Alternatively** custom handlers can bypass the service layer and work directly with the messaging service. ### Topic Resolution From 01fb52530f770e27f091fa504a4125b1701cc87e Mon Sep 17 00:00:00 2001 From: Vitaly Kozyura <58591662+vkozyura@users.noreply.github.com> Date: Thu, 8 Jan 2026 11:23:42 +0100 Subject: [PATCH 13/17] Update messaging.md --- node.js/messaging.md | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index 74d5f7944..9ed2e70cc 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -42,44 +42,6 @@ The message flow follows a clear path through both layers: **Alternatively** custom handlers can bypass the service layer and work directly with the messaging service. -### Topic Resolution - -Topic resolution is an important part of the integration between logical and technical messaging layers. - -Every technical message requires a topic. If a CDS event is declared without an @topic annotation, CAP uses the event’s fully qualified name (FQN) as the topic (e.g., OrderSrv.reviewed). If an event is declared with an @topic: 'foo.bar' annotation, the specified string is used as the topic (e.g., foo.bar). - -### Emitting and Receiving Events - -#### Scenario 1: No @topic Annotation - -When a CDS event is declared without an @topic annotation in a service (for example, an event called 'reviewed' in the OrderSrv service), the messaging behavior follows this pattern: - -**Emitting Events:** -• Using the logical service API, you emit the event with just the event name ('reviewed') -• Using the technical messaging API, you emit using the fully qualified name ('OrderSrv.reviewed') - -**Broker Handling:** -• The message broker receives and routes the message using the topic 'OrderSrv.reviewed' - -**Receiving Events:** -• Using the logical service API, you listen for the event with just the event name ('reviewed') -• Using the technical messaging API, you listen using the fully qualified name ('OrderSrv.reviewed') - -#### Scenario 2: With @topic Annotation - -When a CDS event is declared with a custom @topic annotation (for example, an event called 'reviewed' with @topic: 'foo.bar' in the OrderSrv service), the messaging behavior follows this pattern: - -**Emitting Events:** -• Using the logical service API, you still emit the event with the event name ('reviewed') -• Using the technical messaging API, you emit using the custom topic name ('foo.bar') - -**Broker Handling:** -• The message broker receives and routes the message using the custom topic 'foo.bar' - -**Receiving Events:** -• Using the logical service API, you listen for the event with the event name ('reviewed') -• Using the technical messaging API, you listen using the custom topic name ('foo.bar') - ### Summary Table From 88773381bfa08c892ebf634a163f601b06365937 Mon Sep 17 00:00:00 2001 From: D051920 Date: Thu, 8 Jan 2026 16:05:56 +0100 Subject: [PATCH 14/17] move cloudevents --- node.js/messaging.md | 176 +++++++++++++++++++++++-------------------- 1 file changed, 94 insertions(+), 82 deletions(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index 9ed2e70cc..3a7da83eb 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -114,7 +114,9 @@ service OwnService { } ``` -In _srv/own.js_: +The implementation can use CAP application services or bypass them and work directly with the messaging service. + +In _srv/own.js_ (CAP Application Services): ```js module.exports = async srv => { @@ -125,7 +127,18 @@ module.exports = async srv => { } ``` -#### Custom Topics with Declared Events +In _srv/own.js_ (CAP Messaging Services): +```js +module.exports = async srv => { + const externalService = await cds.connect.to('messaging') + messaging.on('ExternalService.ExternalEvent', async msg => { + await srv.emit('OwnService.OwnEvent', msg.data) + }) +} +``` + + +### Custom Topics with Declared Events You can specify topics to modeled events using the `@topic` annotation. ::: tip @@ -141,86 +154,6 @@ service OwnService { } ``` - -## Emitting Events - -To send a message to the message broker, you can use the `emit` method on a transaction for the connected service. - -Example: - -```js -const messaging = await cds.connect.to('messaging') - -this.after(['CREATE', 'UPDATE', 'DELETE'], 'Reviews', async (_, req) => { - const { ID } = req.data - const { rating } = await cds.run( - SELECT.one(['round(avg(rating),2) as rating']) - .from(Reviews) - .where({ ID })) - - // send to a topic - await messaging.emit('cap/msg/system/my/custom/topic', - { ID, rating }) - - // alternative if you want to send custom headers - await messaging.emit({ event: 'cap/msg/system/my/custom/topic', - data: { ID, rating }, - headers: { 'X-Correlation-ID': req.headers['X-Correlation-ID'] }}) -}) -``` -::: tip -The messages are sent once the transaction is successful. -Per default, a persistent queue is used. See [Messaging - Queue](./queue) for more information. -::: - -## Receiving Events - -To listen to messages from a message broker, you can use the `on` method on the connected service. -This also creates the necessary topic subscriptions. - -Example: - -```js -const messaging = await cds.connect.to('messaging') - -// listen to a topic -messaging.on('cap/msg/system/my/custom/topic', msg => { - const { ID, rating } = msg.data - return cds.run(UPDATE(Books, ID).with({ rating })) -}) -``` - -Once all handlers are executed successfully, the message is acknowledged. -If one handler throws an error, the message broker will be informed that the message couldn't be consumed properly and might send the message again. To avoid endless cycles, consider catching all errors. - -If you want to receive all messages without creating topic subscriptions, you can register on `'*'`. This is useful when consuming messages from a dead letter queue. - -```js -messaging.on('*', async msg => { /*...*/ }) -``` - -::: tip -In general, messages do not contain user information but operate with a technical user. As a consequence, the user of the message processing context (`cds.context.user`) is set to [`cds.User.privileged`](/node.js/authentication#privileged-user) and, hence, any necessary authorization checks must be done in custom handlers. -::: - -### Inbox - -You can store received messages in an inbox before they're processed. Under the hood, it uses the [task queue](./queue) for reliable asynchronous processing. -Enable it by setting the `inboxed` option to `true`, for example: - -```js -{ - cds: { - requires: { - messaging: { - kind: 'enterprise-messaging', - inboxed: true - } - } - } -} -``` - ## CloudEvents Protocol [CloudEvents](https://cloudevents.io/) is a commonly used specification for describing event data. @@ -306,6 +239,85 @@ Examples: | `my/own.namespace/-/ce/` | `/my/own.namespace` | +## Emitting Events + +To send a message to the message broker, you can use the `emit` method on a transaction for the connected service. + +Example: + +```js +const messaging = await cds.connect.to('messaging') + +this.after(['CREATE', 'UPDATE', 'DELETE'], 'Reviews', async (_, req) => { + const { ID } = req.data + const { rating } = await cds.run( + SELECT.one(['round(avg(rating),2) as rating']) + .from(Reviews) + .where({ ID })) + + // send to a topic + await messaging.emit('my/custom/topic', + { ID, rating }) + + // alternative if you want to send custom headers + await messaging.emit({ event: 'my/custom/topic', + data: { ID, rating }, + headers: { 'X-Correlation-ID': req.headers['X-Correlation-ID'] }}) +}) +``` +::: tip +The messages are sent once the transaction is successful. +Per default, a persistent queue is used. See [Messaging - Queue](./queue) for more information. +::: + +## Receiving Events + +To listen to messages from a message broker, you can use the `on` method on the connected service. +This also creates the necessary topic subscriptions. + +Example: + +```js +const messaging = await cds.connect.to('messaging') + +// listen to a topic +messaging.on('my/custom/topic', msg => { + const { ID, rating } = msg.data + return cds.run(UPDATE(Books, ID).with({ rating })) +}) +``` + +Once all handlers are executed successfully, the message is acknowledged. +If one handler throws an error, the message broker will be informed that the message couldn't be consumed properly and might send the message again. To avoid endless cycles, consider catching all errors. + +If you want to receive all messages without creating topic subscriptions, you can register on `'*'`. This is useful when consuming messages from a dead letter queue. + +```js +messaging.on('*', async msg => { /*...*/ }) +``` + +::: tip +In general, messages do not contain user information but operate with a technical user. As a consequence, the user of the message processing context (`cds.context.user`) is set to [`cds.User.privileged`](/node.js/authentication#privileged-user) and, hence, any necessary authorization checks must be done in custom handlers. +::: + +### Inbox + +You can store received messages in an inbox before they're processed. Under the hood, it uses the [task queue](./queue) for reliable asynchronous processing. +Enable it by setting the `inboxed` option to `true`, for example: + +```js +{ + cds: { + requires: { + messaging: { + kind: 'enterprise-messaging', + inboxed: true + } + } + } +} +``` + ## Message Brokers To safely send and receive messages between applications, you need a message broker in-between where you can create queues that listen to topics. All relevant incoming messages are first stored in those queues before they're consumed. This way messages aren't lost when the consuming application isn't available. From 047a59b0580d4b48b738b3552facfb34b113d0ce Mon Sep 17 00:00:00 2001 From: D051920 Date: Thu, 8 Jan 2026 16:10:02 +0100 Subject: [PATCH 15/17] add header variant --- node.js/messaging.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/node.js/messaging.md b/node.js/messaging.md index 3a7da83eb..adca3f7b2 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -260,6 +260,11 @@ this.after(['CREATE', 'UPDATE', 'DELETE'], 'Reviews', async (_, req) => { { ID, rating }) // alternative if you want to send custom headers + await messaging.emit('my/custom/topic', + { ID, rating }, + { 'X-Correlation-ID': req.headers['X-Correlation-ID'] }) + + // or use the object parameter await messaging.emit({ event: 'my/custom/topic', data: { ID, rating }, headers: { 'X-Correlation-ID': req.headers['X-Correlation-ID'] }}) From 0b9670ad093b427fa2521ab3ce8abc5ea47c47b6 Mon Sep 17 00:00:00 2001 From: D051920 Date: Thu, 8 Jan 2026 16:12:19 +0100 Subject: [PATCH 16/17] remove key points --- node.js/messaging.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index adca3f7b2..276768bbd 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -51,13 +51,6 @@ The message flow follows a clear path through both layers: | With `@topic: 'foo.bar'` | `'reviewed'` | `'foo.bar'` | `foo.bar` | `'reviewed'` | `'foo.bar'` | -### Key Points - -- Logical service API (srv.emit, srv.on) uses the event name as declared in CDS. -- Technical messaging API (messaging.emit, messaging.on) uses the resolved topic name. -- If no @topic is specified, the topic defaults to the event’s fully qualified name. -- If @topic is specified, it overrides the default topic name. - ## cds.**MessagingService** class Class `cds.MessagingService` and subclasses thereof are technical services representing asynchronous messaging channels. From 4622b2d1e5c9ee999e1a8d5cb88f8763deceb2ab Mon Sep 17 00:00:00 2001 From: Vitaly Kozyura <58591662+vkozyura@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:22:16 +0100 Subject: [PATCH 17/17] Update node.js/messaging.md Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- node.js/messaging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js/messaging.md b/node.js/messaging.md index 29e75c676..8e8fb3602 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -23,7 +23,7 @@ The **logical layer** consists of three primary components: **Modeled Events**: Events are defined in CDS models with typed schemas, providing compile-time validation and IDE support. These events represent business occurrences like `'orderProcessed'`, or `'stockUpdated'`. -**Event Topics**: Topics organize events into logical channels and are responsible for event routing. Topics can be explicitly defined as anootation or derived from service and event names. +**Event Topics**: Topics organize events into logical channels and are responsible for event routing. Topics can be explicitly defined as annotation or derived from service and event names. **CAP Services**: Services act as event producers or consumers, using simple APIs like `srv.emit('reviewed', data)` or `srv.on('orderProcessed', handler)`. Services communicate using logical event names without needing to know the underlying infrastructure details.