Now accepting guest editorial from SAP, Oracle, Workday and ServiceNow practitioners.Pitch a guide
The Sign-Off Table

Lesson 3 of 6

Defining services and using generic providers

9 min

Listen to this lesson

0:00 / 9:41

A service is the API your app offers. In CAP, services usually expose projections on domain entities: views that select, rename or exclude elements for one use case. The domain model stays stable while each service shows exactly what its consumers need. This lesson defines the fleet app's services and shows how much CAP serves before you write any code.

Service definitions

The fleet desk's service goes in srv/fleet-service.cds:
using { com.contoso.fleet as my } from '../db/schema';
service FleetService @(path: '/fleet') {
entity Inquiries as projection on my.Inquiries;
@readonly entity Topics as projection on my.Topics;
action closeInquiry (ID : UUID, resolution : String);
}
A second service, AdminService, exposes Topics for maintenance by the fleet desk supervisor. @readonly on Topics in FleetService means agents can read the code list but not change it there.

Actions and functions add operations beyond create, read, update and delete. Use an action for something that changes state (closing an inquiry, with a resolution text) and a function for a read-only computation. Actions make intent explicit: rather than letting any client set status = 'Closed' by a generic update, the service offers closeInquiry, whose handler can check that a resolution is given and that the user may close inquiries.

Status changes go through an explicit action: an inquiry is created as New and can only become Closed through closeInquiry with a resolution.
The Sign-Off Table · thesignofftable.com/academy/sap-btp-202

What the generic providers serve

RequestServed by CAP without codeExample against FleetService
Read with filter, sort, pagingOData query optionsGET /odata/v4/fleet/Inquiries?$filter=status eq 'New'&$top=20
SearchSearches text elements; tune with @cds.searchGET /odata/v4/fleet/Inquiries?$search=CMC-ALT-100
Create with childrenDeep insert along compositions; UUID keys filledPOST an inquiry with two notes in one request
Expand related dataNavigation along associations and compositionsGET Inquiries?$expand=notes,topic
Input validation@mandatory, @assert.range, @assert.format, @assert.targetReject priority 5 or a missing salesOrder

Generic provider features the fleet app relies on. Paths follow the default /odata/v4 prefix plus the service path.

Declarative validation first

CAP's documentation recommends declaring input checks in the model rather than coding them. @mandatory rejects missing or empty input. @assert.range checks numbers, dates or enumerations (priority must be 1 to 3). @assert.format checks a string against a regular expression, and @assert.target on a managed to-one association checks that the referenced record exists, so an inquiry cannot point to a topic code that is not in the list. Each annotation can carry a custom message. Declarative checks are visible in the model, consistent across services, and need no handler code.

  1. Request arrivesPOST /odata/v4/fleet/Inquiries from the Fiori app
  2. AuthenticationThe user is identified (mocked users locally, tokens in the cloud)
  3. Authorization@requires and @restrict annotations are checked
  4. Declarative validation@mandatory, @assert.range, @assert.format, @assert.target
  5. Custom handlersbefore, on and after handlers you registered
  6. Generic providerDatabase insert, keys and managed fields filled, response returned
The simplified path of a create request through a CAP service. Lesson 4 adds custom handlers.
The Sign-Off Table · thesignofftable.com/academy/sap-btp-202
ConsumersFleet desk Fiori app; supervisor maintenance app; automated tests
FleetService at /odata/v4/fleetInquiries (projection), Topics (@readonly), SalesOrders (remote, read-only), action closeInquiry
AdminService at /odata/v4/adminTopics with create, update and delete for the supervisor
Domain model com.contoso.fleetInquiries, Notes, Topics: one model, shaped differently by each service
Two use-case services over one domain model in the fleet app.
The Sign-Off Table · thesignofftable.com/academy/sap-btp-202

Projections can do more than pass entities through. A projection can exclude elements a consumer should not see, rename elements to the consumer's vocabulary, or add a where clause so that, for example, a service for fleet customers only ever exposes inquiries that are not marked internal. Because the restriction is part of the service definition, it applies to every request, and a test can prove it once for the whole API instead of screen by screen. Keep in mind that a projection's filter is a modeling choice, not an authorization rule; user-specific restrictions belong in the authorization annotations covered in course sap-btp-204.

What would you do?

A developer has written a before-create handler that checks whether priority is between 1 and 3 and whether salesOrder is filled. Maria reports that the check works when she creates an inquiry, but not when she edits one. What is the better fix?

Key points
  • Services expose projections on domain entities, one service per use case and audience.
  • Default OData V4 paths are /odata/v4/ plus the service path, adjustable with @path.
  • Use actions for state changes with business meaning instead of generic field updates.
  • Generic providers serve query options, search, deep insert, expand and validation without code.
  • Prefer @mandatory and @assert annotations to hand-written validation; they apply on every write.

Check your understanding · not graded

Lesson check

  1. 1. What does @readonly on an entity in a service do?

  2. 2. Which annotation rejects an inquiry whose topic code does not exist?

  3. 3. Why model closing an inquiry as an action instead of a generic status update?

  4. 4. What is the default path of a CAP service named FleetService with @path '/fleet' served over OData V4?