Search

    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.

    Content

    Handling CRUD Events

    Application Services provide a CQN 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 the CqnService interface and their corresponding event-specific Event Context interfaces. These constants and interfaces should be used, when registering and implementing event handlers:

    Event Constant Event Context
    CREATE CqnService.EVENT_CREATE CdsCreateEventContext
    READ CqnService.EVENT_READ CdsReadEventContext
    UPDATE CqnService.EVENT_UPDATE CdsUpdateEventContext
    UPSERT CqnService.EVENT_UPSERT CdsUpsertEventContext
    DELETE CqnService.EVENT_DELETE CdsDeleteEventContext

    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:

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

    To learn more about the entity data argument List<Books> books of the event handler method, have a look at this section.

    OData Requests

    Application Services are used by OData protocol adapters to expose the Application Service’s API as an OData API. 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 verb Event Hint
    POST CREATE  
    GET READ The same event is used for reading a collection or a single entity
    PATCH UPDATE If the update didn’t find an entity, a subsequent CREATE event is triggered
    PUT UPDATE If the update didn’t find an entity, a subsequent CREATE event is triggered
    DELETE DELETE  

    In CAP Java versions < 1.9.0, the UPSERT event was used to implement OData V4 PUT requests. This has been changed, as the semantics of UPSERT didn’t really match the semantics of the OData V4 PUT.

    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, leveraging expand capabilities, READ events aren’t triggered on the expanded entities. The same applies to a deletion of a document, which doesn’t automatically trigger DELETE events on composition entities to which the delete is cascaded.

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

    @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.getAmount() <= 0) {
                throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Invalid amount");
            }
        }
    }
    

    In the example, the OrderItems entity exists as a composition within the Items element of the Orders 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.

    Actions and Functions

    Actions and Functions 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.

    Actions and functions are therefore implemented through event handlers. For each action or function an event handler of the On 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 of completing the event processing.

    The CAP Java SDK Maven Plugin is capable of generating event-specific Event Context interfaces for the action or function, based on its CDS model definition. These Event Context interfaces give direct access to the parameters and the return value of the action or function.

    If an action or function is bound to an entity, the entity needs to be specified while registering the event handler. For bound actions or functions the Event Context interface provides a CqnSelect statement, which targets the entity the action or function was triggered on.

    The following example shows how all of this plays together to implement an event handler for an action:

    CDS Model:

    service CatalogService {
        entity Books {
            key ID: UUID;
            title: String;
        } actions {
          action review(stars: Integer) returns Reviews;
        };
    
        entity Reviews {
            book : Association to Books;
            stars: Integer;
        }
    }
    

    Event-specific Event Context, generated by the CAP Java SDK Maven Plugin:

    @EventName("review")
    public interface ReviewEventContext extends EventContext {
    
        // CqnSelect that points to the entity the action was called on
        CqnSelect getCqn();
        void setCqn(CqnSelect select);
    
        // The 'stars' input parameter
        Integer getStars();
        void setStars(Integer stars);
    
        // The return value
        void setResult(Reviews review);
        Reviews getResult();
    
    }
    

    Event Handler:

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

    The unused methods setCqn(CqnSelect), setStars(Integer), and getResult() are useful when triggering the event on the service.

    Result Builder

    The return value type of all CRUD events is the Result. When implementing custom ON handlers for CRUD events an Result object can be constructed with the ResultBuilder.

    Use the selectedRows or insertedRows method for query and insert results, with the data given as Map or list of maps:

    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 the inlineCount method:

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

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

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

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

    import static com.sap.cds.ResultBuilder.deletedRows;
    
    int deleteCount = 7;
    Result r = deletedRows(deleteCount).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 provides APIs 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 the Persistence 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 the Draft 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.

    Show/Hide Beta Features