Skip to Content
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

 

 

 

 

6 Comments
You must be Logged on to comment or reply to a post.
  • 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

        • 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

          • entity Product:managed {
            keyProductID:String(10);
            TypeCode:String(2);
            Category:String(40);
            Availability_Status:String(40);
            Name:String(255);
            NameLanguage:String(2);
            Description:String(255);
            DescriptionLanguage:String(2);
            SupplierID:String(10);
            SupplierName:String(255);
            TaxTarifCode:String(2);
            MeasureUnit:String(3);
            WeightMeasure:Decimal(13, 3);
            CurrencyCode:String(3);
            Price:Decimal(16, 3);
            Width:Decimal(13, 3);
            Depth:Decimal(13, 3);
            Height:Decimal(13, 3);
            }
            I’m trying to create Odata service using CAP (nodeJS) and by default oData version is coming as V4 and not V2 even though cds env set for v2:
          • 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