28 · Messaging & Event Mesh

Connect CAP to SAP Event Mesh. Publish and subscribe to CloudEvents messages across services.

Episode 28 — Async Messaging with SAP Event Mesh

What you'll learn

Why this matters

In-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.

Concepts

Producer CAP app  ──publish──►  Event Mesh (topic)  ──subscribe──►  Consumer CAP app
                                     │
                                     └──subscribe──►  Another consumer (e.g., workflow)

Installation

npm install @sap/xb-msg-amqp-v100 @sap/cds-messaging

CDS declaration

// srv/bookshop-service.cds
service BookshopService {
  entity Orders as projection on my.Orders;

  event OrderCreated : {
    ID     : UUID;
    total  : Decimal;
    buyer  : String;
  }
}

.cdsrc.json configuration

{
  "requires": {
    "messaging": {
      "[production]": {
        "kind": "enterprise-messaging",
        "format": "cloudevents"
      },
      "[development]": {
        "kind": "file-based-messaging"
      }
    }
  }
}

Binding Event Mesh in BTP

# 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": ["*"] } }
}

Publishing a message

// 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
  })
})

Consuming messages

// 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()
  }
}

Error handling — dead letter queue

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
  }
})

CloudEvents format

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".

Local development

# Messages are written to .mocks/OrderCreated.json
# Consumer reads from that file — no Event Mesh service needed locally
cds watch

Hands-on exercise

  1. Add "messaging" to .cdsrc.json with file-based-messaging for dev
  2. Emit an OrderCreated event after successful order creation
  3. Create a NotificationService that subscribes and logs the message
  4. Run cds watch and verify the .mocks/ files are created

Checkpoint ✓

Sign in for progress tracking & Q&A →