Search

Defining Services

All-in-one Definitions

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

entity LibraryService {
  enity 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 also: Naming Conventions

As detailed out in the guide on Generic Providers, this simplistic all-in-one service definition is all we need to start a full-fledged server, exposed through OData, or plain REST API, that serves CRUD requests automatically.

Services as Views

In contrast to the all-in-one way depicted in the intro above, you would usually rather declare your services as projections on underlying domain models.

We borrowed the well-known concept 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.products as my } from 'db/schema';

entity LibraryService {

  entity Books as SELECT from my.Products { *,
    author.firstname ||' '|| author.lastname as author : String,
    category.name as genre,
  } excluding { createdBy, modifiedBy };

  entity Genres as projection on products.Categories
   where root.ID = 'Books Genres'
   order by name;

}

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 etc. are available to express projections as shown in the example above.

Not bound to SQL data sources

Note that even though we borrowed the well-known, powerful and proven language query language from SQL, this does not mean that such projections are bound to SQL backends. Yes, it allows to do.

The variant as projection on is essentially the same as as SELECT from, with some restrictions to help you building views.

Key for Generic Provides

Having captured projections declaratively like this is a key prerequisite to provide generic handlers to automatically serve many CRUD requests: Upon incomming requests/queries these handlers reflect the views in their services’ models to construct correspoding queries to connected data sources, which may be a database, but can also be a remote service.

For example, an incoming query to the above service like that:

GET .../library/Books

… would be translated and delegated behind the scenes to a product service connected via OData as follows:

GET .../products/Products?
&$select=ID,title,descr,stock,price,currency
&$expand=authors($select=firstname,lastname)
&$expand=category($select=name)

Services as Facades

Thereby services become facades to underlying data, exposing different aspects tailored to respective use-cases.

services

Single-purposed Services

We strongly recommend following to design your services to be tailored 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, likelihood is high, it result. in lots of headaches in the long run:

DO:   One Service per Use Case

For example, assumed that we got a domain model defining Books and Authors more or less as above, and adds 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 entity Authors to end users.

Associations in Services

Auto-excluded Associations

Associations of exposed entities referring to targets not exposed are cut off, in a sense that they are’nt navigable for consumers. Only foreign key information is disclosed.

Auto-redirected Associations

If two entities A and B from an underlying model 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 model below 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;
}

You can resolve this, by explicitly determining a redirection target in the projection of Ax as follows:

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

Actions and Functions

In addition to entities, it’s also possible to define custom actions and functions. They can be modeled directly on service level (unbound) or on entity level (bound). Have a look here for a detailed description of modeling options.

To implement actions and functions custom coding is necessary. The following code example indicates the usage in node.js. For the Java variant, have a look at the Java documentation.

// registering unbound action
srv.on('cancelOrder', (req) => {
  // do something ...
})
// registering bound function
srv.on('getViewCount', 'Products', (req) => {
  // do something ...
})

For simplified usage, the req object contains the action/function parameters in req.data. In case of a bound action/function req.query contains the query to retrieve the entity from the persistence, on which the action/function was called.

Services constitute APIs

Suppress Fields

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 {
  key ID : UUID;
  title : String;
  @cds.api.ignore
  author : Association to Authors;
}

Publish APIs