Skip to content

Event Queues#2608

Draft
sjvans wants to merge 20 commits into
mainfrom
event-queues-reconciled
Draft

Event Queues#2608
sjvans wants to merge 20 commits into
mainfrom
event-queues-reconciled

Conversation

@sjvans
Copy link
Copy Markdown
Contributor

@sjvans sjvans commented Jun 2, 2026

No description provided.

* **Inbox** → for asynchronously handling inbound requests
* **Background tasks** → e.g., scheduled periodically
* **Remote Callbacks** → implementing SAGA patterns
@Before(entity = DeadOutboxMessages_.CDS_NAME)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be:
@before(event = CqnService.EVENT_READ, entity = DeadOutboxMessages_.CDS_NAME)

The handler takes CdsReadEventContext and modifies a CqnSelect query, so it's clearly intended for READ operations. Without event, the annotation matches all events, which is misleading.

Enable the inbox in your configuration:

::: code-group
```json [Node.js — package.json]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a brief note highlighting differences between inboxed and inbox.enabled

const xflights = cds.unqueued(qd_xflights)
```
```java [Java]
CqnService xflights = outbox.unboxed(outboxedXFlights);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unboxed() method appears to be a static method on OutboxService:

CqnService xflights = OutboxService.unboxed(outboxedXFlights);

target : String; // Target service/queue name
msg : LargeString; // Serialized event payload
attempts : Integer default 0; // Number of processing attempts
partition : Integer default 0;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add comment like "// reserved"

::: details When is a message picked up next?
A pending message is *processable* when all three conditions hold:

1. Its scheduled timestamp plus the retry backoff (`attempts × <exponential factor>`) is in the past.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The notation attempts × is potentially misleading. The number of attemps is already in - no need to multiply. If multiplying, then it looks like linear function.

exponential_factor(attempts)

or simply

exponential backoff based on attempts

const xflights = await cds.connect.to('xflights')

// Called when the queued booking succeeds
xflights.after('bookFlight/#succeeded', async (result, req) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not explained what result contains:
The return value of the event handler OR
The response object

lastAttemptTimestamp : Timestamp; // When last attempt occurred
status : String(23); // Current processing status
task : String(255); // Task name for named/singleton tasks
appid : String(255); // Application ID for shared HDI containers
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is quite terse. Consider expanding it to clarify the purpose or add a sentence after the data model explaining like "appid enables multiple applications to share the same outbox table without task name collisions"

})
```
```java [Java]
@On
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just @on, not @on(event = "...") ?

context.setCompleted();
}

@On
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just @on, not @on(event = "...") ?


| Option | Default | Description |
|--------|---------|-------------|
| `maxAttempts` | `20` | Maximum retries before a message becomes a dead letter |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maxAttempts is different for node and java, add to "Stack Differences at a Glance" table


::: details Configuration options for Java

| Option | Default | Description |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java example shows storeLastError: true, but storeLastError is not listed in the Java configuration options

| `maxAttempts` | `20` | Maximum retries before a message becomes a dead letter |
| `chunkSize` | `10` | Number of messages to process per batch |
| `storeLastError` | `true` | Store error information of the last failed attempt |
| `timeout` | `"1h"` | Time after which a `processing` message is considered abandoned |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

abandoned and eligible for reprocessing ?


### Inbox

The inbox mirrors the *'Outbox'* pattern for inbound messages.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Above (line 115): This pattern is widely known as the 'Transactional Outbox',

Event queues are not limited to messaging.
You can schedule arbitrary background tasks such as data replication, cache refresh, or garbage collection.

**Example:** Replicate airport master data from the xflights service every 10 minutes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

italic xflights


This stores the flight booking request in the database together with the travel creation. CAP dispatches it later in the background. If the transaction rolls back, no booking request is sent.

The `xflights` connection in the example stands in for any remote service you've configured under `cds.requires`. The complete setup, including the *XTravels* application and the *xflights* service it consumes, lives in the [@capire/xtravels](https://github.com/capire/xtravels) sample.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

XTravels -> xtravels

### Error Handling

When processing fails, the system retries the message with exponentially increasing delays.
After a configurable maximum number of attempts, the message is moved to the dead letter queue.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not moved, just considered as dead message

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants