Search

Domain Modeling

Find here an introduction to the basics of domain modeling with CDS, complemented with recommended best practices. 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 might also be exposed for reuse in other projects.

The following figure illustrates this central role of domain models:

domain models

Domain-Driven Design

Similar to Domain-driven Design, a key goal of domain modeling 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 refine knowledge about a problem domain iteratively.

Keep Them Simple!

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

  • clean = don’t pollute them with technical details → use Aspects instead
  • concise = be on point, use short names, simple flat models, etc.
  • comprehensible = domain modelling 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 tasks of domain modeling as a service to others.

Or, 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.

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, don’t overstress this and balance it with the equally, if not more, important goals of conciseness and comprehensibility.

  • Don’t be dogmatic, for example, about abstraction
  • Rather, strive for adequate domain models

Domain modeling as a service — Domain models are only 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 tasks of domain modeling as a service to others. At least try not to make the work of your consumers too difficult.

Conceptual Modeling

Apply classical conceptual modeling methods to find your domain models, for example, the good old method of writing requirements in natural language and then highlighting nouns, verbs, prepositions, keywords, etc. Let’s assume that we got the following initial requirement:

“We want to create an online service for our library 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 here, that is…

  • entities to represent addressable, uniquely identifiable, objects
  • Associations to capture relationships; to-many relationships need on conditions

We will revisit and gradually refine this model in the following sections.

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

  • start entity and type names with capital letters …
  • use plural form for entities - for example, Authors
  • use singular form for types - for example, Genre
  • start elements with a lowercase letter - for example, name

Prefer Concise Names

  • don’t repeat contexts - for example, Author.name instead of Author.authorName
  • prefer one-word names - for example, address instead of addressInformation
  • use ID for technical primary keys → see also the following section

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 aren’t 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

  • Use namespaces if your models might be reused in other projects
  • Otherwise, you can go without namespaces
  • Services exposed by your app are rarely reused, so rarely need namespaces.

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’s up to you how to construct them; just a few recommendations you may want to take into account:

  • the reverse-domain-name approach works well in open target scopes
  • avoid having to incompatibly change names in future, hence …
  • avoid short-lived name parts like your current organization’s name
  • avoid overly lengthy cascades of acronyms

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, Types, Aspects

CDS allows defining entities as well as reuse types:

  • Entities represent data that consumers can read and write in a directly addressable way, uniquely identified by their primary keys.

  • Types aren’t connected to data themselves, but rather be used to describe the types of elements within entities.

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 think 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:

  • If you don’t really need to support sophisticated queries addressing individual entries of compound data like emails, don’t normalize.
  • If consumers of related services usually read all entries, not individual ones, don’t normalize.

Prefer Simple, Flat Structures

While CDS provides unrestricted support for structured types, named as well as inline anonymous, you should always think twice before using this. Several technologies, you or your customers want to integrate with, can 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:

  • Avoid overly structured types if you can’t rule out nondocument data stores for your service implementation
  • Avoid overly structured types if you can’t rule out nondocument data stores for your service consumers, that are customers/stakeholders

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
}

See also: Entity and Type Definitions See also: Aspects

Primary Keys

Primary Keys are used to uniquely identify addressable top-level entities. Use the keyword key to signify one or more elements which form an entity’s primary key.

Primary keys can be….

  • simple (that is, a single field) or compound (for example, composed of multiple fields).
  • technical, that is, not carrying semantic information, or semantical.
  • immutable that is, not changing their values after initial creation.

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 following examples:

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 establish more restricted rules and policies. For example they can turn this recommendation of using canonic primary keys into a project-wide policy by using base types and aspects as follows:

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

Actually @sap/cds/common already provides such a predefined 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 can avoid the evil of premature optimization by at least considering these points:

  • UUIDs are universal - that means, 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 to that, 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.

  • DB sequences are hard to guess - assume that you want to insert a SalesOrder with three SalesOrderItems in one transaction. On INSERT SalesOrder it 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.

Rule of Thumb:

  • Use local integer sequences if you really deal with high loads and volumes.
  • Otherwise, prefer UUIDs.

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:

  • 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, re hyphens

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 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 can 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 Constitute Relationships

Associations capture relationships between entities. They’re similar to forward-declared joins with either explicitly (unmanaged) or implicitly (managed) specified join conditions.

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

See CDS Language Reference to learn more about associations

Associations map to reference values in responses to requests. For example, a data record for the Books entity as defined in the previous sample, can 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 nonrelational databases they’re 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. Express and check all additional restrictions about cardinality, such as min 1 or max 2, as constraints, for example, 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 doesn’t 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:

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 document structures through ‘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 fully 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:

  • Composition targets are auto-exposed in service interfaces
  • Deep Insert/Update/Upsert automatically fill in document structures
  • Cascaded Delete is applied as the default strategy when deleting Composition roots

Managed Compositions

Managed Compositions greatly simplify definitions of document structures as follows.

Eliminating to-parent associations and on conditions:

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 @your.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:

  • schema.cds - your core domain model, kept clean and comprehensible
  • audit-model.cds - adds additional fields required for auditing in a file
  • auth-model.cds - adds annotations for authorization.
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 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

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:

  • concise and comprehensible models on your side
  • higher interoperability between all applications using these types
  • proven best practices captured from real applications
  • optimized implementations and runtime performance
  • automatic support for localized code lists and value helps

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
}

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 free lunch with Localized Data:

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