Search

Project Layouts, Reuse, and Dependencies

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 are 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, for example, listing dependencies

This layout and the folder names reflects a majority of projects. Even though one could argue for example, that a domain model is not neccessarily database-related, in case there is 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.

Sharing and 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 is 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 is recommended to get familiar with these concepts and make use of them.

Managing 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 is additionally recommended to run mpm 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 would not receive important fixes for the packages used in your implementations unless you also provide an update.
  3. Reusing CDS models from common reuse packages would not be possible (for example, would already fail for @sap/cds/common).

Therefore, the rules when publishing packages for reuse are:

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 set @sap:registry=https://npm.sap.com
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 is 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:

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 are 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 cannot maintain and support unlimited numbers of branches with fixes. The following rules apply:

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.

Cut Your Projects Reasonably

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:

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:

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.