Technical Articles
SAP Cloud Platform Backend service: Tutorial [5]: CDS : using Property Facets
This blog is about Property Facets, which is a very common way of adding metadata to the metadata of an OData service metadata.
Apart from metadating it is also about coding. Generating coding.
See here for the backgrounding of this blogging posting
Property Facets is a term used in OData (see spec)
An OData service comes along with a metadata document, which describes the structure of the service: entities, properties, etc
Each property has a data type which describes which value can be assigned to a property.
For instance, a name is always a string, an age is always an integer number, a price is always a decimal number
While you may think that this is really enough information….
Yes, enough
… you might be right, enough information for a beginners blog.
But we’ve already reached the number 5 blog in our tutorial series (BTW, an integer number), so it is time to learn little bit more.
In fact, we can add little bit more information to our definition.
Our “name” property is of type string
And we want to declare that this string must not be longer than 10 characters.
Never?
Don’t even think about it.
To achieve this, we add a property facet to the property.
It is like metadata about the property.
In this case like a constraint
It is like a contract. If the user uses our API, then he accepts the contract.
And even the small print…
Property facets are predefined by the OData specification: IN this case it is the MaxLength.
So we, the service developer, the service provider, we declare that our service doesn’t allow names longer than 10 characters.
The coding is generated in the cloud, it takes care about validation. If the user tries to break the contract (which he signed off), then the service replies with unpolite response “Bad Request”
Yes, little unpolite, but correct
(can you please stop these silly comments?)
OK, just one question: we want to learn CDS, not OData
Correct, in this blog, we’ve started explanations from the OData-side, because it is the end-user facing side.
Now the question is: how to define it in CDS ?
CDS supports the same goals, but doesn’t use the term “property facet”
But I like this term…
I like it too.
So let’s go through the relevant facets:
1. not null
2. Precision
3. Scale
4. MaxLength
The full sample model can be found at the end of this post.
1. not null
Imagine: you have a little pet-shop and you want to create a little application.
Of course, using SAP Cloud Platform Backend service, is by far the best choice, so you get your API and scalable data storage very quickly and easily, and…
Thanks, don’t need more marketing here
OK, you have a pet-model and a property to store the race of an animal.
If you forget to specify that race should be not null, then your supplier (using your API) might create a pet entry with no race and you might sell an animal without information about its race….
And what if it is toxic…?!?!?!
Yes, I understand. in fact, it is a real problem
Solution: add the “not null” and like that you make sure that you’ll always know the race of the animals you sell.
good example, I buy it.
So, if you want to avoid that the user of your OData service ignores a property (i.e. he doesn’t enter a value when creating an entity), then you can express this wish in your CDS model
You just need to add “not null” after the data type:
category : String not null;
After being translated to OData, it looks like this:
<Property Name="category" Type="Edm.String" Nullable="false"/>
If your user sends a POST request without passing this property (or PATCH with value null) in the request payload, then your fully-automated OData service rejects the request and sends an appropriate error message (Status 400 Bad Request)
Note:
You don’t want to add the not null to all of your properties
Note about key properties:
You don’t need to add the not null to a key property. This is added implicitly under the hood. You’ll find this facet in the generated OData service.
2. and 3. Precision and Scale for Decimal
A decimal number is typically used for e.g. a price of a product.
For instance, the iPhone has a price like 1200 dollar
I don’t get it
Oh, sorry, this was a stupid example
For instance, an iMouse could have a price like 19.99 dollar
That’s a decimal.
Cool, now I get it. What else?
Why I’m discussing it here is the option of adding metadata to a decimal
Ah, like “the price is too high for a mouse”?
No, no a bit more serious
Why the need of adding metadata to a number?
Because a decimal number can look very different:
899.99 a price for a (golden) mouse
899.995 this price doesn’t make sense (of course, too expensive..)
66.5 the average age of mouse users (younger people use smartphones)
3.14159265 the value of pi
As a service provider, you don’t want to allow that a price like 5.995 dollar can be created.
As such, you need metadata to restrict the number of digits after the dot
OK?
Yes, and the term?
It is called Scale
Definition?
More polite please
Definition, please?
Here it is:
Scale = the maximum number of digits allowed to the right of the decimal point.
(such nice definition can only be quoted from the spec)
BTW: less than the maximum is allowed, too
As such,
Scale=2
is a good choice for price of a product
Now precision
…..?
Now precision, p l e a s e
I don’t know how many times in my life I’ve googled the meaning of this term … always confound with “scale”… so I used to forget it and after some time forget again…
“Precision” can be used along with decimal data type.
Precision = total count of digits that form the number, ignoring the dot of a decimal number.
(See here for professional definition in the context of OData)
A simple explanation could be:
Based on this constraint, the UI-developers can know how long a box in the UI needs to be.
Examples:
Defining a facet like Precision=5
allows numbers like
123.45
1234.5
11001
Typically, both facets are used together when specifying Decimal as data type for a field
For instance, you have a shop and you create an OData service and you want to avoid that any mouse could ever have a too high price…So you just specify the price property as follows
mousePrice : Decimal(4, 2);
This ensures that the API doesn’t allow a price higher than 99.99 dollar for a mouse
Cool, isn’t it?
so simple to avoid price increase…
But what is 4 and what is 2 ?
Yes, in CDS you have to know the meaning of the numbers:
first digit is precision, second is scale
In OData it is more verbosy:
<Property Name="productPrice" Type="Edm.Decimal" Precision="4" Scale="2"/>
Note:
if you use CDS editor, the code completion feature does not only the completion for the word “Decimal”, but moreover, it proposes the (precision, scale)
This helps a lot to avoid confusion.
and since then, google has much less searches for “precision”…..
Test
The magically generated OData service ensures magically that the user of your service doesn’t send values which don’t match the precision and scale definition
As such, if your user tries to create a mouse with mousePrice = 12.999, then the service fails with Status 400 Bad Request
Because the property was declared with Precision=4 and Scale=2
4. max length
We can declare the maximum allowed length for the value of a property
Any link to a prof spec, please?
here
The MaxLength facet is usually used for String properties
It allows you to declare that a property value can only have e.g. 1 character.
For instance, your pets in your pet-shop can be only “m” or “f” (male or female)
Like that, the user of your service, who wants to build a UI for a simple application, does get some semantic information about your property, so he can know how long the InputField in his screen should be.
And so on.
“And so on” means?
It means: enough for today. See you again soon.
Because the next blog is “not null”…?
Yes, and precise as usual.
Appendix: The sample model
service FacetService {
entity ProductEntity{
key id : String; // "not null" is implicitly added for key property
name : String(10);
category : String not null;
price : Decimal(9, 2);
}
}
Appendix: The test
Please enable the OData V2 and use this endpoint for your testing.
URL https://../odatav2/DEFAULT/FACETSERVICE;v=1/Products
1. Not null : Create an entry without category
Payload for POST request:
{
"id": "1",
"name": "GoldMouse",
"price": "99.99"
}
Expected result: error: cannot insert NULL or update to NULL: category
1.1. Implicit not null: Create an entry without key
Sample payload for POST request:
{
"name": "GoldMouse",
"category": "mice",
"price": "99.99"
}
Expected result: Error: 400 Bad Request
2. Scale: Create an entry with invalid scale
Sample payload for POST request:
{
"id" : "2",
"name": "GoldMouse",
"category": "mice",
"price": "99.1234"
}
Expected result: Error: 400 Bad Request
3. Precision: Create an entry with invalid precision
Sample payload for POST request:
{
"id" : "2",
"name": "GoldMouse",
"category": "mice",
"price": "1234.99"
}
Expected result: Error: 400 Bad Request
4. MaxLength: Create an entry with name too long
Sample payload for POST request:
{
"id" : "2",
"name": "ExtremelyExpensiveMouse",
"category": "mice",
"price": "12.99"
}
Expected result: Error: 400 Bad Request
Hello Carlos,
for the below payload Im getting the following error:
{
"ProductID": "AD-1000",
"TypeCode": "AD",
"Category": "Computer system accessories",
"Availability_Status": "Out of stock",
"Name": "Portable DVD player",
"NameLanguage": "EN",
"Description": "Flyer for our product palette",
"DescriptionLanguage": "EN",
"SupplierID": "0100000000",
"SupplierName": "SAP",
"TaxTarifCode": "1",
"MeasureUnit": "EA",
"CurrencyCode": "EUR",
"WeightMeasure": "4.9M"
}
{
"error": {
"code": null,
"message": "Error while deserializing payload. An error occurred during deserialization of the entity. A JSON number is not supported as Edm.Decimal value."
}
}
WeightMeasure is of type WeightMeasure : Decimal(13, 3);
Could you please help me here?
Thanks in advance
Ashish
Hi, pls try following valies:
OData V2: “WeightMeasure”: "4.9"
OData V4: “WeightMeasure”: 4.9
Kind Regards,
Carlos
Already tried nothing worked 🙁
Hi Ashish, the above examples I've given do work for me in my test. Maybe you can post a full model (the relevant parts), such that I can try it out? Are you using V2 or V4 ? Regards, Carlos
I'm sorry, Ashish, I cannot repro your issue, as I'm not using CAP and node runtime. There, the underlying odata library is the Okra node.js lib, it might have slightly different implementation. Although an odata service should always behave the same.
You might want to raise an issue at okra homepage or cap-tagged question in the community.
Sorry again,
Carlos