Connect CAP to SAP Event Mesh. Publish and subscribe to CloudEvents messages across services.
@sap/cds-messaging pluginIn-process events work within one deployment. Event Mesh crosses deployment boundaries — a CAP app in one BTP subaccount can trigger a workflow in another, or react to S/4HANA business events. Event Mesh provides guaranteed delivery, durability, and fan-out across heterogeneous systems.
Producer CAP app ──publish──► Event Mesh (topic) ──subscribe──► Consumer CAP app
│
└──subscribe──► Another consumer (e.g., workflow)
sap/cap/bookshop/orders/v1/OrderCreatednpm install @sap/xb-msg-amqp-v100 @sap/cds-messaging
// srv/bookshop-service.cds
service BookshopService {
entity Orders as projection on my.Orders;
event OrderCreated : {
ID : UUID;
total : Decimal;
buyer : String;
}
}
{
"requires": {
"messaging": {
"[production]": {
"kind": "enterprise-messaging",
"format": "cloudevents"
},
"[development]": {
"kind": "file-based-messaging"
}
}
}
}
enterprise-messaging → real SAP Event Meshfile-based-messaging → local mock (writes events to .mocks/ folder)# mta.yaml (service resource)
- name: event-mesh
type: org.cloudfoundry.managed-service
parameters:
service: enterprise-messaging
service-plan: default
path: ./em-config.json
// em-config.json
{
"emname": "bookshop-em",
"namespace": "sap/cap/bookshop",
"version": "1.1.0",
"options": { "management": true, "messagingrest": true },
"rules": { "topicRules": { "publishFilter": ["*"], "subscribeFilter": ["*"] } }
}
// srv/bookshop-service.js
const messaging = await cds.connect.to('messaging')
this.after('CREATE', 'Orders', async (order) => {
await messaging.emit('sap/cap/bookshop/orders/v1/OrderCreated', {
ID: order.ID,
total: order.total,
buyer: order.buyer_ID
})
})
// srv/fulfillment-service.js
module.exports = class FulfillmentService extends cds.ApplicationService {
async init () {
const messaging = await cds.connect.to('messaging')
messaging.on('sap/cap/bookshop/orders/v1/OrderCreated', async (msg) => {
const { ID, total, buyer } = msg.data
await INSERT.into('FulfillmentJobs').entries({
order_ID: ID,
status: 'PENDING',
assignedAt: new Date()
})
})
await super.init()
}
}
messaging.on('sap/cap/bookshop/orders/v1/OrderCreated', async (msg) => {
try {
await processOrder(msg.data)
await msg.ack() // confirm successful processing
} catch (err) {
console.error('Processing failed:', err)
await msg.nack() // reject → message goes to DLQ after max retries
}
})
SAP Event Mesh uses the CloudEvents spec:
{
"specversion": "1.0",
"type": "sap.cap.bookshop.orders.v1.OrderCreated",
"source": "/sap/cap/bookshop",
"id": "abc-123",
"time": "2026-09-17T10:00:00Z",
"data": { "ID": "...", "total": 49.99, "buyer": "alice" }
}
CAP wraps/unwraps this automatically when "format": "cloudevents".
# Messages are written to .mocks/OrderCreated.json
# Consumer reads from that file — no Event Mesh service needed locally
cds watch
"messaging" to .cdsrc.json with file-based-messaging for devOrderCreated event after successful order creationNotificationService that subscribes and logs the messagecds watch and verify the .mocks/ files are createdfile-based-messaging mocks locally; enterprise-messaging in productionmsg.ack() / msg.nack() controls at-least-once delivery