Search

    Domain Modeling with CDS

    Most projects start with capturing the essential objects of their domain in a respective domain model. Find here an introduction to the basics of domain modeling with CDS, complemented with recommended best practices.

    The following figure illustrates this central role of domain models:

    domain models

    See also the introduction in the About page

    Content

    Capture Intent → What, not How

    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.

    Promoting 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 close collaboration of developers and domain experts to iteratively refine knowledge about a given problem domain.

    “Keep it simple, stupid!”

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

    Strive for Adequate Models
    If the vast majority of your clients uses relational databases, don’t overly abstract from that, as that would have all suffer from common denominator syndromes.

    Prefer Simple, 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:

    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;
    }
    

    ❗ Avoid overly structured models
    If you can’t rule out SQL databases or alike to serve your data that might impede adoption significantly. Both, in your own projects, as well as in other projects that have to consume your models, or integrate with the services you provide.

    Use Higher-level Options Provided by CDS

    CAP and CDS provide several higher-level language constructs and pre-build modelling elements which help to greatly reduce complexity of models. Such as for:

    Higher-level concepts provided by CAP and CDS allow capturing intent on conceptual levels, as well as greatly reducing complexity of your domain models, hence promoting conciseness and comprehensibility.

    Use Aspects for Separation of Concerns and Verticalization

    The major reason for complexity are extensive and complex requirements, of course, and the number of non-functional requirements to be addressed in addition. This is especially true for common reuse models that are meant to ease integration.

    For example, starting out with a simple core model of Contacts, we might be requested to additionally address these requirements:

    • support middle initials, or more than three names in general
    • add postal addresses, email and phone numbers
    • add payment options → required for shops and the like
    • track when records are created and changed, and by whom
    • keep track of the history of all changes
    • support scripted names, for example, in Han characters, or Kanji
    • etc…

    Adding all this to our model would quite likely leave us with a very complex model, difficult to adopt. Many might rather decide to opt out, and go for simple home-brewed variants, thus we’d end up having reached the exact opposite of our initial goals of improved integration through broad adoption of common reuse models.

    Use CDS Aspects to mitigate this. CDS Aspects greatly help keeping your models clean and concise.

    Factor out technical concerns, as well as modularize into common core models plus pre-built extension packages for advanced use cases.

    Probably there are different extension packages in different usage contexts supporting verticalization.

    Entities & Types → Conceptual Modeling

    Entities Represent Domain Data

    Let’s start conceptual with the good old method of writing requirements in natural language, then highlighting nouns, verbs, prepositions, keywords, etc., as in this:

    “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”.

    We could translate that into CDS as follows:

    namespace capire.bookshop;
    
    entity Books {
      key ID : UUID;
      title  : String;
      descr  : String;
      genre  : Genre;
      author : Association to Authors;
    }
    
    entity Authors {
      key ID : UUID;
      name   : String;
      books  : Association to many Books on books.author=$self;
    }
    
    type Genre : String enum {
      Mystery; Fiction; Drama;
    }
    

    Find this source also in cap/samples.

    We used CDS’s basic means for capturing data structures here.

    Entities represent data, with records identified by primary keys.
    Types are used to describe the types of elements within entities.

    Custom-Defined Derived Types

    The basic way to declare custom-defined types is using the : syntax to derive new types from given base types like so:

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

    ❗ Use Custom-defined types reasonably
    They’re valuable when you have a decent reuse ratio. Without reuse, your models would then be much harder to read and understand, as one always has to look up respective type definitions.
    Here is a bad practice example:
    entity Book { key ID:BookID; name:BookName; descr:BookDescr; ... }

    Derived Types with Includes

    While the preceding samples only showed derived scalar types, the same is available for structured types, for example:

    type Person : { firstname: String; lastname: String; }
    type Contact : Person {
      company   : String;
      isCompany : boolean;
      // addresses, ...
    }
    

    … as well as for entities:

    using { managed, temporal } from '@sap/cds/common';
    entity Contacts : Contact, managed, temporal {
      key ID : UUID;
    }
    

    While the use of : very much looks like and works like (multiple) inheritance, it’s actually based on and applying CDS Aspects.

    ❗ Avoid Dogmatic Separation of Entity Types and Entity Sets
    It’s possible to use includes and always have separate declarations of entity types and entity sets (for example, as in OData), following this pattern:
    type <EntityType> : {...}
    entity <EntitySet> : <EntityType> {}
    We strongly discourage doing that, as it’s counterproductive to our goals of concise and comprehensible domain models.

    Using localized Type Qualifiers

    With CAP, you stay on a conceptual level. For example, when having to serve localized data, you would simply add the localized qualifier to respective text fields in your model like so.

    Do:

    entity Books { ...
      title : localized String;
      descr : localized String;
    }
    
    Don’t:

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

    entity Books { ...
      title : String;
      descr : String;
      texts : Composition of many Books_texts on texts.book = $self;
    }
    entity Books_texts { ...
      book  : Association to Books;
      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.

    By placing _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.

    Using @sap/cds/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
    • Out-of-the-box 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
    }
    

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

    Use common reuse types and aspects, such as from @sap/cds/common, to keep models concise, as well as benefitting from improved interoperability, proven best practices, and centrally optimized out-of-the-box support through generic implementations in CAP runtimes or other reuse libraries.

    Naming Conventions — Recommendations

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

    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
    Example:
    entity Books {
      key ID : UUID;
      title : String;
      genre : Genre;
      author : Association to Authors;
    }
    type Genre : String enum {
      Mystery; Fiction; Drama;
    }
    

    Prefer Concise and Comprehensible Naming
    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. 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 don’t have to use namespaces or prefixes in general. You 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, just make sure 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 change names in future, so 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. 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 {}
    }

    Primary Keys — Best Practices

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

    In general, primary keys can be….

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

    While CAP supports all of the above, our recommended best practice is:

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

    Find More In

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

    • DB 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.

    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 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!

    We strongly recommend to always follow 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, about hyphens

    Ultimate Rule: UUIDs are immutable opaque values!

    UUIDs should be 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 usages!

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

    Finally, existing data that shall be imported into or processed by a CAP app may not be compliant to RFC 4122. Examples:

    • ABAP’s GUID_32s are uppercase and may need to be processed during remote service consumption.
    • SAP HANA’s SYSUUID function generates something like CF773C584E3A6213E2008AC320F0F2BE.

    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.

    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 representations. The choice of a string type over 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 → Structured Models

    Associations Capture Relationships

    CAP uses Associations to capture relationships between entities. This is rather in line with Object-oriented and NoSQL.

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

    See CDS Language Reference to learn more about Associations.

    In payloads of requests and responses, Associations map to reference values:

    {
      ID: '201', title: 'Wuthering Heights',
      author: { ID:'101' }
    }
    

    Associations Are like Forward-Declared Joins

    In contrast to Associations in CDS, persistence modeling using SQL DDL, would capture relationships mere foreign keys, and the respective JOINs have to be done in each query.

    Example in SQL DDL:

    Schema Definition:

    CREATE TABLE Books (
      ID Integer,
      title String,
      author_ID Integer
    )
    CREATE TABLE Authors (
      ID Integer,
      name String
    )
    

    Queries:

    SELECT ID, title, author.name from Books
    JOIN Authors author on author_ID = author.ID
    
    Same Example in CAP CDL:

    Schema Definition:

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

    Queries:

    SELECT ID, title, author.name from Books
    

    JOIN Authors author on author_ID = author.ID

    That Associations act like forward-declared joins means:
    Queries just use them much like forward-declared table aliases. There’s no need to repeat the respective JOINs and ON conditions all over the place.

    Prefer Managed Associations

    When deploying to relational databases Associations are mapped to foreign keys. Yet, when mapped to non-relational databases they’re just references. Keep your domain models agnostic to that by using managed associations. Associations - especially managed ones - capture your intent, while foreign keys are rather an imperative technical discipline.

    Do:

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

    Don’t:

    entity Books { ...
      author_ID : UUID;
      author : Association to Authors on author.ID = author_ID;
    }
    

    Prefer using Managed Associations as much as possible, and avoid declaring and dealing with low-level foreign keys.

    (One-) To-Many Associations

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

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

    If your models are meant to target APIs, this is all that is required. When targeting deployment to databases though, we need to specify a reverse to-one associations on target sides, and connect to that using SQL-like on conditions:

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

    One-to-many Associations always need on conditions referring to some reverse association (or foreign key) on the target side.

    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, assumed we want to capture that Books can have multiple Authors:

    You can break that down into two one-to-many associations as follows:

    entity Authors { ...
      books : Association to many Books_Authors on books.author = $self;
    }
    entity Books { ...
      author : Association to many Books_Authors on author.book = $self;
    }
    // link table entity for many-to-many relationship
    entity Books_Authors { ...
      book : Association to Books;
      author : Association to Authors;
    }
    

    Addition: For uni-directional many-to-many relationships, you can and should use Compositions of Aspects to reduce complexity and raise comprehensibility.

    Compositions → Document-Oriented Modeling

    Compositions Capture Contained-In Relationships

    Compositions are used to model document structures through “contained-in” relationships. For example, in the following definition of Orders, the Orders.Items composition refers to the Order_Items entity, 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 { // to 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

    CAP runtimes provide out-of-the-box support to serve structured document data through generic service provider implementations.

    Using Managed Compositions of Aspects

    Compositions of Aspects greatly simplify definitions of document structures as follows:

    Eliminating to-parent associations and on conditions:

    entity Orders {
      key ID   : UUID;
      Items    : Composition of many Order_Items;
    }
    aspect Order_Items {
      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.

    Prefer using Compositions of Aspects to model document structures with contained-in relationships.

    Many-to-many Relationships with Compositions of Aspects

    If you don’t need bidirectional associations, you can use compositions of aspects to model many-to-many relationships as follows: Let’s start with an example without composition of aspects, based on a many-to-many relationship sample, for example, as in GitHub repositories as projects:

    entity Projects { ...
       members : Composition of many Users_in_Projects on members.project = $self;
    }
    entity Users { ... }
    // link table to reflect many-to-many relationship:
    entity Users_in_Projects { ...
       project : Association to Projects;
       member : Association to Users; 
    }
    

    With Compositions of Aspects this simplifies to:

    entity Projects { ...
       members : Composition of many { key user : Association to Users; } 
    }
    entity Users { ... }
    

    Prefer using Compositions of Aspects to model uni-directional many-to-many relationships.

    Avoid Overly Normalized Models → use array of

    For example, let’s say we have to model Contacts with e-mail addresses and phone numbers. This is how you would probably think about this on a conceptual level:

    Good, as this would allow you 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 {...}
    }
    

    Bad, because this would end up with 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, such as emails, don’t normalize.
    • If consumers of related services usually read all entries, not individual ones, don’t normalize.

    Prefer using array of for things like lists of emails or phone numbers in a contacts entity.

    Aspects → Aspect-Oriented Modeling

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

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

    Content

    Adding New Fields

    Essentially, Aspects allow you to extend any kind of definitions, for example with new elements, from wherever you are, like this:

    using { SomeEntity } from 'somewhere';
    extend SomeEntity with {
      // adding new fields
      someNewField: String;
    }
    

    This will be merged into an effective model, which looks to consumers as if someNewField would have always been in SomeEntity from the very beginning.

    Adding New Entities and Relationships

    You can also add new entities or types and extend existing ones with Associations or Compositions:

    using { SomeEntity } from 'somewhere';
    
    // defining new entities
    entity SomeNewEntity {...}
    
    extend SomeEntity with {
      // adding new relationships 
      associated : Association to SomeNewEntity;
      composed   : Composition of {...};
    }
    

    Such extensions can be placed anywhere, in the same files as definitions, in separate files in same project, or even in separate projects, and applied to any kind of definition - those defined by you in your project, or those reused by you from some other project, as we’ll see in the following sections…

    Adding and Overriding Annotations

    The syntax variant annotate allows you to add and/or override annotations as follows:

    using { SomeEntity } from 'somewhere';
    // adding or overriding annotations
    annotate SomeEntity with @another.annotation {
      someField @some.field.level.annotation;
    }
    

    Summary — All-in-One

    So basically, you can extend an entity with new fields and associations, as well as add and override annotations in one statement, for example:

    using { SomeEntity } from 'somewhere';
    
    // defining new entities
    entity SomeNewEntity {...}
    
    extend SomeEntity with @your.annotation {
    // adding new fields 
      someNewField: String @with.some.annotations;
    // adding new relationships 
      associated : Association to SomeNewEntity;
      composed   : Composition of {...};
    }
    
    // adding or overriding annotations
    annotate SomeEntity with @another.annotation {
      someNewField @some.field.level.annotation;
    }
    

    Rules of the Game:

    • Fields (including new Associations) can only be added, not changed in type-related details.
    • Annotations can be added, overridden, or removed (by assigning null).

    Aspects allow you to flexibly and transparently add new entities, fields, and relationships to existing models, as well as to add or override annotations.

    Using Aspects for Separation of Concerns

    Aspects allow you to factor out cross-cutting or technical concerns into separate models/files, potentially with different ownerships and lifecycles. This greatly facilitates keeping core domain models concise and comprehensible.

    For example, you could separate concerns into separate files as follows:

    • schema.cds - your core domain model, kept clean and comprehensible
    • admin-model.cds - adds additional fields required for maintenance and auditing
    • auth-model.cds - adds annotations for authorization in a separate file
    • analytics-model.cds - adds annotations required for analytics use cases
    • fiori-app.cds - adds annotations required for SAP Fiori UIs
    // schema.cds -- basic structure definitions, kept tidy...
    entity Books {
      key ID : UUID;
      title  : String;
      author : Association to Authors;
    }
    entity Authors { ... }
    

    Using Ad-Hoc Anonymous Aspects

    You can also place extensions into separate files within the same project:

    // admin-model.cds -- technical concerns as separate aspects
    using { sap.capire.bookshop as our } from './schema';
    extend our.Books with {
      modifiedBy : User;
      modifiedAt : DateTime;
    }
    extend our.Authors with {
      modifiedBy : User;
      modifiedAt : DateTime;
    }
    

    Aspects allow you to flexibly and transparently separate concerns, in the same or separate artifacts within a project.

    Using Predefined Named Aspects

    Instead of extending several definitions repeatedly with the same aspects, you can also predefine named aspects and then easily apply these extensions repeatedly. Actually, this is commonly done with the managed aspects provided through @sap/cds/common, defined as follows:

    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;
    }
    

    The annotations fuel generic runtime behavior to automatically fill in Managed Data.

    Given that, we can simplify the previous extensions as follows:

    // admin-model.cds -- technical concerns as separate aspects
    using { sap.capire.bookshop as our } from '@capire/bookshop';
    using { managed } from '@sap/cds/common';
    extend our.Books with managed;
    extend our.Authors with managed;
    

    In addition to ad-hoc extensions, named aspects allow you to predefine, share, and repeatedly apply common aspects.

    Using Aspects to Reuse, Compose and Extend

    Using Aspects to Extend Reused Definitions

    Extending definitions as shown above isn’t limited to your own models in one project, but also works cross-project in reuse scenarios. Assuming we have…

    • One project, which provides a reuse model → for example @capire/bookshop
    • Another project which uses the models from bookshop like so:
    1. Add reuse dependency to consuming project via npm:
      npm add @capire/bookshop
      
    2. Reuse models from bookshop in our models:
      using { sap.capire.bookshop as our } from '@capire/bookshop';
      ...
      
    3. Extend imported definitions as before:
      using { sap.capire.bookshop as our } from '@capire/bookshop';
      extend our.Books with {
        modifiedBy : User;
        modifiedAt : DateTime;
      }
      extend our.Authors with {
        modifiedBy : User;
        modifiedAt : DateTime;
      }
      

      See also Adapting to Your Needs in @sap/cds/common.

    Aspects allow you to flexibly extend and adapt reused definitions, in the same way as if they were owned by the consuming project.

    Using Aspects to Compose Models from Reuse Packages

    We can take this a step further and reuse stuff and models from two reuse packages that we mash up — again using Aspects.

    There’s also an example for that in cap/samples:

    The two projects are independent from each other.

    1. Add reuse dependencies to the third project using npm:
      npm add @capire/bookshop
      npm add @capire/reviews
      
    2. Mash up and Extend imported models to extend Books with Reviews:
      using { sap.capire.bookshop.Books } from '@capire/bookshop';
      using { ReviewsService.Reviews } from '@capire/reviews';
      extend Books with {
        reviews : Composition of many Reviews on reviews.subject = $self.ID;
        rating : Reviews.rating;
      }
      

    Aspects greatly promote composing new solutions from two or more reuse packages by extending imported definitions to flexibly mash up imported content into an integrated target solution.

    An Advanced Example of Extending Reused Entities

    Here is another example that is a bit more complex, showing you the power of Aspects, by showing how to apply them to reuse definitions, and hence transitively to all definitions reusing those definitions.

    1. A foundation package, like @sap/cds/common, could provide these definitions to capture change information and/or manage temporal data for arbitrary entities.
      aspect tracked {
        created: { _by: User; at: DateTime; };
        modified: { _by: User; at: DateTime; };
      }
      type User : String @title:'User ID';
      

      See also @sap/cds/common

    2. An application, like @capire/bookshop, would selectively use these as follows:
      entity Books : tracked, temporal { ... }
      entity Authors : tracked { ... }
      

      Learn more about the inheritance-like :-syntax variant of applying aspects.

    3. A third party, such as individual SaaS customers of that application, ISVs, or partners providing prebuilt extension packages, could extend the foundation aspects as follows:
      extend tracked with {
        changes : Composition of many ChangeNotes on changes.subject = $self;
      }
      entity ChangeNotes {
        key subject : UUID;
        key at : DateTime;
        user : User;
        note : String(666);
      }
      

    Using Aspects for Modularization & Verticalization

    We can use the same techniques as depicted above not only to factor out technical concerns but also to construct a complex model as a core model covering the common needs of all use cases, plus a set of prebuilt extension packages to address different advanced use cases.

    Domain models

    For example, see Adding own Code Lists to see how to extend the Countries reuse entities from @sap/cds/common with a new code list Regions.

    This helps us to achieve greatly lowered entry barriers for the 80% of the masses, as well as serving the 20% by allowing all to flexibly and selectively choose what they need, without them suffering from “one-size-fits-all” complexity syndromes.

    The same applies to verticalization scenarios in which the prebuilt extension packages of some common core modes could address the needs of certain industries or regions by providing respective extensions and adaptations. The originators of the base solution, partners or specialized ISVs could provide such prebuilt industry-specific or region-specific packages.

    Using Aspects for SaaS-level Customization
    (aka Dynamic Extensibility)

    Finally, taking that one 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, in order to adapt subscribed SaaS software to their needs. In that case, the extensions are applied dynamically at runtime of the SaaS application.

    Learn more on that in the SaaS Extensibility guide.

    Show/Hide Beta Features