Primary reference: https://cap.cloud.sap/docs/guides/messaging/ Event Mesh: https://cap.cloud.sap/docs/guides/messaging/event-mesh
Defining events in CDS
// In the emitting service
service OrderService {
entity Orders as projection on db.Orders;
// Declare events (these become CloudEvents topics)
event OrderSubmitted {
orderID : UUID;
customer : String;
amount : Decimal(9,2);
currency : String(3);
}
event OrderShipped { orderID : UUID; trackingNr : String; } }
## Emitting events
```js
module.exports = class OrderService extends cds.ApplicationService {
async init() {
this.on('submitOrder', this.onSubmitOrder)
return super.init()
}
async onSubmitOrder(req) {
const { orderID } = req.data
// ... business logic, DB writes ...
// Emit the event (persisted in outbox by default in CAP >= 7)
await this.emit('OrderSubmitted', {
orderID,
customer: req.user.id,
amount: order.totalAmount,
currency: order.currency_code
})
return { success: true }
}
}
Consuming events from another service
// In the consuming service
module.exports = class ShippingService extends cds.ApplicationService {
async init() {
// Subscribe to events from OrderService (same process or remote)
const OrderSrv = await cds.connect.to('OrderService')
OrderSrv.on('OrderSubmitted', this.onOrderSubmitted.bind(this))
return super.init()
}
async onOrderSubmitted({ data }) {
const { orderID, amount } = data
// ... trigger shipment process
console.log(`Preparing shipment for order ${orderID}`)
}
}
Messaging Kinds — Choosing the right provider
| Kind | Protocol | Best for |
|---|---|---|
enterprise-messaging |
AMQP | Legacy, backward compatibility |
event-mesh |
HTTP/webhooks | Production cloud deployments |
event-mesh-shared |
AMQP | Shared/hybrid scenarios, multitenant apps |
Quick setup (CDS 10+):
cds add event-mesh # HTTP/webhooks — for production
cds add event-mesh-shared # AMQP — for hybrid testing
SAP Event Mesh configuration
package.json / .cdsrc.json:
{
"cds": {
"requires": {
"messaging": {
"kind": "enterprise-messaging",
"format": "cloudevents"
}
}
}
}
Bind the enterprise-messaging service instance in mta.yaml:
- name: my-cap-app-em
type: org.cloudfoundry.managed-service
parameters:
service: enterprise-messaging
service-plan: default
path: ./em-config.json
em-config.json (Event Mesh descriptor):
{
"emname": "my-cap-app-em",
"namespace": "my/company/app",
"version": "1.1.0",
"options": { "management": true, "messagingrest": true },
"rules": {
"topicRules": {
"publishFilter": ["*"],
"subscribeFilter": ["*"]
},
"queueRules": {
"publishFilter": ["*"],
"subscribeFilter": ["*"]
}
},
"authorities": ["$ACCEPT_GRANTED_AUTHORITIES"]
}
Topic naming (CloudEvents)
CAP auto-derives the topic from the service and event name:
sap/cap/{namespace}/{service}/{event}
For example: sap/cap/com.acme/OrderService/OrderSubmitted
You can override:
event OrderSubmitted @(topic: 'com/acme/orders/submitted/v1') { ... }
The Outbox pattern (transactional safety)
CAP 7+ uses the outbox by default — events are written to the DB in the same transaction as your data writes, then delivered asynchronously. No lost events on crash.
{
"cds": {
"requires": {
"outbox": true
}
}
}
To opt out (fire-and-forget):
this.emit('OrderSubmitted', data, { queue: false })
Local development (file-based mock)
{
"cds": {
"requires": {
"messaging": {
"kind": "file-based-messaging"
}
}
}
}
Events are written to ~/.cds-msg-box/ — no Event Mesh instance needed locally.
Common mistakes to avoid
- ❌ Emitting events before the DB transaction commits (use the outbox!)
- ❌ Forgetting to declare events in CDS — type safety and documentation matter
- ❌ Subscribing in
cds.on('bootstrap', ...)instead ofservice.init()— order matters - ❌ Using generic topic names — namespace them to avoid collisions across apps
- ❌ Not handling duplicate delivery — Event Mesh delivers at-least-once; make consumers idempotent
- ❌ Hardcoding the Event Mesh namespace in code — derive from service binding at runtime