Technical Articles
CAP: Consume External Service – Part 3
Updated on 9 November 2022 The limitations mentioned in this blog post have already been addressed by the CAP framework, which makes this blog post obsolete!! |
In my previous blog posts, I discussed how CAP handles the consumption of external service. Overall, it’s elegant and simple to consume external service especially with the fact that CAP handles the complexity of consuming destination service configuration in SCP. However, there are limitations to which CAP can handle external services, i.e. it can handle only OData services.
In this blog post, I will show how you can break away from this limitation and handle the consumption of external service by doing the handling on your own. It is worth noting here as well, that there are certain scenarios on consuming an OData service that CAP framework might not work very well, and hence, you still need to resort on workaround solutions, like for example the issue that was raised in this Q&A.
Prerequisites
- SAP Cloud Platform for the Cloud Foundry environment
- SAP Business Application Studio / Visual Studio Code
Blog Post Series
- Consume External Service – Part 1
- Consume External Service – Part 2
- Consume External Service – Part 3
Introduction – Limit Break
Compared to Part 1 and Part 2 of this blog post series, where we use the CAP way of consuming external services, here, we will identify the CAP limitations and I will provide a way to break away on those limitations.
Whenever I come across a scenario where I break away on the limitations (which happens a lot of times), I’m reminded of my first encounter with a full-length RPG game – Final Fantasy VIII. Simply because of the Limit Break skill called Lion Heart which (I guess) meant to be overpowered. I have put a reference video below, although the graphics might not be so appealing now, it was a class of its own back then.
Okay, enough with the digression!
While working with external service consumption, I’ve come across a number of scenarios where the CAP framework doesn’t work well. Here they are (with additional items where it is a known limitation of CAP):
- Some operation didn’t work well when using Connectivity Proxy (SAP Cloud Connector)
- Can’t specify custom header information
- Can’t handle complex querries for OData Services
- Non-OData services are not supported (OpenAPI/RESTful web services)
For these scenarios, the advice is to handle the service consumption on your own. This means that you need to handle the service consumption in the Node.js way. It will be good if you are already familiar with developing in Node.js, but how about if you want to continue using Destination Service so that you don’t hardcode the service definitions in your logic? Also, how about the convenience of testing the service locally without much change when deploying the service to SCP?
These are the questions I had when I was thinking of handling the service consumption on my own. I was thinking that there should be some node module that can help with consuming destination service in SCP. And as a matter of fact, there is a node module called sap-cf-destconn. This node module helps in retrieving the destination and connectivity configuration from SCP. Okay, this helped to answer my first question, but how about the convenience of testing the service locally? Unfortunately, for this scenario, there’s no solution I can find. So what I did is I created my own solution — the cdse node module.
CDSE Node Module
To answer my own questions, I have implemented my own logic to reuse the CDS configuration for external service, and cater to the local testing and SCP configuration scenario. Also, I made sure that the implementation is kept simple and adopting the fluent API concept of CAP. Therefore, maintaining the simplicity and elegance that we know from the CAP framework. And of course, some good things are meant to be shared, so I’ve published the solution to npmjs.com. It was my first time to publish a node module. Refer to the link below for the cdse node module:
https://www.npmjs.com/package/cdse
Kindly refer to the README documentation on how to install and how to use the module.
Demo of service consumption
The CAP base project for this demo is the solution from the previous blog post – Consume External Service – Part 2. The source code is provided through the link below:
https://github.com/jcailan/cap-samples/tree/blog-es-part2
Note: If you didn’t go through part 2 of this blog post series, then you should refer to the Setup Destination Configuration section of Consume External Service – Part 2 before you proceed with the steps below. |
- 1. Once the source code is loaded in your development IDE, start installing the node module by executing the command:
> npm install cdse
- 2. Replace the implementation of the custom handler file catalog-service.js, with the logic below:
const cds = require('@sap/cds');
const cdse = require('cdse');
module.exports = cds.service.impl(async function() {
const { Products } = this.entities;
const service = await cdse.connect.to('NorthWind');
this.on('READ', Products, async () => {
const result = await service.run({ url: 'Products' });
return result.value;
});
});
Compared to the previous code, the new implementation of external service consumption is still looking neat and clean, but at the same time offers more flexibility for calling the service, because it is using axios parameter pattern.
Axios is the node module used by the CAP framework itself to consume external services.
That’s it! We’re done and it can’t be any more simple than that.
Testing the service
To show that the CDSE module caters both local testing and SCP destination configuration options, let’s put it to test.
- 1. Let’s first test the service locally. To do that, change the configuration of the external service to point to NorthWind service URL directly:
"cds": {
"requires": {
"NorthWind": {
"kind": "odata",
"model": "srv/external/NorthWind",
"credentials": {
"url": "https://services.odata.org/Experimental/OData/OData.svc"
}
}
}
}
- 2. Test the service, and you should get the results below:
- 3. Next, let’s test using the SCP Destination Configuration. To do that, change the CDS configuration to point to the NorthWind destination.
"cds": {
"requires": {
"NorthWind": {
"kind": "odata",
"model": "srv/external/NorthWind",
"credentials": {
"destination": "NorthWind"
}
}
}
}
- 4. Test the service locally and it should give you the same results. Additionally, you can build and deploy the CAP project to SCP and test using the deployed version, and should still give you the same results.
When testing the service locally and you get an error saying No service matches destination then you need to look into getting the destination credentials from SCP.
|
Closing
There you have it! A simple way of consuming external service for scenarios that you are not able to use CAP due to some constraints or limitations. The good thing about the CDSE module is that it is able to read the CDS configuration from package.json file and adapt the runtime conditions for both local and SCP testing.
Just take note that while CDSE extends the capability of external service consumption in the CAP Model, it also has its own limitation. Feel free to post a comment here or raise an issue in GitHub if you find any issues. I hope that it can help you in your CAP-based projects.
~~~~~~~~~~~~~~~~
Appreciate it if you have any comments, suggestions, or questions. Cheers!~
Excellent. Thanks
Thanks too Karthiheyan Murugesan !
Hello Jhodel Cailan,
Great couple of articles mate, well done. Just wanted to ask about connecting from
1- A CAP project to an external service (another MTA nodejs project) via destinations with authentication (JWT) ?
2- An MTA nodeJS project to a CAP project via destinations with authentication (JWT) ?
I know that everything without Authentication and Scopes work fine, the question is with :).
Cheers,
Hi Abdel Elghanam
Thanks for your comment!
For both of the cases, you mentioned the approach is the same, it is through Cross-MTA dependencies.
And while you can create destinations and still be able to make it work, I would suggest not to use that approach because it creates an additional configuration for you to set up (which is the destination config). A more straightforward approach is still using Cross-MTA dependencies.
Lastly, whether you use JWT authentication or not, the approach is still the same, except the fact that you need to configure the setting
Hi all,
IMO, we shouldn‘t mix up the topic of MTA with the topic of authentication. MTA cross-dependencies is just one way to exchange configuration data between MTAs at time of deployment. Further, the setting forwardAuthtoken: true is relevant for the approuter only.
If you want to have two services on SAP Cloud Platform communicating with each other with authentication, this is a pure matter of xsuaa configuration. This is nothing directly related to CAP. Both services (CF apps) will be bound to an xsuaa-instance. Configuration is easy, if both apps are bound to the same xsuaa instance. This will likely not be the case, if you are working with two MTAs. In this case, the xsuaa-instance of the providing service must be configured in a way, so that it accepts requests from the consuming service. You should consult the xsuaa documentation to learn how to achieve this. A prerequisite is of course, that the consuming service forwards an appropriate JWT.
If the consuming service is made with CAP, you will implement this request within a handler. I think JWT forwarding will happen, if you create a cds transaction, but I‘ve never tried this myself …
Thanks for calling that out Klaus Kopecz ! You’re right! And now that you highlighted it, the JWT forwarding might not be as straightforward as in the non-CAP node service (im thinking of XSJS by the way). But there’s only one way to find out, which is to put it into a test.
Hi, Jhodel,
Excellent series of articles, have helped me a lot to understand and implement external services, especially On-premise.
With the last CAP update (CDS 4.1.7) my external services that were running, now present the following error message.
Thanks in advance
Hi Luis Guillermo León Guzmán
Thanks for your comment!
I haven't moved to the latest CDS version yet, but I already saw some bugs that were reported like the one below:
https://answers.sap.com/questions/13121182/problem-in-cds-deploy-to-hana-in-cap-with-cf-cli-v.html
I suggest reporting your findings there in the Q&A as well so that the CAP dev team might be able to see and fix it.
Thanks Jhodel Cailan
I reported the issue in this Q&A
https://answers.sap.com/questions/13123132/problem-consuming-on-premise-external-services-wit.html
Hi Jhodel,
Can we expose the code developed in CAP using API?
Hi Raziuddin Mohammed
CAP exposes REST API in the form of OData, so what do you mean by your question?
Hi Jhodel Cailan ,
using the latest CDS 4.2. i am also facing a lot of issues with external services.
I want to use my cap service standalone for enterprise messagin and therefore not using a route behind approuter. Now i try to use onprem connectivity destination with set basicauth user and PW.
This seems to be not working locally.
I also figured out, that using cds watch the service url host will be changed to localhost:4004 while using cds run, the service url will be used as defined in credentials?!?
Did you successfully tried onprem destination with a given technical user and BasicAuth?
I also tried your CDSE but i ran into timeout.
Best Regards
Holger
Hi Holger Schäfer
I tried this before, use Connectivity + Destination services while running my CAP app locally, and my finding is that you cannot use the Connectivity service when running the CAP app locally. It just simply blocks requests coming from non-SCP IP addresses. And this is why in my blog post series, I used a direct URL to the on-prem service when testing locally.
And CDSE ends up in a timeout because you still go through Connectivity service using this approach.
I believe this is a security feature to prevent outside attacks to on-premise environments.
Hi,
I am getting an error " [ERROR] VError: No service matches destination".
Can you please help me on this?
Hi Atanu Khan
I have answered it here.
Hi Jhodel Cailan,
I have created CAPM Fiori element list report application, which shows data using CAP local services. I have a action button on the list.
My requirement is user can select any record by selecting checkbox of Fiori element list report and click on action button. On click of action button, I need to call external OData service from on-premise S4 HANA system. The external OData service will create Billing document in S4 system (it is POST call).
This creation of billing document is implemented on function import of OData service. I am finding difficulty of call this external function import from my CAP application.
Please can you suggest how I can call this external function import .
If I convert this function import to CREATE ENTITY whether I can call external Odata service by POST method.
Please let me know for both the cases which one is achievable and how.
Thanks,
Deepak
I got an ECONNRESET error after I used your blog and I dont know how to fix it
Hello Jhodel,
Nice blog !! Just wanted to check with you how to connect Onpremise -S4 (Odata service) with side by side extension ?Is that is the same way or any hints will be highly appreciated?
Br
DD