Search

Grow As You Go…

Find here best practices how to speed up development by jump-starting projects with zero setup, eliminating boilerplate and parallelizing work.

Jump-Starting Projects

Assumed that you’ve installed @sap/cds-dk, jump-starting a project in CAP is as simple as that:

mkdir demo && cd demo  # create a new project folder
cds watch              # ask cds to watch for things to come
Convention over Configuration

Following the principles of Convention over Configuration, CAP provides defaults for many things, you’d have to configure in other frameworks. Stay within the confines of these defaults to benefit from things just working automatically, or override them by adding your configuration if you feel uncomfortable with our defaults. And: there always is a configuration option to override our defaults.

Zero Setup

The project folder is empty at the beginning, so there’s indeed zero setup required. No config files, no manifests whatsoever. The simple reason is, that we don’t need them in these early phases of a project. We may need some later on, for example, when we reach the step about Deploying to the Cloud, but there’s no need to bother about this now.

Contracts First

Most frequently, CAP projects are about creating services. Service definitions describe the API contract between a service provider and its consumers. To speed up things, you can quickly create an all-in-one service as follows:

copy and paste this into a file srv/cat-service.cds:

@path:'/browse'
service CatalogService {

  entity Books {
    key ID : UUID;
    title  : String;
    descr  : String;
    author : Association to Authors;
  }

  entity Authors {
    key ID : UUID;
    name   : String;
    books  : Association to many Books on books.author=$self;
    birth  : Date;
    death  : Date;
  }

}

Prefer Top-Down Approaches

Instead of following a bottom-up approach, starting from the data model then putting services on top, then adding UIs, and so on, it’s much better to apply a top-down approach as follows:

  1. Roughly sketch your application’s usages and use-cases, for example, your UIs
  2. Capture required interfaces in use case-oriented service definitions
  3. Start building the actual UIs on top, and in parallel..…
  4. Have other teams working on the data models below

This (a) allows you to separate and parallelize work loads and (b) results in much better service interfaces than the one you’d get by the bottom-up approach.

Running Out-of-the-Box

When we save the file we created in the former step, the cds watcher in the terminal immediately reacts, showing this output:

[cds] - connect to datasource - sqlite::memory:
[cds] - serving CatalogService at /browse
[cds] - service definitions loaded from:

  srv/cat-service.cds
  node_modules/@sap/cds/common.cds

[cds] - server listening on http://localhost:4004 ... (terminate with ^C)
[cds] - launched in: 355.732ms
Full-fledged OData Services

Choose the displayed link http://localhost:4004, … et voila, we are in contact with a full-fledged OData service (for example, see $metadata​) in which we can even start Fiori previews to get idea of what a UI might look like.

Let’s do some ad hoc tests:

Served by Generic Providers

What we see here are the effects of Generic Providers, which handle many things out of the box, such as compiling the CDS models into OData $metadata documents on the fly, as well automatically serving all CRUD requests, thereby handling all the OData protocol features such as $batch etc., up to complex choreographies like Fiori Draft. This saves us numerous work at this point and hence allow us to immediately go on in our walkthrough with the next steps instead implementing all this imperatively in boilerplate code.

Learn more about generic providers…

Mocking App Services

Use cds run --in-memory to quickly start a lightweight Node.js server with sqlite’s transient in-memory database instead of always deploying to and connecting to your target database. Do that not only in Node.js projects but also as mock server in Java projects, for example, for frontend-related tasks, or as mock for remote services to integrate with.

Prerequisites

Note: The sample mock server created in the following is based on the mock server developed in the TechEd 2019 tutorial Creating an SAP S/4HANA Extension with SAP Cloud Application Programming Model and SAP Cloud SDK.

Create a Project for the Mock Server
  1. Create an empty project for the mock server by executing cds init mockserver in the terminal.
  2. Execute code mockserver to open the newly created project in VSCode.
  3. Open the package.json file and add "@sap/cds-dk": "^1.0.0" as dependency. Execute npm i to install all dependencies.
Add Service API Definition
  1. Download the service API definition from the SAP API Business Hub in EDMX format.
  2. Import the downloaded API definition by running cds import ~/Downloads/API_BUSINESS_PARTNER.edmx. This converts the EDMX service API definition to a Core Schema Notation (CSN) definition and places it into local subfolder srv/external.
Add a Dummy services.cds File
  1. In the srv folder, create a file named services.cds.
  2. Add this line into it:
using { API_BUSINESS_PARTNER } from './external/API_BUSINESS_PARTNER.csn';

Just keep this file empty for serving imported APIs the default way. It can be used to tailor the mock server to your specific needs, for example, by adding or overriding certain definitions.

Run the Mock Server
  1. Execute cds run --with-mocks --in-memory? to start the mock server with in-memory database.

Alternatively you can execute cds watch, which essentially is a shortcut to the same cds run command plus starting a monitor to restart the server automatically if sources are changed.

Optionally Add Sample Data
  1. Create a new file init.js in the srv folder.
  2. Paste the following code:
module.exports = db => {
  const { A_BusinessPartnerAddress: Addresses } = db.entities(
    'API_BUSINESS_PARTNER'
  )

  INSERT.into(Addresses).entries(
    {
      BusinessPartner: '1003764',
      AddressID: '28238',
      CityName: 'Walldorf',
      StreetName: 'Dietmar-Hopp-Allee',
    },
    {
      BusinessPartner: '1003765',
      AddressID: '28241',
      CityName: 'Palo Alto',
      StreetName: 'Hillview Avenue',
    },
    {
      BusinessPartner: '1003766',
      AddressID: '28244',
      CityName: 'Hallbergmoos',
      StreetName: 'Zeppelinstraße',
    },
    {
      BusinessPartner: '1003767',
      AddressID: '28247',
      CityName: 'Potsdam',
      StreetName: 'Konrad-Zuse-Ring',
    }
  )
}
Mock Custom Responses

To extend the mock server with custom logic, you can create a custom handler. To do so, create an equally named .js file next to the imported service definition file, that is, srv/external/API_BUSINESS_PARTNER.js in our case. Add the custom logic there:

module.exports = cds.service.impl (srv => {
  // add your custom handlers here...
})
Mock Error Cases

To create error cases, explicitly return errors in a custom handler by using the req.error or req.reject functions. For example, add the following code in the API_BUSINESS_PARTNER.js file:

module.exports = cds.service.impl(srv => {
  srv.before('READ', 'A_BusinessPartnerAddress', req => {
    const { BusinessPartner, AddressID:ID } = req.data
    if (BusinessPartner === '1003764' && ID === '28238')
      req.reject (500, 'Your error message.')
  })
})

To trigger this error, use the following request:

http://localhost:4004/api-business-partner/A_BusinessPartnerAddress(BusinessPartner='1003764',AddressID='28238')
Reset Mock Data at Runtime

To reset the mock data at runtime without restarting the mock server, define an unbound action.

When using cds watch executing rs in the terminal with the running watch command, will restart the mock server and reset the mock data without the need of an unbound action.

Declare the action in the mock service definition. In srv/services.cds add the following code:

extend service API_BUSINESS_PARTNER with {
  action reset();
}

In srv/external/API_BUSINESS_PARTNER.js add the implementation of the action:

    srv.on('reset',async () => {
        const db = cds.connect.to('db')
        await db.run(()=> require('../init')(db))
    })

This will delete the data from the database and fill it with the initial data.

Trigger the reset action with the following POST request:

http://localhost:4004/api-business-partner/reset

… for External Services

Growing On…

  • Integrating foreign Services
  • Serving UIs
  • Adding i18n
  • Adding Persistent Data
  • Adding Localized Data
  • Adding Temporal Data
  • Adding Authorization
  • Adding Audit Logging
  • Adding Audit Logging
  • Adding Data Privacy
  • Connecting to the Cloud
  • Deploying to the Cloud
  • Adding Tests
  • Adding CI/CD

Deploying to the Cloud

CAP applications can be deployed into the Cloud Foundry Environment of SAP Cloud Platform. At the end, it’s about deploying regular Node.js and/or Java applications, and about creating and binding appropriate service instances (see the CF Developer Guide). For more details, see Deploying to the Cloud.