Here is a new concept that is going to revolutionise CAP app development 😃
Let me first give you an example and then explain why it matters:
I have a business requirement to add a new feature to my CAP application. Some of the strings that my CAP application delivers should have a random emoji added at the end.
So if we look at our typical bookshop example, the title of the book would be appended by a random emoji whenever we fetch the data:
Now usually what you would do is write a custom handler directly in your app, which adds the emojis.
If another project later on wants to have the same functionality, you copy that custom handler around. If something needs to be changed in the implementation you now have to maintain the same code in multiple places.
But what if we would have this feature in a truly reusable fashion, cleanly separated away from the rest of your code?
This is where cds-plugin comes in!
Lets create a new project with executing this in the terminal:
cds init && cds add samples
Now let's create a plugin that extends the functionality of this base application.
Create a folder emojiplugin and run npm init -y in that folder:
mkdir emojiplugin && cd emojiplugin && npm init -y && cd ..
console.log('my awesome plugin')
This is where the magic happens: If we set a dependency in the package.json of the main project to our emojiplugin, cds will go and look for cds-plugin.js and execute the content.
So let's set this dependency.
In the package.json in the main folder (not the plugin folder!) we add a dependency to our emojiplugin and we also define emojiplugin as an npm workspace
{
...,
"workspaces": [
"emojiplugin"
],
"dependencies": {
...
"emojiplugin": "*"
},
...
}
Now all that is needed is to run npm i to install the dependencies. Afterwards run the cds server locally with cds w
npm i && cds w
You should see something like this in the console:
cds serve all --with-mocks --in-memory?
live reload enabled for browsers
___________________________
my awesome plugin
[cds] - loaded plugin: { impl: 'emojiplugin/cds-plugin' }
[cds] - loaded model from 2 file(s):
db/data-model.cds
srv/cat-service.cds
To see the output from the plugins in the logs, raise the log level on the plugin component with DEBUG=plugins cds w
We can see the my awesome pluginoutput from our cds-plugin.js file followed by [cds] - loaded plugin: { impl: 'emojiplugin/cds-plugin' }which confirms that the plugin was loaded. Congratulations! You have successfully created your first cds plugin!
So far we have only printed a console statement with our plugin. But we wanted emojis!
In the srv/cat-service.cds add the following at the end of the file:
annotate CatalogService.Books with {
title @randomEmoji;
};
We made up our own annotation here, called @randomEmoji. What we want to achieve is that any field that has this annotation is affected by our plugin.
Now we are going to add the implementation to our plugin, which will look for this annotation and append the emoji. In the emojiplugin/cds-plugin.js file replace the console.log statement with the following:
const cds = require('@sap/cds')
//most important --> define emojis!
const emojis = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
//our business logic...
function getRandomEmoji() {
return emojis[Math.floor(Math.random() * emojis.length)]
}
// we register ourselves to the cds once served event
// a one-time event, emitted when all services have been bootstrapped and added to the express app
cds.once('served', () => {
// go through all services
for (let srv of cds.services) {
// go through all entities
for (let entity of srv.entities) {
// go through all elements in the entity and collect those with @randomEmoji annotation
const emojiElements = []
for (const key in entity.elements) {
const element = entity.elements[key]
// check if there is an annotation called randomEmoji on the element
if (element['@randomEmoji']) emojiElements.push(element.name)
}
if (emojiElements.length) {
// register a new handler on the service, that is called on every read operation
srv.after('READ', entity, data => {
if (!data) return
// if we read a single entry, we don't get an array of data, so let's make sure we deal with an array
let myData = Array.isArray(data) ? data : [data]
// go through all query read results (in this case the books)
for (let entry of myData) {
for (const element of emojiElements) {
if (entry[element]) {
entry[element] += getRandomEmoji()
}
}
}
})
}
}
}
})
We are consuming the npm package locally from our file system. But this could of course be a published npm package or linked via npm link. When deploying the application, we need to make sure that this dependency is also resolved correctly.
If this is not a published package, the workspace support allows to build and deploy the application to BTP with the plugin included: https://pages.github.tools.sap/cap/docs/guides/deployment/custom-builds#build-ws
In a multi tenancy scenario with extensibility the plugin would be loaded before the extensibility is applied, so the example above would need to be written differently (otherwise the annotation is added AFTER the plugin was loaded and is not considered)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
17 | |
11 | |
11 | |
11 | |
11 | |
8 | |
6 | |
5 | |
5 | |
5 |