Task 4 – Plain “REST” endpoint (July Developer Challenge – “Reverse APIs”)

This is a task in the July Developer Challenge – “Reverse APIs”.

In this task, you’ll create a new, second service, and within that you’ll add a single API endpoint. The difference to the previous service and endpoints is that here a plain “REST” protocol is required.

Background

What does that mean, exactly? Well, you may know that CAP’s design is wonderfully modular, and agnostic as well as opinionated. I’m sure you will also know that if you define a service in your CDS model and serve it with the CAP server, you’ll basically have an OData service. Standing up and serving OData services was the original killer app for the SAP Cloud Application Programming Model. It’s hard now to remember how much effort it was, before CAP came along, to create and serve an OData service – and there was even more effort, much more, in fact, to facilitate all Create, Read, Update, Delete & Query (CRUD+Q) operations for the data model you had defined. We take it for granted that with CAP we can spin up an OData service with fully functional support for all standard CRUD+Q operations in a couple of minutes.

When talking about OData, I refer specifically to OData V4. This is for two reasons: CAP’s default for OData is also V4, and, well, OData V4 is already over a decade old.

Protocols and CAP’s modular design

While CAP will default to serving OData services, its modular design allows for services to be served using different protocols. Yes, OData is more than a protocol, but the protocol component is critical.

And that leads us on to the other protocol that CAP can serve, out of the box: “REST”. I personally put “REST” here in quotes, because REST is not a protocol, it is an architectural style, with a set of constraints that should inform the design of HTTP-based APIs, if they are to be accurately referred to as “RESTful”. Incidentally, if an API conforms to all of the architectural constraints described, it is referred to as “Fully RESTful”, and yes, that’s the “hidden” (second, or first) meaning behind the name of my narrowboat where I live and work.

fullyrestful.png

When you see “REST” referred to as a protocol, think of it as a “plain HTTP” style API.

Anyway, for the sake of this Developer Challenge, and for common understanding and consistency with Capire, the CAP documentation, we can think of “REST” as a protocol. You can see in the cds.serve() – cds.protocols section of Capire which protocol adapters are available both out of the box and as an open source package.

It’s important then to think of a CAP service in different contexts, or at different layers:

  • the definition (in CDL, within the CDS model as a whole)
  • the implementation (in Node.js or Java)
  • the protocol used to serve it

And as the primary protocols used to serve it are all based on the application protocol that is HTTP, there’s a link between the protocol used, and how that is indicated, or exposed, as part of the URL path. Here are the default paths for the standard protocols:

  • OData V4: /odata/v4
  • “REST”: /rest
  • GraphQL: /graphql

And OData V4 is the default protocol. So by default, if you define a service x, it will be served as an OData V4 service, at the service path /odata/v4/x.

With the @path annotation you can specify a custom path for the service, and this is what you were required to do for the service that contained the API endpoints described in Tasks 1, 2 and 3, in that the required path for the OData service was /basic, rather than /odata/v4/basic.

With the @protocol annotation you can specify the protocol.

Differences between the OData V4 and “REST” protocols

If you take a basic CAP service x that defaults to being served as an OData protocol, at /odata/v4/x, and then switch protocols by annotating it with @protocol: ‘rest’ (or simply @rest) you’ll see that while there are differences between the key resources (such as the entity sets) they are only very slight. Perhaps most notably there’s an absence of any notion of metadata or metadata context. Even the standard OData system query options (such as $filter and $select) are supported.

That lack of differences, in my opinion, is because it makes a lot of sense, based on a combination of reasons:

  • OData has a well thought out, battle-tested and mature protocol, set of URL convention and schema definition language
  • Not being an actual protocol, REST needs some concrete decisions with respect not only to addressing and accessing & manipulating resources, but also to providing those resources in specific representations. And OData’s approach is both well designed and well understood, from a URL convention perspective (addressing and accessing resources), from a protocol perspective (accessing & manipulating resources) and resource provision perspective (providing those resources in specific representations). So why re-invent?
  • a well-designed plain HTTP protocol today should work with, rather than fight against, the HTTP constructs and philosophy of being an application protocol (yes I’m looking at you, GraphQL)

This and the next couple of tasks give you a chance to explore these ideas, and the “REST” protocol in particular.

The requirements

Here are the specific requirements for this task.

You must create a new service called plain. One separate to the basic service that you already have. The service must be served via the “REST” protocol, at the default endpoint for such a service.

Within this new service, you should define a very simple API endpoint that returns a static value. Very much like the endpoint in Task 1 – Your first service and endpoint.

It should expect no arguments (and therefore be defined with no parameters), and be standalone, i.e. “unbound”. It should be callable via the HTTP GET method and have no side-effects, i.e. a “function”.

The terms bound and unbound, and the idea of and semantic differences between functions and actions, are taken from the OData world, but make sense here in the land of more plain HTTP based APIs, especially as with CAP, the service definition (CDS model), and the serving of the service (protocol), are separate.

To underline, however, that we’ve now moved away from the OData protocol, the API endpoint should be addressable via the following simpler path:

/rest/plain/theAnswer
Note the lack of parentheses at the end of the last segment.

Like always, once you’ve got your service defined, and a simple implementation ready, you’re done.

It is definitely worth testing it yourself first, e.g. with curl, Postman, or even the REST Client extension to VS Code that some of you are using (going on what I can see from some of your responses to the previous task). Use 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/rest/plain/theAnswer"
and the reponse should look like this:
42
For some bonus kudos, share your observation in the comments below on the default representation here. What is the value of the Content-Type header in the HTTP responses served for your endpoint?. What would it be if you served an array of Integers?

Defining and implementing this second service

CAP offers flexibility, not least in service definitions (in the overall CDS model) and implementations. In needing to define and implement a new, second service, you have lots of choices:

  • define the service in the same, existing .cds file that you already have
  • create a new .cds file

and of course you could always:

  • create a completely new CAP project

Which way you go is up to you. It would be great to hear from you, again, in the comments below, which approach you took.

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: plain-theAnswer.

You’ll have already done this sort of thing previously so just head back there for the more detailed instructions if you need them, or to the the section titled “The Tester service, and making a test request” in the main challenge blog post.

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": "plain-theAnswer"
}
And, just as with the previous (and all further tasks):
  • the value for the communityid property should be your ID on this SAP Community platform (e.g. mine is “qmacro”)

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

That’s it!

Logging of test results

Remember that you can check on your progress, and the progress of your fellow participants – all requests are logged and 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.

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

Scroll to Top