Application Services
Class cds.ApplicationService
Class cds.ApplicationService is the default service provider implementation, adding generic handlers as introduced in the Cookbook guides on Providing Services, Localized Data and Temporal Data.
Take this service definition for example:
service AdminService {
entity Authors as projection on my.Authors;
entity Books as projection on my.Books;
entity Genre as projection on my.Genre;
}Without any custom service implementation in place, cds.serve would create and instantiate instances of cds.ApplicationService by default like so:
// srv/admin-service.cds
let name = 'AdminService', options = {...}
let srv = new cds.ApplicationService (name, cds.model, options)
await srv.init()If you add a custom implementation, this would comonly be derived from cds.ApplicationService:
// srv/admin-service.js
const cds = require('@sap/cds')
module.exports = class AdminService extends cds.ApplicationService {
init() {
// register your handlers ...
return super.init()
}
}Generic Handlers in srv.init()
Generic handlers are registered by via respective class methods documented below in cds.ApplicationService.prototype.init() like so:
class cds.ApplicationService extends cds.Service {
init() {
const generics = //... all static method with prefix 'handle_'
for (let each of generics) this[each].call(this)
return super.init()
}
static handle_authorization() {...}
static handle_etags() {...}
static handle_validations() {...}
static handle_temporal_data() {...}
static handle_localized_data() {...}
static handle_managed_data() {...}
static handle_paging() {...}
static handle_fiori() {...}
static handle_crud() {...}
}The reason we used
staticmethods was to (a) give you an easy way of overriding and adding new generic handlers / features, and (b) without getting into conflicts with instance methods of subclasses.
static handle_authorization()
This method is adding request handlers for initial authorization checks, as documented in the Authorization guide.
static handle_etags()
This method is adding request handlers for out-of-the-box concurrency control using ETags, as documented in the Providing Services guide.
static handle_validations()
This method is adding request handlers for input validation based in @assert annotations, and other, as documented in the Providing Services guide.
static handle_temporal_data()
This method is adding request handlers for handling temporal data, as documented in the Temporal Data guide.
static handle_localized_data()
This method is adding request handlers for handling localized data, as documented in the Localized Data guide.
static handle_managed_data()
This method is adding request handlers for handling managed data, as documented in the Providing Services guide.
static handle_paging()
This method is adding request handlers for paging & implicit sorting, as documented in the Providing Services guide.
static handle_fiori()
This method is adding request handlers for handling Fiori Drafts and other Fiori-specifics, as documented in the Serving Fiori guide.
static handle_crud()
This method is adding request handlers for all CRUD operations including deep CRUD, as documented in the Providing Services guide.
Overriding Generic Handlers
You can override some of these methods in subclasses, for example to skip certain generic features, or to add additional ones. For example like that:
class YourService extends cds.ApplicationService {
static handle_validations() {
// Note: this is an instance of YourService here:
this.on('CREATE','*', req => {...})
return super.handle_validations()
}
}Adding Generic Handlers
You can also add own sets of generic handlers to all instances of cds.ApplicationService, and subclasses thereof, by simply adding a new class method prefixed with handle_ like so:
const cds = require('@sap/cds')
cds.ApplicationService.handle_log_events = cds.service.impl (function(){
this.on('*', req => console.log(req.event))
})Results of Generic CRUD Handlers
When CAP's generic handlers run a CRUD operation, the result follows a consistent shape (custom .on handlers may return any value):
| Operation | Return value |
|---|---|
READ | Array of matching records, or a single record / null when read by key |
CREATE | Array with .affected (rows created); iterate to access the created rows' generated keys |
UPDATE | Array with .affected (rows changed); reserved for rows from a RETURNING clause |
UPSERT | Array with .affected (rows written); reserved for rows from a RETURNING clause |
DELETE | Array with .affected (rows deleted); reserved for rows from a RETURNING clause |
For CREATE, the array will be populated with rows from an SQL RETURNING clause once that is supported. Until then, the result is a lazy array that computes the created rows' generated primary keys on demand: iterating it ([...result], for…of, JSON.stringify) populates those keys, avoiding the cost when you don't need them.
Iterate before indexing
Direct index access (result[0]) returns undefined until the array has been iterated at least once. Spread or loop over the result first.
const created = await srv.create(Books).entries({title:'Catweazle'})
created.affected // 1
const [row] = [...created] // iterate first — row holds the generated keyFor UPDATE, UPSERT, and DELETE, the array is reserved for rows returned by an SQL RETURNING clause. Unlike CREATE, there are no generated keys to synthesize client-side, so — with RETURNING not yet supported — the array is currently always empty:
const updated = await srv.update(Books).set({discount:'10%'}).where({stock:{'>':111}})
updated.affected // number of rows updatedWhen a write targets a single row by key (for example, srv.update(Books, 201) or srv.delete(Books, '1')) and no row matches, the handler throws a 404 error. A where clause that matches zero rows returns an array with affected: 0 without throwing.
Consistent Results Across Local and Remote Services
This shape was introduced in cds 10 so that local services, HCQL-proxied remote services, and database services return the same thing. To restore the previous behavior, set cds.features.legacy_srv_results: true.