Movatterモバイル変換


[0]ホーム

URL:


re>≡CAP 2025 agenda is online - check it out here!
Skip to content

Appearance

Application Services

Application Services define the APIs that a CAP application exposes to its clients, for example through OData. This section describes how to add business logic to these services, by extending CRUD events and implementing actions and functions.

Handling CRUD Events

Application Services provide aCQN query API. When running a CQN query on an Application Service CRUD events are triggered. The processing of these events is usually extended when adding business logic to the Application Service.

The following table lists the static event name constants that exist for these event names on theCqnService interface and their correspondingevent-specific Event Context interfaces. These constants and interfaces should be used, when registering and implementing event handlers:

EventConstantEvent Context
CREATECqnService.EVENT_CREATECdsCreateEventContext
READCqnService.EVENT_READCdsReadEventContext
UPDATECqnService.EVENT_UPDATECdsUpdateEventContext
UPSERTCqnService.EVENT_UPSERTCdsUpsertEventContext
DELETECqnService.EVENT_DELETECdsDeleteEventContext

The following example shows how these constants and Event Context interfaces can be leveraged, when adding an event handler to be run when new books are created:

java
@Before(event = CqnService.EVENT_CREATE,entity = Books_.CDS_NAME)public void createBooks(CdsCreateEventContext context, List<Books> books) { }

TIP

To learn more about the entity data argumentList<Books> books of the event handler method, have a look atthis section.

OData Requests

Application Services are used by OData protocol adapters to expose the Application Service's API as an OData API on a path with the following pattern:

txt
http(s)://<application_url>/<base_path>/<service_name>
ParameterDescription
<base_path>For the OData V2 and OData V4 protocol adapters,<base_path> can be configured with the application configuration propertiescds.odataV2.endpoint.path andcds.odataV4.endpoint.path respectively. Please seeCDS Properties for their default values.
<service_name>The name of the Application Service, which by default is the fully qualified name of its definition in the CDS model. However, you can override this default per service by means of the@path annotation (seeService Definitions in CDL).

Learn more about how OData URLs are configured.

The OData protocol adapters use the CQN query APIs to retrieve a response for the requests they receive. They transform OData-specific requests into a CQN query, which is run on the Application Service.

The following table shows which CRUD events are triggered by which kind of OData request:

HTTP VerbEventHint
POSTCREATE
GETREADThe same event is used for reading a collection or a single entity
PATCHUPDATEIf the update didn't find an entity, a subsequentCREATE event is triggered
PUTUPDATEIf the update didn't find an entity, a subsequentCREATE event is triggered
DELETEDELETE

In CAP Java versions < 1.9.0, theUPSERT event was used to implement OData V4PUT requests. This has been changed, as the semantics ofUPSERT didn't really match the semantics of the OData V4PUT.

Deeply Structured Documents

Events on deeply structured documents, are only triggered on the target entity of the CRUD event's CQN statement. This means, that if a document is created or updated, events aren't automatically triggered on composition entities. Also when reading a deep document, leveragingexpand capabilities,READ events aren't triggered on the expanded entities. The same applies to a deletion of a document, which doesn't automatically triggerDELETE events on composition entities to which the delete is cascaded.

When implementing validation logic, this can be handled like shown in the following example:

java
@Before(event = CqnService.EVENT_CREATE,entity = Orders_.CDS_NAME)public void validateOrders(List<Orders> orders) {    for(Orders order: orders) {        if (order.getItems()!= null) {            validateItems(order.getItems());        }    }}@Before(event = CqnService.EVENT_CREATE,entity = OrderItems_.CDS_NAME)public void validateItems(List<OrderItems> items) {    for(OrderItems item: items) {        if (item.getQuantity()<= 0) {            throw new ServiceException(ErrorStatuses.BAD_REQUEST,"Invalid quantity");        }    }}

In the example, theOrderItems entity exists as a composition within theItems element of theOrders entity. When creating an order a deeply structured document can be passed, which contains order items. For this reason, the event handler method to validate order items (validateItems) is called as part of the order validation (validateOrders). In case an order item is directly created (for example through a containment navigation in OData V4) only the event handler for validation of the order items is triggered.

Result Handling

@On handlers forREAD,UPDATE, andDELETE eventsmust set a result, either by returning the result, or using the event context'ssetResult method.

READ Result

READ event handlers must return the data that was read, either as anIterable<Map> orResult object created via theResultBuilder. For queries with inline count, aResult objectmust be used as the inline count is obtained from theResult interface.

READ event handlers are also called, for OData/$count requests. These requests determine the total amount of entity instances of a specific entity. When handling these requests in a custom@On event handler aMap with a single keycount needs to be returned as a result:

java
@On(entity = MyEntity_.CDS_NAME)List<Map<String, Object>> readMyEntity(CdsReadEventContext context) {if (CqnAnalyzer.isCountQuery(context.getCqn())) {int count= 100;// determine correct count valuereturn List.of(Collections.singletonMap("count", count));}// handle non /$count requests}

UPDATE and DELETE Results

UPDATE andDELETE statements have an optional filter condition (where clause) which determines the entities to be updated/deleted. Handlersmust return aResult object with the number of entities that match this filter condition and have been updated/deleted. Use theResultBuilder to create theResult object.

❗ Warning

If an event handler for anUPDATE orDELETE event does not specify a result the number of updated/deleted rows is automatically set to 0 and the OData protocol adapter will translate this into an HTTP response with status code404 (Not Found).

INSERT and UPSERT Results

Event handlers forINSERT andUPSERT events can return a result representing the data that was inserted/upserted.

A failed insert is indicated by throwing an exception, for example, aUniqueConstraintException or aCdsServiceException with error statusCONFLICT.

Result Builder

When implementing custom@On handlers for CRUD events, aResult object can be constructed with theResultBuilder.

The semantics of the constructedResult differ between the CRUD events. Clients of Application Services, for example the OData protocol adapters, rely on these specific semantics for each event. It is therefore important that custom ON handlers fulfill these semantics as well, when returning or setting aResult using thesetResult() method of the respective event context.

The following table lists the events and the expectedResult:

EventExpected SemanticResultBuilder method
CREATEThe data of all created entity rowsinsertedRows
READThe data of all read entity rows and (if requested) the inline countselectedRows
UPDATEThe number of updated entity rows and (optionally) the updated dataupdatedRows
UPSERTThe data of all upserted entity rowsinsertedRows
DELETEThe number of deleted entity rowsdeletedRows

Use theselectedRows orinsertedRows method for query and insert results, with the data given asMap or list of maps:

java
import static java.util.Arrays.asList;import static com.sap.cds.ResultBuilder.selectedRows;Map<String,Object> row= new HashMap<>();row.put("title","Capire");Result res= selectedRows(asList(row)).result();context.setResult(res);// CdsReadEventContext

For query results, the inline count can be set through theinlineCount method:

java
Result r= selectedRows(asList(row)).inlineCount(inlineCount).result();

For update results, use theupdatedRows method with the update count and the update data:

java
import static com.sap.cds.ResultBuilder.updatedRows;int updateCount= 1;// number of updated rowsMap<String,Object> data= new HashMap<>();data.put("title","CAP Java");Result r= updatedRows(updateCount, data).result();

For delete results, use thedeletedRows method and provide the number of deleted rows:

java
import static com.sap.cds.ResultBuilder.deletedRows;int deleteCount= 7;Result r= deletedRows(deleteCount).result();

Actions and Functions

Actions andFunctions enhance the API provided by an Application Service with custom operations. They have well-defined input parameters and a return value, that are modelled in CDS. Actions or functions are handled - just like CRUD events - using event handlers. To trigger an action or function on an Application Service an event with the action's or function's name is emitted on it.

Implement Event Handler

The CAP Java runtime doesn't provide any defaultOn handlers for actions and functions. For each action or function an event handler of theOn phase should be defined, which implements the business logic and provides the return value of the operation, if applicable. The event handler needs to take care ofcompleting the event processing. If an action or function isbound to an entity, the entity needs to be specified while registering the event handler. The following example shows how to implement an event handler for an action:

Given this CDS model:

cds
service CatalogService {    entity Books {        keyID: UUID;        title: String;    }actions {      action review(stars: Integer)returns Reviews;    };    entity Reviews {        book : Association toBooks;        stars: Integer;    }}

Thecds-maven-plugin generates event context interfaces for the action or function, based on its CDS model definition. These event context interfaces provide direct access to the parameters and the return value of the action or function. For bound actions or functions the event context interface provides aCqnSelect statement, which targets the entity on which the action or function was triggered.

Action-specific event context, generated by the CAP Java SDK Maven Plugin:

java
@EventName("review")public interface ReviewEventContext extends EventContext {    // CqnSelect that points to the entity the action was called on    CqnSelectgetCqn();    void setCqn(CqnSelectselect);    // The 'stars' input parameter    IntegergetStars();    void setStars(Integerstars);    // The return value    void setResult(Reviewsreview);    ReviewsgetResult();}

The event handler registration and implementation is as follows:

java
@Component@ServiceName(CatalogService_.CDS_NAME)public class CatalogServiceHandler implements EventHandler {    @On(event = "review",entity = Books_.CDS_NAME)    public void reviewAction(ReviewEventContextcontext) {        CqnSelect selectBook= context.getCqn();        Integer stars= context.getStars();        Reviews review= ...;// create the review        context.setResult(review);    }}

Trigger Action or Function

As of version 2.4.0, theCAP Java SDK Maven Plugin is capable of generating specific interfaces for services in the CDS model. These service interfaces also provide Java methods for actions and functions, which allow direct access to the action's or function's parameters. You can just call them in custom Java code. If an action or function is bound to an entity, the first argument of the method is an entity reference providing the required information to address the entity instance.

Given the same CDS model as in the previous section, the corresponding generated Java service interface looks like the following:

java
@CdsName(CatalogService_.CDS_NAME)public interface CatalogService extends CqnService {  @CdsName(ReviewContext.CDS_NAME)  Reviewsreview(Books_ref, @CdsName(ReviewContext.STARS) Integerstars);  interface Application extends ApplicationService,CatalogService {  }  interface Remote extends RemoteService,CatalogService {  }}

In the custom handler class, the specific service interface can be injected as it is already known for generic service interfaces:

java
  ...  @Autowired  private CatalogService catService;  ...

Now, just call the review action from custom handler code:

java
  ...  private void someCustomMethod() {    String bookId= "myBookId";    Books_ ref= CQL.entity(Books_.class).filter(b-> b.ID().eq(bookId));    this.catService.review(ref,5);  }  ...

Alternatively, the event context can be used to trigger the action or function. This approach is useful for generic use cases, where typed interfaces are not available. The event context needs to be filled with the parameter values and emitted on the service:

java
    EventContext context= EventContext.create("review","CatalogService.Books");    context.put("cqn", Select.from("CatalogService.Books").byId("myBookId"));    context.put("rating", review.getRating());    this.catService.emit(context);    Map<String,Object> result= (Map<String, Object>) context.get("result");

Best Practices and FAQs

This section summarizes some best practices for implementing event handlers and provides answers to frequently asked questions.

  1. On which service should I register my event handler?

    Event handlers implementing business or domain logic should be registered on an Application Service. When implementing rather technical requirements, like triggering some code whenever an entity is written to the database, you can register event handlers on the Persistence Service.

  2. Which services should my event handlers usually interact with?

    The CAP Java SDK providesAPIs that can be used in event handlers to interact with other services. These other services can be used to request data, that is required by the event handler implementation.

    If you're implementing an event handler of an Application Service, and require additional data of other entities part of that service for validation purposes, it's a good practice to read this data from the database using thePersistence Service. When using the Persistence Service, no user authentication checks are performed.

    If you're mashing up your service with another Application Service and also return data from that service to the client, it's a good practice to consume the other service through its service API. This keeps you decoupled from the possibility that the service might be moved into a dedicated micro-service in the future (late-cut micro services) and automatically lets you consume the business or domain logic of that service. If you do not require this decoupling, you can also access the service's entities directly from the database.

    In case you're working with draft-enabled entities and your event handler requires access to draft states, you should use theDraft Service to query and interact with drafts.

  3. How should I implement business or domain logic shared across services?

    In general, it's a good practice to design your services with specific use cases in mind. Nevertheless, it might be necessary to share certain business or domain logic across multiple services. To achieve this, simple utility methods can be implemented, which can be called from different event handlers.

    If the entities for which a utility method is implemented are different projections of the same database-level entity, you can manually map the entities to the database-level representation and use this to implement your utility method.

    If they're independent from each other, a suitable self-defined representation needs to be found to implement the utility method.

Serve Configuration

Configure how application services are served. You can define per service which ones are served by which protocol adapters. In addition, you configure on which path they are available. Finally, the combined path an application service is served on, is composed of the base path of a protocol adapter and the relative path of the application service.

Configure Base Path

Each protocol adapter has its own and unique base path.

By default, the CAP Java SDK provides protocol adapters for OData V4 and V2 and the base paths of both can be configured withCDS Properties in theapplication.yaml:

ProtocolDefault base pathCDS Property
OData V4/odata/v4cds.odataV4.endpoint.path
OData V2/odata/v2cds.odataV2.endpoint.path

The following example shows, how to deviate from the defaults:

yaml
cds:  odataV4.endpoint.path:'/api'  odataV2.endpoint.path:'/api-v2'

Configure Path and Protocol

With the annotation@path, you can configure the relative path of a service under which it's served by protocol adapters. The path is appended to the protocol adapter's base path.

With the annotations@protocol or@protocols, you can configure a list of protocol adapters a service should be served by. By default, a service is served by all installed protocol adapters. If you explicitly define a protocol, the service is only served by that protocol adapter.

In the following example, the serviceCatalogService is available on the combined paths/odata/v4/browse with OData V4 and/odata/v2/browse with OData V2:

cds
@path : 'browse'@protocols: ['odata-v4','odata-v2' ]service CatalogService {    ...}

The same can also be configured in theapplication.yaml in thecds.application.services.<key>.serve section. Replace<key> with the service name to configure path and protocols:

yml
cds.application.services.CatalogService.serve:  path:'browse'  protocols:    -'odata-v4'    -'odata-v2'

You can also disable serving a service if needed:

cds
@path : 'browse'@protocol: 'none'service InternalService {    ...}

Learn more about allcds.application.services.<key>.serve configuration possibilities.

Configure Endpoints

With the annotations@endpoints.path and@endpoints.protocol, you can provide more complex service endpoint configurations. Use them to serve an application service on different paths for different protocols. The value of@endpoints.path is appended to theprotocol adapter's base path.

In the following example, the serviceCatalogService is available on different paths for the different OData protocols:

cds
@endpoints: [  {path : 'browse',protocol: 'odata-v4'},  {path : 'list',protocol: 'odata-v2'}]service CatalogService {    ...}

TheCatalogService is accessible on the combined path/odata/v4/browse with the OData V4 protocol and on/odata/v2/list with the OData V2 protocol.

The same can also be configured in theapplication.yaml in thecds.application.services.<key>.serve.endpoints section. Replace<key> with the service name to configure the endpoints:

yml
cds.application.services.CatalogService.serve.endpoints:  -path:'browse'    protocol:'odata-v4'  -path:'list'    protocol:'odata-v2'

Learn more about allcds.application.services.<key>.serve.endpoints configuration possibilities.

Was this page helpful?


[8]ページ先頭

©2009-2025 Movatter.jp