Application Development Discussions
Join the discussions or start your own on all things application development, including tools and APIs, programming models, and keeping your skills sharp.
cancel
Showing results for 
Search instead for 
Did you mean: 

Task 4 - Plain "REST" endpoint (July Developer Challenge - "Reverse APIs")

qmacro
Developer Advocate
Developer Advocate
3,021

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, the answer to life, the universe, and everything. 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!

36 REPLIES 36

AndrewBarnard
Contributor
2,915

Q: What is the value of the Content-Type header in the HTTP responses served for your endpoint?
A: content-type: text/plain; charset=utf-8

Q: What is the value of the Content-Type header in the HTTP response when serving an array of Integers?
A: content-type:application/json; charset=utf-8

0 Kudos
2,820

Excellent, yes - the different Content Type values were worth noticing, I think 👍

nidhi_m
Product and Topic Expert
Product and Topic Expert
2,894

Done!

nidhi_m_0-1721027387713.png

value of the Content-Type header in the HTTP responses served for your endpoint : text/plain;charset=utf-8

for an array of Integers: application/json;charset=utf-8

qmacro
Developer Advocate
Developer Advocate
0 Kudos
2,820

Nice. Yes, different, right? Makes sense, too!

mwn
Participant
2,873

I kept the same project, and created a new .js/.cds pair of files.

During development I use cds watch, and then cf push for the final deployment.
I prefer cf push, as it creates a new CF app, which means I can run it even if my BAS workspace is stopped.

mwn_0-1721029912032.png

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
2,820

Nicely done again 🙂

sudarshan_b
Participant
2,806

My submission for Task 4, PASS 

sudarshan_b_0-1721043969605.png

 

tobiasz_h
Active Participant
2,770

Hello,
Good explanation of the difference between Odata V4 and REST!

My submission:

tobiasz_h_0-1721052533297.png

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
2,725

Thank you!

geek
Participant
2,753

geek_0-1721054435364.png

geek_1-1721054716036.png

Response header:

geek_2-1721055032257.png

As an array:

geek_3-1721055952175.png

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
2,725

Nice

Alpesa1990
Participant
2,679

My submission for task 4.

Alpesa1990_0-1721068453778.png

 

M-K
Participant
2,564

Here's my submission:

MK_0-1721073543394.png

Content-Type for Integer (or for String aswell) : "text/plain; charset=utf-8"
Content-Type for Array of Integer: "application/json; charset=utf-8"

But I have a question for you too: why are the values for task (or for communityid) translated to lower case? "plain-theAnswer" becomes "plain-theanswer" here.

 

qmacro
Developer Advocate
Developer Advocate
2,412

Nicely observed! I love it when folks have eagle eyes 🙂 

It is simply because I wanted to "normalise" the task identifiers, mostly because I load the tasks dynamically in the TESTER, like this:

Screenshot 2024-07-16 at 06.43.19.png

Each task's requirement is encapsulated into a separate JS file.

But also partially because it makes reporting (via the Testlog entity set) easier and more consistent.

cheers!

dj

 

gphadnis2000
Participant
2,393

getting below error.Dont know what is wrong

gphadnis2000_0-1721110861394.png

 

2,384

Check your URL, you have to send an POST request to "/tester/testServer"

Cmdd
Participant
2,316

Task 4 submission

Cmdd_0-1721119671737.png

 

YogSSohanee
Participant
2,247

Hello @qmacro ,

I was getting some issues earlier but @nidhi_m helped me resolve them, appreciate her help!

PFB the REST API response

YogSSohanee_0-1721125254700.png

The tester response:

YogSSohanee_1-1721125319709.png

 

 

 

qmacro
Developer Advocate
Developer Advocate
2,120

Great teamwork!

ManojKumarVarma
Explorer
2,211

Hello,

Here is my submission for Task-4

ManojKumarVarma_0-1721130517692.png

Thanks,

Manoj Kumar Potharaju.

MioYasutake
Active Contributor
2,080

My submission for task 4

MioYasutake_0-1721160568431.png

What is the value of the Content-Type header in the HTTP responses served for your endpoint? - Content-Type: text/plain; charset=utf-8

MioYasutake_1-1721160699484.png

What would it be if you served an array of Integers? - Content-Type: application/json; charset=utf-8

MioYasutake_2-1721160737559.png

mxmw
Explorer
2,008

My submission!

mxmw_0-1721202387048.png

 

gphadnis2000
Participant
0 Kudos
1,920

@qmacro : can you able to check logs and let me know the reason for failure

i m still getting it as failed.

gphadnis2000_0-1721279925462.png

 

gphadnis2000
Participant
1,907

Thanks @YogSSohanee for help.

here is my submission for week 4.

gphadnis2000_0-1721284219856.png

 

Ruthiel
Product and Topic Expert
Product and Topic Expert
0 Kudos
1,866

Hello!

Having difficulties to understand why it is failing.
When I run it in the browser I get the Answer:

Ruthiel_1-1721318604714.png

However the result is always the same:

Ruthiel_2-1721318662766.png

Someone can help me to understand why?

 

 

 

1,754

Hi @Ruthiel, 101 is an answer, but not the answer (to life, the universe, and everything) 😉

Ruthiel
Product and Topic Expert
Product and Topic Expert
1,656

Thank you very much @M-K !
Indeed 101 is not the Answer!

qmacro
Developer Advocate
Developer Advocate
0 Kudos
1,472

Thank you @M-K , I didn't actually make that clear enough for some folks (I wrongly assumed that _everyone_ has read The Hitchhiker's Guide To The Galaxy :-))

Ruthiel
Product and Topic Expert
Product and Topic Expert
1,652

Solved!

Ruthiel_0-1721378568863.png

 

vineelaallamnen
Explorer
1,851

Task4 is complete

vineelaallamnen_0-1721321570330.png

 

spassaro
Participant
1,596

FULL RESTFUL!!!

spassaro_1-1721401379920.png

 

 

 

MatLakaemper
Participant
1,400

my submission:

MatLakaemper_0-1721537879391.png

 

MatLakaemper_1-1721537900118.png

content-Type Integer:

Content-Type: text/plain; charset=utf-8

MatLakaemper_2-1721537946191.png

 

Content-Type array of integers

Content-Type: application/json; charset=utf-8

 

MatLakaemper_3-1721537978859.png

 

 

Liyon_SV
Explorer
1,325

The answer to life, the universe and everything 😀

Liyon_SV_0-1721709073428.png

 

qmacro
Developer Advocate
Developer Advocate
0 Kudos
1,318

Indeed! 🙂

emiliocampo
Explorer
1,100

My Task4 Submission

emiliocampo_0-1722085452076.png
emiliocampo_1-1722085471811.png

 

harsh_itaverma
Participant
0 Kudos
938

Pass 🙂

harsh_itaverma_0-1722606117004.pngharsh_itaverma_1-1722606164908.png

When array is served the Content-Type header in the response application/json; charset=utf-8 otherwise text/plain; charset=utf-8.