Search

    Providing & Consuming Services

    Every active thing in CAP is a service. This applies to the application services you define in your project, as well as technical services delivered as parts of the CAP framework. This guide introduces the basics about defining, implementing, and publishing services, as well consuming other services.

    Defining Services

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

    We recommend plural forms for entities exposed by services.
    See Naming Conventions for more details.

    In fact, this simplistic service definition is all we need to start a full-fledged server, exposed through OData, or plain REST APIs, that serves CRUD requests automatically.

    Services as Facades — Exposing Views on Underlying Entities

    In contrast to all-in-one service definitions as shown before, services usually expose projections/views on domain model entities. In this way, services become facades to underlying data, exposing different aspects tailored to respective use cases.

    services

    We borrowed well-known concepts of view building from SQL, which in turn uses a reflexive query language to express projections and selections of entities based on underlying entities recursively.

    Example:

    using { sap.capire.bookshop as my } from '../db/schema';
    service CatalogService @(path:'/browse') {
      @readonly entity Books as SELECT from my.Books {*,
        author.name as author
      } excluding { createdBy, modifiedBy };
    }
    

    See this source in cap/samples.

    Not bound to SQL data sources
    Note that even though we borrowed the well-known, powerful, and proven language query language from SQL, this doesn’t mean that such projections are bound to SQL backends.
    Key for Generic Providers
    Capturing projections declaratively like this is a key prerequisite for providing generic providers, which automatically serve CRUD requests. Upon incoming requests, the generic handlers reflect the views in their services’ models to construct corresponding queries to connected data sources, which may be a database, but can also be a remote service.

    CQL, the query language used in CAP, is based on standard SQL, hence, 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.

    Auto-Redirected Associations

    If two entities from an underlying model, A and B, are exposed in a service by projected entities Ax and Bx, all exposed associations from A to B and vice versa are redirected in the projection to Ax and Bx, respectively. This ensures that clients can navigate between projected entities as expected.

    If there are two projections with the same ‘distance’ to the source, the algorithm to resolve the correct target may fail. For example, compiling the following model would result in this warning:

    Warning
    Target “C” is exposed in service “S” by multiple projections “S.Cx”, “S.Dx” - no implicit redirection CDS (redirected-implicitly-ambiguous).

    using { cuid } from '@sap/cds/common';
    
    entity A : cuid { b: Association to B; c: Association to C; }
    entity B : cuid { a: Association to A; }
    entity C : cuid { a: Association to A; }
    
    service S {
      entity Ax as projection on A;
      entity Bx as projection on B;
      entity Cx as projection on C;
      entity Dx as projection on C;
    }
    

    Resolve this, by explicitly determining a redirection target in the projection of Ax. You can use redirected to and choose Cx for the association, to resolve that ambiguity as follows:

    ...
    service S {
      entity Ax as projection on A { *, c : redirected to Cx };
      ...
    }
    

    Alternatively, you can use the Boolean annotation @cds.redirection.target with value true to make an entity a preferred redirection target, or with value false to exclude an entity as target for auto-redirection.

    ...
    service S {
      ...
      @cds.redirection.target: true
      entity Cx as projection on C;
      ...
    }
    

    Auto-Exposed Entities

    Annotate entities with @cds.autoexpose to automatically include them in services containing entities with Association references to them.

    For example, given the following entity definitions:

    // schema.cds
    using { sap.common.CodeList } from '@sap/cds/common';
    entity Bar : CodeList { key code: Integer; }
    entity Car @cds.autoexpose { key id: Integer; }
    

    … a service definition like this:

    using { Bar, Car } from './schema.cds';
    service Zoo {
      entity Foo { //...
        bar : Association to Bar;
        car : Association to Car;
      }
    }
    

    … would result in the equivalent of the following unfolded service definition:

    service Zoo {
      entity Foo { //...
        bar : Association to Zoo.Bar;
        car : Association to Zoo.Car;
      }
      entity Bar as projection on Bar;
      entity Car as projection on Car;
    }
    

    Learn more about CodeLists in @sap/cds/common.

    Handling Events

    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.

    Use Cases for Custom Handlers

    Most standard tasks and use cases are covered by generic handlers, so the need to implement event handlers in CAP-based projects, and hence the amount of individual boilerplate coding, is greatly reduced and minified. The remaining cases that need custom handlers are:

    • 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

    Assigning Custom Implementation Classes/Modules

    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.

    Consuming other Services

    Frequently event handler implementations send requests to other services — mostly in the form of queries constructed using the respective incarnations of cds.ql libraries. Note that from CAP’s perspective, databases are also just services, consumed in your project, as shown in these examples:

    const db = await cds.connect.to('db'), {Orders} = db.entities
    this.on ('READ',`MyOrders`, async (req)=>{
      const tx = db.tx(req) //> ensure tenant isolation & transaction management
      return tx.read (Orders) .where ({ user: req.user.id })
    })
    

    Learn more about consuming services in Node.js.

    import static bookshop.Bookshop_.MY_ORDERS;
    
    @Autowired
    PersistenceService db; // get access to the service
    
    @On(event="READ", entity="MyOrders")
    public Result onRead(EventContext req) {
      CqnSelect query = Select.from(MY_ORDERS).where(o -> o.user().eq(req.getUserInfo().getName()));
      return db.run(query);
    }
    

    Learn more about consuming services in Java.

    Emitting New Events

    Event handlers may also emit new asynchronous events, as also showcased in cap/samples, here a simplified variant:

    this.after (['CREATE','UPDATE','DELETE'], 'Reviews', async(_,req) => {
       const {subject,rating} = req.data
       this.emit ('reviewed', { subject, rating })
    })
    

    Learn more about emitting events in Node.js.

    Java: coming soon.

    Using Model Reflection

    As stated, you can also implement generic custom handlers. In this case you need to frequently reflect on related models, as shown in the following samples:

    const m = this.model //> this = service instance
    for (let each in this.entities) {
      const e = this.entities [each]
      console.log (`entity ${e.name} {`)
      for (let a of m.each (cds.Association, /*in:*/ e.elements))
        if (a.is2many)  console.log (`   ${a.name} : Association to many ${a._target.name};`)
      console.log (`}`)
    }
    

    Learn more about model reflection in Node.js.

    Java: coming soon.

    Consuming Services

    Every active thing in CAP is a service. This applies to the application services you define in your project, as well as technical services delivered as parts of the CAP framework. This guide introduces the basics about defining, implementing, and publishing services, as well consuming other services.

    Each service has an associated data model or service definition as depicted in the following figure.

    REST and OData

    External services can be consumed using the Cloud SDK in combination with the destination service. To configure the destination service properly, see Consuming the Destination Service.

    🚫 Warning
    Make sure, that destination configurations for deployment aren’t shared on any file shares (GitHub, etc.).

    In the package.json or in the .cdsrc.json, the consumed service needs to be configured.

    • destination: Name of the destination (optional).
    • path: Relative path that will be appended to the resolved destination (optional).
    • requestTimeout: Number of milliseconds until a request times out. Default is 60,000 ms (optional).
    • model: The CSN model of the external service.
    • pool: Connections to the external service are pooled to prevent DOS-Attacks. See the .pool section of the cds.connect documentation (optional).
    {
      "cds": {
        "requires": {
          "externalService": {
            "kind": "rest/odata",
            "model": "path/to/model",
            "credentials": {
              "destination": "destinationName",
              "path": "/relativePath",
              "requestTimeout": 30000
            },
            "pool": {
              "min": 1,
              "max": 10
            }
          }
        }
      }
    }
    

    If the destination is omitted, the runtime looks for a destination with the name of the configured data source. In the example, it looks for externalService.

    For local usage, the runtime auto-wires the services, if the consumed service is started first. In the example, the externalService needs to be started before the service requiring it can be started.

    If the external service isn’t served locally, the credentials to connect to the external service could be provided as part of the credentials section. Example:

    {
      "destination": "destinationName",
      "url": "...",
      "username": "...",
      "password": "..",
      "requestTimeout": 30000
    }
    

    Sending Requests

    There are multiple convenient methods for sending requests. For examples, have a look at the API documentation of tx.run.

    ❗ Warning
    Currently, not all methods of the fluent query API can be translated to a URL of the configured service kind. So far, it’s possible to send create, update, and delete requests. For read, it’s only supported to read an entire collection or a single entity (query parameters, for example, filtering or ordering the result set isn’t supported).

    Simple examples for reading data (works also for create, update, and delete):

    // connect to external server
    const srv = cds.connect.to('my.external.service')
    const { Authors } = srv.entities
    
    // share request context with the external service
    // inside a custom handler
    const tx = srv.transaction(req)
    
    // URL string input
    const response = await tx.get('/Authors?$select=name')
    
    // CSN entity input and fluent query API
    const response = await tx.read(Authors).where({ID: 1})
    
    // CQN input from fluent query API
    const cqn = SELECT.from(Authors)
    const response = await tx.run(cqn)
    

    Uniform Consumption

    With these fundamental concepts in place, we can implement additional features as derived concepts, offering the same uniform APIs to consumers of services. For example, consuming databases essentially looks the same as consuming local services served from the same server process, or remote services through OData or REST. Here’s a snippet in Node.js demonstrating this:

    const srv = await cds.connect.to('some-service')
    // could be any of...
    // - local service
    // - remote service client
    // - database client
    const { Books } = srv.entities
    let query = SELECT.from(Books).where({ID:111})
    let books = await srv.run (query)
    
    Databases as Services

    From a CAP perspective, databases are special services, and integrating a specific database technology is as easy as providing a service with a set of generic event handlers to translate inbound queries to native ones in calls to the underlying database technology (could be SQL as well as NoSQL).

    Late-cut Micro Services

    The uniform and transparent consumption APIs allow projects to flexibly change topologies - for example, starting with locally embedded services, which later on get externalized into separate micro services - without changing all consumers.

    Protocols & APIs

    Omitting Elements from APIs

    Add annotation @cds.api.ignore to suppress unwanted entity fields (for example, foreign-key fields) in APIs exposed from this the CDS model, that is, OData or Open API. For example:

    entity Books { ...
      @cds.api.ignore 
      author : Association to Authors;
    }
    

    OData (Open Data Protocol)

    OData is an OASIS standard, which essentially enhances plain REST by standardized query options like $select, $expand, $filter, etc. CAP provides extensive support for OData.

    Learn more in the OData Support guide.

    Best Practices

    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 Authorsentity to end users.

    Show/Hide Beta Features