diff --git a/node.js/messaging.md b/node.js/messaging.md
index 74a18b65b..3656b2a80 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
@@ -55,7 +146,7 @@ In _srv/external/external.cds_:
service ExternalService {
event ExternalEvent {
ID: UUID;
- name: String;
+ rating: Decimal;
}
}
```
@@ -66,7 +157,7 @@ In _srv/own.cds_:
service OwnService {
event OwnEvent {
ID: UUID;
- name: String;
+ rating: Decimal;
}
}
```
@@ -94,7 +185,7 @@ Example:
```cds
service OwnService {
@topic: 'my.custom.topic'
- event OwnEvent { ID: UUID; name: String; }
+ event OwnEvent { ID: UUID; rating: Decimal; }
}
```
@@ -109,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'] }})
})
```
@@ -141,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 }))
})
```