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 forpersistence models deployed to databases as well as forservice definitions.
Introduction
Capture Intent —What, not How!
CDS focuses onconceptual modelling: we want to capture 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:
using {cuid,managed }from '@sap/cds/common';entity Books : cuid,managed { title :localized String; descr :localized String; author : Association toAuthors;}
In that model we used thepre-defined aspectscuid
andmanaged
, as well as thequalifierlocalized
to capture generic aspects. We also usedmanaged 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 browseBooks andAuthors, andnavigate from Books to Authors and vice versa. Books are classified byGenre".
Using CDS, we would translate that into an initial domain model as follows:
using {cuid }from '@sap/cds/common';entity Books : cuid { title : String; descr : String; genre : Genre; author : Association toAuthors;}entity Authors : cuid { name : String; books : Association to manyBooks on books.author = $self;}type Genre : String enum { Mystery;Fiction;Drama;}
Aspect-oriented Modeling
CDS Aspects and Annotations provide powerful means forseparation 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 chapterAspects below.
Fuelling 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.
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 withDomain-driven Design:
- Placing projects' primaryfocus on the core domain
- Close collaboration ofdevelopers anddomain experts
- Iteratively refiningdomain 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.
Best Practices
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 modelsconcise andcomprehensible
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 indesign, 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.
Prefer Flat Models
While CDS provides great support, you should always think twice before using structured types. Some technologies you or your customers use might not integrate with those out of the box. Moreover, 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 : 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;}
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 differentpeople.
We strongly recommend to make use of that as much as possible.
Naming Conventions
We recommend adopting the following simple naming conventions as commonly used in many communities, for example, Java, JavaScript, C, SQL, etc.
To easily distinguish type / entity names from elements names we recommend to...
CapitalizeType / Entity Names
- Startentity andtype names with capital letters — for example,
Authors
- Startelements with a lowercase letter — for example,
name
As entities represent not only data types, but also data sets, from which we can read from, we recommend following common SQL convention:
PluralizeEntity Names
- Useplural form forentities — for example,
Authors
- Usesingular form fortypes — for example,
Genre
In general always prefer conciseness, comprehensibility and readability, and avoid overly lengthy names, probably dictated by overly strict systematics:
PreferConcise Names
- Don't repeat contexts → for example
Authors.name
instead ofAuthors.authorName
- Prefer one-word names → for example
address
instead ofaddressInformation
- Use
ID
for technical primary keys → see alsoUse Canonic Primary Keys
Core Concepts
Namespaces
You can usenamespaces to get to unique names without bloating your code with fully qualified names. For example:
namespace foo.bar;entity Boo {}entity Moo : Boo {}
... is equivalent to:
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.
- Thereverse 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.
Domain Entities
Entities represent a domain's data. When translated to persistence models, especially relational ones, entities become tables.
Entity definitions essentially declare structured types with named and typed elements, plus theprimary key elements used to identify entries.
entity name { keyelement1 : Type; element2 : Type; ...}
Learn more about entity definitions.
Views / Projections
Borrowing powerful view building from SQL, we can declare entities as (denormalized) views on other entities:
entity ProjectedEntity as select from BaseEntity { element1,element2 as name,/*...*/};
Learn more about views and projections.
Primary Keys
Use the keywordkey
to signify one or more elements that form an entity's primary key:
entity Books { keyID : UUID; ...}
Do:
Don't:
- Don't use binary data as keys!
- Don't interpret UUIDs!
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 promotedby aspectcuid
from @sap/cds/common.
// @sap/cds/commonaspect cuid {keyID : UUID}
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 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 avoidthe 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 aSalesOrder with threeSalesOrderItems in one transaction. INSERTSalesOrder 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 theSalesOrderItems?
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 toRFC 4122. This not only means useless processing, it also impedes integration with existing data sources. For example, ABAP'sGUID_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 asjava.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
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 ofBuilt-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:
using {Country,managed }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:
type User : String;//> merely for increasing expressivenesstype 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 decentreuse 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:
using {sap.capire.bookshop.types }from './types';namespace sap.capire.bookshop;entity Books { keyID : types.BookID; name : types.BookName; descr : types.BookDescr; ...}
// types.cdsnamespace sap.capire.bookshop.types;type BookID : UUID;type BookName : String;type BookDescr : String;
Associations
UseAssociations to capture relationships between entities.
entity Books { ... author : Association toAuthors;//> to one}entity Authors { ... books : Association to manyBooks on books.author = $self;}
Learn more about Associations in theCDS Language Reference.
Managed :1 Associations
The associationBooks:author
in the sample above is a so-calledmanaged association, with foreign key columns and on conditions added automatically behind the scenes.
entity Books { ... author : Association toAuthors;}
In contrast to that we could also useunmanaged associations with all foreign keys and on conditions specified manually:
entity Books { ... author : Association toAuthors 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.
Prefer managed associations
For the sake of conciseness and comprehensibility of your models always prefermanaged Associations for to-one associations.
To-Many Associations
Simply add themany
qualifier keyword to indicate a to-many cardinality:
entity Authors { ... books : Association to manyBooks;}
If your models are meant to target APIs, this is all that is required. When targeting databases though, we need to add anon
condition, like so:
entity Authors { ... books : Association to manyBooks 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, for examplebooks.author.ID = ID
.
Many-to-Many Associations
CDS currently doesn't provide dedicated support formany-to-many associations. Unless we add some, you have to resolvemany-to-many associations into twoone-to-many associations using a link entity to connect both. For example:
entity Projects { ... members : Composition of manyMembers on members.project = $self;}entity Users { ... projects : Composition of manyMembers on projects.user = $self;}entity Members: cuid {// link table project : Association toProjects; user : Association toUsers;}
We can useCompositions of Aspects to reduce noise a bit:
entity Projects { ... members : Composition of many{keyuser : Association toUsers };}entity Users { ... projects : Composition of manyProjects.members on projects.user = $self;}
Behind the scenes the equivalent of the model above would be generated, with the link table calledProjects.members
and the backlink association toProjects
in there calledup_
. Consider that for SAP Fiori elements 'project' and 'user' shall not be keys, even if their combination is unique, because as keys those fields can't be edited on the UI. In this case a different key is required, for example a UUID, and the unique constraint forproject
anduser
can be expressed via@assert.unique
.
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 areauto-exposed in service interfaces
Modeling Document Structures
Compositions are used to model document structures. For example, in the following definition ofOrders
, theOrders:Items
composition refers to theOrderItems
entity, with the entries of the latter being fully dependent objects ofOrders
.
entity Orders { ... Items : Composition of manyOrderItems on Items.parent = $self;}entity OrderItems {// to be accessed through Orders only key parent : Association toOrders; keypos : Integer; quantity : Integer;}
Learn more about Compositions in theCDS Language Reference.
Composition of Aspects
We can use anonymous inline aspects to rewrite the above with less noise as follows:
entity Orders { ... Items : Composition of many{ keypos : Integer; quantity : Integer; };}
Learn more about Compositions of Aspects in theCDS Language Reference.
Behind the scenes this will add an entity namedOrders.Items
with a backlink association namedup_
, so effectively generating the same model as above. You can annotate the inline composition with UI annotations as follows:
annotate Orders.Items with @( UI.LineItem : [ {Value:pos}, {Value:quantity}, ],);
Aspects
CDS'sAspects provide powerful mechanisms to separate concerns. It allows decomposing models and definitions into separate files with potentially different life cycles, contributed by differentpeople.
The basic mechanism use theextend
orannotate
directives to add secondary aspects to a core domain entity like so:
extend Books with { someAdditionalField : String;}
annotate Books with @some.entity.level.annotations { title @some.field.level.annotations;};
Variants of this allow declaring and applyingnamed aspects like so:
aspect NamedAspect {someAdditionalField : String}extend Books with NamedAspect;
We can also apply named aspects asincludes in an inheritance-like syntax:
entity Books : NamedAspect { ... }
Learn more about the usage of aspects in theAspect-oriented Modeling section..
TIP
Consumers always see the merged effective models, with the separation into aspects fully transparent to them.
Authorization
CAP supports out-of-the-box authorization by annotating services and entities with@requires
and@restrict
annotations like that:
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:
// core domain model in schema.cdsentity Books { ... }entity Authors { ... }
// authorization modelusing {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: [ ...];
Fiori Annotations
Similarly to authorization annotations we would frequently add annotations which are related to UIs, starting with@title
annotations 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:
// core domain model in db/schema.cdsentity Books : cuid { ... }entity Authors : cuid { ... }
// common annotations in app/common.cdsusing {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;}
// Specific UI Annotations for Fiori Object & List Pagesusing {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';};
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 thelocalized
qualifier to tag respective text fields in your as follows.
Do:
entity Books { ... title :localized String; descr :localized String;}
Don't:
In contrast to that, this is what you would have to do without CAP'slocalized
support:
entity Books { keyID : UUID; title : String; descr : String; texts : Composition of manyBooks.texts on texts.book = $self; ...}entity Books.texts { keylocale : Locale; keyID : 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'sout-of-the-box support forlocalized
data avoids polluting your models with doubled numbers of entities, and detrimental effects on comprehensibility.
Learn more in theLocalized Data guide.
Managed Data
@cds.on.insert
@cds.on.update
Use the annotations@cds.on.insert
and@cds.on.update
to signify elements to be auto-filled by the generic handlers upon insert and update. For example, you could add fields to track who created and updated data records and when:
entity Foo {//... 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;}
Learn more about pseudo variables$now
and$user
below.
Theserules apply:
- Datacannot be filled in from external clients → payloads are cleansed
- Datacan be filled in from custom handlers or from
.csv
files
Note the differences todefaults...
... for example, given this model:
entity Foo {//... managed : Timestamp@cds.on.insert:$now; defaulted : Timestampdefault $now;}
While both behave identical for database-levelINSERT
s, they differ forCREATE
requests on higher-level service providers: Values formanaged
in the request payload will be ignored, while provided values fordefault
will be written to the database.
In Essence:
Managed data fields are filled in automatically and are write-protected for external clients.
Limitations
In case ofUPSERT
operations, the handlers for@cds.on.update
are executed, but not the ones for@cds.on.insert
.
Domain Modeling > Managed Data
Aspectmanaged
You can also use thepre-defined aspectmanaged
from@sap/cds/common to get the very same as by the definition above:
using {managed }from '@sap/cds/common';entity Foo : managed {/*...*/ }
Learn more about@sap/cds/common
.
With this we keep our core domain model clean and comprehensible.
Pseudo Variables
The pseudo variables used in the annotations above are resolved as follows:
$now
is replaced by the current server time (in UTC)$user
is the current user's ID as obtained from the authentication middleware$user.<attr>
is replaced by the value of the respective attribute of the current user$uuid
is replaced by a version 4 UUID
Learn more aboutAuthentication in Node.js.Learn more aboutAuthentication in Java.