TL;DR This is the main blog post for the SAP Developer Challenge in July and contains a general overview, hints and tips, and the list of tasks that you need to carry out during this challenge. If this is your first time reading this post, jump down to the "Introduction" section and start there. Otherwise, find the list of tasks you need to complete directly below, in the "Tasks" section.
There are 12 tasks in total (plus a final bonus "feedback" task). The first (Task 0) is just for you to get things set up for the subsequent tasks. Each task listed here is just a one line summary, and links to a separate task-specific thread in the Application Development Discussions area of this SAP Community platform. Each thread title will follow this pattern:
Task <task number> - <Task short description> (July Developer Challenge - "Reverse APIs")
🔔This task list will be updated throughout the month, check back regularly to see what's next - and if there's a task that's hyperlinked, then you can follow that hyperlink to start the task!
0 - Server and service provisioning: Get comfortable with spinning up a CAP server and basic service and provisioning a route to it in the cloud.
1 - Your first service and first endpoint: Nice and simple to start off with, an unbound function that takes no parameters and should return a specific value.
2 - Capire's "Hello World": An unbound function with a single parameter, that should return a value that combines static text with the value of the argument passed to that parameter.
3 - Basic sum function: The final of the basic unbound function style API endpoints in the context of an OData V4 service, before moving on to a different protocol.
4 - Plain "REST" endpoint: Moving away from the default OData V4 protocol, this is an API endpoint you must define within a new service and have served via the "REST" protocol.
5 - A "REST" service document: Continuing to explore the difference between CAP's OData and "REST" protocol adapters.
6 - An API endpoint with a payload required: Another endpoint in the plain "REST" service, but a little different in its definition, implementation, and how it's called.
7 - Using CQL in an unbound function implementation: In your implementation for a brand new service similar to Northwind, you'll have to use some CQL to respond to a request to an unbound function.
8 - Responding to an OData query with navigation: Learning how useful out-of-the-box supported standard OData mechanisms can be.
9 - Using CQL in an unbound action: Similar to Task 8 but with a little bit of added spice in the form of some JavaScript calculations.
10 - The power of CDL with as-select: Extending the service in the CDS model, with no extra implementation required.
11 - Using implicit parameters with a bound function: Understanding implicit vs explicit parameters, and destructuring to get the value, with bound actions and (in this case) functions.
Bonus (and important) task:
12 - Give us feedback about this challenge: This is an opportunity for you to tell us what you liked, what you didn't like, and what you learned from this month's challenge.
This SAP Developer Challenge for the month of July echoes last year's August challenge on APIs, but turns it around. The August challenge last year had you making API calls to different endpoints on various services. This month's challenge will have you setting up and running various services, each with different endpoints.
The idea is for us all to go on a small journey of discovery and experience the joy of setting up APIs with the power of CAP - defining them in CDS models (in Core Definition Language, CDL) and implementing them, where required* in either Node.js or Java. If you want help along the way, you'll get it in the form of hints in the Node.js flavour of CAP, but if you want, you're welcome to use Java too of course. After all, the API contract is what's important, right?
Over the month you'll set up 10 API endpoints over 3 different services.
* remember that CAP has a rich set of generic providers that means for much of the standard CRUD+Q request landscape you don't have to implement anything yourself.
How will the challenge work? What happens when you set up a service with some API endpoints? How will we know when a specific endpoint has been set up and served successfully? Well, here's a small diagram that will help to answer that, and some terminology.
The mechanism that we will be using to "run" the challenge is the TESTER. It's actually just a very simple CAP service that has a single endpoint `testService` that you will need to call once you have a service and endpoint ready.
The service you set up is known as the CANDIDATE. Each CANDIDATE service will have one or more endpoints.
The TESTREQUEST is when you have a service and endpoint ready to be tested, and you make a request for it to be tested, supplying three pieces of information.
The TESTRESULT is the result of such a TESTREQUEST, and will be either PASS or FAIL.
As well as returning PASS or FAIL to the TESTREQUEST call, the results of the test will be logged and retrievable as an OData V4 entity set (details on this will follow later).
The three services you'll set up over this challenge are deliberately simple, and are intended to help you discover various aspects of service definition and implementation. You will define two of the services as OData V4 protocol based (this is the default in CAP anyway), and one as plain "REST" protocol based.
Over these three services you'll define multiple endpoints, of different "types", including:
To give you a couple of examples, in the first service you'll define, called `basic`, you must create an endpoint `sum` defined as an unbound function that takes two parameters `a` and `b` and returns the sum of the values of those two parameters. In another service you'll define, called `northbreeze`, which you'll have to seed with data too, you must create an endpoint `stockValue` as a bound action that takes no parameters* and returns the total stock value for a product.
* remember that a bound action or function has an implicit "parameter" in terms of the specific entity to which it is bound at call time.
The question right now on your mind is likely to be "How does step 4 (Endpoint is tested by calling it) work?". It's easy to set up a CAP service locally. But how can a service running in the cloud connect to and test your service?
Well, you have many options, and what you end up doing is down to you - your personal preferences, what else you want to learn or practise along the way, and how "temporary" you want to make your service availability.
While there is some data that the third of the three services in this challenge will need to have and to serve, it can be served via the SQLite in-memory mechanism built in to the CAP server, and doesn't need a persistence layer, i.e. doesn't need any sort of backing service. All three of the CAP-based services you will need to create are simple enough to be deployed as-is, in other words, on their own.
In fact, it is perfectly possible for you to serve all three services in a single CAP server instance, so if you choose to, you can just have a single CAP project, with a single CAP server (started with `cds serve` for example) and have that server provide all three services.
Given that, it's actually rather straightforward to deploy a test CAP project to BTP, specifically to a Cloud Foundry runtime. Everyone has access to a trial account on BTP at no cost (and no credit card required), and a Cloud Foundry runtime instance is set up automatically when you create a trial account (or you can provision one after the fact if you don't have one).
Assuming you have a Cloud Foundry runtime instance in a trial account, and have logged in with the cf CLI, here's a (deliberately short) one-liner that:
cds init --add tiny-sample qmacro-simplest-deployment \
&& cd $_ \
&& jq '.+{cds:{features:{in_memory_db:true},requires:{auth:"mocked",db:{kind:"sqlite",credentials:{database:":memory:"}}}}}' package.json > tempfile \
&& mv tempfile package.json \
&& npm add sqlite3 \
&& cf push "$(basename "$PWD")"
(Yes, for those fellow shell nerds out there, I am using `"$(basename "$PWD")"` on the last line to avoid repeating the "qmacro-simplest-deployment" name and keep things DRY).
*if you haven't got jq, first of all, why not? But seriously, just use the terminal in a "Full Stack Cloud Application" Dev Space in SAP Business Application Studio, the default shell is Bash and it has lots of useful tools including all of those used in this invocation (cds, jq, npm and cf). It's a very useful environment.
Once the server is running in Cloud Foundry, you can check the URL like this:
cf app qmacro-simplest-deployment
and you should see something like this:
Showing health and status for app qmacro-simplest-deployment in org dj-adams-42-ajjk4v42-org / space dev as dj.adams@sap.com...
name: qmacro-simplest-deployment
requested state: started
routes: qmacro-simplest-deployment.cfapps.eu10.hana.ondemand.com
last uploaded: Wed 03 Jul 07:14:25 UTC 2024
stack: cflinuxfs4
buildpacks:
name version detect output buildpack name
nodejs_buildpack 1.8.24 nodejs nodejs
type: web
sidecars:
instances: 1/1
memory usage: 1024M
state since cpu memory disk logging details
#0 running 2024-07-03T07:14:37Z 1.3% 92.5M of 1G 195.2M of 1G 0B/s of unlimited
The URL that the CAP server is publicly available on is shown in the "routes" line, here it is:
https://qmacro-simplest-deployment.cfapps.eu10.hana.ondemand.com
If you want to keep your CAP development local, you can do, of course. This is great especially if you want to use all the goodness of the short, tight development cycle that `cds watch` affords. But you need some way of provisioning a route to your locally running CAP server so that the TESTER can reach it. This is where a tool like ngrok comes in. It's a "secure unified ingress platform" and has many features and functions, but, in its basic form, it's a way of setting up a secure reverse proxy tunnel to a port on your local machine. This sort of facility is free, and I use it often.
Here's the "equivalent" of the above invocation if you want to take this approach:
cds init --add tiny-sample qmacro-local-execution \
&& cd $_ \
&& cds watch
The `cds watch` command should start up the CAP server on the default port of 4004.
Then, in a separate terminal window, invoke ngrok* like this:
ngrok http 4004
and you should see a reverse proxy endpoint set up, and a monitor showing something like this:
ngrok (Ctrl+C to quit)
Try our new Traffic Inspector: https://ngrok.com/r/ti
Session Status online
Account DJ Adams (Plan: Free)
Version 3.12.0
Region Europe (eu)
Latency 57ms
Web Interface http://127.0.0.1:4040
Forwarding https://421f-85-255-232-142.ngrok-free.app -> http://localhost:4004
Connections ttl opn rt1 rt5 p50 p90
1 0 0.02 0.00 6.58 6.58
HTTP Requests
-------------
07:55:58.878 UTC GET /odata/v4/catalog/Books 200 OK
07:55:57.531 UTC GET /favicon.ico 200 OK
07:55:57.363 UTC GET / 200 OK
The URL that the CAP server is publicly available on in this case is shown in the "Forwarding" line, here it is:
https://421f-85-255-232-142.ngrok-free.app
There's something relating to URLs that is important to remember that in all cases, including both these two specific approaches (CF-on-BTP and ngrok-based) to running and exposing routes to CAP server based services.
There's a difference between the URL of the CAP server generally, and a service (served by that CAP server) in particular.
Taking the CAP server URLs we saw from both these approaches, we have:
Accessing either of these URLs (they're not active at this time) would give you the classic CAP server landing page:
The CAP server that is presenting the landing page at these URLs is not only serving that landing page (at the root path, i.e. at `/`, after the fully qualified domain name e.g. `qmacro-simplest-deployment.cfapps.eu10.hana.ondemand.com` or `421f-85-255-232-142.ngrok-free.app`), let's call that the "root URL", but also a single service, here at `/odata/v4/catalog` which of course would return the "service document" for that specific service.
This distinction is important to remember when making test requests throughout the course of this month's tasks.
Once you have created a service, and have an endpoint in that service that corresponds to a task in this challenge, you are ready to have that endpoint tested to see if you've fulfilled the task criteria and created an endpoint that responds appropriately.
This is step 3 (Request a test of that service's endpoint) in the diagram shown earlier.
The "root URL" of the CAP server representing the TESTER side of the diagram is:
https://developer-challenge-2024-07.cfapps.eu10.hana.ondemand.com/
The URL of the tester service itself is:
https://developer-challenge-2024-07.cfapps.eu10.hana.ondemand.com/tester
and as you can see from the response for that URL, it's an OData service (you get the service document which includes some `@odata` based properties), with a single entity set "Testlog" which we can ignore for now.
The service metadata document at:
https://developer-challenge-2024-07.cfapps.eu10.hana.ondemand.com/tester/$metadata
shows us that there's an `ActionImport` called `testServer`, defined as an unbound action with three parameters:
When you want to request that the TESTER makes a test to one of your service endpoints, you must call this action import and supply values for each of the three parameters:
Being an action, the call must be made using the HTTP POST method. Provide the values in a JSON object in the body of the HTTP request. Here's an example of such a payload for a task that will come later this month in this challenge, which is called "northbreeze-selectproduct", based on a CANDIDATE server available via the earlier ngrok-based provisioning option, at a CAP server base URL of https://421f-85-255-232-142.ngrok-free.app:
{
"communityid": "qmacro",
"serviceurl": "https://421f-85-255-232-142.ngrok-free.app/odata/v4/northbreeze",
"task": "northbreeze-selectproduct"
}
Note the "communityid" value is your name on this SAP Community platform, for example mine is "qmacro". My numeric SAP Community user ID is 53 but the SAP Community ID we're using here is exactly the same as the one we used for the Developer Challenge on APIs last year.
Note that the value for the `serviceurl` property is a combination of the CAP server base URL plus the relative path of the service itself i.e. `/odata/v4/northbreeze`.
This needs to be sent, in the body of an HTTP POST request, to the following URL:
https://developer-challenge-2024-07.cfapps.eu10.hana.ondemand.com/tester/testServer
Don't forget to include a Content-Type header specifying the media type `application/json`:
Content-Type: application/json
Here's a (reduced) verbose output from a `curl` request that makes this exact request with the payload shown above:
./request-test qmacro /odata/v4/northbreeze northbreeze-selectproduct
* Connected to developer-challenge-2024-07.cfapps.eu10.hana.ondemand.com (3.124.222.77) port 443 (#0)
> POST /tester/testServer HTTP/2
> Host: developer-challenge-2024-07.cfapps.eu10.hana.ondemand.com
> user-agent: curl/7.88.1
> accept: */*
> content-type: application/json
> content-length: 138
>
< HTTP/2 200
< content-type: application/json;odata.metadata=minimal
< date: Wed, 03 Jul 2024 09:40:12 GMT
< odata-version: 4.0
< x-powered-by: Express
< content-length: 56
<
{"@odata.context":"$metadata#Edm.String","value":"PASS"}
(Let me know in the comments if you'd like to see the `request-test` script here).
What's happened between the request and the response here is essentially what's shown in step 4 (Endpoint is tested by calling it) in the diagram, and the output returned:
{"@odata.context":"$metadata#Edm.String","value":"PASS"}
is the TESTER's response, here showing that the endpoint passed the task test (if it hadn't, the value would be "FAIL").
The idea for this challenge, and the idea for last year's API challenge to which this is a reflection, both came from my lovely son Joseph. Everything that you like about these challenges are down to him. Anything that you don't like, anything that goes wrong, is down to me and my inability to execute properly.
Sorry about the gratuitous whitespace before and after code sections - they are added automatically and I cannot get rid of them.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
5 | |
4 | |
4 | |
4 | |
2 | |
2 | |
2 | |
2 | |
2 | |
2 |