Skip to content

review messaging guide #1995

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

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions guides/messaging/event-mesh.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The following guide is based on a productive (paid) account on SAP BTP. It's not
- Alternatively follow [one of the guides in SAP Help Portal](https://help.sap.com/docs/SAP_EM/bf82e6b26456494cbdd197057c09979f/3ef34ffcbbe94d3e8fff0f9ea2d5911d.html).

::: tip
**Important:** You don't need to manually create queues or queue subscriptions as CAP takes care for that automatically based on declared events and subscriptions.
**Important:** You don't need to manually create queues or queue subscriptions as CAP takes care of that automatically based on declared events and subscriptions.
:::


Expand Down Expand Up @@ -55,7 +55,7 @@ The `enterprise-messaging` implementation handles these things automatically and

### Optional: Add `namespace` Prefixing Rules

SAP Event Mesh documentation recommends to prefix all event names with the service instance's configured `namespace`, both, when emitting as well as when subscribing to events. If you followed these rules, add corresponding rules to your configuration in _package.json_ to have CAP's messaging service implementations enforcing these rules automatically:
SAP Event Mesh documentation recommends to prefix all event names with the service instance's configured `namespace`, both, when emitting as well as when subscribing to events. If you followed these rules, add corresponding rules to your configuration in _package.json_ to have CAP's messaging service implementations enforce these rules automatically:

```json
"cds": {
Expand Down Expand Up @@ -124,7 +124,7 @@ Before [deploying to the cloud](#deploy-to-the-cloud-with-mta), you may want to

### CAP Automatically Creates Queues and Subscriptions

When you run the services with a bound instance of SAP Event Mesh as documented in a previous section, CAP messaging service implementations will automatically create a queue for each receiver process. The queue name is chosen automatically and the receiver's subscriptions added.
When you run the services with a bound instance of SAP Event Mesh as documented in a previous section, CAP messaging service implementations will automatically create a queue for each receiver process. The queue name is chosen automatically and the receiver's subscriptions are added.


### Optional: Configure Queue Names
Expand Down
56 changes: 33 additions & 23 deletions guides/messaging/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The core of CAP's processing model: all services are event emitters. Events can

### Typical Emitter and Receiver Roles

In contrast to the previous code sample, emitters and receivers of events are decoupled, in different services and processes. And as all active things in CAP are services, so are usually emitters and receivers of events. Typical patterns look like that:
In contrast to the previous code sample, emitters and receivers of events are decoupled, in different services and processes. And as in CAP all active things are services, usually so are emitters and receivers of events. Typical patterns look like that:

```js
class Emitter extends cds.Service { async someMethod() {
Expand Down Expand Up @@ -91,7 +91,7 @@ In essence, services receive events. The emitting service itself or other servic
:::


### Why Using Messaging?
### Why Use Messaging?

Using messaging has two major advantages:

Expand Down Expand Up @@ -128,8 +128,8 @@ service ReviewsService {

// Sync API
entity Reviews as projection on my.Reviews excluding { likes }
action like (review: Reviews:ID);
action unlike (review: Reviews:ID);
action like (review: type of Reviews:ID);
action unlike (review: type of Reviews:ID);

// Async API
event reviewed : {
Expand All @@ -146,7 +146,7 @@ service ReviewsService {
As you can read from the definitions, the service's synchronous API allows to create, read, update, and delete user `Reviews` for arbitrary review subjects. In addition, the service's asynchronous API declares the `reviewed` event that shall be emitted whenever a subject's average rating changes.

::: tip
**Services in CAP** combine **synchronous** *and* **asynchronous** APIs. Events are declared on conceptual level focusing on domain, instead of low-level wire protocols.
**Services in CAP** combine **synchronous** *and* **asynchronous** APIs. Events are declared on a conceptual level focusing on domain, instead of low-level wire protocols.
:::

### Emitting Events
Expand All @@ -157,9 +157,9 @@ Find the code to emit events in *[@capire/reviews/srv/reviews-service.js](https:
class ReviewsService extends cds.ApplicationService { async init() {

// Emit a `reviewed` event whenever a subject's avg rating changes
this.after (['CREATE','UPDATE','DELETE'], 'Reviews', (req) => {
this.after (['CREATE','UPDATE','DELETE'], 'Reviews', async (req) => {
let { subject } = req.data, count, rating //= ...
return this.emit ('reviewed', { subject, count, rating })
await this.emit ('reviewed', { subject, count, rating })
})

}}
Expand All @@ -169,8 +169,8 @@ class ReviewsService extends cds.ApplicationService { async init() {

Method `srv.emit()` is used to emit event messages. As you can see, emitters usually emit messages to themselves, that is, `this`, to inform potential listeners about certain events. Emitters don't know the receivers of the events they emit. There might be none, there might be local ones in the same process, or remote ones in separate processes.

::: tip Messaging on Conceptual Level
Simply use `srv.emit()` to emit events, and let the CAP framework care for wire protocols like CloudEvents, transports via message brokers, multitenancy handling, and so forth.
::: tip Messaging on a Conceptual Level
Simply use `srv.emit()` to emit events, and let the CAP framework take care of wire protocols like CloudEvents, transports via message brokers, multitenancy handling, and so forth.
:::

### Receiving Events
Expand Down Expand Up @@ -232,10 +232,10 @@ Now, open [http://localhost:4004/reviews](http://localhost:4004/reviews) to disp

![A vue.js UI, showing the bookshop sample with the adding a review functionality](assets/capire-reviews.png)

- Enter username *bob* with empty password to authenticate.
- Choose one of the reviews.
- Change the 5-star rating with the dropdown.
- Choose *Submit*.
- Enter *bob* to authenticate.

→ In the terminal window you should see a server reaction like this:

Expand All @@ -256,13 +256,13 @@ Open [http://localhost:4004/bookshop](http://localhost:4004/bookshop) to see the

## Using Message Channels

When emitters and receivers live in separate processes, you need to add a message channel to forward event messages. CAP provides messaging services, which take care for that message channel behind the scenes as illustrated in the following graphic:
When emitters and receivers live in separate processes, you need to add a message channel to forward event messages. CAP provides messaging services, which take care of that message channel behind the scenes as illustrated in the following graphic:

![The reviews service and the catalog service, each in a seperate process, are connected to the messaging service which holds the messaging channel behind the scenes.](assets/remote.drawio.svg)


::: tip Uniform, Agnostic Messaging
CAP provides messaging services, which transport messages behind the scenes using different messaging channels and brokers. All of this happens without the need to touch your code, which stays on conceptual level.
CAP provides messaging services, which transport messages behind the scenes using different messaging channels and brokers. All of this happens without the need to touch your code, which remains on a conceptual level.
:::

### 1. Use `file-based-messaging` in Development
Expand Down Expand Up @@ -295,11 +295,16 @@ cds watch reviews
The trace output should contain these lines, confirming that you're using `file-based-messaging`, and that the `ReviewsService` is served by that process at port 4005:

```log
[cds] - connect to messaging > file-based-messaging { file: '~/.cds-msg-box' }
[cds] - serving ReviewsService { path: '/reviews', impl: '../reviews/srv/reviews-service.js' }
[cds] - connect to messaging > file-based-messaging
[cds] - using auth strategy {
kind: 'mocked',
impl: '../../cds/lib/srv/middlewares/auth/basic-auth'
}

[cds] - serving ReviewsService { impl: 'reviews/srv/reviews-service.js', path: '/reviews' }

[cds] - server listening on { url: 'http://localhost:4005' }
[cds] - launched at 5/25/2023, 4:53:46 PM, version: 7.0.0, in: 593.274ms
[cds] - server launched in: 309.992ms
```

Then, in a separate terminal start the `bookstore` server as before:
Expand All @@ -308,17 +313,22 @@ Then, in a separate terminal start the `bookstore` server as before:
cds watch bookstore
```

This time the trace output is different to [when you started all in a single server](#start-single-server). The output confirms that you're using `file-based-messaging`, and that you now *connected* to the separately started `ReviewsService` at port 4005:
This time the trace output is different from [when you started all services in a single server](#start-single-server). The output confirms that you're using `file-based-messaging`, and that you now *connected* to the separately started `ReviewsService` at port 4005:

```log
[cds] - connect to messaging > file-based-messaging { file: '~/.cds-msg-box' }
[cds] - mocking OrdersService { path: '/orders', impl: '../orders/srv/orders-service.js' }
[cds] - serving CatalogService { path: '/browse', impl: '../reviews/srv/cat-service.js' }
[cds] - serving AdminService { path: '/admin', impl: '../reviews/srv/admin-service.js' }
[cds] - connect to messaging > file-based-messaging
[cds] - serving CatalogService { impl: 'bookshop/srv/cat-service.js', path: '/browse' }
[cds] - serving AdminService { impl: 'bookshop/srv/admin-service.js', path: '/admin' }
[cds] - serving UserService { impl: 'bookshop/srv/user-service.js', path: '/user' }
[cds] - serving DataService {
impl: 'etc/data-viewer/srv/data-service.js',
path: '/odata/v4/-data'
}
[cds] - connect to ReviewsService > odata { url: 'http://localhost:4005/reviews' }
[cds] - connect to OrdersService > odata { url: 'http://localhost:4004/odata/v4/orders' }

[cds] - server listening on { url: 'http://localhost:4004' }
[cds] - launched at 5/25/2023, 4:55:46 PM, version: 7.0.0, in: 1.053s
[cds] - server launched in: 598.628ms
```

### 3. Add or Update Reviews {#add-or-update-reviews-2}
Expand Down Expand Up @@ -557,7 +567,7 @@ Application developers shouldn't have to care for such technical details. CAP en
## [Using SAP Cloud Application Event Hub](./event-broker) {#sap-event-broker}

CAP has growing out-of-the-box support for SAP Cloud Application Event Hub.
As an application developer, all you need to do is configuring CAP to use `event-broker`, as in this excerpt from a _package.json_:
As an application developer, all you need to do is configure CAP to use `event-broker`, as in this excerpt from a _package.json_:

```jsonc
"cds": {
Expand All @@ -584,7 +594,7 @@ Find additional information about deploying SAP Cloud Application Event Hub on S
## [Using SAP Event Mesh](./event-mesh) {#sap-event-mesh}

CAP has out-of-the-box support for SAP Event Mesh.
As an application developer, all you need to do is configuring CAP to use `enterprise-messaging`, usually in combination with `cloudevents` format, as in this excerpt from a _package.json_:
As an application developer, all you need to do is configure CAP to use `enterprise-messaging`, usually in combination with `cloudevents` format, as in this excerpt from a _package.json_:

```jsonc
"cds": {
Expand Down
2 changes: 1 addition & 1 deletion java/outbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ status: released

## Concepts

Usually the emit of messages should be delayed until the main transaction succeeded, otherwise recipients also receive messages in case of a rollback.
Usually the emit of messages should be delayed until the main transaction has succeeded, otherwise recipients also receive messages in case of a rollback.
To solve this problem, a transactional outbox can be used to defer the emit of messages until the success of the current transaction.

## In-Memory Outbox (Default) { #in-memory}
Expand Down
63 changes: 32 additions & 31 deletions node.js/messaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ messaging.on('cap/msg/system/review/reviewed', msg => {
```

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 one handler throws an error, the message broker will be informed that the message couldn't be consumed properly and it 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.

Expand Down Expand Up @@ -283,20 +283,21 @@ Example:

```json
{
"requires": {
"messaging": {
"kind": "enterprise-messaging-shared",
"queue": {
"name": "my/enterprise/messaging/queue",
"accessType": "EXCLUSIVE",
"maxMessageSizeInBytes": 19000000
},
"amqp": {
"incomingSessionWindow": 100
}
}
"requires": {
"messaging": {
"kind": "enterprise-messaging-shared",
"queue": {
"name": "my/enterprise/messaging/queue",
"accessType": "EXCLUSIVE",
"maxMessageSizeInBytes": 19000000
},
"amqp": {
"incomingSessionWindow": 100
}
}
}
}

```

::: warning _❗ Warning_
Expand Down Expand Up @@ -325,21 +326,20 @@ Example:

```json
{
"requires": {
"messaging": {
"kind": "enterprise-messaging",
"queue": {
"name": "my/enterprise/messaging/queue",
"accessType": "EXCLUSIVE",
"maxMessageSizeInBytes": 19000000
},
"webhook": {
"waitingPeriod": 7000
}
}
"requires": {
"messaging": {
"kind": "enterprise-messaging",
"queue": {
"name": "my/enterprise/messaging/queue",
"accessType": "EXCLUSIVE",
"maxMessageSizeInBytes": 19000000
},
"webhook": {
"waitingPeriod": 7000
}
}
}
}

```
<!-- ```js -->
<!-- { -->
Expand Down Expand Up @@ -451,13 +451,14 @@ Example:

```json
{
"requires": {
"messaging": {
"kind": "file-based-messaging",
"file": "../msg-box"
}
"requires": {
"messaging": {
"kind": "file-based-messaging",
"file": "../msg-box"
}
}
}

```

::: warning No tenant isolation in multitenant scenario
Expand Down
2 changes: 1 addition & 1 deletion node.js/outbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ status: released

## Overview

Often, remote operations should be delayed until the main transaction succeeded. Otherwise, the remote operations are also triggered in case of a rollback.
Often, remote operations should be delayed until the main transaction has succeeded. Otherwise, the remote operations are also triggered in case of a rollback.
To enable this, an outbox can be used to defer remote operations until the success of the current transaction.

Every CAP service can be _outboxed_ that means event dispatching becomes _asynchronous_.
Expand Down