Search

Consuming Services

(This guide is currently specific to the node.js runtime.)

This guide is about consuming services in general, including Local Services, External Services and Databases. Each service has an associated data model or service definition as depicted in the figure below.

Content

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

From 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 on get externalized into separate micro services - without changing all consumers.

Importing Service Definitions

Configuring Required Services

For Event-Based Consumption

Per default, services of the same node process asynchronously interact through the standard Node.js event emitter. However, external services have to be configured through the package.json or cdsrc.json file and can be enabled for messaging by setting the kind property to one of the following values:

Example

{
  "cds": {
    "requires": {
      "my.external.Service": {
        "kind": "enterprise-messaging",
        "credentials": {
          "namespace": "my/custom/namespace"
        }
      },
      "my.testing.Service": {
        "kind": "file-based-messaging",
        "credentials": {
          "file": "myMessageBox"
        }
      }
    }
  }
}

If you want to enable messaging for all internal and external (odata/rest) services, you can add a messaging section.

Example

{
  "cds": {
    "requires": {
      "my.external.Service": {
        "kind": "odata"
      },
      "messaging": {
        "kind": "enterprise-messaging",
        "credentials": {
          "namespace": "my/custom/namespace"
        }
      }
    }
  }
}

For HTTP-based consumption

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

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

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

{
  "cds": {
    "requires": {
      "externalService": {
        "kind": "rest/odata",
        "model": "path/to/model",
        "credentials": {
          "destination": "destinationName",
          "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 above example, it will look for externalService.

For local usage, the runtime autowires the services, if the consumed service is started first. In above example the externalService should 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
}

Providing Service Bindings

Providing Services

If you want to declare your own service to be an event emitter, you need to specify the kind of your service in the cds.provides section in your package.json or cdsrc.json file.

Example

{
  "cds": {
    "provides": {
      "my.own.Service": {
        "kind": "enterprise-messaging"
      }
    }
  }
}

Alternatively you can add a messaging section, which affects all provided and external (odata/rest) services.

Example

{
  "cds": {
    "requires": {
      "messaging": {
        "kind": "enterprise-messaging"
      }
    }
  }
}

In case of conflict cds.provides supersedes cds.requires.messaging.

Connecting to Required Services

To connect to an external service, cds.connect.to should be used. Examples can be found in the linked API description.

Sending Requests

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

Caution: Please note, that not all methods of the fluent query API can be translated to an url of the configured service kind yet. So far, it’s possible to send create, update, and delete requests. In case of 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)

Adding Event Handlers

You can register event handlers to consume events from services as described in srv.on, however there are minor differences:

There are two possibilities to register on handlers:

Example:

const srv = cds.connect.to('my.external.service')
srv.on('myEvent', msg => console.log(msg.data))
srv.on(['myEvent', 'myOtherEvent'], msg => console.log(msg.data))
srv.on('myBoundEvent', 'myEntity', msg => {
    if (msg.data.age < 18) {
      throw new Error('Cannot handle this event.')
    }
})

srv.on.error(err => {
 // handle error
})

Caution: If you use the Enterprise Messaging service on SAP Cloud Platform, a queue is automatically created for each consuming service once you register an .on handler. There’s no mechanism in place to remove unused queues, this has to be done manually. The queue name is generated if not explicitly set by the queue property of your external service. The generated queue name starts with the namespace followed by the application name, then by a slash and the first four characters of the application ID, then by a slash and the to be consumed service name (without the namespace) and then by a slash and the first four characters of the MD5 checksum of the service namespace, for example,

my/custom/namespace/bookstore/25f0/ReviewsService/3de6

Make sure that your namespace, application name, and service name doesn’t create a queue name greater than 100 characters.

To remove all queues programmatically, you can use the following function.

srv.removeAllListeners()

It is also possible to create queues listening to custom topics.

Example

srv.on('my/custom/namespace/my/custom/topic', msg => {
  console.log(msg.data)
})

Note: The namespace has to be prepended since it may be possible, depending on the rules of the service descriptor of your messaging client, to listen to topics beginning with a different namespace. Strings with slashes / are automatically interpreted as topics. In case you have a one-segment topic you must write the following:

srv.on('topic:myTopic', msg => {
  console.log(msg.data)
}

Emitting Events

To emit events you can use the .emit method described in srv.emit.

There are two possibilities to construct the topic of the message:

The next parameters are the payload of the message and custom headers (optional).

Example:

const srv = cds.connect.to('my.external.service')

srv.emit('myEvent', {some: 'payload'})
srv.emit('myBoundEvent', 'myEntity', {some: 'payload'})
srv.emit('my/custom/namespace/my/custom/topic', {some: 'payload'})
srv.emit('myEvent', {some: 'payload'}, {some: 'headers'})

Adding Request Handlers

Service-level Mashups

Data-level Mashups

Consuming Events

etc…