-
Notifications
You must be signed in to change notification settings - Fork 138
wip: Improve messaging docs #2265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -14,11 +14,102 @@ status: released | |||||
| <!--- % include links-for-node.md %} --> | ||||||
| <!--- % include _chapters toc="2,3" %} --> | ||||||
|
|
||||||
| ## 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. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Don't add AI slop ;) |
||||||
|
|
||||||
| 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. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "structured event categorization"? What is that? |
||||||
|
|
||||||
| **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. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| 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. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems weird. Maybe "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 |
||||||
|
|
||||||
| **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. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe mention that |
||||||
|
|
||||||
| **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)`. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe also mention that the existence of a |
||||||
|
|
||||||
| **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 | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds a bit misleading, not |
||||||
| 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') | ||||||
|
Comment on lines
+65
to
+91
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a lot of explanation for the topic mapping -> should be condensed |
||||||
|
|
||||||
| ### 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** <i> class </i> | ||||||
|
|
||||||
| 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** <i> extends cds.Service </i> | ||||||
|
|
||||||
|
|
@@ -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 | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. headers can also be the 3rd argument, i.e. |
||||||
| 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 })) | ||||||
| }) | ||||||
| ``` | ||||||
|
|
||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.