With other words:
How to create a
CAP Application
which sends messages to
Enterprise Messaging
in SAP Cloud Platform
Quicklinks:
Create CAP app
Deploy and Test
All Project Files
In the
previous blog we’ve prepared ourselves and the service instance of
SAP Cloud Platform Enterprise Messaging
Actually, there wasn’t much to do: just create the service instance, that’s all
We learned how to open the Dashboard and create queue and subscription
In this blog we’re doing the first step to use Enterprise Messaging from within a CAP application
As usual, I like to use most simplistic example to show the functionality
So what we want to achieve is:
Create a CAP based application which sends messages to Enterprise Messaging
In a the next tutorial step, we deploy a second application which will receive those messages
This example is written in Node.js, however, in Java it would be similar
Note:
The messaging-integration in CAP is currently in BETA-mode
The code described below is valid from version 3.30 onward
If you need to work with old version, see
appendix for legacy api
Overview
Prerequisites
Create CAP application
Deploy and Test
Prerequisites
You have access to
SAP Cloud Platform,
Both Trial account and productive account can be used. It doesn't make a difference on CAP-side
You have gone through the
previous tutorial and created messaging instance with name “msgcustcare” and service plan “default”
You have made yourself familiar with
CAP
Create CAP Application
Since you are familiar with CAP projects, I’m only mentioning the few steps required to send messages to Enterprise Messaging with CAP.
Note that below description is valid for CAP version from 3.30 onward.
The old API can be viewed in the
appendix
See the
appendix section for full content of files
manifest.yml
The first step is to bind the CAP application to the instance of
Enterprise Messaging, which we created earlier
As such, we add the service instance name to our
manifest.yml file
applications:
- name: capsender
. . .
services:
- msgcustcare
As usual, make sure to adapt the host name to any unique name, in case it is already taken
package.json
We need 2 little additions to “normal”
package.json of CAP applications:
1)
To use the messaging support of CAP, we need the following dependency
"dependencies": {
"@sap/xb-msg-amqp-v100": "latest",
This is a client library provided by
Enterprise Messaging
It can be used to receive/send messages from/to
Enterprise Messaging
This client is used by CAP under the hood, so the dependency is required
2)
In addition, we need to add small configuration for messaging:
"cds": {
"requires": {
"messaging": {
"kind": "enterprise-messaging"
}
}
},
This snippet instructs CAP (roughly speaking) to connect to the
Enterprise Messaging instance, which is configured in
manifest.yml file.
sender-service.cds
I like to keep first samples very minimalistic, to stay focused on the very basic required steps
As such, our CAP project doesn't contain any data-model.
Only a service model.
And in service model, we don't need any "Entity", we only need a "function", which can be used to send messages:
service SenderService {
function send() returns String;
sender-service.js
We need a custom handler to write the implementation of the "function"
This is the interesting section of this blog
Which code is required to send messages to enterprise messaging?
First: compose the message
In our first example, we just want to send a simple message.
Note:
The CAP framework has one restriction:
The message has to be a JSON object.
So in our sample code, we construct some arbitrary message.
const message = {
'myProp': 'Sending message. Current Time: ' + new Date().toLocaleString()
}
Second: The target
We need to specify to which target we want to send our message
Now we need more detailed explanation, otherwise we get crazy if the message doesn’t reach the broker
Background:
In the manifest, we’ve declared a binding to the service instance of
Enterprise Messaging
After deployment, our app receives the connection data in the environment variables
The CAP framework reads it and picks the URL credentials of the broker
But, as we learned in the
intro-blog, there can be multiple queues and topics defined in the messaging dashboard.
As such, in the code we have to specify to which queue (more precise: topic) we want to send our silly message
const topic = 'company/customer/care/demo/customer/created'
BUT: how to construct the topic?
Here we have to consult the messaging dashboard:
Whoever created the messaging instance, has defined some settings which we need to consider
Note:
This description is only valid in productive account, for messaging service plan “default”
1. Namespace
The namespace is defined when service instance of Enterprise Messaging is created, in the JSON parameters (see
intro)
If we didn't create the service instance, we don’t have the JSON params.
But we can check the “Service Descriptor” tab in the dashboard
2. Topic Rules
Once we’ve figured out the "namespace", we need to check the "topic rules"
The Service Descriptor contains a section “rules”:
In our example, we want to "send" messages with our application.
With other words: we want to “publish”
And we want to publish to a "topic"
As such, we have to check the corresponding rule
In our example, a very common pattern, the rule (as defined on instance creation) requires that a topic starts with the namespace
3. Prepare Enterprise Messaging
In messaging dashboard, we need a "queue" and a "subscription"
Otherwise we cannot send messages
3.1. Queue
We need to manually create a queue and we need to follow the "queue rules", as defined in the Service Descriptor.
In our example, the rule is the same:
queue name has to start with namespace
Note:
The creation dialog automatically adds the namespace as prefix
We can specify any arbitrary name, it has no effect on our CAP application code
3.2. Topic
After queue creation, we need a subscription to a "topic"
This time we need to manually type the full name of the topic, including namespace
In our example, let’s choose a topic, which is intended for newly created customers
(in our example, this name can be freely chosen by us. In other examples, it might already exists, or might need to follow backend requirements)
demo/customer/created
Then we concatenate it to the namespace
company/customer/care/demo/customer/created
Result in dashboard:
Note:
Here we can enter multiple topics, e.g. for deleted customers and changed customers, etc
To save time, placeholder and patterns can be used
Note:
In Trial account: there are no rules
Note:
Just to prevent you from problems:
If you choose a topic with only one segment, then CAP cannot identify the topic. In that case you have to add prefix:
srv.on(‘ topic:customercreated’
4. Application Code
Now that we have collected information and defined queue and topic, we can continue with our application code
Remember: we have composed the topic string in our custom handler:
const topic = 'company/customer/care/demo/customer/created'
Now we can finally send the message:
srv.emit (topic, message)
That’s all
Note;
Have you noticed?
The required code and configuration is REALLY VERY small and convenient...!
That's CAP...
CAP takes care to use the underlying library to send the message to the
Enterprise Messaging instance.
Please see the
appendix for the full content of the custom handler
Only few more lines are required to implement the function
Deploy and Test
After writing this minimalistic application, we can deploy it to SAP Cloud Platform.
After successful deployment, we open the messaging dashboard.
There, we can see the “Number of Messages” which have reached the broker and have been put into the queue
Obviously, the number is zero
Now we want to send a message with our CAP application
Thus, we invoke the function import, as designed
In my example, the URL is as follows:
https://capsender.cfapps.eu10.hana.ondemand.com/sender/send()
Afterwards, we go back to the dashboard, press the refresh icon, and see that there’s one message in the queue:
The number increases every time we execute the function import
What can we do with the messages?
The message broker promises that messages are delivered asynchronously and that they don’t get lost.
As such, these messages will remain in the queue until we follow the next tutorial, where we learn how to write a CAP application which receives (consumes) messages
However... in the meantime... there’s a little tool which allows playing around with messaging:
The “Test” tab in the dashboard
So for now, let’s use it
In the “Test” tab, choose “Consume” action and choose our queue
Then press the button “Consume Message”
As a result, we can see the content of the message which we sent with CAP application
And we realize that the current time is more or less correct:
Summary
In this tutorial we’ve learned which steps are required in a CAP application in order to send messages to
Enterprise Messaging in SAP Cloud Platform
Briefly:
package.json: add dependency and "requires" section for messaging
service.js: do a connect and an emit (to topic and with JSON content)
In this tutorial, we’ve manually configured the
Enterprise Messaging client (create queue and subscription with matching topic name)
In the
next tutorial, we’ll see that CAP can add some more convenience to our life
Troubleshooting
Make sure that you have created queue and subscription in the dashboard
Make sure that in your code the topic matches exactly the topic in the dashboard
Make sure you're using the current CAP API and declared the code accordingly
If you have headache because messages aren't arriving in Messaging Queue, you should check
this guide
Links
Intro Blog: see
here
Next Blog: create CAP app which
receives messages from Enterprise Messaging
Extra Blog:
local development
CAP managed messaging using keyword "
event" in CDS model
CAP documentation :
https://cap.cloud.sap
CAP messaging:
https://cap.cloud.sap/docs/guides/consuming-services
SAP Help Portal:
What is Enterprise Messaging
SAP Help Portal:
Enterprise Messaging: Naming Syntax
Appendix 1: All Project Files
As mentioned above, our little sample app doesn't contain a data model.
Only a service model file, to be placed in the "srv"-folder
manifest.yml
---
applications:
- name: capsender
host: capsender
path: .
memory: 128M
buildpacks:
- nodejs_buildpack
services:
- msgcustcare
package.json
{
"dependencies": {
"@sap/cds": "^3",
"express": "^4",
"@sap/xb-msg-amqp-v100": "latest"
},
"cds": {
"requires": {
"messaging": {
"kind": "enterprise-messaging"
}
}
},
"scripts": {
"start": "npx cds run"
}
}
sender-service.cds
service SenderService {
function send() returns String;
}
sender-service.js
const cds = require ('@sap/cds')
module.exports = cds.service.impl ((srv) => {
srv.on ('send', async(req)=>{
const message = {
'myProp': 'Sending message. Current Time: ' + new Date().toLocaleString()
}
const topic = 'company/customer/care/demo/customer/created'
srv.emit (topic, message)
return "Successfully sent event"
})
})
Appendix 2: Legacy API
As mentioned above, the API for integrating CAP applications with Enterprise Messaging has changed from version 3.30 onward
In case you're working with older version, see below for the relevant code changes
Note:
In old version, cds.connect.to() is required
and the returned instance is used for emit
package.json
{
"cds": {
"requires": {
"myMessagingDef": {
"kind": "enterprise-messaging"
}
}
}
}
This connection has a name: in our example the name is “myMessagingDef”
As some of you know, I like to use silly names. Like that, whenever a silly name comes across, I know that it is not a predefined name, but instead, it was given by me. No framework would use a silly name as predefined name.
For you this means: whenever you come across a silly name, you can adapt it to a (silly) name of your choice. However, make sure to remember it, it will be used in the handler implementation
On the other side, this also means that the name “enterprise-messaging” which is configured as “kind”, MUST be a predefined name.
sender-service.js
const cds = require ('@sap/cds')
module.exports = cds.service.impl ((srv) => {
const myMessaging = cds.connect.to("myMessagingDef")
srv.on ('send', async(req)=>{
const message = {
'myProp': 'Sending message. Current Time: ' + new Date().toLocaleString()
}
const topic = 'company/customer/care/demo/customer/created'
myMessaging.emit (topic, message)
return "Successfully sent event"
})
})
Note:
The name to be given in the "connect" statement ("myMessagingDef") has to match the definition has been configured in
package.json file
The result of the "connect" statement is an instance which can be used to interact with the messaging broker