This is a follow-up blog post to my previous one called
Keep the Core Clean with RAP Model.
Like in the previous blog post, this is not about convincing you to adopt a clean core policy and why it is important, but instead, this blog post is more about asking the question: Can we keep the Core Clean with CAP Model?
I would suggest reading the introduction of the previous blog post to understand the motivation for keeping the core clean.
With the concept and motivation already out of the way (and you are on board with it), let’s get into the action of applying it, and for this blog post, we will be using the CAP framework for NodeJS.
Just note that this blog post will cover the
Side-by-Side Extensibility scenario which will make use of an OData API service.
The Goal
The goal is to create a custom Fiori application on BTP (side-by-side extensibility scenario) using the CAP framework while leveraging the standard OData API service. The remote OData service is capable of providing full OData entity operations namely:
create
,
read
,
update
, and
delete
— and for the CAP service, we will need to add custom handlers to implement the call to those external service operations. And since CAP is supporting the Fiori Elements framework, we will add the draft feature into the mix.
The Demo Project
The demo project is available at the GitHub repository:
https://github.com/jcailan/cap-fe-samples.
External Service: ProductAPI
For the sake of simplicity, I created my own external service using the same CAP project so that it will be easy to test the app that consumes this external service. Also, this is the same CAP-based external service that I used in my previous blog post
Keep the Core Clean with RAP Model.
Service Model: srv >
ProductAPI.cds
using {md} from '../db/schema';
service ProductAPI {
entity Products as projection on md.Products;
}
I used this service to generate the
metadata.xml
file that will be used for
cds import
later.
CAP Project
In order to consume the external service, we need to first do the
cds import
to generate the service model definition for the external service. You can refer to this blog post if you're not familiar yet with how this is done:
CAP: Consume External Service – Part 1.
The configuration in the
package.json
file shown below will make use of mocked external service.
Configuration:
package.json > cds > requires:
"product_external": {
"kind": "odata-v2",
"model": "srv/external/products"
}
Again, this is just for the simplicity of testing. You would typically want to use an SAP standard external OData Service (i.e. S/4HANA) for this case.
Service Model: srv >
ProductRemoteService.cds
using {product_external as external} from './external/products';
@impl: './handler/ProductRemoteService.js'
service ProductRemoteService {
@odata.draft.enabled
entity Products as projection on external.Products;
}
Service Handler: srv > handler >
ProductRemoteService.js
module.exports = async service => {
const external = await cds.connect.to("product_external");
const { Products } = service.entities;
service.on(["CREATE", "READ", "UPDATE", "DELETE"], Products, async request => {
try {
return await external.run(request.query);
} catch (error) {
const errors = error.reason.response.body.error.innererror.errordetails;
errors.forEach(({ code, message, target }) => {
request.error({ code, message: message.value || message, target });
});
return error;
}
});
};
The service model is using a custom handler for handling
CRUD
operations.
Fiori Element App
There are two Fiori Element apps that are generated using SAP Fiori Tools:
- Manage Products (External Service) for OData V4
- Manage Products (External Service) for OData V2
Both apps can be launched from the sandbox launchpad and can be found on
app/products-remote
and
app/products-remote-v2
folders respectively.
Note that though we have two apps, the annotations are added one-time only and can be found in
app/products-remote-v2
folder.
Testing
Like any other CAP project, start testing by running the
cds watch
command in the terminal, then run the respective app as already enumerated above.
OData V4 Service
The app to test will be
Manage Products (External Service) OData V4.
As in the previous blog post, CAP also doesn't support draft scenarios for handling external services. When pushing my luck, I was greeted with the error message below:
So technically, CAP's service API doesn't support draft-generated properties out-of-the-box. Theoretically, we can add some custom logic to handle the difference between services with draft properties and external service that doesn't have these properties. It may be tricky, but in theory, this should be possible. However, this topic is beyond the scope of this blog post, so we will not dig into this.
So now we don't have any choice but to turn off the
@odata.draft.enabled
annotation. And then test the app:
As you can see, just like in the RAP sample project, we also don't have the
create
and
edit
buttons, this is again, because Fiori Elements for OData V4 doesn't support non-draft scenarios. Once again, we are heading into the not-so-fortunate case of downgrading to OData V2 service.
OData V2 Service
The app to test will be
Manage Products (External Service) OData V2.
The app is capable of performing all the
create
,
read
,
update
, and
delete
operations. See the below screenshots as proof that the functionality is ready for you to use:
Closing
So going back to the question: Can we keep the Core Clean with CAP Model? The answer is not so straightforward, YES and NO answers.
If you’re expecting that we can use CAP, OData V4, and Fiori Elements — the answer is NO, this is because CAP doesn't support handling draft-generated properties out-of-the-box but maybe if you put a little more effort you can handle it yourself. But if you’re willing to settle for the OData V2 version (non-draft Fiori Elements) then you got a YES as an answer.
If I were to draw a conclusion now between the experience of keeping the core clean with RAP vs CAP, I would say that we reached a stalemate here because both of the frameworks are having the same capabilities and limitations. However, CAP has a distinct advantage over RAP, that being said, CAP didn't block us completely from implementing the gap of supporting the draft functionality for external service.
In the end, I hit the limitations of the frameworks I tried so far. However, there's one possibility that I purposely avoided discussing in this blog post, because I already reserved that topic for the next blog post:
Keep the Core Clean with Rizing CDSX!
~~~~~~~~~~~~~~~~
Appreciate it if you have any comments, suggestions, or questions. Cheers!~