Search

Project Setup & Reuse

Learn more details about recommended project layouts, how to reuse content from other projects/packages, as well as about managing dependencies.

Content

Default Project Layouts

While you’re free to choose your own project layout, we recommend adopting the following default one to leverage built-in support and zero configuration:

Files/Folders Description
app/ all your UI apps go in here; one or more of such in subfolders
srv/ all your service definitions and implementations go in here
db/ all domain models, and database-related content go in here
package.json your project descriptor

This layout and the folder names reflects preferences of a majority of projects. You get it when jumpstarting projects with cds init.

Even though one could argue, for example, that a domain model isn’t necessarily database-related, in case there’s a database involved, domain models can usually be deployed to it as is. Hence, in case of question just put it there and thus benefit from CAP’s convention over configuration default, to deploy all content from ./db to a database.

Customizing Layouts

Many cds commands work with the above project layout by default. For example, when you run cds watch, CDS models will be automatically fetched and loaded from these locations:

  • db/index.cds or db/*.cds
  • srv/index.cds or srv/*.cds
  • app/index.cds or app/*.cds
  • schema.cds
  • services.cds

These locations are taken from a configuration option cds.roots, which in turn you can inspect using the cds env command:

cds env get roots
#> [ 'db/', 'srv/', 'app/', 'schema', 'services' ]
cds env get folders
#> { db:'db/', srv:'srv/', app:'app/' }

You can change these settings to adjust your project layout, for example, in your package.json file as documented in the Node.js Configuration guide:

{
  "cds": {
    "folders": { "db":"db/", "srv":"srv/", "app":"app/" },
    "roots": [ <...cds.folders>, "schema", "services" ],
    ...
  }
}

The values shown above reflect the defaults.

Reusing Content

CDS uses npm as a package manager to share and install packages.

Import Other Packages

Use npm install to reuse models from other packages in your project, for example following these steps:

npm install some-other-package

This downloads some-other-package’s exported models and stores them to a local node_modules folder and adds corresponding dependency to your package.json, for example, as in:

{
  "name": "test", "version": "1.1.0",
  "dependencies": {
    "some-other-package": "^1.0.4",
    "yet-another-import": "^2.0.1"
  }
}

These dependencies allow to subsequently use npm outdated, npm update, and npm install to get latest versions of all imported models.

Use the Imports in Your Models

Add using directives to your models to refer to imported definitions.

using { foo.bar.Foo } from 'some-imported-package/foo-bar';
entity Bar : Foo {}

With that cds finds the imported content in node_modules when processing imports with absolute target references in your model.

Use index.cds Files to Share Your Models

Following the Node.js and ECMAScript approach, there’s no public/private mechanism in cds. Instead, it’s good and proven practice to add an index.cds in the root folder of your package very much the same to the use of index.js files in Node. For example, as in the foundation samples:

namespace caps.foundation;
using from './codes';
using from './common';
using from './contacts';
using from './measures';

This allows your users to just refer to the package name containing such index.cds as in:

using { Country } from 'caps-foundation';

@sap/cds/common

Each installation of @sap/cds contains a reuse model that is available as @sap/cds/common. Reusing definitions from that works exactly in the same ways as described above.

For example, usage is as simple as this:

using { Country } from '@sap/cds/common';
entity Addresses {
  street  : String;
  town    : String;
  country : Country; //> using reuse type
}

Extend Imported Definitions

You can use CDS’ Aspects feature to extend imported definitions, to flexibly add elements, or to add or override annotations.

using { sap.common.Countries } from '@sap/cds/common';

extend Countries {
  numcode : Integer; //> ISO 3166-1 three-digit numeric codes
  alpha3 : String(3); //> ISO 3166-1 three-letter alpha codes
  alpha4 : String(4); //> ISO 3166-3 four-letter alpha codes
}

annotate Countries with @(
  title: 'Countries Code List'
){
  code @title:'ISO Country Code'
}

Aspects provide powerful mechanisms to adapt important definitions to the needs of consumers. It’s recommended to get familiar with these concepts and make use of them.

Dependencies

Projects using CAP need to manage dependencies to the respective tools and libraries in their package.json and/or pom.xml respectively. Please follow the guidelines below to ensure that you consume latest fixes and avoid vulnerabilities and version incompatibilities. These guidelines apply to you as a consumer of reuse packages as well as a provider of such reuse packages.

Always Use Latest Minor Releases → for Example, ^3.1.0

This applies to both, @sap packages as well as open source ones. It ensures that projects receive important fixes during development. It also ensures bundles with minimal footprint by leveraging npm’s dedupe feature.

Example:

"dependencies": {
  "@sap/cds": "^3.1.0",
  "@sap/some-reuse-package": "^1.1.2",
  "express": "^4.16.4"
}

We recommend using the caret form, that is, ^1.0.2 to add your dependencies, which are also the default for npm install, as that clearly captures the minimum patch version, that you depend on.

With that given, it’s additionally recommended to run npm update regularly and frequently during development to ensure that you receive latest fixes.

Keep Open Ranges When Publishing for Reuse

Let’s explain this by looking at counter examples …

Assumed that you developed a reuse package which others may use in their projects, and you in turn do use a reuse package. And assumed, for whatever reason, you decided to violate the above rules and use exact dependencies in your package.json as follows:

"name": "@sap/your-reuse-package",
"version": "1.1.2",
"dependencies": {
  "@sap/cds": "3.0.3",
  "@sap/foundation": "2.0.1",
  "express": "4.16.3"
}

The effects would be as follows:

  1. Consuming projects would get duplicate versions of each package they also use directly, for example, @sap/cds, @sap/foundation, and express.
  2. Consuming projects wouldn’t receive important fixes for the packages used in your implementations unless you also provide an update.
  3. Reusing CDS models from common reuse packages wouldn’t be possible (for example, would already fail for @sap/cds/common).

Therefore, the rules when publishing packages for reuse are:

  • Keep the open ranges in your package.json (just don’t touch them).
  • Do an npm update before publishing and test thoroughly (→ ideally automated in your CI/CD pipeline).
  • Do the vulnerability checks for your software and all open-source software used by you or by packages you used (→ see below).
  • Don’t do npm shrinkwrap → see also npm’s docs: “It’s discouraged for library authors to publish this file, …”

Add 3: If both, your package as well as a consuming one would reuse the same CDS models, loading those models would fail with as it’s impossible to automatically merge the two versions, nor is it possible to load two independent versions. Reason is that it’s actually the idea of reusing models that one share the same single definitions.

Shrinkwrap When Releasing to End Consumers

When releasing a service or an application to end consumers, use npm shrinkwrap to ensure frozen dependencies. This allows you to guarantee it works correctly as for the last time you tested it and checked it re vulnerabilities.

Overall, the process for your release should include these steps:

npm update      # ensure you got latest fixes
npm dedupe      # ensure all is in shape
npm shrinkwrap  # freeze dependencies
# conduct all test and vulnerability checks

The npm-shrinkwrap.json file in your project root freezes all dependencies and is shipped and deployed with your package. Subsequent npm installs, such as by users or by Cloud deployers or build packs always get the same versions, which you checked upon your release.

Thereby it’s ensured the deployed tool/service/app doesn’t receive new vulnerabilities, for example, through updated open source packages, without you being able to apply the requisite tests as prescribed by our security standards.

Minimize Usage of Open Source Packages

The above rule to keep open ranges for dependencies during development as well as when publishing for reuse applies also for open source packages.

Since open source packages have less reliability with respect to vulnerability checks, this effect in the situation that end-of-chain projects have to ensure respective checks for all the open source packages they use directly as well as those they ‘inherit’ transitively from reuse packages.

So, please always take into account these rules:

  • When releasing to end consumers, you always have to conduct vulnerability checks for all open source packages you used directly or transitively.

  • As provider of reuse packages you should minimize usage of open source packages to a reasonable minimum.

Q: Why not freezing open source dependencies when releasing for reuse?

A: Because that would only affect directly consumed packages, while packages from transitive dependencies would still reach your consumers.

Good approach is also to provide certain features in combination with third-party packages but keep them and hence the dependencies optional; for example, express.js does so.

Upgrade to Latest Majors as Soon as Possible

As providers of evolving SDKs we’re asked to and do want to provide major feature updates, enhancements and improvements in 6-12 months release cycles. These updates come with an increment of major release numbers.

At the same time, we can’t maintain and support unlimited numbers of branches with fixes. The following rules apply:

  • Fixes and nonbreaking enhancements are made available frequently in upstream release branches (current major)
  • Critical fixes also reach recent majors in a 2-months grace period

Hence, to ensure that you do receive fixes ongoingly, you should ensure to also adopt latest major releases in a timely fashion in your actively maintained projects, that is, following the 6-12 months cycle.

Additional Advice

Using package-lock.json — To ensure reproducible setups with your teammates in a project you might consider using package-lock.json as recommended by npm.

Note that this is in line with the guidelines and recommendations above. Also then, please npm update frequently during development and check in the updated package-lock.json file subsequently so to share the updated setup again in your project.

Best Practices

A project may comprise data models plus content for multiple databases, multiple services as well as multiple UI apps. These may end up into several deployables that go to different runtime containers. For example:

  • all UI apps are deployed as static web content to frontend servers
  • all services including implementation go to single Node.js servers
  • the data models plus content go to different database servers

Distributed deployment is common and by no means a reason to split everything into separate projects as you certainly want all pieces to be deployed in concert in an all-or-nothing fashion, ensured by the multitarget deployment architecture (MTA). So, always put related modules together in one project.

Rules of Thumb:

  • Go for separate projects if the result has an independent lifecycle
  • Otherwise, put all related modules in one project
  • In particular never split along private interfaces

Bad: Splitting into Projects Per Deployable

In contrast to that, if you’d split into separate projects - worst case one per UI app and per each service - would mean that each module would have an individual lifecycle and to be deployed separately. Suppose that you made changes in your service and corresponding ones to the UI app that uses it. You’d have to synchronize the deployments and if one fails the other one might be unusable.