Search

    Providing Services

    Every active thing in CAP is a service. They embody the behavioral aspects of a domain in terms of exposed entities, actions, and events. This guide introduces how to define and implement services, leveraging generic implementations provided by the CAP runtimes.

    Modeling Services in CDS

    All-in-One Service Definitions

    In its most basic form, a service definition simply declares the types of entities it provides access to. For example:

    service AdminService {
    
      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;
      }
    
    }
    

    In fact, this simplistic service definition is all we need to run a full-fledged server out-of-the-box, which serves CRUD requests automatically, without you having to add any implementation code for that.

    Services as Facades

    In contrast to the all-in-one definition above, services usually expose views, aka projections, on underlying domain model entities:

    using { sap.capire.bookshop as my } from '../db/schema';
    
    /** For serving administrators */
    service AdminService @(requires: 'admin') {
      entity Books as projection on my.Books;
      entity Authors as projection on my.Authors;
    }
    

    See this source in cap/samples.

    In this way, they become facades to domain data, exposing different aspects tailored to respective use cases.

    services

    Exposing Denormalized Views

    The definition of AdminService shown above simply exposes unrestricted access to underlying domain model entities in a 1:1 fashion. While this is ok for classic data entry applications used by admins, you should always strive for tailoring your services to specific use cases (→ see also Prefer Single-Purposed Services below), and expose denormalized views on underlying data:

    using { sap.capire.bookshop as my } from '../db/schema';
    
    /** For serving end users */
    service CatalogService @(path:'/browse') {
    
      /** For displaying lists of Books */
      @readonly entity ListOfBooks as projection on Books
      excluding { descr };
    
      /** For display in details pages */
      @readonly entity Books as projection on my.Books { *,
        author.name as author
      } excluding { createdBy, modifiedBy };
    
    }
    

    See this source in cap/samples.

    In this example, we undisclosed information about maintainers from end users and also marked the entities as @readonly.

    Learn more about Common Annotations.

    To express projections, we borrowed well-known concepts of view building from SQL to express projections on underlying entities: CQL, the query language used in CAP, is based on standard SQL, with as projection on being essentially a derivate of SQL’s as select from. All the standard SQL clauses and language constructs, like where, order by, and so on, are available to express projections as shown in the previous example.

    Learn more about CQL in the reference docs.

    Auto-Redirected Associations

    When exposing related entities, associations are automatically redirected. This ensures that clients can navigate between projected entities as expected. For example:

    service AdminService {
      entity Books as projection on my.Books;
      entity Authors as projection on my.Authors;
      //> AdminService.Authors.books refers to AdminService.Books
    }
    

    Learn more about Redirected Associations in the CDS reference docs.

    Auto-Exposed Entities

    Annotate entities with @cds.autoexpose to automatically include them in services containing entities with Association referencing to them. For example, this is commonly done for code list entities in order to serve Value Lists dropdowns on UIs:

    service Zoo {
      entity Foo { //...
        code : Association to SomeCodeList;
      }
    }
    @cds.autoexpose entity SomeCodeList {...}
    

    … would result in SomeCodeList be automaticall auto-exposed @readonly.

    Learn more about Auto-Exposed Entities in the CDS reference docs.

    Custom Actions / Functions

    Entitites exposed by services support common CRUD operations. In addition you can declare custom operations, as shown below:

    service CatalogService @(path:'/browse') {
      //...
      @requires: 'authenticated-user'
      action submitOrder ( book: Books:ID, amount: Integer ) 
      returns { stock: Integer };
     }
    

    Learn more about Actions and Functions in the reference docs.

    Prefer using dedicated custom actions or functions over overly ‘crud-ish’ entity-centric APIs only to increase both, expressiveness of your APIs as well as fine-grained control, for example re authorization, as shown above.

    Prefer Single-Purposed Services

    We strongly recommend designing your services for single use cases. Services in CAP are cheap, so there’s no need to save on them.

    DON’T: Single Services exposing all entities 1:1

    The anti-pattern to that are single services exposing all underlying entities in your app in a 1:1 fashion. While that may save you some thoughts in the beginning, it’s very likely that it will result in lots of headaches in the long run:

    • They open a huge entry door to your clients with only few restrictions
    • Individual use-cases aren’t reflected in your API design
    • You have to add numerous checks on a per-request basis…
    • Which have to reflect on the actual use cases in complex and expensive evaluations

    DO: One Service per Use Case

    For example, let’s assume that we have a domain model defining Books and Authors more or less as above, and then we add Orders. We could define the following services:

    using { my.domain as my } from './db/schema';
    
    /** Serves end users browsing books and place orders */
    service CatalogService {
      @readonly entity Books as select from my.Books {
        ID, title, author.name as author
      };
      @requires: 'authenticated-user'
      @insertonly entity Orders as projection on my.Orders;
    }
    
    /** Serves registered users managing their account and their orders */
    service UsersService {
      @readonly entity Orders as projection on my.Orders
        where buyer = $user; // limit to own ones
      action cancelOrder ( ID:Orders.ID, reason:String );
    }
    
    /** Serves administrators managing everything */
    @requires: 'authenticated-user'
    service AdminService {
      entity Books   as projection on my.Books;
      entity Authors as projection on my.Authors;
      entity Orders  as projection on my.Orders;
    }
    

    These services serve different use cases and are tailored for each. Note, for example, that we intentionally don’t expose the Authors entity to end users.

    Late-Cut Microservices

    Compared to Microservices, CAP services are ‘Nano’. As shown in the previous sections, you should design your application as a set of single-purposed services, which can all be served embedded in a single-server process at first (that is, a monolith).

    Yet, given the uniform way in which services are defined and consumed, you can decide later on to separate, deploy, and run your services as separate microservices, without any changes to your models or your code.

    This flexibility allows you to, again, focus on solving your domain problem first, and avoid the efforts and costs of premature microservice design and DevOps overhead, at least in the early phases of development.

    Implementing Services

    Leveraging Generic Providers

    The CAP runtimes for Node.js and Java provide generic handlers, which automatically serve all CRUD requests to exposed entities, including dedicated support for pagination, text search, input validation, and much more (→ see sections below).

    In effect, a service definition as introduced above is all we need to run a full-fledged server out-of-the-box, which serves CRUD requests automatically, without you having to add any implementation code for that.

    Learn more in section Generic Providers.

    Adding Custom Logic

    As most standard tasks and use cases are covered by generic handlers, the need to add service implementation code is greatly reduced and minified, and hence the amount of individual boilerplate coding.

    The remaining cases that need custom handlers, reduce to real custom logic, specific to your domain and application, such as:

    • Domain-specific programmatic Validations
    • Augmenting result sets, for example to add computed fields for frontends
    • Programmatic Authorization Enforcements
    • Triggering follow-up actions, for example calling other services or emitting outbound events in response to inbound events
    • And more… In general, all the things not (yet) covered by generic handlers

    The following sections explain how to do so, starting with an introduction to the core concepts of events and event handlers.

    About Events and Event Handlers

    As introduced in About CAP, everything happening at runtime is in response to events, emphasizing the ubiquitous notion of events in CAP. This means that all service implementations take place in event handlers, as highlighted in the figure.

    Events essentially comprise:

    • Synchronous Requests sent from UIs, frontends, or other services:
      • Common CRUD methods: CREATE, READ, UPDATE, UPSERT, DELETE
      • Common REST methods: POST, GET, PUT, PATCH, DELETE
      • Custom-defined actions and functions
    • Asynchronous Event Messages received through message brokers:

    Event Handlers comprise:

    So, lines are very much blurred, both with respect to synchronous and asynchronous protocols, as well as regarding generic handlers and custom handlers. In fact, all generic features are provided in event handlers, which are registered and implemented using the same APIs and libraries that you would use to implement your custom logic. Vice versa, you can implement your own generic handlers in the same way as we did in CAP runtimes.

    Providing Custom Implementations

    At runtime, the CAP frameworks usually bootstrap and instantiate service provider instances automatically (→ see also options for project-specific bootstrapping in Node.js and Java). In order to register event handlers with a service provider instance, you first need to associate a respective class or module with a given service definition.

    In Node.js, the easiest way to do so is by equally named .js files placed next to a service definition’s .cds file. Alternatively, you can add @impl: annotations to services, or provide implementation functions programmatically, for example:

    service org.acme.Foo @(impl:'lib/foo.js') {...}
    
    cds.serve('org.acme.Foo') .with (require('./lib/foo.js'))
    

    In Java, you’d assign Event Handler Classes using Spring Dependency Injection with Java annotation @ServiceName as follows:

    @Component
    @ServiceName("org.acme.Foo")
    public class FooServiceImpl implements EventHandler {...}
    

    Registering Event Handlers

    Given assigned implementation classes/modules, you can register individual event handlers for each potential event, on different hooks of the event processing cycle, for example:

    const cds = require('@sap/cds')
    module.exports = function (){
      this.on ('submitOrder', (req)=>{...}) //> custom actions
      this.on ('CREATE',`Books`, (req)=>{...})
      this.before ('UPDATE',`*`, (req)=>{...})
      this.after ('READ',`Books`, (each)=>{...})
    }
    

    Learn more about adding event handlers in Node.js.

    @Component
    @ServiceName("BookshopService")
    public class BookshopServiceImpl implements EventHandler {
      @On(event="submitOrder") public void onSubmitOrder (EventContext req) {...}
      @On(event="CREATE", entity="Books") public void onCreateBooks (EventContext req) {...}
      @Before(event="UPDATE", entity="*") public void onUpdate (EventContext req) {...}
      @After(event="READ", entity="Books") public void onReadBooks (EventContext req) {...}
    }
    

    Learn more about adding event handlers in Java.

    In general, event handlers are registered as follows:
    • <hook:on|before|after> , <events> , <entities> → handler function
    • <hook:on|before|after> , <events> → handler function
    Hooks: on | before | after
    • on handlers can run instead of the generic/default handlers
    • before handlers run before the on handlers
    • after handlers run after the on handlers, and get the result set as input
    Entity-bound vs unbound events
    As apparent from these two variants, <events> can be bound to <entities>, as with the common CRUD or REST methods, or unbound, as with custom-defined, service-level actions or functions. Entity-bound events are only triggered for requests directly targeting that entity.

    For example, the following GET requests will only trigger the READ handler for MyEntity:

    GET MyEntity
    
    GET MyEntity?$expand=myNavigation
    

    Consistently, a GET request like the following will only trigger the READ event handler for myNavigation’s target:

    GET MyEntity(1)/myNavigation
    

    Implementing Event Handlers

    Event handlers all get a uniform Request/Event Message context object as their primary argument, which, among others, provides access to the following:

    • The event name — that is, a CRUD method name, or a custom-defined one
    • The target entity, if any
    • The query in CQN format, for CRUD requests
    • The data payload
    • The user, if identified/authenticated
    • The tenant using your SaaS application, if enabled

    Learn more about implementing event handlers in Node.js. Learn more about implementing event handlers in Java.

    Generic Providers

    The CAP runtimes for Node.js and Java provide generic handlers, which automatically serve all CRUD requests to exposed entities under these conditions:

    • A default datasource is configured (for example, cds.env.requires.db in Node.js)
    • The exposed entity wasn’t defined using UNION or JOIN

    The generic handlers also serve document-oriented data, that is structured documents modeled using compositions and associations, through deep read and write operations as explained in the sections below.

    Auto-Generated Primary Keys

    On CREATE and UPSERT operations, key elements of type UUID are filled in automatically. In addition, on deep inserts and upserts, respective foreign keys of nested objects are filled in accordingly.

    PUT Requests = UPDATE or PATCH?

    Deep INSERT for Compositions

    To create the parent entity and its child entities, use a single POST request. That means that the body of the POST request must contain a complete set of data for all entities. On success, this request will create all entities and relate them. On failure, none of the entities will be created.

    Sample POST request with to-one composition:

    POST .../Orders {
      ID:1, title: 'new order', header: {
        ID:2, status: 'open', note: {
          ID:3, description: 'child of child entity'
        }
      }
    }
    

    A POST request for a to-many relationship:

    POST .../Orders {
      ID:1, title: 'new order', header: {
        ID:2, status: 'open', notes: [{
          ID:3, description: 'child of child entity'
        },{
          ID:4,description: 'another child of child entity'
        }]
      }]
    }
    

    Deep UPDATE for Compositions

    Similar to the deep INSERT case, the deep UPDATE of the structured document must be made with a single PUT or PATCH request to the parent entity. Depending on the request body, child entities will be created, updated, or deleted:

    • Child entities existing on the database, but not in the payload, will be deleted.
    • Child entities existing on the database and in the payload will be updated (specified fields overwrite the values on the database).
    • Child entities not existing on the database will be created.

    Consider the POST request with to-one composition from above. The following request would update this Orders entry:

    PUT .../Orders/1 {
      title: 'another order', header: {
        ID:4, status: 'canceled'
      }
    }
    

    The title property of the Orders parent entity will be changed, the child entities with the ID=2 and ID=3 will be deleted, and a new child entity with the ID=4 will be created.

    Deep DELETE for Compositions

    Since child entities of compositions can’t exist without their parents, a DELETE of the parent entity is a cascading delete of all nested composition targets.

    DELETE .../Orders/1
    

    Would delete Orders, OrderHeaders, and SpecialNotes from the example above.

    INSERT with Associations

    Entities with association relationships must be created with separate POST requests. It means that, unlike in the case of deep INSERT with compositions, the association targets (Authors in the example above) must be created before the parent entity.

    Then you can send a request to create the entity Books with the body looking like a deep INSERT. The association to the existing entity Authors is created by providing the key of the entity.

    POST .../Authors {
      ID:12, name: 'Charlotte Brontë'
    }
    
    POST .../Books {
      ID:121, title: 'Jane Eyre', author: { ID:12 }
    }
    

    A POST request with the body looking like a deep INSERT can only be sent for targets of managed to-one associations. In other cases, you can either send the foreign keys explicitly or implement your own custom handler.

    Note, that unlike the case of composition, there’s no deep Insert/Update for associated entities, therefore all non-key fields of author in the POST request on Books are ignored and their values aren’t changed.

    UPDATE with Associations

    An UPDATE request of structured documents with association relationships can be made with a PATCH or PUT request to the parent entity. You can use it to update the parent entity’s data or to change references to association targets. The restrictions of the INSERT operation are valid for the UPDATE case as well.

    DELETE with Associations

    A DELETE operation is forbidden if a managed to-one association exists with the foreign key pointing to the entity that you want to delete.

    Consider the example:

    entity Books {
      key ID : Integer;
      orders : Association to many Orders on orders.book = $self;
    }
    entity Orders {
      book : Association to Books;
    }
    

    The DELETE Request:

    DELETE .../Books(ID=3)
    

    will fail if the entity Orders has a foreign key book_ID=3. In all other cases the DELETE operation will be successful, but unlike deep DELETE, for compositions, no association targets will be affected by it.

    Programmatic Usage w/ Node.js

    Similar to the HTTP request-based examples above, you can also construct queries programmatically and send them to connected or local services for execution. See the following examples:

    Deep READ:

    const srv = cds.connect.to ('<some queryable service>')
    const orders = srv.run (SELECT.from ('Orders', o => { // projection
      o('*'), o.header (h => { // nested projection => 1st $expand
        h('*'), h.note('*') // nested projection => 2nd $expand
      })
    }))
    

    Alternatively, use srv.read() as a shortcut to srv.run (SELECT.from(...)):

    const srv = cds.connect.to ('<some queryable service>')
    const orders = await srv.read ('Orders', o => { // projection
      o('*'), o.header (h => { // nested projection => 1st $expand
        h('*'), h.note('*') // nested projection => 2nd $expand
      })
    })
    

    See Node.js API Reference to learn more about Node.js API.

    Deep INSERT:

    const srv = cds.connect.to ('<some queryable service>')
    srv.run (INSERT.into ('Orders').entries ({
       ID:1, title: 'new order', header: {
         ID:2, status: 'open', note: {
           ID:3, description: 'child of child entity'
         }
       }
    }))
    

    Alternatively, use srv.create() as a shortcut to srv.run (INSERT.from(...)):

    const srv = cds.connect.to ('<some queryable service>')
    srv.create ('Orders') .entries ({
       ID:1, title: 'new order', header: {
         ID:2, status: 'open', note: {
           ID:3, description: 'child of child entity'
         }
       }
    })
    

    See Node.js API Reference to learn more about Node.js API.

    Pagination & Sorting

    Implicit Pagination

    By default, the generic handlers for READ requests automatically truncate result sets to a size of 1,000 records max. If there are more entries available, a link is added to the response allowing clients to fetch the next page of records.

    The OData response body for truncated result sets contains a nextLink as follows:

    GET .../Books
    >{
      value: [
        {... first record ...},
        {... second record ...},
        ...
      ],
      @odata.nextLink: "Books?$skiptoken=1000"
    }
    

    To retrieve the next page of records from the server, the client would use this nextLink in a follow-up request, like so:

    GET .../Books?$skiptoken=1000
    

    On firing this query, you get the second set of 1,000 records with a link to the next page, and so on, until the last page is returned, with the response not containing a nextLink.

    Configuring Defaults with cds.query.limit

    You can configure default and maximum limits in the app environment as follows.

    • The maximum limit defines the maximum number of items that can get retrieved, regardless of $top.
    • The default limit defines the number of items that are retrieved if no $top was specified.

    The two limits can be specified as follows:

    Environment:

    query: {
      limit: {
        default: 20, //> no default
        max: 100     //> default 1,000
      }
    }
    

    Annotation @cds.query.limit

    You can override the defaults by applying the @cds.query.limit annotation on the service or entity level, as follows:

    @cds.query.limit: { default?, max? } | Number
    

    The limit definitions for CatalogService and AdminService in the following example are equivalent.

    @cds.query.limit.default: 20
    @cds.query.limit.max: 100
    service CatalogService {
      [...]
    }
    @cds.query.limit: { default: 20, max: 100 }
    service AdminService {
      [...]
    }
    

    @cds.query.limit can be used as shorthand if no maximum limit needs to be specified at the same level.

    @cds.query.limit: 100
    service CatalogService {
      entity Books as projection on my.Books;     //> pages at 100
      @cds.query.limit: 20
      entity Authors as projection on my.Authors; //> pages at 20
    }
    service AdminService {
      entity Books as projection on my.Books;     //> pages at 1000 (default)
    }
    

    Precedence

    The closest limit applies, that means, an entity-level limit overrides that of its service, and a service-level limit overrides the global setting. The value 0 disables the respective limit at the respective level.

    @cds.query.limit.default: 20
    service CatalogService {
      @cds.query.limit.max: 100
      entity Books as projection on my.Books;     //> default = 20 (from CatalogService), max = 100
      @cds.query.limit: 0
      entity Authors as projection on my.Authors; //> no default, max = 1,000 (from environment)
    }
    

    Implicit Sorting

    Paging requires implied sorting, otherwise records might be skipped accidentally when reading follow-up pages. By default the entity’s primary key is used as a sort criterion.

    For example, given a service definition like this:

    service CatalogService {
      entity Books as projection on my.Books;
    }
    

    The SQL query executed in response to incoming requests to Books will be enhanced with an additional where clause as follows:

    SELECT ... from my_Books
    ORDER BY ID; -- default: order by the entity's primary key
    

    If the request specifies a sort order, for example, GET .../Books?$orderby=author, both are applied as follows:

    SELECT ... from my_Books ORDER BY
      author,     -- request-specific order has precedence
      ID;         -- default order still applied in addition
    

    We can also define a default order when serving books as follows:

    service CatalogService {
      entity Books as projection on my.Books order by title asc;
    }
    

    Now, the resulting order by clauses are as follows for GET .../Books:

    SELECT ... from my_Books ORDER BY
      title asc,  -- from entity definition
      ID;         -- default order still applied in addition
    

    … and for GET .../Books?$orderby=author:

    SELECT ... from my_Books ORDER BY
      author,     -- request-specific order has precedence
      title asc,  -- from entity definition
      ID;         -- default order still applied in addition
    

    Searching Data

    The search capability is enabled by default, allowing any structured document (entity with associated entities) to be searched for a search term. By default all elements of type String of an entity are searchable.

    There are situations, when you would need to deviate from this default and specify a different set of searchable elements, or to extend the search to associated entities. The @cds.search annotation is used for that purpose:

    General Usage

    The @cds.search annotation allows you to include or exclude elements from the set of searchable elements.

    In general, the @cds.search annotation looks like this:

    @cds.search : {
        element1,         // included
        element2 : true,  // included
        element3 : false, // excluded
        accoc1,           // searchable elements from associated entity
        accoc2.elementA   // included one element from associated entity
    }
    entity E { }
    

    Examples

    Let’s consider the following example with two entities defined:

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

    Search in All String Elements

    No need to annotate anything at all.

    Search in Certain Elements

    @cds.search : {title}
    entity Books { ... }
    

    Searches the title element only.

    Exclude Elements from Being Searched

    @cds.search : {isbn : false}
    entity Books { ... }
    

    Searches all elements of type String excluding the element isbn, which leaves the title and descr elements to be searched.

    Search Elements of Associated Entity

    @cds.search : {author}
    entity Books { ... }
    
    @cds.search : {biography : false}
    entity Authors { ... }
    

    Searches all elements of the Books entity, as well as all searchable elements of the associated Authors entity. Which elements of the associated entity are searchable is determined by the @cds.search annotation on the associated entity. So, from Authors all elements of type String are searched but biography is excluded.

    Search Using Path Expression

    @cds.search : {
        isbn : false,
        author.name
    }
    entity Books { ... }
    

    Searches the title and descr elements from Books as well as the element name of the associated Authors entity.


    Input Validation

    @readonly Fields

    Elements annotated with @readonly, as well as calculated fields, are protected against write operations. That is, if a CREATE or UPDATE operation specifies values for such fields, these values are silently ignored.

    By default virtual elements are also calculated.

    The same applies for fields with the OData Annotations @FieldControl.ReadOnly, @Core.Computed, or @Core.Immutable (the latter only on UPDATEs).

    @mandatory Fields

    Elements marked with @mandatory are checked for nonempty input: null and (trimmed) empty strings are rejected.

    The same applies for fields with the OData Annotation @FieldControl.Mandatory.

    @assert.unique Constraints

    Annotate an entity with @assert.unique.<constraintName>, specifying one or more element combinations to enforce uniqueness checks on all CREATE and UPDATE operations. This annotation is applicable to definitions that are rendered as tables. Those are either non-query entities or query entities annotated with @cds.persistence.table. The value of the annotation is an array of paths referring to elements in the entity. The path leaf may be an element of a scalar, structured, or managed association type. Individual foreign keys or unmanaged associations can’t be accessed. If the path points to a structured element, the unique constraint will contain all columns stemming from the structured type. If the path points to a managed association, the unique constraint will contain all foreign key columns stemming from this managed association.

    For example:

    @assert.unique: {
      locale: [ parent, locale ],
      timeslice: [ parent, validFrom ],
    }
    entity LocalizedTemporalData {
      key record_ID : UUID; // technical primary key
      parent    : Association to Data;
      locale    : String;
      validFrom : Date;  validTo : Date;
    }
    

    In essence, key elements constitute special cases of @assert.unique constraints.

    @assert.integrity Constraint for to-one Associations

    All Association to one are automatically checked for referential integrity, that is:

    • CREATEs and UPDATEs are rejected if a reference’s target doesn’t exist
    • DELETEs are rejected if it would result in dangling references
    • … except for associations, entities, or services annotated with @assert.integrity:false
    -- Equivalent SQL DDL statement:
    CREATE TABLE Books ( -- elements ...
      CONSTRAINT FK_author FOREIGN KEY (author_ID) REFERENCES Authors (ID)
    )
    

    This feature is currently only available on the CAP Node.js stack.

    In the Node.js stack, the integrity checks can be skipped via global config cds.env.features.assert_integrity = false.

    @assert.format Pattern Check Constraints

    Allows you to specify a regular expression string (in ECMA 262 format in CAP Node.js and java.util.regex.Pattern format in CAP Java) that all string input must match.

    entity Foo {
      bar : String @assert.format: '[a-z]ear';
    }
    

    @assert.range Check Constraints

    Allows you to specify [ min, max ] ranges for elements with ordinal types — that is, numeric or date/time types. For enum elements, true can be specified to restrict all input to the defined enum values.

    entity Foo {
      bar : Integer  @assert.range: [ 0, 3 ];
      boo : Decimal  @assert.range: [ 2.1, 10.25 ];
      car : DateTime @assert.range: ['2018-10-31', '2019-01-15'];
      zoo : String   @assert.range enum { high; medium; low; };
    }
    

    Specified ranges are interpreted as closed intervals, that means, the performed checks are min ≤ input ≤ max.

    @assert.notNull

    Annotate a property with @assert.notNull: false to have it ignored during the generic not null check, for example if your persistence fills it automatically.

    entity Foo {
      bar : String not null @assert.notNull: false;
    }
    

    Managed Data

    Use the annotations @cds.on.insert and @cds.on.update to signify elements to be auto-filled by the generic handlers. For example, you could add fields to track who created and updated data records and when, as follows:

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

    Equivalent definition, using pre-defined aspect managed from @sap/cds/common:

    using { managed } from '@sap/cds/common';
    entity Foo : managed { /*...*/ }
    

    Generic Handlers for Managed Data

    These rules apply:

    • Data is auto-filled, that is, data is ignored if provided in the request payload.
    • Data can be filled with initial data, for example, through .csv files.
    • Data can be set explicitly in custom handlers. For example:

      Foo.modifiedBy = req.user.id
      Foo.modifiedAt = new Date()
      

    In effect, values for these elements are handled automatically and are write-protected for external service clients.

    Pseudo Variables

    The pseudo variables used in the annotations are resolved as follows:

    • $now is replaced by the current server time (in UTC)
      • The value of $now is stable for the current transaction
    • $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 about Authentication in Node.js. Learn more about Authentication in Java.

    $user.<attr> and $uuid are currently only supported by the Node.js runtime.

    Note the differences to defaults, for example, given this model:

    entity Foo { //...
      managed   : Timestamp @cds.on.insert: $now;
      defaulted : Timestamp default $now;
    }
    

    While both behave identical INSERTs on database-level operations, they differ for CREATE requests on higher-level service providers: Values for managed in the request payload will be ignored, while provided values for defaulted will be written to the database.

    Media Data

    The following annotations can be used in the service model to indicate that an element in an entity contains media data.

    @Core.MediaType
    Indicates that the element contains media data (directly or using a redirect). The value of this annotation is either a string with the contained MIME type (as shown in the first example), or is a path to the element that contains the MIME type (as shown in the second example).
    @Core.IsMediaType
    Indicates that the element contains a MIME type. The @Core.MediaType annotation of another element can reference this element.
    @Core.IsURL @Core.MediaType
    Indicates that the element contains a URL pointing to the media data (redirect scenario).
    @Core.ContentDisposition.Filename
    Indicates that the element is expected to be displayed as an attachment, that is downloaded and saved locally. The value of this annotation is a path to the element that contains the Filename (as shown in the fourth example ).

    The following examples show these annotations in action:

    1. Media data is stored in a database with a fixed media type image/png:
      entity Books { //...
        image : LargeBinary @Core.MediaType: 'image/png';
      }
      
    2. Media data is stored in a database with a variable media type:
      entity Books { //...
        image : LargeBinary @Core.MediaType: imageType;
        imageType : String  @Core.IsMediaType;
      }
      
    3. Media data is stored in an external repository:
      entity Books { //...
        imageUrl  : String @Core.IsURL @Core.MediaType: imageType;
        imageType : String @Core.IsMediaType;
      }
      
    4. Content disposition data is stored in a database with a variable disposition:
      entity Authors { //...
        image : LargeBinary @Core.MediaType: imageType @Core.ContentDisposition.Filename: fileName;
        fileName : String;
      }
      

    Note: In case you rename the properties holding the media type or content disposition information in a projection, you need to update the annotation’s value as well.

    Reading Media Resources

    Read media data using GET requests of the form /Entity(<ID>)/mediaProperty:

    GET ../Books(201)/image
    > Content-Type: application/octet-stream
    

    The response’s Content-Type header is typically application/octet-stream.

    Although allowed by RFC 2231, Node.js does not support line breaks in HTTP headers. Hence, make sure you remove any line breaks from your @Core.IsMediaType content.

    Read media data with @Core.ContentDisposition.Filename in the model:

    GET ../Authors(201)/image
    > Content-Disposition: 'attachment; filename="foo.jpg"'
    

    The media data is streamed automatically.

    Creating a Media Resource

    As a first step, create an entity without media data using a POST request to the entity. After creating the entity, you can insert a media property using the PUT method. The MIME type is passed in the Content-Type header. Here are some sample requests:

    POST ../Books
    Content-Type: application/json
    { <JSON> }
    
    PUT ../Books(201)/image
    Content-Type: image/png
    <MEDIA>
    

    The media data is streamed automatically.

    Updating Media Resources

    The media data for an entity can be updated using the PUT method:

    PUT ../Books(201)/image
    Content-Type: image/png
    <MEDIA>
    

    The media data is streamed automatically.

    Deleting Media Resources

    One option is to delete the complete entity, including all media data:

    DELETE ../Books(201)
    

    Alternatively, you can delete a media data element individually:

    DELETE ../Books(201)/image
    

    Reading External Media Resources

    The following are requests and responses for the entity containing redirected media data from the third example, “Media data is stored in an external repository”.

    This format is used by OData-Version: 4.0. To be changed in OData-Version: 4.01.

    GET: ../Books(201)
    >{ ...
        image@odata.mediaReadLink: "http://other-server/image.jpeg",
        image@odata.mediaContentType: "image/jpeg",
        imageType: "image/jpeg"
    }
    

    Localized Data

    See separate guide on Localized Data.

    Temporal Data

    See separate guide on Temporal Data.

    Concurrency Control

    Conflict Detection Using ETags

    The CAP runtimes support optimistic concurrency control and caching techniques using ETags. An ETag identifies a specific version of a resource found at a URL.

    Enable ETags by adding the @odata.etag annotation to an element to be used to calculate an ETag value as follows:

    using { managed } from '@sap/cds/common';
    entity Foo : managed {...}
    annotate Foo with { modifiedAt @odata.etag } 
    

    The value of an ETag element should uniquely change with each update per row. The modifiedAt element from the pre-defined managed aspect is a good candidate, as this is automatically updated. You could also use update counters or UUIDs, which are recalculated on each update.

    You use ETags when updating, deleting, or invoking the action bound to an entity by using the ETag value in an If-Match or If-None-Match header. The following examples represent typical requests and responses:

    POST Employees { ID:111, name:'Name' }
    > 201 Created {'@odata.etag': 'W/"2000-01-01T01:10:10.100Z"',...}
    //> Got new ETag to be used for subsequent requests...
    
    GET Employees/111
    If-None-Match: "2000-01-01T01:10:10.100Z"
    > 304 Not Modified // Record was not changed
    
    GET Employees/111
    If-Match: "2000-01-01T01:10:10.100Z"
    > 412 Precondition Failed // Record was changed by another user
    
    UPDATE Employees/111
    If-Match: "2000-01-01T01:10:10.100Z"
    > 200 Ok {'@odata.etag': 'W/"2000-02-02T02:20:20.200Z"',...} 
    //> Got new ETag to be used for subsequent requests...
    
    UPDATE Employees/111
    If-Match: "2000-02-02T02:20:20.200Z"
    > 412 Precondition Failed // Record was modified by another user 
    
    DELETE Employees/111
    If-Match: "2000-02-02T02:20:20.200Z"
    > 412 Precondition Failed // Record was modified by another user 
    

    SELECT for Update

    If you want to read and update the retrieved data within the same transaction, you need to make sure that the obtained data isn’t modified by another transaction at the same time.

    The SELECT FOR UPDATE statement allows you to lock the selected records so that other transactions are blocked from changing the records in any way.

    The records are locked until the end of the transaction by commit or rollback statement.

    Learn more about using the SELECT FOR UPDATE statement in Node.js runtime.

    Persistent Locks

    Show/Hide Beta Features