08-16-2023 7:06 AM - edited 08-16-2023 8:09 AM
(Check out the SAP Developer Challenge - APIs blog post for everything you need to know about the challenge to which this task relates!)
You've made various read-only requests so far; now it's time to create some data.
For this task we return briefly to the Northbreeze service from Task 2. Until now, mostly due to the nature of the deliberate simplicity of the tasks in this Developer Challenge, the actions you've been performing from an HTTP perspective have been read-only.
This time you'll write some data, albeit data that won't last long (the Northbreeze service runs in-memory and has been set up not to use a permanent persistence layer). Then you'll read it back, with a minor difference, to examine what the response looks like. It's that response, compressed, that you'll need for the hash.
The Northwind service traditionally has products, which you've encountered already. Those products are organized into categories, of which there are just a few. A category (taking the first one) looks like this:
{ "CategoryID": 1, "CategoryName": "Beverages", "Description": "Soft drinks, coffees, teas, beers, and ales" }
You can get a feel for the distribution of products across categories by asking for the count of products by category ID which is expressed like this (showing the path, with whitespace, for easier reading):
/odata/v4/northbreeze/Products ?$apply=groupby( (Category_CategoryID), aggregate($count as productcount) )
This returns a response that looks like this:
{ "@odata.context": "$metadata#Products(Category_CategoryID,productcount)", "value": [ { "productcount@odata.type": "#Decimal", "productcount": 12, "Category_CategoryID": 1, "@odata.id": null }, { "productcount@odata.type": "#Decimal", "productcount": 12, "Category_CategoryID": 2, "@odata.id": null }, { "productcount@odata.type": "#Decimal", "productcount": 13, "Category_CategoryID": 3, "@odata.id": null }, { "productcount@odata.type": "#Decimal", "productcount": 10, "Category_CategoryID": 4, "@odata.id": null }, { "productcount@odata.type": "#Decimal", "productcount": 7, "Category_CategoryID": 5, "@odata.id": null }, { "productcount@odata.type": "#Decimal", "productcount": 6, "Category_CategoryID": 6, "@odata.id": null }, { "productcount@odata.type": "#Decimal", "productcount": 5, "Category_CategoryID": 7, "@odata.id": null }, { "productcount@odata.type": "#Decimal", "productcount": 12, "Category_CategoryID": 8, "@odata.id": null } ]}
As you can see, the OData V4 aggregate facilities, and CAP's support for them, is pretty neat!
Your task is to create a new category, and then request it. In other words, you must contruct an appropriate HTTP request to form an OData CREATE operation, to add a new category entity into the entity set. You can see from above that a category has just three properties:
You must supply the following values for these properties when creating your new category:
You can tell that the CategoryID property must be a number, because of how it's defined in the OData service's metadata document.
Once you've created your new category, you must then construct an appropriate HTTP request to form an OData READ operation to request a representation of your category. There are some important points here:
The representation of the response is the value you need to hash and share as a new reply to this discussion thread, as always, and as described in Task 0. Note that you'll have to URL encode the entire value, as you will be sending it within the value=('...') part of the hash service URL.
You should ensure that the order of the properties in the JSON object structure returned is sorted (it should already be, but double check or just add that processing to whatever mechanism you build to do this) and that there is no extraneous whitespace in the structure at all.
Here's an example of what a response representation will look like - note the single line and no whitespace, and that the CategoryName value is all lower case:
{"@odata.context":"some-context-specification","CategoryID":53,"CategoryName":"qmacro"}
You already know about your SAP Community ID. For example, qmacro is the SAP Community ID for DJ Adams. To get your SAP Community ID number, which is unique, just go to your profile page, and get the number from your profile page's URL. For example, the profile page for DJ Adams is at https://groups.community.sap.com/t5/user/viewprofilepage/user-id/53 and looks like this:
You can see the SAP Community ID number in the URL - it's 53. Go to your profile page and get the number from the resulting URL for your profile.
What about the OData READ vs OData QUERY operation question? Both use the same HTTP method (GET) but the structure of the data returned is different. Given that the data and its structure is what you need to ensure is correct before hashing it, it's important you think about how you would request that single new category.
What is the definition of the CategoryID property, according to the metadata document?
What is the difference between the result of an OData QUERY operation and the result of an OData READ operation? How does this relate to OData's origins? What is the significant difference in data structure returned?
Did you try to request a different representation of your category, such as XML? What happened?
08-16-2023 7:32 AM - edited 08-16-2023 10:32 AM
08-16-2023 7:56 AM
08-16-2023 8:11 AM
Well done - yes, you are correct. I've just added a note to the "Your task" section to help make this more explicit for others. Thanks for asking! 👍
08-16-2023 10:58 AM
08-16-2023 8:10 AM
08-16-2023 8:41 AM
08-16-2023 8:42 AM
I've used ChatGTP to explain to me what is the difference between OData READ and QUERY
finger cross🤞that it explains me correctly 😄
08-16-2023 8:45 AM
08-16-2023 8:51 AM
08-16-2023 10:14 AM - edited 08-16-2023 10:24 AM
@ajmaradiaga you would find it funny but I checked my answers imitating yours and @seVladimirs answer and was happy that it's matching 😛
My bad as I posted the wrong hash that I got with the JSON representation without removing the whitespaces.
Thanks a lot for the heads up.
08-16-2023 10:43 AM
@ajmaradiaga can you please check once how you tried getting the hash for me cause looks like it was correct 😞
08-16-2023 9:29 AM
08-16-2023 10:58 AM
08-16-2023 11:27 AM
08-16-2023 11:36 AM
08-16-2023 1:00 PM
08-16-2023 1:07 PM
08-16-2023 1:36 PM
This was fun!
Like some others, I also decided to ask Chat GPT for some help (OData operations, filters etc..) and was surprised by very helpful and on-point answers. In this sense "Googling" is much worse.
Then I just used REST client. In my case one of the browser extensions 🙂
08-16-2023 1:39 PM
Great! That's one aspect of this challenge that I wanted to stay with - to have tasks that are fun. So that makes me happy that you had fun 🙂
08-16-2023 1:48 PM
08-16-2023 1:54 PM
08-16-2023 2:54 PM
08-16-2023 3:05 PM
08-16-2023 3:44 PM - edited 08-17-2023 3:00 AM
for discussion:
What is the difference between the result of an OData QUERY operation and the result of an OData READ operation?
OData READ return a single entity as a result. We have to provide the key of the entity we wanted in the resource path (with pattern <entityset name>/<key> or <entityset name>(key).
Whilst, the Q operation will create a list of the entities matched with query options then return this array as the value of property "value".
Did you try to request a different representation of your category, such as XML? What happened?
I am not sure that I truly understand the question, what I did was I asked for $format=xml as the following URL
I got code 406 with error message Missing format for representation kind 'ENTITY'
<error xmlns="http://docs.oasis-open.org/odata/ns/metadata">
<code>406</code>
<message>Missing format for representation kind 'ENTITY'</message>
</error>
I also tried to do a similar thing with the Northwind services but right here I got a different response as below
<m:error xmlns:m="http://docs.oasis-open.org/odata/ns/metadata">
<m:code/>
<m:message>Unsupported media type requested.</m:message>
<m:innererror>
<m:message>A supported MIME type could not be found that matches the acceptable MIME types for the request. The supported type(s) 'application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false, application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=true, application/json;odata.metadata=minimal;odata.streaming=true, application/json;odata.metadata=minimal;odata.streaming=false;IEEE754Compatible=false, application/json;odata.metadata=minimal;odata.streaming=false;IEEE754Compatible=true, application/json;odata.metadata=minimal;odata.streaming=false, application/json;odata.metadata=minimal;IEEE754Compatible=false, application/json;odata.metadata=minimal;IEEE754Compatible=true, application/json;odata.metadata=minimal, application/json;odata.metadata=full;odata.streaming=true;IEEE754Compatible=false, application/json;odata.metadata=full;odata.streaming=true;IEEE754Compatible=true, application/json;odata.metadata=full;odata.streaming=true, application/json;odata.metadata=full;odata.streaming=false;IEEE754Compatible=false, application/json;odata.metadata=full;odata.streaming=false;IEEE754Compatib...' do not match any of the acceptable MIME types 'application/xml'.</m:message>
<m:type>Microsoft.OData.Core.ODataContentTypeException</m:type>
<m:stacktrace> at Microsoft.OData.Core.MediaTypeUtils.GetContentTypeFromSettings(ODataMessageWriterSettings settings, ODataPayloadKind payloadKind, ODataMediaTypeResolver mediaTypeResolver, ODataMediaType& mediaType, Encoding& encoding) at Microsoft.OData.Core.ODataMessageWriter.EnsureODataFormatAndContentType() at Microsoft.OData.Core.ODataMessageWriter.SetHeaders(ODataPayloadKind payloadKind) at Microsoft.OData.Core.ODataUtils.SetHeadersForPayload(ODataMessageWriter messageWriter, ODataPayloadKind payloadKind) at Microsoft.OData.Service.ResponseContentTypeNegotiator.DetermineResponseFormat(ODataPayloadKind payloadKind, String acceptableMediaTypes, String acceptableCharSets)</m:stacktrace>
</m:innererror>
</m:error>
I think I still don't get it, waiting for other member to share their thoughts about this. 😁
and the last thing, TBH, I am not confident that I submitted the right or wrong answer.
Already tried to cross check with the hash from other members but I feel like I am lost now. 🤔
Regards,
Wises
08-16-2023 5:06 PM - edited 08-16-2023 9:10 PM
Thanks for digging in and getting involved in the discussion! You're absolutely right about the difference between OData QUERY and READ operation responses. And while now with OData V4 the responses to such operations come often exclusively in JSON representations, with OData V2 it was the other way around, in that the responses to these operations were primarily in XML.
Specifically Atom XML.
With content negotiation, or with the $format system query option, one could convey a preference for a different representation when performing an QUERY or READ operation.
As an example, the default representation for an entity set in OData V2 was XML. To illustrate, here's an example of a QUERY operation:
https://services.odata.org/V2/Northwind/Northwind.svc/Products?$top=2
and here's what the response looks like (note the outermost <feed> element):
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<feed xml:base="https://services.odata.org/V2/Northwind/Northwind.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
<title type="text">Products</title>
<id>https://services.odata.org/V2/Northwind/Northwind.svc/Products</id>
<updated>2023-08-16T15:53:41Z</updated>
<link rel="self" title="Products" href="Products" />
<entry>
<id>https://services.odata.org/V2/Northwind/Northwind.svc/Products(1)</id>
<title type="text"></title>
<updated>2023-08-16T15:53:41Z</updated>
<author>
<name />
</author>
<link rel="edit" title="Product" href="Products(1)" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Category" type="application/atom+xml;type=entry" title="Category" href="Products(1)/Category" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Order_Details" type="application/atom+xml;type=feed" title="Order_Details" href="Products(1)/Order_Details" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Supplier" type="application/atom+xml;type=entry" title="Supplier" href="Products(1)/Supplier" />
<category term="NorthwindModel.Product" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<content type="application/xml">
<m:properties>
<d:ProductID m:type="Edm.Int32">1</d:ProductID>
<d:ProductName m:type="Edm.String">Chai</d:ProductName>
<d:SupplierID m:type="Edm.Int32">1</d:SupplierID>
<d:CategoryID m:type="Edm.Int32">1</d:CategoryID>
<d:QuantityPerUnit m:type="Edm.String">10 boxes x 20 bags</d:QuantityPerUnit>
<d:UnitPrice m:type="Edm.Decimal">18.0000</d:UnitPrice>
<d:UnitsInStock m:type="Edm.Int16">39</d:UnitsInStock>
<d:UnitsOnOrder m:type="Edm.Int16">0</d:UnitsOnOrder>
<d:ReorderLevel m:type="Edm.Int16">10</d:ReorderLevel>
<d:Discontinued m:type="Edm.Boolean">false</d:Discontinued>
</m:properties>
</content>
</entry>
<entry>
<id>https://services.odata.org/V2/Northwind/Northwind.svc/Products(2)</id>
<title type="text"></title>
<updated>2023-08-16T15:53:41Z</updated>
<author>
<name />
</author>
<link rel="edit" title="Product" href="Products(2)" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Category" type="application/atom+xml;type=entry" title="Category" href="Products(2)/Category" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Order_Details" type="application/atom+xml;type=feed" title="Order_Details" href="Products(2)/Order_Details" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Supplier" type="application/atom+xml;type=entry" title="Supplier" href="Products(2)/Supplier" />
<category term="NorthwindModel.Product" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<content type="application/xml">
<m:properties>
<d:ProductID m:type="Edm.Int32">2</d:ProductID>
<d:ProductName m:type="Edm.String">Chang</d:ProductName>
<d:SupplierID m:type="Edm.Int32">1</d:SupplierID>
<d:CategoryID m:type="Edm.Int32">1</d:CategoryID>
<d:QuantityPerUnit m:type="Edm.String">24 - 12 oz bottles</d:QuantityPerUnit>
<d:UnitPrice m:type="Edm.Decimal">19.0000</d:UnitPrice>
<d:UnitsInStock m:type="Edm.Int16">17</d:UnitsInStock>
<d:UnitsOnOrder m:type="Edm.Int16">40</d:UnitsOnOrder>
<d:ReorderLevel m:type="Edm.Int16">25</d:ReorderLevel>
<d:Discontinued m:type="Edm.Boolean">false</d:Discontinued>
</m:properties>
</content>
</entry>
</feed>
You could also ask for this same resource in JSON (a different JSON representation to that of V4, by the way):
https://services.odata.org/V2/Northwind/Northwind.svc/Products?$top=2&$format=json
Conversely, the default representation for an entity set in OData V4 is JSON (but, as noted a different JSON format to that of V2):
https://services.odata.org/V4/Northwind/Northwind.svc/Products
And some OData V4 service implementation support the older XML format, which can be requested with the $format system query option as before. But note that the value for this option should be 'atom', not 'xml':
https://services.odata.org/V4/Northwind/Northwind.svc/Products?$format=atom
Before moving on to the other question (about the relation to OData's origins), let's also look at an Atom XML representation of a single product, i.e. perform an OData READ operation on a product.
First, from an OData V2 service:
https://services.odata.org/V2/Northwind/Northwind.svc/Products(1)
This gives us (note the outermost <entry> element):
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<entry xml:base="https://services.odata.org/V2/Northwind/Northwind.svc/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
<id>https://services.odata.org/V2/Northwind/Northwind.svc/Products(1)</id>
<title type="text"></title>
<updated>2023-08-16T15:52:14Z</updated>
<author>
<name />
</author>
<link rel="edit" title="Product" href="Products(1)" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Category" type="application/atom+xml;type=entry" title="Category" href="Products(1)/Category" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Order_Details" type="application/atom+xml;type=feed" title="Order_Details" href="Products(1)/Order_Details" />
<link rel="http://schemas.microsoft.com/ado/2007/08/dataservices/related/Supplier" type="application/atom+xml;type=entry" title="Supplier" href="Products(1)/Supplier" />
<category term="NorthwindModel.Product" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
<content type="application/xml">
<m:properties>
<d:ProductID m:type="Edm.Int32">1</d:ProductID>
<d:ProductName m:type="Edm.String">Chai</d:ProductName>
<d:SupplierID m:type="Edm.Int32">1</d:SupplierID>
<d:CategoryID m:type="Edm.Int32">1</d:CategoryID>
<d:QuantityPerUnit m:type="Edm.String">10 boxes x 20 bags</d:QuantityPerUnit>
<d:UnitPrice m:type="Edm.Decimal">18.0000</d:UnitPrice>
<d:UnitsInStock m:type="Edm.Int16">39</d:UnitsInStock>
<d:UnitsOnOrder m:type="Edm.Int16">0</d:UnitsOnOrder>
<d:ReorderLevel m:type="Edm.Int16">10</d:ReorderLevel>
<d:Discontinued m:type="Edm.Boolean">false</d:Discontinued>
</m:properties>
</content>
</entry>
Now from an OData V4 service (where we have to explicitly convey our preference for a representation in Atom XML):
https://services.odata.org/V4/Northwind/Northwind.svc/Products(1)?$format=atom
This gives us (note, again, the outermost <entry> element):
<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://docs.oasis-open.org/odata/ns/data" xmlns:m="http://docs.oasis-open.org/odata/ns/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml" xml:base="https://services.odata.org/V4/Northwind/Northwind.svc/" m:context="https://services.odata.org/V4/Northwind/Northwind.svc/$metadata#Products/$entity">
<id>https://services.odata.org/V4/Northwind/Northwind.svc/Products(1)</id>
<category term="#NorthwindModel.Product" scheme="http://docs.oasis-open.org/odata/ns/scheme"/>
<link rel="edit" title="Product" href="Products(1)"/>
<link rel="http://docs.oasis-open.org/odata/ns/related/Category" type="application/atom+xml;type=entry" title="Category" href="Products(1)/Category"/>
<link rel="http://docs.oasis-open.org/odata/ns/related/Order_Details" type="application/atom+xml;type=feed" title="Order_Details" href="Products(1)/Order_Details"/>
<link rel="http://docs.oasis-open.org/odata/ns/related/Supplier" type="application/atom+xml;type=entry" title="Supplier" href="Products(1)/Supplier"/>
<title/>
<updated>2023-08-16T15:57:24Z</updated>
<author>
<name/>
</author>
<content type="application/xml">
<m:properties>
<d:ProductID m:type="Int32">1</d:ProductID>
<d:ProductName>Chai</d:ProductName>
<d:SupplierID m:type="Int32">1</d:SupplierID>
<d:CategoryID m:type="Int32">1</d:CategoryID>
<d:QuantityPerUnit>10 boxes x 20 bags</d:QuantityPerUnit>
<d:UnitPrice m:type="Decimal">18.0000</d:UnitPrice>
<d:UnitsInStock m:type="Int16">39</d:UnitsInStock>
<d:UnitsOnOrder m:type="Int16">0</d:UnitsOnOrder>
<d:ReorderLevel m:type="Int16">10</d:ReorderLevel>
<d:Discontinued m:type="Boolean">false</d:Discontinued>
</m:properties>
</content>
</entry>
So, now to the question about OData's origins You can read all about OData's origins in the blog post Monday morning thoughts: OData, but in summary, it was a direct descendant of Atom, and the Atom Publishing Protocol, which in turn was a follow-on from RSS.
That's why the XML representation of OData entity sets (retrieved via QUERY operations) is based around an outermost <feed> element, i.e. an RSS or Atom feed for a blog*, and why the XML representation of OData entities (retrieved via READ operations) is based around an outermost <entry> element, i.e. a blog post, i.e. an entry in the feed.
Hope that helps to clarify and to join the dots!
* a "blog" is the term for a collection of entries, of posts, of articles. It is not, and has never been, an individual post or article. If you see the term abused this way, call it out! 🙂 For example, my blog is at https://qmacro.org/blog/ and consists of many, many posts, like these:
- https://qmacro.org/blog/posts/2023/07/22/curing-my-stove-aboard-the-narrowboat/
and so on.
08-17-2023 2:51 AM
Thanks DJ for the explanations, it's atom, not xml (telling myself 😅).
1 year after the back-to-basic OData hands-on sessions, I can say now that I understand and be able to use basic operations of OData APIs. 🙏
08-16-2023 3:19 PM
08-16-2023 3:33 PM
08-16-2023 3:36 PM
I am not sure if my encoded value is true but I tried 🙂
I tried to get my data as XML, it gave me a response like this. It does not have @odata.context field.
08-16-2023 5:15 PM
08-16-2023 5:36 PM
08-23-2023 7:05 AM
Hey @eakucuk
I'm not sure what's happening here, perhaps the suite is modifying the response to create the XML that you see. I can't imagine that the service would return XML like that natively.
Also, I note you're making a QUERY operation. If you're trying to complete the task, that's not what you need to do 🙂
08-16-2023 3:49 PM
08-16-2023 3:51 PM
08-16-2023 9:33 PM
08-17-2023 4:52 AM
08-17-2023 6:10 AM
08-17-2023 6:16 AM
Hopefully this one is the correct syntax, as I'm not so sure on the difference between OData read and query, and just like the others before me, I used ChatGPT to find the differences, but these may be inaccurate.
OData query is more structured with their requests, whereas read is the entire process of obtaining data.
When I tried to request Atom like how it is presented with bztoy with the Northbreeze dataset, it leads to an error message saying that atom is the wrong accept header value.
What I can answer is the the type for CategoryID is Edm.Int32, instead of Edm.String like the other parts of the entity.
08-17-2023 5:22 PM - edited 08-17-2023 5:23 PM