Skip to content
On this page

Domain Modeling

Domain Models capture the static, data-related aspects of a problem domain in terms of entity-relationship models. They serve as the basis for persistence models deployed to databases as well as for service definitions.

Capture Intent → What, not How

CDS focuses on conceptual modelling: we want to capure intent, not imperative implementations — that is: What, not How. Not only does that keep domain models concise and comprehensible, it also allows us to provide optimized generic implementations.

For example, given an entity definition like that:

cds
using { cuid, managed } from '@sap/cds/common';
entity Books : cuid, managed {
   title  : localized String;
   descr  : localized String;
   author : Association to Authors;
}
using { cuid, managed } from '@sap/cds/common';
entity Books : cuid, managed {
   title  : localized String;
   descr  : localized String;
   author : Association to Authors;
}

In that model we used the pre-defined aspects cuid and managed, as well as the qualifier localized to capture generic aspects. We also used managed associations.

In all these cases, we focus on capturing our intent, while leaving it to generic implementations to provide best-possible implementations.

Entity-Relationship Modeling

Entity-Relationship Modelling (ERM) is likely the most widely known and applied conceptual modelling technique for data-centric applications. It is also one of the foundations for CDS.

Assume we had been given this requirement:

"We want to create a bookshop allowing users to browse Books and Authors, and navigate from Books to Authors and vice versa. Books are classified by Genre".

Using CDS, we would translate that into an initial domain model as follows:

cds
using { cuid } from '@sap/cds/common';

entity Books : cuid {
  title  : String;
  descr  : String;
  genre  : Genre;
  author : Association to Authors;
}

entity Authors : cuid {
  name   : String;
  books  : Association to many Books on books.author = $self;
}

type Genre : String enum {
  Mystery; Fiction; Drama;
}
using { cuid } from '@sap/cds/common';

entity Books : cuid {
  title  : String;
  descr  : String;
  genre  : Genre;
  author : Association to Authors;
}

entity Authors : cuid {
  name   : String;
  books  : Association to many Books on books.author = $self;
}

type Genre : String enum {
  Mystery; Fiction; Drama;
}

Fueling Generic Providers

As depicted in the illustration below, domain models serve as the sources for persistence models, deployed to databases, as well as the underlying model for services acting as API facades to access data.

cds-fueling-generic-providers.drawio

The more we succeeded in capturing intent over imperative implementations, the more we can provide optimized generic implementations.

Domain-Driven Design

CAP shares these goals and approaches with Domain-driven Design:

  1. Placing projects' primary focus on the core domain
  2. Close collaboration of developers and domain experts
  3. Iteratively refining domain knowledge

We use CDS as our ubiquitous modelling language, with CDS Aspects giving us the means to separate core domain aspects from generic aspects. CDS's human-readable nature fosters collaboration of developers and domain experts.

As CDS models are used to fuel generic providers — the database as well as application services — we ensure the models are applied in the implementation. And as coding is minimized we can more easily refine and revise our models, without having to refactor large boilerplate code based.

Keep it Simple, Stupid

Domain modeling is a means to an end; your clients and consumers are the ones who have to understand and work with your models the most, much more than you as their creator. Keep that in mind and understand the task of domain modeling as a service to others.

Keep models concise and comprehensible

As said in the "Keep it simple, stupid!" wikipedia entry: "... most systems work best if they're kept simple rather than made complicated; therefore, simplicity should be a key goal in design, and unnecessary complexity should be avoided."

Avoid overly abstract models

Even though domain models should abstract from technical implementations, don’t overstress this and balance it with ease of adoption. For example if the vast majority of your clients use relational databases, don't try to overly abstract from that, as that would have all suffer from common denominator syndromes.

Aspect-oriented Modeling

CDS Aspects and Annotations provide powerful means for separation of concerns. This greatly helps to keep our core domain model clean, while putting secondary concerns into separate files and model fragments. → Find details in chapter Add Secondary Aspects below.

① Define Domain Entities

Entities represent a domain's data. When translated to persistence models, especially relational ones, entities become tables.

With Typed Elements

Entity definitions essentially declare structured types with named and typed elements, plus the primary key elements used to identify entries.

cds
entity name {
   key element1 : Type;
       element2 : Type;
   ...
}
entity name {
   key element1 : Type;
       element2 : Type;
   ...
}

Learn more about entity definitions

As Projections of Others

In addition, borrowing powerful view building from SQL, we can declare entities as (denormalized) views on other entities:

cds
entity ProjectedEntity as select from BaseEntity {
   element1, element2 as name, /*...*/
};
entity ProjectedEntity as select from BaseEntity {
   element1, element2 as name, /*...*/
};

Learn more about views and projections

Naming Conventions

We recommend adopting the following simple naming conventions as commonly used in many communities, for example, Java, JavaScript, C, SQL, etc.

Capitalize Type / Entity Names

To easily distinguish type / entity names from elements names we recommend to...

  • Start entity and type names with capital letters — e.g., Authors
  • Start elements with a lowercase letter — e.g., name

Pluralize Entity Names

As entities represent not only data types, but also data sets, from which we can read from, we recommend following common SQL convention:

  • Use plural form for entities — e.g., Authors
  • Use singular form for types — e.g., Genre

Prefer Concise Names

  • Don't repeat contexts → e.g. Author.name instead of Author.authorName
  • Prefer one-word names → e.g. address instead of addressInformation
  • Use ID for technical primary keys → see also Use Canonic Primary Keys

Using Namespaces

You can use namespaces to help getting to unique names without bloating your code with fully qualified names. For example:

cds
namespace foo.bar;
entity Boo {}
entity Moo : Boo {}
namespace foo.bar;
entity Boo {}
entity Moo : Boo {}

... is equivalent to:

cds
entity foo.bar.Boo {}
entity foo.bar.Moo : foo.bar.Boo {}
entity foo.bar.Boo {}
entity foo.bar.Moo : foo.bar.Boo {}

Note:

  • Namespaces are just prefixes — which are automatically applied to all relevant names in a file. Beyond this there's nothing special about them.
  • Namespaces are optional — use namespaces if your models might be reused in other projects; otherwise, you can go without namespaces.
  • The reverse domain name approach works well for choosing namespaces.

WARNING

Avoid short-lived ingredients in namespaces, or names in general, such as your current organization's name, or project code names.

② Using Data Types

Standard Built-in Types

CDS comes with a small set of built-in types:

  • UUID,
  • Boolean,
  • Date, Time, DateTime, Timestamp
  • Integer, UInt8, Int16, Int32, Int64
  • Double, Decimal
  • String, LargeString
  • Binary, LargeBinary

See list of Built-in Types in the CDS reference docs

Common Reuse Types

In addition, a set of common reuse types and aspects is provided with package @sap/cds/common, such as:

  • Types Country, Currency, Language with corresponding value list entities
  • Aspects cuid, managed, temporal

For example, usage is as simple as this:

cds
using { Country } from '@sap/cds/common';
entity Addresses : managed { //> using reuse aspect
  street  : String;
  town    : String;
  country : Country; //> using reuse type
}
using { Country } from '@sap/cds/common';
entity Addresses : managed { //> using reuse aspect
  street  : String;
  town    : String;
  country : Country; //> using reuse type
}

Learn more about reuse types provided by @sap/cds/common.

Use common reuse types and aspects...

... to keep models concise, and benefitting from improved interoperability, proven best practices, and out-of-the-box support through generic implementations in CAP runtimes.

Custom-defined Types

Declare custom-defined types to increase semantic expressiveness of your models, or to share details and annotations as follows:

cds
type User : String; //> merely for increasing expressiveness
type Genre : String enum { Mystery; Fiction; ... }
type DayOfWeek : Number @assert.range:[1,7];
type User : String; //> merely for increasing expressiveness
type Genre : String enum { Mystery; Fiction; ... }
type DayOfWeek : Number @assert.range:[1,7];

Use Custom Types Reasonably

Avoid overly excessive use of custom-defined types. They’re valuable when you have a decent reuse ratio. Without reuse, your models just become harder to read and understand, as one always has to look up respective type definitions, as in the following example:

cds
using { sap.capire.bookshop.types } from './types';
namespace sap.capire.bookshop;
entity Books {
  key ID : types.BookID;
  name : types.BookName;
  descr : types.BookDescr;
  ...
}
using { sap.capire.bookshop.types } from './types';
namespace sap.capire.bookshop;
entity Books {
  key ID : types.BookID;
  name : types.BookName;
  descr : types.BookDescr;
  ...
}
cds
// types.cds
namespace sap.capire.bookshop.types;
type BookID : UUID;
type BookName : String;
type BookDescr : String;
// types.cds
namespace sap.capire.bookshop.types;
type BookID : UUID;
type BookName : String;
type BookDescr : String;

③ Add Primary Keys

Use the keyword key to signify one or more elements that form an entity's primary key:

cds
entity Books {
  key ID : UUID; 
  ...
}
entity Books {
  key ID : UUID; 
  ...
}

WARNING

Prefer Simple, Technical Keys

While you can use arbitrary combinations of fields as primary keys, keep in mind that primary keys are frequently used in joins all over the place. And the more fields there are to compare for a join the more you'll suffer from poor performance. So prefer primary keys consisting of single fields only.

Moreover, primary keys should be immutable, that means once assigned on creation of a record they should not change subsequently, as that would break references you might have handed out. Think of them as a fingerprint of a record.

Prefer Canonic Keys

We recommend using canonically named and typed primary keys, as promoted by aspect cuid from @sap/cds/common.

cds
// @sap/cds/common
aspect cuid { key ID : UUID }
// @sap/cds/common
aspect cuid { key ID : UUID }
cds
using { cuid } from '@sap/cds/common';
entity Books : cuid { ... }
entity Authors : cuid { ... }
using { cuid } from '@sap/cds/common';
entity Books : cuid { ... }
entity Authors : cuid { ... }

This eases the implementation of generic functions that can apply the same ways of addressing instances across different types of entities.Prefer Simple, Technical Keys

Prefer UUIDs for Keys

While UUIDs certainly come with an overhead and a performance penalty when looking at single databases, they have several advantages when we consider the total bill. So, you can avoid the evil of premature optimization by at least considering these points:

  • UUIDs are universal — that means that they’re unique across every system in the world, while sequences are only unique in the source system's boundaries. Whenever you want to exchange data with other systems you'd anyways add something to make your records 'universally' addressable.

  • UUIDs allow distributed seeds — for example, in clients. In contrast, database sequences or other sequential generators always need a central service, for example, a single database instance and schema. This becomes even more a problem in distributed landscape topologies.

  • Database sequences are hard to guess — assume that you want to insert a SalesOrder with three SalesOrderItems in one transaction. INSERT SalesOrder will automatically get a new ID from the sequence. How would you get this new ID in order to use it for the foreign keys in subsequent INSERTs of the SalesOrderItems?

  • Auto-filled primary keys — primary key elements with type UUID are automatically filled by generic service providers in Java and Node.js upon INSERT.

Prefer UUIDs for Keys

  • Use DB sequences only if you really deal with high data volumes.
  • Otherwise, prefer UUIDs.

You can also have semantic primary keys such as order numbers constructed by customer name+date, etc. And if so, they usually range between UUIDs and DB sequences with respect to the pros and cons listed above.

Don't Interpret UUIDs!

It is an unfortunate anti pattern to validate UUIDs, such as for compliance to RFC 4122. This not only means useless processing, it also impedes integration with existing data sources. For example, ABAP's GUID_32s are uppercase without hyphens.

UUIDs are unique opaque values! — The only assumption required and allowed is that UUIDs are unique so that they can be used for lookups and compared by equality — nothing else! It's the task of the UUID generator to ensure uniqueness, not the task of subsequent processors!

On the same note, converting UUID values obtained as strings from the database into binary representations such as java.lang.UUID, only to render them back to strings in responses to HTTP requests, is useless overhead.

WARNING

  • Avoid unnecessary assumptions, for example, about uppercase or lowercase
  • Avoid useless conversions, for example, from strings to binary and back
  • Avoid useless validations of UUID formats, for example, about hyphens
See also: Mapping UUIDs to OData
See also: Mapping UUIDs to SQL

④ Add Associations

Use Associations to capture relationships between entities.

cds
entity Books { ...
  author : Association to Authors; //> to one
}
entity Authors { ...
  books : Association to many Books on books.author = $self;
}
entity Books { ...
  author : Association to Authors; //> to one
}
entity Authors { ...
  books : Association to many Books on books.author = $self;
}

Learn more about Associations in the CDS Language Reference

Managed :1 Associations

The association Books:author in the sample above is a so-called managed association, with foreign key columns and on conditions added automatically behind the scenes.

cds
entity Books { ...
  author : Association to Authors;
}
entity Books { ...
  author : Association to Authors;
}

In contrast to that we could also use unmanaged associations with all foreign keys and on conditions specified manually:

cds
entity Books { ...
  author : Association to Authors on author.ID = author_ID;
  author_ID : type of Authors:ID;
}
entity Books { ...
  author : Association to Authors on author.ID = author_ID;
  author_ID : type of Authors:ID;
}

Note: To-many associations are unmanaged by nature as we always have to specify an on condition. Reason for that is that backlink associations or foreign keys cannot be guessed reliably.

TIP

For the sake of conciseness and comprehensibility of your models always prefer managed Associations for to-one associations.

To-Many Associations

Simply add the many qualifier keyword to indicate a to-many cardinality:

cds
entity Authors { ...
  books : Association to many Books;
}
entity Authors { ...
  books : Association to many Books;
}

If your models are meant to target APIs, this is all that is required. When targeting databases though, we need to add an on condition, like so:

cds
entity Authors { ...
  books : Association to many Books on books.author = $self;
}
entity Authors { ...
  books : Association to many Books on books.author = $self;
}

The on condition can either compare a backlink association to $self, or a backlink foreign key to the own primary key, e.g. books.author.ID = ID.

Many-to-Many Associations

CDS currently doesn’t provide dedicated support for many-to-many associations. Unless we add some, you have to resolve many-to-many associations into two one-to-many associations using a link entity to connect both. For example:

cds
entity Projects { ...
  members : Composition of many Members on members.project = $self;
}
entity Users { ...
  projects : Composition of many Members on projects.user = $self;
}
entity Members { // link table
  key project : Association to Project;
  key user : Association to Users;
}
entity Projects { ...
  members : Composition of many Members on members.project = $self;
}
entity Users { ...
  projects : Composition of many Members on projects.user = $self;
}
entity Members { // link table
  key project : Association to Project;
  key user : Association to Users;
}

We can use Compositions of Aspects to reduce noise a bit:

cds
entity Projects { ...
  members : Composition of many { key user : Association to Users };
}
entity Users { ...
  projects : Composition of many Projects.members on projects.member = $self;
}
entity Projects { ...
  members : Composition of many { key user : Association to Users };
}
entity Users { ...
  projects : Composition of many Projects.members on projects.member = $self;
}

Behind the scenes the equivalent of the model above would be generated, with the link table called Projects.members and the backlink association to Projects in there called up_.

⑤ Add Compositions

Compositions represent contained-in relationships. CAP runtimes provide these special treatments to Compositions out of the box:

  • Deep Insert / Update automatically filling in document structures
  • Cascaded Delete is when deleting Composition roots
  • Composition targets are auto-exposed in service interfaces

Modeling Document Structures

Compositions are used to model document structures. For example, in the following definition of Orders, the Orders:Items composition refers to the OrderItems entity, with the entries of the latter being fully dependent objects of Orders.

cds
entity Orders { ...
  Items : Composition of many OrderItems on Items.parent = $self;
}
entity OrderItems { // to be accessed through Orders only
  key parent : Association to Orders;
  key pos    : Integer;
  quantity   : Integer;
}
entity Orders { ...
  Items : Composition of many OrderItems on Items.parent = $self;
}
entity OrderItems { // to be accessed through Orders only
  key parent : Association to Orders;
  key pos    : Integer;
  quantity   : Integer;
}

Learn more about Compositions in the CDS Language Reference

Composition of Aspects

We can use anonymous inline aspects to rewrite the above with less noise as follows:

cds
entity Orders { ...
  Items : Composition of many {
    key pos  : Integer;
    quantity : Integer;
  };
}
entity Orders { ...
  Items : Composition of many {
    key pos  : Integer;
    quantity : Integer;
  };
}

Learn more about Compositions of Aspects in the CDS Language Reference

Behind the scenes this will add an entity named Orders.Items with a backlink association named up_, so effectively generating the same model as above.

⑥ Add Secondary Aspects

CDS's Aspects provide powerful mechanisms to separate concerns. It allows decomposing models and definitions into separate files with potentially different life cycles, contributed by different people.

The basic mechanism use the extend or annotate directives to add secondary aspects to a core domain entity like so:

cds
extend Books with {
   someAdditionalField : String;
}
extend Books with {
   someAdditionalField : String;
}
cds
annotate Books with @some.entity.level.annotations {
  title @some.field.level.annotations;
};
annotate Books with @some.entity.level.annotations {
  title @some.field.level.annotations;
};

Variants of this allow declaring and applying named aspects like so:

cds
aspect NamedAspect { someAdditionalField : String }
extend Books with NamedAspect;
aspect NamedAspect { someAdditionalField : String }
extend Books with NamedAspect;

We can also apply named aspects as includes in an inheritence-like syntax:

cds
entity Books : NamedAspect { ... }
entity Books : NamedAspect { ... }

Learn more about Aspects in the CDS Language Reference

TIP

Consumers always see the merged effective models, with the separation into aspects fully transparent to them.

Managed Data

Package @sap/cds/common provides a pre-defined aspect managed for managed data, which is defined as follows:

cds
/**
 * Aspect to capture changes by user and name
 */
aspect managed {
  createdAt  : Timestamp @cds.on.insert : $now;
  createdBy  : User      @cds.on.insert : $user;
  modifiedAt : Timestamp @cds.on.insert : $now  @cds.on.update : $now;
  modifiedBy : User      @cds.on.insert : $user @cds.on.update : $user;
}
/**
 * Aspect to capture changes by user and name
 */
aspect managed {
  createdAt  : Timestamp @cds.on.insert : $now;
  createdBy  : User      @cds.on.insert : $user;
  modifiedAt : Timestamp @cds.on.insert : $now  @cds.on.update : $now;
  modifiedBy : User      @cds.on.insert : $user @cds.on.update : $user;
}

We use that as includes when defining our core domain entities:

cds
using { managed } from '@sap/cds/common';
entity Books : managed { ... }
entity Authors : managed { ... }
using { managed } from '@sap/cds/common';
entity Books : managed { ... }
entity Authors : managed { ... }

With this we keep our core domain model clean and comprehensible.

Localized Data

Business applications frequently need localized data, for example to display books titles and descriptions in the user's preferred language. With CDS we simply use the localized qualifier to tag respective text fields in your as follows.

Do:

cds
entity Books { ...
  title : localized String;
  descr : localized String;
}
entity Books { ...
  title : localized String;
  descr : localized String;
}

Don't:

In contrast to that, this is what you would have to do without CAP's localized support:

cds
entity Books {
  key ID : UUID;
   title : String;
   descr : String;
   texts : Composition of many Books.texts on texts.book = $self;
   ...
}

entity Books.texts {
  key locale : Locale;
  key ID : UUID;
  title  : String;
  descr  : String;
}
entity Books {
  key ID : UUID;
   title : String;
   descr : String;
   texts : Composition of many Books.texts on texts.book = $self;
   ...
}

entity Books.texts {
  key locale : Locale;
  key ID : UUID;
  title  : String;
  descr  : String;
}

Essentially, this is also what CAP generates behind the scenes, plus many more things to ease working with localized data and serving it out-of-the-box.

TIP

By generating .texts entities and associations behind the scenes, CAP's out-of-the-box support for localized data avoids polluting your models with doubled numbers of entities, and detrimental effects on comprehensibility.

Learn more in the Localized Data guide.

Authorization Model

CAP supports out-of-the-box authorization by annotating services and entites with @requires and @restrict annotations like that:

cds
entity Books @(restrict: [
  { grant: 'READ',   to: 'authenticated-user' },
  { grant: 'CREATE', to: 'content-maintainer' },
  { grant: 'UPDATE', to: 'content-maintainer' },
  { grant: 'DELETE', to: 'admin' },
]) {
  ...
}
entity Books @(restrict: [
  { grant: 'READ',   to: 'authenticated-user' },
  { grant: 'CREATE', to: 'content-maintainer' },
  { grant: 'UPDATE', to: 'content-maintainer' },
  { grant: 'DELETE', to: 'admin' },
]) {
  ...
}

To avoid polluting our core domain model with the generic aspect of authorization, we can use aspects to separate concerns, putting the authorization annotations into a separate file, maintained by security experts like so:

cds
// core domain model in schema.cds
entity Books { ... }
entity Authors { ... }
// core domain model in schema.cds
entity Books { ... }
entity Authors { ... }
cds
// authorization model
using { Books, Authors } from './schema.cds';

annotate Books with @restrict: [
  { grant: 'READ',   to: 'authenticated-user' },
  { grant: 'CREATE', to: 'content-maintainer' },
  { grant: 'UPDATE', to: 'content-maintainer' },
  { grant: 'DELETE', to: 'admin' },
];

annotate Authors with @restrict: [
  ...
];
// authorization model
using { Books, Authors } from './schema.cds';

annotate Books with @restrict: [
  { grant: 'READ',   to: 'authenticated-user' },
  { grant: 'CREATE', to: 'content-maintainer' },
  { grant: 'UPDATE', to: 'content-maintainer' },
  { grant: 'DELETE', to: 'admin' },
];

annotate Authors with @restrict: [
  ...
];

Similarly to authorization annotations we would frequently add annotations which are related to UIs, starting with @titles used for field or column labels in UIs, or specific Fiori annotations in @UI, @Common, etc. vocabularies.

Also here we strongly recommend to keep the core domain models clean of that, but put such annotation into respective frontend models:

cds
// core domain model in db/schema.cds
entity Books : cuid { ... }
entity Authors : cuid { ... }
// core domain model in db/schema.cds
entity Books : cuid { ... }
entity Authors : cuid { ... }
cds
// common annotations in app/common.cds
using { sap.capire.bookshop as my } from '../db/schema';

annotate my.Books with {
  ID     @title: '{i18n>ID}';
  title  @title: '{i18n>Title}';
  genre  @title: '{i18n>Genre}'   @Common: { Text: genre.name, TextArrangement: #TextOnly };
  author @title: '{i18n>Author}'  @Common: { Text: author.name, TextArrangement: #TextOnly };
  price  @title: '{i18n>Price}'   @Measures.ISOCurrency : currency_code;
  descr  @title: '{i18n>Description}'  @UI.MultiLineText;
}
// common annotations in app/common.cds
using { sap.capire.bookshop as my } from '../db/schema';

annotate my.Books with {
  ID     @title: '{i18n>ID}';
  title  @title: '{i18n>Title}';
  genre  @title: '{i18n>Genre}'   @Common: { Text: genre.name, TextArrangement: #TextOnly };
  author @title: '{i18n>Author}'  @Common: { Text: author.name, TextArrangement: #TextOnly };
  price  @title: '{i18n>Price}'   @Measures.ISOCurrency : currency_code;
  descr  @title: '{i18n>Description}'  @UI.MultiLineText;
}
cds
// Specific UI Annotations for Fiori Object & List Pages
using { sap.capire.bookshop as my } from '../db/schema';

annotate my.Books with @(
  Common.SemanticKey : [ID],
  UI: {
    Identification  : [{ Value: title }],
    SelectionFields : [ ID, author_ID, price, currency_code ],
    LineItem        : [
      { Value: ID, Label: '{i18n>Title}' },
      { Value: author.ID, Label: '{i18n>Author}' },
      { Value: genre.name },
      { Value: stock },
      { Value: price },
      { Value: currency.symbol },
    ]
  }
) {
  ID @Common: {
    SemanticObject : 'Books',
    Text: title, TextArrangement : #TextOnly
  };
  author @ValueList.entity: 'Authors';
};
// Specific UI Annotations for Fiori Object & List Pages
using { sap.capire.bookshop as my } from '../db/schema';

annotate my.Books with @(
  Common.SemanticKey : [ID],
  UI: {
    Identification  : [{ Value: title }],
    SelectionFields : [ ID, author_ID, price, currency_code ],
    LineItem        : [
      { Value: ID, Label: '{i18n>Title}' },
      { Value: author.ID, Label: '{i18n>Author}' },
      { Value: genre.name },
      { Value: stock },
      { Value: price },
      { Value: currency.symbol },
    ]
  }
) {
  ID @Common: {
    SemanticObject : 'Books',
    Text: title, TextArrangement : #TextOnly
  };
  author @ValueList.entity: 'Authors';
};

.

Best Practices

Separation of Concerns

As highlighted with a few samples in the chapter above, always strive to keep your core domain model clean, concise and comprehensible.

CDS Aspects help you to do so, by decomposing models and definitions into separate files with potentially different life cycles, contributed by different people.

We strongly recommend to make use of that as much as possible.

Prefer Flat Models

While CDS provides great support for structured types, you should always think twice before using this, as several technologies that you or your customers might want to integrate with, may have difficulties with this. Moreover, flat structures are easier to understand and consume.

Good:

cds
entity Contacts {
  isCompany : Boolean;
  company   : String;
  title     : String;
  firstname : String;
  lastname  : String;
}
entity Contacts {
  isCompany : Boolean;
  company   : String;
  title     : String;
  firstname : String;
  lastname  : String;
}

Bad:

cds
entity Contacts {
  isCompany   : Boolean;
  companyData : CompanyDetails;
  personData  : PersonDetails;
}
type CompanyDetails {
  name : String;
}
type PersonDetails {
  titles : AcademicTitles;
  name   : PersonName;
}
type PersonName : {
  first  : String;
  last   : String;
}
type AcademicTitles : {
  primary   : String;
  secondary : String;
}
entity Contacts {
  isCompany   : Boolean;
  companyData : CompanyDetails;
  personData  : PersonDetails;
}
type CompanyDetails {
  name : String;
}
type PersonDetails {
  titles : AcademicTitles;
  name   : PersonName;
}
type PersonName : {
  first  : String;
  last   : String;
}
type AcademicTitles : {
  primary   : String;
  secondary : String;
}