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 layout to leverage built-in support and zero configuration:

Files/Folders Description
app/ all your UI apps go in here; one or more 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 reflect the preferences of most projects. You get this layout when jumpstarting projects with cds init.

You could argue that a domain model isn’t necessarily database-related, and if there’s a database involved, domain models can usually be deployed to it as is. Putting the domain models there help you to 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 a corresponding dependency to your package.json, for example:

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

These dependencies allow you to then use npm outdated, npm update, and npm install to get the 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 {}

In this way, 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 similar 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 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 there 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. We recommend that you 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. Follow the guidelines below to make sure that you consume the 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 the Latest Minor Releases → for Example, ^3.1.0

This applies to both, @sap packages as well as open source ones. It makes sure that your projects receive important fixes during development. It also makes sure that bundles have a minimal footprint by leveraging npm’s dedupe:target=”_blank”} 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.

We also recommend running npm update regularly and frequently during development to ensure that you receive the latest fixes.

Keep Open Ranges When Publishing for Reuse

Let’s explain this by looking at counter examples.

Let’s assume that you’ve developed a reuse package that others can use in their projects, and you also use a reuse package. 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 effect would be as follows:

  1. Consuming projects would get duplicate versions of each package that 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. It wouldn’t be possible to reuse CDS models from common reuse packages (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, …”

If both your package and a consuming package reuse the same CDS models, loading those models would fail because it’s impossible to automatically merge the two versions, nor is it possible to load two independent versions. The reason for this is that it’s reusing models that 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 that it works correctly as it did the last time you tested it and checked it for 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.

This ensures that the deployed tool/service/app doesn’t receive new vulnerabilities, for example, through updated open source packages, without you being able to apply the necessary tests as prescribed by our security standards.

Minimize Usage of Open Source Packages

This rule for keeping open ranges for dependencies during development, as well as when publishing for reuse, also applies for open source packages.

Because open source packages are less reliable with respect to vulnerability checks, this means 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 that you used directly or transitively.

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

Q: Why not freeze 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.

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

Upgrade to Latest Majors as Soon as Possible

As providers of evolving SDKs we provide major feature updates, enhancements, and improvements in 6-12 month 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-month grace period

To make sure that you receive ongoing fixes, make sure to also adopt the latest major releases in a timely fashion in your actively maintained projects, that is, following the 6-12 month cycle.

Additional Advice

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

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

Best Practices

A project can comprise data models as well as content for multiple databases, multiple services, and multiple UI apps. These may end up in 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 and 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 we recommend to 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, if you split into separate projects - worst case one per UI app and per each service - this would mean that each module would have an individual lifecycle and would have to be deployed separately. Suppose that you made changes to 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.