Skip to Content
Technical Articles

SAP Cloud Platform Backend service: Tutorial [6]: CDS : using Custom Types

This blog is meant to clarify a doubt that you’ll be facing sooner or later, while using SAP Cloud Platform Backend service more and more.

It is meant to help you understand how to define custom types in CDS, but also how to use them in OData.

We assume that you’ve already gone through a couple of tutorials of this series to learn how to create APIs with SAP Cloud Platform Backend service.

I promised that it is easy – and in fact, it is still easy.

In the first part we’ll discuss why and how to create custom data types in CDS
In the second part we’ll see that there’s a little…humm… let’s call it difference
You might be surprised if you see it and if you aren’t prepared then you might wonder
and complain…
The difference I’m talking about is between CDS modelling and the resulting OData model

Part 1: How to define custom types

But first let’s see how custom types are used.

1. Example for structural type definition

Let’s have a look at an example model:

 

entity Products{
     key productID : UUID;
     productName : String;

     // info about the company which creates the product
     supplierCompanyName : String;
     supplierCity : String;
     supplierStreet : String;
     supplierStreetNumber : Integer;

     // info about the employee who sells the product
     responsibleEmployeeName : String;
     responsibleCity : String;
     responsibleStreet : String;
     responsibleStreetNumber : Integer;

     // info about the customer who buys the product
     deliveryCustomerName : String;
     deliveryCustomerCity : String;
     deliveryCustomerStreet : String;
     deliveryCustomerStreetNumber : Integer;
}

As you can easily see, repeated properties.
City, street, number (etc) is typical information which is required in different contexts.

Obviously, it makes sense to extract such recurring information into a separate structural type.
And this is how it is done with CDS:

type AddressType {
     city : String;
     street : String;
     streetNumber : Integer;
}

entity ProductEntity{
     key productID : UUID;
     productName : String;

     // info about the company which creates the product
     supplierCompanyName : String;
     supplierAddress : AddressType;

     // info about the employee who sells the product
     responsibleEmployeeName : String;
     responsibleEmployeeAddress : AddressType;

     // info about the customer who buys the product
     deliveryCustomerName : String;
     deliveryCustomerAddress : AddressType;
}

Note:
The names which I’ve chosen are not good (suffix “Type” and suffix “Entity”)
But I’m using it here to make things as transparent as possible

 

Type and entity look very similar. What is the difference?
A type doesn’t have a key field, it is just a separate structure with semantic set of properties

 

2. Example for simple type definition

A custom defined type does not necessarily need to be a structure, it can as well be a simple type, enriched with semantic information
Please have a look at below example.

The type definition is only a wrapper around a built in CDS type, but with some semantic information:

    type priceDecimal : Decimal(9, 2);
    type averageDecimal : Decimal(11, 1);
    type limitDecimal : Decimal(1,5);

However, when using it in an entity, it makes it somehow more readable, doesn’t it?

entity PharmProductEntity {
     key id : String;
     orderPrice : priceDecimal;
     sellingPrice : priceDecimal;
     averageCount : averageDecimal;
     toxLimit : limitDecimal;
}

BTW, again my naming is not good, it is meant to make things as transparent as possible

 

3. Another example

In this example we’re defining a custom type using the inline syntax.

First a “normal” outer definition of a type:

type AddressType {
     city : String;
     street : String;
     streetNumber : Integer;
}

It is used by a custom type which is defined inline:

entity CustomerEntity {
     key id : String;
     name : String;
     contact : {
         homepage : String;
         email : String;
         address : AddressType;
     }; 
     company : String;
}

This kind of custom type cannot be used from outside of the entity. I mean, you cannot create a second entity and refer to that inline type.

Note
When using inline type definition, don’t forget the semicolon at the end, after the closing bracket….. a common mistake leading to strange error message when creating API

 

Part 2 How custom types are used in OData requests

Let’s put it all together and  create an API.

This is the model:

service ProductService{

    type priceDecimal : Decimal(9, 2);
    type averageDecimal : Decimal(11, 1);
    type limitDecimal : Decimal(6, 5);
    type AddressType {
        city : String;
        street : String;
        streetNumber : Integer;
    }

    entity ProductEntity{
        key productID : UUID;
        productName : String;

        supplierCompanyName : String;
        supplierAddress : AddressType;

        responsibleEmployeeName : String;
        responsibleEmployeeAddress : AddressType;

        deliveryCustomerName : String;
        deliverCustomerAddress : AddressType;
    }

    entity PharmProductEntity {
        key id : String;
        orderPrice : priceDecimal;
        sellingPrice : priceDecimal;
        averageCount : averageDecimal;
        toxLimit : limitDecimal;
    }

    entity CustomerEntity {
        key id : String;
        name : String;
        contact : {
            homepage : String;
            email : String;
            address : AddressType;
        }; 
        company : String;
    }
}

 

After API is created, we check the metadata document of the generated OData service

 

1. Example for structural type definition

The ProductEntity metadata looks like this in OData:

<EntityType Name="ProductEntity">
   <Key>
      <PropertyRef Name="productID"/>
   </Key>
   <Property Name="productID" Type="Edm.Guid" Nullable="false"/>
   <Property Name="productName" Type="Edm.String"/>
   <Property Name="supplierCompanyName" Type="Edm.String"/>
   <Property Name="responsibleEmployeeName" Type="Edm.String"/>
   <Property Name="deliveryCustomerName" Type="Edm.String"/>
   <Property Name="supplierAddress_city" Type="Edm.String"/>
   <Property Name="supplierAddress_street" Type="Edm.String"/>
   <Property Name="supplierAddress_streetNumber" Type="Edm.Int32"/>
   <Property Name="responsibleEmployeeAddress_city" Type="Edm.String"/>
   <Property Name="responsibleEmployeeAddress_street" Type="Edm.String"/>
   <Property Name="responsibleEmployeeAddress_streetNumber" Type="Edm.Int32"/>
   <Property Name="deliveryCustomerAddress_city" Type="Edm.String"/>
   <Property Name="deliveryCustomerAddress_street" Type="Edm.String"/>
   <Property Name="deliveryCustomerAddress_streetNumber" Type="Edm.Int32"/>
</EntityType>

urgh, can you point me at the important aspects?
As you can see, the custom type which we had nicely extracted, is now flattened.
All redundant repetitions are back

The structural type (BTW, in OData this concept is called Complex Type) is flattened corresponding to this naming convention:

<propertyName>_<propertyName>

Where the second property name is taken from the custom type. The custom type itself is not used for the naming.

OK, I got it. But why is it relevant?
Because when composing the json payload for a POST request, you have to use the correct property names.
As you’ve seen, you cannot rely on your CDS model in this case, you have to take the property structure and names from the OData metadata document

Of course, when using the testing tool of the cockpit of the Backend service, you’ll automatically get proposals for the correct property names, but when when writing your own application on top of the OData service you have to ….
yes, what? …. “we have to…” what ?
Well you have to remember my tutorials.
sure, we’ll never forget them…
Why does this have to sound ironical…?

2. Example for simple type definition

In case of simple type definitions, there’s no structure which could be flattened.
So let’s see how the PharmProductEntity is translated in OData:

<EntityType Name="PharmProductEntity">
   <Key>
      <PropertyRef Name="id"/>
   </Key>
   <Property Name="id" Type="Edm.String" Nullable="false"/>
   <Property Name="orderPrice" Type="Edm.Decimal" Precision="9" Scale="2"/>
   <Property Name="sellingPrice" Type="Edm.Decimal" Precision="9" Scale="2"/>
   <Property Name="averageCount" Type="Edm.Decimal" Precision="11" Scale="1"/>
   <Property Name="toxLimit" Type="Edm.Decimal" Precision="6" Scale="5"/>
</EntityType>

? [question mark]

We can see that the type definitions have been resolved, the underlying built in type and facets are used instead of the wrapping type definition

3. Example for inline type definition

The CustomerEntity metadata looks like this:

<EntityType Name="CustomerEntity">
   <Key>
      <PropertyRef Name="id"/>
   </Key>
   <Property Name="id" Type="Edm.String" Nullable="false"/>
   <Property Name="name" Type="Edm.String"/>
   <Property Name="company" Type="Edm.String"/>
   <Property Name="contact_homepage" Type="Edm.String"/>
   <Property Name="contact_email" Type="Edm.String"/>
   <Property Name="contact_address_city" Type="Edm.String"/>
   <Property Name="contact_address_street" Type="Edm.String"/>
   <Property Name="contact_address_streetNumber" Type="Edm.Int32"/>
</EntityType>

Here we can see how 2-level nested custom type is flattened.

The naming convention is as expected:

<propertyName>_<inlineTypePropertyName> for one level

and

<propertyName>_<inlineTypePropertyName>_<customTypePropertyName>

 

Summary

In this tutorial we’ve learned how to define custom types in CDS.

Custom types can be structured or simple.
They can be defined separately (reusable) or inline.

The second Part has shown how custom types are translated into OData.
In OData, custom types are flattened, they are merged into the using entity.
When using the API with e.g. POST request, the payload properties must correspond to the OData service metadata

 

Sorry, one question: why would I define custom structural type if it is anyways not used in OData?
Good question.
Thanks. Any answer?
The answer is a bit complicated and very technical and very if I’m honest I have to say that I don’t know the details.
It is related to characteristics of the underlying HANA database.

 

Sorry again, why would I define custom structural type if it is anyways not used in OData?
Because CDS is an abstraction and is agnostic of protocol and database
Then why do you explain the protocol specifics in all of your blogs?
Because you have to know the details if you plan to use your own OData service
What if I’m the persona which does the modelling only?
Yes, you might ignore all those details.
But do you really want to model only and never try out?
hummm…

 

Links

 

Official CDS Docu in SAP Help Portal

 

Official OData specification:Simple Type Definition

Inofficial tutorial series starts here

 

Appendix: the full sample model

service ProductService{

    // type definitions

    type priceDecimal : Decimal(9, 2);
    type averageDecimal : Decimal(11, 1);
    type limitDecimal : Decimal(6, 5);

    type AddressType {
        city : String;
        street : String;
        streetNumber : Integer;
    }

    // example for using custom structure type
    entity ProductEntity{
        key productID : UUID;
        productName : String;

        // info about the company which creates the product
        supplierCompanyName : String;
        supplierAddress : AddressType;

        // info about the employee who sells the product
        responsibleEmployeeName : String;
        responsibleEmployeeAddress : AddressType;

        // info about the customer who buys the product
        deliveryCustomerName : String;
        deliverCustomerAddress : AddressType;
    }

    // example for using simple type definitions
    entity PharmProductEntity {
        key id : String;
        orderPrice : priceDecimal;
        sellingPrice : priceDecimal;
        averageCount : averageDecimal;
        toxLimit : limitDecimal;
    }

    // example for inline custom type definition syntax
    entity CustomerEntity {
        key id : String;
        name : String;
        contact : {
            homepage : String;
            email : String;
            address : AddressType;
        }; // note: don't forget this colon after property definition ;)
        company : String;
    }
}
4 Comments
You must be Logged on to comment or reply to a post.