diff --git a/node.js/messaging.md b/node.js/messaging.md index 9335ad2a4..1f48aa6e0 100644 --- a/node.js/messaging.md +++ b/node.js/messaging.md @@ -123,7 +123,8 @@ module.exports = async srv => { In _srv/own.js_ (CAP Messaging Services): ```js module.exports = async srv => { - const externalService = await cds.connect.to('messaging') + // Connect directly to the messaging service; use fully qualified event names + const messaging = await cds.connect.to('messaging') messaging.on('ExternalService.ExternalEvent', async msg => { await srv.emit('OwnService.OwnEvent', msg.data) }) @@ -243,10 +244,11 @@ const messaging = await cds.connect.to('messaging') this.after(['CREATE', 'UPDATE', 'DELETE'], 'Reviews', async (_, req) => { const { ID } = req.data + // average rating across all reviews for this book const { rating } = await cds.run( SELECT.one(['round(avg(rating),2) as rating']) .from(Reviews) - .where({ ID })) + .where({ book_ID: ID })) // send to a topic await messaging.emit('my/custom/topic', @@ -345,7 +347,7 @@ If you register at least one handler, a queue will automatically be created if n You have the following configuration options: - `queue`: An object containing the `name` property as the name of your queue, additional properties are described [in the SAP Business Accelerator Hub](https://hub.sap.com/api/SAPEventMeshDefaultManagementAPIs/path/putQueue). -- `amqp`: AQMP client options as described in the [`@sap/xb-msg-amqp-v100` documentation](https://www.npmjs.com/package/@sap/xb-msg-amqp-v100?activeTab=readme) +- `amqp`: AMQP client options as described in the [`@sap/xb-msg-amqp-v100` documentation](https://www.npmjs.com/package/@sap/xb-msg-amqp-v100?activeTab=readme) If the queue name isn't specified, it's derived from `application_name` and the first four characters of `application_id` of your `VCAP_APPLICATION` environmental variable, as well as the `namespace` property of your SAP Event Mesh binding in `VCAP_SERVICES`: `{namespace}/{application_name}/{truncated_application_id}`. This makes sure that every application has its own queue. @@ -555,7 +557,13 @@ You can use local messaging to communicate inside one Node.js process. It's espe `kind`: `composite-messaging` -If you have several messaging services and don't want to mention them explicitly in your code, you can create a `composite-messaging` service where you can define routes for incoming and outgoing messages. In those routes, you can use glob patterns to match topics (`**` for any number of any character, `*` for any number of any character except `/` and `.`, `?` for a single character). +If you have several messaging services and don't want to mention them explicitly in your code, you can create a `composite-messaging` service where you can define routes for incoming and outgoing messages. In those routes, you can use glob patterns to match topics: + +| Pattern | Matches | +|---------|---------| +| `?` | Any single character | +| `*` | Any sequence of characters, but does **not** cross `/` or `.` separators | +| `**` | Any sequence of characters, including `/` and `.` separators | Example: diff --git a/node.js/queue.md b/node.js/queue.md index 1d10ba32f..8a02d9cad 100644 --- a/node.js/queue.md +++ b/node.js/queue.md @@ -114,7 +114,7 @@ The optional parameters are: - `maxAttempts` (default `20`): The number of unsuccessful emits until the message is considered unprocessable. The message will remain in the database table! - `storeLastError` (default `true`): Specifies whether error information of the last failed emit is stored in the tasks table. - `legacyLocking` (default `true`): If set to `false`, database locks are only used to set the status of the message to `processing` to prevent long-kept database locks. Although this is the recommended approach, it is incompatible with task runners still on `@sap/cds^8`. -- `timeout` (default `"1h"`): The time after which a message with `status === "processing"` is considered to be abandoned and eligable to be processed again. Only for `legacyLocking === false`. +- `timeout` (default `"1h"`): The time after which a message with `status === "processing"` is considered to be abandoned and eligible to be processed again. Only for `legacyLocking === false`. ::: @@ -126,7 +126,8 @@ therefore also acts as a dead letter queue. See [Managing the Dead Letter Queue](#managing-the-dead-letter-queue), to learn about how to handle such messages. There is only one active message processor per service, tenant, app instance, and message. -This ensures that no duplicate emits happen, except in the highly unlikely case of an app crash right after successful processing but before the message could be deleted. +In a single-instance deployment this ensures that no duplicate emits happen, except in the highly unlikely case of an app crash right after successful processing but before the message could be deleted. +In multi-instance deployments, database-level locking (enabled via `legacyLocking: false`) is recommended to prevent two instances from picking up the same message concurrently. ::: tip Unrecoverable errors Some errors during the emit are identified as unrecoverable, for example in [SAP Event Mesh](../guides/events/event-mesh) if the used topic is forbidden. @@ -226,7 +227,7 @@ To manually trigger the message processing, for example if your server is restar ```js const srv = await cds.connect.to('yourService') -cds.queued(srv).flush() +await cds.queued(srv).flush() ``` #### Task Callbacks @@ -310,6 +311,9 @@ entity: ```js const db = await cds.connect.to('db') +// Delete only entries that have exceeded max attempts (dead letters) +await DELETE.from('cds.outbox.Messages').where({ status: 'failed' }) +// Or delete everything (use with caution in a running system) await DELETE.from('cds.outbox.Messages') ```