Search

    Consuming Services

    Learn how to consume local or remote services using uniform APIs.

    Content

    Overview

    Each service has an associated data model or service definition as shown in the following figure.

    Importing Service Definitions

    Consuming other Services

    Frequently, event handler implementations send requests to other services — mostly in the form of queries constructed using the respective incarnations of cds.ql libraries. Note that from CAP’s perspective, databases are also just services consumed in your project, as shown in these examples:

    const db = await cds.connect.to('db'), {Orders} = db.entities
    this.on ('READ',`MyOrders`, async (req)=>{
      const tx = db.tx(req) //> ensure tenant isolation & transaction management
      return tx.read (Orders) .where ({ user: req.user.id })
    })
    

    Learn more about consuming services in Node.js.

    import static bookshop.Bookshop_.MY_ORDERS;
    
    @Autowired
    PersistenceService db; // get access to the service
    
    @On(event="READ", entity="MyOrders")
    public Result onRead(EventContext req) {
      CqnSelect query = Select.from(MY_ORDERS).where(o -> o.user().eq(req.getUserInfo().getName()));
      return db.run(query);
    }
    

    Learn more about consuming services in Java.

    REST and OData

    External services can be consumed using the Cloud SDK in combination with the destination service. To configure the destination service properly, see Consuming the Destination Service.

    🚫 Warning
    Make sure that destination configurations for deployment aren’t shared on any file shares (GitHub, etc.).

    The consumed service needs to be configured in the package.json or in the .cdsrc.json.

    • destination: Name of the destination (optional).
    • path: Relative path that will be appended to the resolved destination (optional).
    • requestTimeout: Number of milliseconds until a request times out. Default is 60,000 ms (optional).
    • model: The CSN model of the external service.
    • pool: Connections to the external service are pooled to prevent DoS attacks. See the .pool section of the cds.connect documentation (optional).
    {
      "cds": {
        "requires": {
          "externalService": {
            "kind": "rest/odata",
            "model": "path/to/model",
            "credentials": {
              "destination": "destinationName",
              "path": "/relativePath",
              "requestTimeout": 30000
            },
            "pool": {
              "min": 1,
              "max": 10
            }
          }
        }
      }
    }
    

    If the destination is omitted, the runtime looks for a destination with the name of the configured data source. In the example, it looks for externalService.

    For local usage, the runtime auto-wires the services, if the consumed service is started first. In the example, the externalService needs to be started before the service requiring it can be started.

    If the external service isn’t served locally, the credentials to connect to the external service could be provided as part of the credentials section. Example:

    {
      "destination": "destinationName",
      "url": "...",
      "username": "...",
      "password": "..",
      "requestTimeout": 30000
    }
    

    Sending Requests

    There are a number of convenient methods for sending requests. For examples, have a look at the API documentation of tx.run.

    ❗ Warning
    Currently, not all methods of the fluent query API can be translated to a URL of the configured service kind. So far, it’s possible to send create, update, and delete requests. For read, it’s only supported to read an entire collection or a single entity (query parameters, for example, filtering or ordering the result set isn’t supported).

    Simple examples for reading data (works also for create, update, and delete):

    // connect to external server
    const srv = cds.connect.to('my.external.service')
    const { Authors } = srv.entities
    
    // share request context with the external service
    // inside a custom handler
    const tx = srv.transaction(req)
    
    // URL string input
    const response = await tx.get('/Authors?$select=name')
    
    // CSN entity input and fluent query API
    const response = await tx.read(Authors).where({ID: 1})
    
    // CQN input from fluent query API
    const cqn = SELECT.from(Authors)
    const response = await tx.run(cqn)
    

    Uniform Consumption

    With these fundamental concepts in place, we can implement additional features as derived concepts, offering the same uniform APIs to consumers of services. For example, consuming databases essentially looks the same as consuming local services served from the same server process, or remote services through OData or REST. Here’s a snippet in Node.js demonstrating this:

    const srv = await cds.connect.to('some-service')
    // could be any of...
    // - local service
    // - remote service client
    // - database client
    const { Books } = srv.entities
    let query = SELECT.from(Books).where({ID:111})
    let books = await srv.run (query)
    
    Databases as Services

    From a CAP perspective, databases are special services, and integrating a specific database technology is as easy as providing a service with a set of generic event handlers to translate inbound queries to native ones in calls to the underlying database technology (could be SQL as well as NoSQL).

    Late-cut Micro Services

    The uniform and transparent consumption APIs allow projects to flexibly change topologies - for example, starting with locally embedded services, which later get externalized into separate micro services - without changing all consumers.

    Show/Hide Beta Features