Search

Domain Modelling

Find here an introduction to the basics of domain modelling with CDS, complemented with recommended best practicses. See also the introduction in the About page.

Content

About Domain Models

Domain Models describe 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. Sometimes, domain models may as well be exposed for reuse in other projects.

The figure below illustrates this central role of domain models:

domain models

Domain-driven Design

Similar to Domain-driven Design a key goal of domain modelling in CAP is to place the primary focus of projects on the problem domain, thereby promoting technical and domain experts working in close collaboration to capture and iteratively refine knowledge about a problem domain.

Keep them simple!

To reach these goals of domain focus, but also for the sake of simplicity and hence quality, robustness and consumability, it is of utter importance to keep your models:

Or said with the words in the article on “Keep it simple, stupid!”:

… 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.

Be pragmatic about abstraction

Even though domain models should abstract from technical implementations, because they serve as the basis for services as well as persistence models, do not overstress this and balance it to equally important, if not more important goals of conciseness and comprehensibility.

Domain modelling as a service — Domain models ar only a means to an end: your clients and consumers are the ones who mostly have to understand and work with your models, much more than you as a creator of them. Keep that in mind and understand the tasks of domain modelling as a service to others. At least try to not make life of your consumers too difficult.

Conceptual Modeling

Apply classical conceptual modelling methods to find your domain models. For example the good old method of writing down requirements in natural language then highlighting nouns, verbs, prepositions, keywords, etc. For example, assumed we got that initial requirement:

“We want to create an online service allowing users to browse Books.
In addition, they should be able browse Authors, and navigate from Authors to Books and vice versa.”

We could translate that into CDS as follows:

namespace 'our.library';

entity Books {
  key ID : UUID;
  title  : String;
  descr  : String;
  author : Association to Authors;
}

entity Authors {
  key ID : UUID;
  name   : String;
  books  : Association to many Books on books.author=$self;
  birth  : Date;
  death  : Date;
}

We used CDS’s basic means for entity-relationship modeling in here, that is …

We’ll revisit and gradually refine that model in the subsequent chapters below.

Naming Conventions

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

Example:

entity Books {
  key ID : UUID;
  title : String;
  genre : Genre;
  author : Association to Authors;
}
type Genre : String enum {
  Mystery; Fiction; Drama;
}

Distinguish entities, types, and elements

Prefer concise names

Using Namespaces

You can use namespaces to help getting to unique names without bloating your code with fully qualified names. Beyond this, there’s nothing special about them. At the end of the day they’re just prefixes, which are automatically applied to all relevant names in a file. For example:

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

..… is equivalent to:

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

Namespaces are optional

You are not forced to use namespaces or prefixes in general, though, as you also can come up with unique names by any other reasonable way, as long as you are in control of your target ‘universe of discourse’.

Rule of thumb:

Whatever you choose, what you always have to ensure is that all your names are unique within your project.

Recommendations:

In case you decide to use namespaces, it is up to you how to construct them; just a few recommendations you may want to take into account:

Prefer namespaces over top-level contexts

Namespaces and contexts are similar. In former uses of CDS in SAP HANA, you were forced to put all definitions into an enclosing top-level context. This limitation doesn’t exist anymore and is in fact rather detrimental. Hence, always prefer namespaces over single top-level contexts, for example, …

Do: Don’t:
namespace foo.bar;
entity Boo {}
entity Car {}
context foo.bar {
  entity Boo {}
  entity Car {}
}

Entities and Types

CDS allows defining entities as well as reuse types:

Example:

entity Books {
  key ID : UUID;
  title : String;
  genre : Genre;
  author : Association to Authors;
}
type Genre : String enum {
  Mystery; Fiction; Drama;
}

Avoid dogmatic separation of entity types

One could also use types to fully separate entity types from entity sets, for example:

type Book {
  title : String;
  descr : String;
}
entity Books : Book {
  key ID : UUID;
  author : Association to Authors;
}

However, this usually is rather counterproductive to conciseness and comprehensibility, so we rather discourage from doing so.

Avoid overly normalized models

For example, assumed we’d have to model Contacts with e-mail addresses and phone numbers. This is how one would likely thinks about that on a conceptual level:

Good, as that would allow to map emails and phones to simple JSON attributes and speed up reading and writing data significantly:

entity Contacts {
  key ID : UUID;
  name : String;
  emails : array of {
    kind : String;
    address : String;
    primary : Boolean;
  }
  phones : array of {...}
}

Caveat: array of is currently disallowed for element types, a restriction, which is removed in upcoming releases of CDS. You can reach that today by storing JSON content to String elements.

Bad, because that would end up in lots of JOINs when mapping to relational databases, with detrimental effects on read and write performance, as well as making the model harder to read:

entity Contacts {
  key ID : UUID;
  name : String;
  emails : Composition of many EmailAddresses on emails.contact=$self;
  phones : Composition of many PhoneNumbers on phones.contact=$self;
}
entity EmailAddresses {
  contact : Association to Contacts;
  key ID  : UUID;
  kind    : String;
  address : String;
  primary : Boolean;
}
entity PhoneNumbers {...}

Rules of thumb:

Prefer simple, flat structures

While CDS provides unrestricted support for structured types, named as well as inline anonymous, you should always think twice of making use of this as several technologies, you or your customers might have to integrate with, might have difficulties with that. Moreover, maybe against wrong belief, flat structures are easier to understand and consume.

Good:

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

Bad:

entity Contacts {
  isCompany : Boolean;
  companyData : {
    name : String;
  }
  personData : {
    title : {
      primary : String;
      secondary : String;
    }
    name : {
      firstname : String;
      lastname : String;
      ...
    }
  }
  ...
}

Rules of thumb:

Use Custom-defined Types reasonably

Custom-defined Types are valuable when you have a decent reuse ratio, they’re counter-productive when not.

Good:

type Currency : String(3) @title: 'Currency Code' @ValueList.entity: Currencies;
entity Currencies {
  key code : Currency;
  name : String;
}
entity Order {
  price: Decimal;
  currency : Currency; //> will inherit title and value help
}

Bad, because there’s close to no reuse and it’s harder to understand models without always looking up the type definitions:

entity Foo {
  key ID : FooID;
  name : FooName;
  descr : FooDescr;
  // ... and so on
}

Primary Keys

Primary Keys are used to uniquely identify addressable top-level entities. Use the keyword key to signify the element(s) which form an entity’s primary key.

Primary keys can be….

Best Practice: Prefer simple, technical and immutable primary keys as much as possible.

Use Canonic Primary Keys

We recommend using canonically named and typed primary keys, as in the examples below:

entity Books {
  key ID : UUID;
  title : String;
  ...
}
entity Authors {
  key ID : UUID;
  name : String;
  ...
}

This eases the implementation of generic functions that can apply the same ways of addressing instances across different types of entities.

While CDS has to stay open for any kind of use cases, projects can of course establish more restricted rules and policies. For example they can turn this recommendation of using canonic primary keys into a project-wide policy by usins base types and aspects as follows:

// common definitions
entity StandardEntity {
  key ID : UUID;
}
using { StandardEntity } from './common';
entity Books : StandardEntity {
  title : String;
  ...
}
entity Auhtors : StandardEntity {
  name : String;
  ...
}

Actually @sap/cds/common already provides such a pre-defined entity named cuid.

Use UUIDs for technical keys

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

Rule of thumb:

Of course, you can also have semantic primary keys, like order numbers constructed by customer name+date or so. And if so, they usually range between UUIDs and DB sequences with respect to the pros and cons listed above.

Don’t interpret UUIDs!

We strongly recommend to always following these rules when dealing with UUIDs:

Ultimate rule is:

UUIDs are immutable opaque values!

… which are supposed to be unique in order to be used for lookups and hence compared by equality - nothing else! It’s the task of the UUID generator to ensure uniqueness, not the one of subsequent usages!

Also: 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 the response to an HTTP request is pretty useless overhead.

Mapping UUIDs to OData

By default, cds maps UUIDs to Edm.Guid in OData models. However, the OData standard puts up restrictive rules for Edm.Guid values - for example, only hyphenated strings are allowed - which may conflict with existing data. Therefore, we allow the default mapping to be overridden as follows:

entity Books {
  key ID : UUID @odata.Type:'Edm.String';
  ...
}

If necessary, you can also add the annotation @odata.MaxLength to override the corresponding property, too.

Mapping UUIDs to SQL

By default, cds maps UUIDs to nvarchar(36) in SQL databases. The length is to accommodate representations with hyphens as well as any other. The choice of a string type vs a raw/binary type is in line with this recommendation from SAP HANA:

If the client side needs to work with the UUID, VARBINARY would lead to CAST operations or binary array handling at the client side. Here NVARCHAR would be the data type of choice to avoid handling binary arrays on the client side.

Associations

Associations capture relationships between entities. They map to reference values in result sets. For example, a data record for the Books entity as defined above might look like this:

{
  ID: '22234-4648xd-...',
  title: 'Wuthering Heights',
  author: { ID:'550e8400-e29b-11d4-a716-446655440000' }
}

Use Managed Associations

Associations are mapped to foreign keys in relational databases and result in joins when reading data. However, when mapped to non-relational databases they are just references. Keep your domain models to a conceptual level by using managed associations. So, avoid fiddling around with foreign keys. Associations - especially managed ones - capture your intent, while foreign keys are rather an imperative technical discipline.

Do:

entity Books {
  key ID : UUID;
  title : String;
  author : Association to Authors;
}
entity Authors {
  key ID : UUID;
  name : String;
  books : Association to many Books on books.author = $self;
}

Don’t:

entity Books {
  key ID : UUID;
  title : String;
  author_ID : UUID;
  author : Association to Authors on author.ID = author_ID;
}
entity Authors {
  key ID : UUID;
  name : String;
  books : Association to many Books on books.author_ID = ID;
}

Managed To-Many Associations

Simply add the keyword many to indicate a 0..* cardinality. Note that all additional restrictions about cardinality such as min 1 or max 2 should be expressed and checked as constraints, e.g. using not null.

In order to manage to-many associations by generic providers later on, we usually need to specify a reverse to-one association on the target side using SQL-like on condition. Use the canonic form of on <target>.assoc = $self as in our definition of Authors:

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

Managed Many-To-Many Associations

CAP currently does not provide managed support for many-to-many associations. Unless we do so, you have to resolve many-to-many associations into two one-to-many associations using a link entity to connect both. For example, assumed we’d want to extend our model to allow multiple authors per book, there’s no to-one association on the target side to refer to in a canonic on condition as explained above:

entity Authors { ...
  books : Association to many Books on ...???;
}
entity Books { ...
  author : Association to many Authors on ...???;
}

Break that down into two one-to-many associations as follows:

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

Compositions

Compositions are used to model ‘part-of’ or ‘contained-in’ relationships. For example, in the following definition of Orders, the composition Orders.Items refers to entity Order_Items, with the entries of the latter being dependent objects of Orders.

// Define Orders with contained OrderItems
entity Orders {
  key ID   : UUID;
  Items    : Composition of many Order_Items on Items.parent=$self;
}
entity Order_Items { // shall be accessed through Orders only
  key parent : Association to Orders;
  key book   : Association to Books;
  quantity   : Integer;
}

CAP provides these special treatments to Compositions out of the box:

Outlook: Composition of Types

We will release support for Composition of types which will allow us to greatly simplify definition of document structures as follows.

entity Orders {
  key ID   : UUID;
  Items    : Composition of many OrderItems;
}
type Order_Item {
  key book : Association to Books;
  quantity : Integer;
}

Alternatively with anonymous inline types:

entity Orders {
  key ID   : UUID;
  Items    : Composition of many {
    key book : Association to Books;
    quantity : Integer;
  };
}

Both definitions are equivalent to and unfolded behind the scenes to the initial sample above using Composition of entities.

Aspects, Extensibility

CDS introduces Aspects, a powerful concept borrowed from Aspect-oriented programming, which basically, allows us to extend any kind of definitions from wherever we are like that:

extend SomeEntity with @some.annotation {
  someNewField: String;
}

That is comparable to features like Categories in Objective-C, partial classes in C#, or modifying prototypes in JavaScript.

Separation of Concerns

Aspects allow factoring out cross-cutting or technical concerns into separate models/files, which greatly facilitates keeping core domain models clean and comprehensible. For example, you could separate concerns into separate files as follows:

Extensions, Verticalization, Customization

We can go one step further and place extensions not only into separate files in the same project, but into separate projects, which provide reuse packages to consumers. Such reuse packages would provide common extensions, for example, to add technical features as well as for verticalization, that is, add customizations and extensions for certain industries or regions. Frequently, also projects building apps based on reuse models use that for customization of reused definitions to their specific needs.

Dynamic Extensibility

Finally, taking that even another step further, CAP applies the very same techniques when we allow subscribers of a SaaS solution to add extension fields or adjust labels and other annotation-based properties to their needs. In that case the extensions are applied dynamically at runtime of the SaaS application. See the Extensibility guide for details on that.

Aspects → Separate Concerns

CDS’s Aspects provide powerful mechanisms to separate concerns by decomposing models up to individual definitions into files with different lifecycles. Effective definitions are transparently composed of the combined partial definitions. Essentially, this means that …

Different concerns of a definition can be contributed by different people at different points in time - that is Separation of Concerns.

Use cases range from distributed development scenarios - for example, when one developer might provide the core structure definition, while another one might add annotations for consumption in UIs, and yet another one those for analytics, etc. - up to Verticalization and Customization.

… to Separate Concerns

But now we lost the managed aspects for the AdminService. Instead of polluting our domain model, we use Aspects to keep it clean and add the required aspects in the model where it actually belongs.

add this to the end of src/admin-service.cds:

...
using { managed } from `@sap/cds/common`;
extend our.Books with managed;
extend our.Authors with managed;

using { CatalogService as cats } from `./cat-service`;
extend cats.Books with @excluding:[ createdBy, managedBy ];
extend cats.Authors with @excluding:[ createdBy, managedBy ];

… for Separation of Concerns

Annotations can be placed in original definitions or in extensions thereof in the same or different files. For example, the following examples result in the same effective model:

1) inline with structure (→ bad)

define entity Foo @description: 'A Foo' {
  bar : String @description: 'A Foo''s bar';
}

2) separate from structure (→ good)

define entity Foo { bar : String; }
annotate Foo with @description: 'A Foo' {
  bar @description: 'A Foo''s bar';
}

3) multiple separate annotation sources (→ good)

define entity Foo { bar : String; }
annotate Foo with @description: 'A Foo';
annotate Foo with { bar @description: 'A Foo''s bar' };

It is recommended to make use of this option to keep your models concise and comprehensible by separating concerns and also reflect different ownerships and lifecycles of different concerns up to verticalization scenarios.

… for Verticalization and Customization

Aspects are key to start with streamlined minimalistic models that serve exactly what you need at a given point in time, while being able to add new elements, features and even complete reuse packages later on, even transparently to your existing models.

See …

Minimalistic design by intent Adapting to your needs Adding own Code Lists Code Lists with validity

… in the guide on @sap/cds/common

… to Extend Reused Entities

For example, a foundation package could provide these definitions to capture change information and/or manage temporal data for arbitrary entities.

abstract entity tracked {
  created: { _by: User; at: DateTime; }
  modified: { _by: User; at: DateTime; }
}
abstract entity temporal {
  valid: { from: DateTime; to: DateTime; }
}
type User : String @title 'User ID';

Application on top could selectively use these as follows:

entity Books : tracked, temporal { ... }
entity Authors : tracked { ... }

And clients of that application could even extend the cross-cutting aspects and have these extensions applied to all ‘derived’ entities in turn:

extend tracked with {
  changes : Composition of many ChangeNotes on changes.subject = $self;
}
entity ChangeNotes {
  key subject : Association to any { ID: UUID };
  key at : DateTime;
  user : User;
  note : String(666);
}

Use Common Reuse Types

Make yourself familiar with and use the common types and aspects provided through @sap/cds/common instead of redefining the semantically same things again in different flavors. You benefit from:

For example, usage is as simple as this:

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

Note: These reuse types and code lists are intentionally minimalistic to keep the entry barriers as low as possible, essentially focusing on the bare minimum of what ~80% of all apps need. Yet, in case you need more, you can use Aspects to adapt and extend these base models to your needs.

Use Enterprise Features

Localized Data

Oh, and we’re targeting interested users world-wide, of course, so we need to add translations of Books’ data …

Well, that’s an easy one, as CAP gives us pretty free lunch with Localized Data:

...
  title  : localized String;
  descr  : localized String;
...