2024-07 Developer Challenge – Task 1 – Your first service and first endpoint

This is a task in the 2024-07 “Reverse APIs” SAP Developer Challenge.

So, you’ve completed the warm-up task 0 and have managed to get a simple CAP server up, running and accessible from the cloud, and therefore (critically for this month’s challenge) accessible by the TESTER component. Great! Now it’s time to create your first API endpoint. What does that mean, and what do you have to do?

As this is your first task where you have to create both a service and an endpoint, some help and hints are given. So it’s a little more reading for you, but don’t worry, the subsequent task descriptions won’t be as verbose.

API endpoints and services

An API endpoint very often exists in the wider context of a service, which you can think of as being a “container” for one or more endpoints. An OData service will typically offer multiple entity set resources, plus perhaps some action and function imports.

For example, the classic Northwind service offers various “collections” which normally translate into “entity sets”, such as Customers, Products and Categories. And as for function and action imports, the TripPin service, for example, sports a function import called GetNearestAirport*.

*I was surprised to see that my nearest airport is apparently in Rome, but then remembered that there are only 15 airports in the dataset.

Not every service is OData, of course. But in the context of enterprise computing, it’s an extremely well respected, understood and used open standard. And CAP makes it child’s play to create an OData service and fill it with API endpoints.

Creating your first service

And as the idea of this challenge is to use CAP, then the first thing you’ll need to do is create a simple service. You can modify the existing CatalogService that you got for free when you initialised the CAP project in Task 0. You know, the one that exposes a Books entity set, and is defined declaratively, with CDL, in srv/cat-service.cds:

using my.bookshop as my from '../db/data-model';

service CatalogService {
    @readonly entity Books as projection on my.Books;
}
 

But what we recommend is that you leave that CatalogService as it is, and create your own new service. And while you could define it in the same srv/cat-service.cds file, instead, we recommend you take advantage of the wonderfully flexible nature of the CDS compiler … and create a (pair of) new file(s), in the srv/ directory, in which you will define your service.

Hint: As you’ll see, the name of the service you’ll be required to create here is basic, so, within the srv/ directory, why not use the filename basic.cds. Furthermore, the API endpoint you’ll be required to create in this task is an unbound function within that service, so (as with all actions and functions) you’ll need to provide an implementation, so why not use the filename basic.js for that and place that file next to (in the same directory as) basic.cds.

The requirements

Here are the specific requirements for this task.

Define a new service.

Make sure the service name is basic.

Have it be served via the OData V4 protocol (this is what all services are served via by default with CAP). But don’t have it served at the standard path for an OData V4 protocol based service (which would be /odata/v4/basic), instead, have it served at the simpler path /basic.

The service, at least at this point in the challenge, needs to have a single API endpoint, specifically one that is called ping, that can be called with the HTTP GET method, takes no parameters, and returns a JSON payload that looks like this:

{
  "@odata.context": "$metadata#Edm.String",
  "value": "pong"
}
 

It is critical (to test success) that what’s returned in the value property here is the JSON string “pong”.

In other words, this API endpoint should be defined as an unbound function.

When the service, containing this API endpoint, is fully defined, and served via the OData V4 protocol, the metadata document should look like this:

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
  <edmx:Reference Uri="https://sap.github.io/odata-vocabularies/vocabularies/Common.xml">
    <edmx:Include Alias="Common" Namespace="com.sap.vocabularies.Common.v1"/>
  </edmx:Reference>
  <edmx:Reference Uri="https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.xml">
    <edmx:Include Alias="Core" Namespace="Org.OData.Core.V1"/>
  </edmx:Reference>
  <edmx:DataServices>
    <Schema Namespace="basic" xmlns="http://docs.oasis-open.org/odata/ns/edm">
      <EntityContainer Name="EntityContainer">
        <FunctionImport Name="ping" Function="basic.ping"/>
      </EntityContainer>
      <Function Name="ping" IsBound="false" IsComposable="false">
        <ReturnType Type="Edm.String"/>
      </Function>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>
 

If you’ve not defined (here: in srv/basic.cds) and written an implementation (here: in srv/basic.js) for a function or action before, then you need to know a couple of things.

First, know that you need to implement such functions or actions via the on event. Also, know that there are two implementation styles in CAP Node.js*, described in the Capire section How to provide custom service implementations?:

  • the ES6 class based style, extending the cds.ApplicationService class
  • the older and simpler cds.service.impl style

Note that the older style is translated automatically, behind the scenes, to the ES6 class based style anyway.

* As mentioned in the Introduction to this challenge, hints are for the Node.js flavour of CAP, but of course you’re welcome to use the Java flavour if you prefer.

Once you’ve got your service defined, and a simple implementation ready, with an on handler for the ping event, you’re ready.

It is definitely worth testing it yourself first, e.g. with curl, or Postman, or whatever tool you prefer for making HTTP calls. With your server running (on, let’s say, the default local CAP server port of 4004), make a request like this:

curl -s --url 'localhost:4004/basic/ping()'
 

and the reponse should look like this:

{"@odata.context":"$metadata#Edm.String","value":"pong"}
 

Submitting your API endpoint to the TESTER

Now you’re ready to submit your CANDIDATE service, with the specific API endpoint, to the TESTER!

The payload

The task identifier you need to supply in the payload of your submission is: basic-ping.

While some of you lovely folks with a more rebellious and hacker nature (I’m looking at you @ajmaradiaga and @gphadnis2000 :-)) have already kicked the tyres of the TESTER service, this should be the first time you’ll be using the TESTER service.

Most of what you need to know is described in the the section titled “The Tester service, and making a test request” in the main challenge blog post, so head over to that section for a quick refresher first, then come back here.

Now, to have your freshly minted API endpoint in this task tested, you’ll need to submit a JSON payload like this:

{
  "communityid": "<your-community-id>",
  "serviceurl": "<the-URL-of-your-service>",
  "task": "basic-ping"
}
 

Here’s an example (don’t use these values for communityid and serviceurl, they’re specific to me and provided here just for illustration):

{
  "communityid": "qmacro",
  "serviceurl": "https://c0df-85-255-235-188.ngrok-free.app/basic",
  "task": "basic-ping"
}
 

Note that the value for the communityid property should be your ID on this SAP Community platform (e.g. mine is “qmacro”).

Note also the value for the serviceurl property should be the absolute URL (i.e. including the scheme), of your CANDIDATE service (see ℹ️ A note on URLs and services), not the specific API endpoint.

The service URL

Of course, you need to make sure your new service and API endpoint are available to the TESTER, so supplying http://localhost:4004/basic as the value for the serviceurl property is not going to work. Make sure you either redeploy your CAP project (if you’re pushing to Cloud Foundry) or still have your ngrok tunnel up and running (if you’re using the ngrok tunnel approach).

Submitting the test request

You’ll need to submit that JSON payload in a POST request to this endpoint:

https://developer-challenge-2024-07.cfapps.eu10.hana.ondemand.com/tester/testServer

and you’ll need to supply a Content-Type header stating that the representation of the payload resource has a media type of application/json.

Here’s a curl invocation doing exactly that, by way of example (and using my specific values shown in the illustration above):

curl 
  --data '{"communityid":"qmacro","serviceurl":"https://c0df-85-255-235-188.ngrok-free.app/basic","task":"basic-ping"}' 
  --header 'Content-Type: application/json' 
  --url 'https://developer-challenge-2024-07.cfapps.eu10.hana.ondemand.com/tester/testServer'
 

Types of responses

You’ll see one of two types of responses- an error response, or a test result response.

Error response

If there’s something wrong with your call then you’ll get an error response. For example, if you haven’t supplied a value for the communityid property, you’ll get an HTTP 400 error response with a payload like this:

{
  "error": {
    "code": "400",
    "message": "Missing Community ID value",
    "@Common.numericSeverity": 4
  }
}
 

Similarly, if the TESTER cannot reach your endpoint, you’ll get HTTP 500 error response with a payload like this:

{
  "error": {
    "code": "500",
    "message": "Error calling service endpoint",
    "@Common.numericSeverity": 4
  }
}
 

Other similar errors may occur, for example if you supply a missing or invalid task identifier or service URL.

Test result response

If the TESTER manages to reach your API endpoint successfully, it will then check that the response returned is what is required and expected. In the case of this task, the response required and expected is the static value “pong”. If that is what is received, a positive test result response will be returned, and will look like this:

{"@odata.context":"$metadata#Edm.String","value":"PASS"}
 

If a different value is received, then a negative test response will be returned, and will look like this:

{"@odata.context":"$metadata#Edm.String","value":"FAIL"}
 

That’s it!

Logging of test results

Each time there’s a call to the TESTER by any participant, and a test result response can be issued, that test request is logged. So you can check on your progress, and the progress of your fellow participants.

The logged requests are available in an entity set served by the TESTER service. The entity set URL is https://developer-challenge-2024-07.cfapps.eu10.hana.ondemand.com/tester/Testlog and being an OData V4 entity set, all the normal OData system query options are available to you for digging into that information.

But that’s all for now on that.

Until the next task, have fun, and if you have any questions or comments, leave them below!

Scroll to Top