# Getting Started > Source: /docs/get-started/ Jumpstart & Grow as You Go... {.subtitle} ## Initial Setup > Source: /docs/get-started/#initial-setup A most minimalistic setup needs [CAP's _cds-dk_](https://www.npmjs.com/package/@sap/cds-dk) installed, which in turn requires [Node.js](https://nodejs.org). Add optional setups for [Java](https://sapmachine.io), [GitHub](https://github.com), and [Visual Studio Code](https://code.visualstudio.com), as appropriate, and as outlined below. On macOS, Linux and WSL (Windows Subsystem for Linux), we recommend using [Homebrew](https://brew.sh), and run the commands in the subsequent sections in your terminal to get everything set up. ::: code-group ```shell [macOS] bash -c "$( curl https://raw.githubusercontent.com/homebrew/install/HEAD/install.sh )" ``` ```shell [Linux] # install curl (required to get Homebrew) and git (required to run Homebrew) > Source: /docs/get-started/#install-curl-required-to-get-homebrew-and-git-required-to-run-homebrew sudo apt install curl git -y bash -c "$( curl https://raw.githubusercontent.com/homebrew/install/HEAD/install.sh )" ``` ::: ### Node.js and _cds-dk_ > Source: /docs/get-started/#nodejs-and-cds-dk ::: code-group ```shell [macOS/Linux] brew install node # Node.js LTS npm i -g @sap/cds-dk # install CAP's cds-dk globally ``` ```PowerShell [Windows] # We use multiline console commands to improve usability in Windows PowerShell. > Source: /docs/get-started/#we-use-multiline-console-commands-to-improve-usability-in-windows-powershell # PowerShell will ask for confirmation when you paste these commands, adding an extra safety step. > Source: /docs/get-started/#powershell-will-ask-for-confirmation-when-you-paste-these-commands-adding-an-extra-safety-step winget install --silent OpenJS.NodeJS.LTS winget install --silent SQLite.SQLite # Reload PATH from registry to access newly installed tools > Source: /docs/get-started/#reload-path-from-registry-to-access-newly-installed-tools $env:PATH = [Environment]::GetEnvironmentVariable("PATH","Machine") ` + ";" + [Environment]::GetEnvironmentVariable("PATH","User") npm i -g @sap/cds-dk # install CAP's cds-dk globally cds -v # check cds version sqlite3 -version # done > Source: /docs/get-started/#done ``` ::: ### Java and Maven > Source: /docs/get-started/#java-and-maven ::: code-group ```shell [macOS/Linux] brew tap sap/sapmachine brew install sapmachine25-jdk brew install maven ``` ```PowerShell [Windows] winget install --silent SAP.SapMachine.25.JDK # Apache Maven is not available using winget so download it directly > Source: /docs/get-started/#apache-maven-is-not-available-using-winget-so-download-it-directly $v="3.9.16"; ` $url="https://dlcdn.apache.org/maven/maven-3/$v/binaries/apache-maven-$v-bin.zip"; ` $mvnzip="$env:LOCALAPPDATA\maven.zip"; ` curl $url -o $mvnzip; ` tar -xf $mvnzip -C "$env:LOCALAPPDATA"; ` setx PATH "$env:PATH;$env:LOCALAPPDATA\apache-maven-$v\bin"; ` rm $mvnzip # Reload PATH from registry to access newly installed tools > Source: /docs/get-started/#reload-path-from-registry-to-access-newly-installed-tools-1 $env:PATH = [Environment]::GetEnvironmentVariable("PATH","Machine") ` + ";" + [Environment]::GetEnvironmentVariable("PATH","User") mvn -version # display Maven and Java versions # done > Source: /docs/get-started/#done-1 ``` ::: ### Git and GitHub > Source: /docs/get-started/#git-and-github ::: code-group ```shell [macOS] brew install git # Git CLI brew install gh # GitHub CLI brew install github # GitHub Desktop App ``` ```shell [Linux] brew install git # Git CLI brew install gh # GitHub CLI # Github-Desktop on Homebrew is only supported for macOS > Source: /docs/get-started/#github-desktop-on-homebrew-is-only-supported-for-macos GHD_VERSION="3.3.12" GHD_HOST="https://github.com/shiftkey/desktop/releases/download" curl -L ${GHD_HOST}/release-${GHD_VERSION}-linux1/ GitHubDesktop-linux-amd64-${GHD_VERSION}-linux1.deb -o github-desktop.deb sudo apt install ./github-desktop.deb rm ./github-desktop.deb ``` ```PowerShell [Windows] winget install --silent Git.Git winget install --silent GitHub.cli winget install --silent GitHub.GitHubDesktop # Reload PATH from registry to access newly installed tools > Source: /docs/get-started/#reload-path-from-registry-to-access-newly-installed-tools-2 $env:PATH = [Environment]::GetEnvironmentVariable("PATH","Machine") ` + ";" + [Environment]::GetEnvironmentVariable("PATH","User") git -v # display Git cli version # done > Source: /docs/get-started/#done-2 ``` ::: ### Visual Studio Code > Source: /docs/get-started/#visual-studio-code ::: code-group ```shell [macOS] brew install --cask visual-studio-code # VS Code itself ``` ```bash [Linux] # VS Code on Homebrew is only supported for macOS > Source: /docs/get-started/#vs-code-on-homebrew-is-only-supported-for-macos sudo snap install --classic code code -v # display VS Code's version ``` ```PowerShell [Windows] winget install --silent Microsoft.VisualStudioCode # Reload PATH from registry to access newly installed tools > Source: /docs/get-started/#reload-path-from-registry-to-access-newly-installed-tools-3 $env:PATH = [Environment]::GetEnvironmentVariable("PATH","Machine") ` + ";" + [Environment]::GetEnvironmentVariable("PATH","User") code -v # display VS Code's version # done > Source: /docs/get-started/#done-3 ``` ::: #### Visual Studio Code Proposed Extensions > Source: /docs/get-started/#visual-studio-code-proposed-extensions ```shell code --install-extension sapse.vscode-cds # for .cds models code --install-extension mechatroner.rainbow-csv # for .csv files code --install-extension qwtel.sqlite-viewer # for .sqlite files code --install-extension humao.rest-client # for REST requests code --install-extension dbaeumer.vscode-eslint # for linting ``` ```shell code --install-extension oracle.oracle-java # for Java code --install-extension vscjava.vscode-maven # for Maven ``` > You can of course also use other IDEs or editors of your choice, such as [IntelliJ IDEA](https://www.jetbrains.com/idea/), for which we also provide [support](../tools/cds-editors#intellij). Yet we strongly recommend Visual Studio Code for the best experience with CAP. ::: details Alternative manual setup ... You can also manually download and install the required packages from their respective websites: | Package | Install from | Remarks | |---------|----------------------------------|---------------------------------------------------------| | Node.js | https://nodejs.org | _required_ | | Java | https://sapmachine.io | _optional_ | | Git | https://git-scm.com | _optional_ | | VS Code | https://code.visualstudio.com | + [recommended extensions](../tools/cds-editors#vscode) | | SQLite | https://sqlite.org/download.html | _required_ on Windows | ::: ## Command Line Interface > Source: /docs/get-started/#command-line-interface ### The `cds` command > Source: /docs/get-started/#the-cds-command Run the `cds` command in your terminal to verify your installation and view an overview of available commands, as shown below: ```shell cds ``` ```zsh SYNOPSIS cds [ ] cds = cds compile cds = cds help COMMANDS i | init jumpstart cap projects a | add add facets to projects to grow as you go s | serve run your services in local server w | watch run with auto-restarts on changes | mock mock a single service r | repl read-eval-event loop e | env inspect effective configuration c | compile compile cds models to various outputs b | build prepare for deployment d | deploy deploy to databases or cloud | up one stop build and deploy to cloud v | version get detailed version information ? | help get detailed usage information Learn more about each command using: cds --help cds help ``` > Use `cds help` to get help on any command. ### `cds version` > Source: /docs/get-started/#cds-version Use `cds version` to check your installed versions of _cds-dk_ , as well as your project's local dependencies, with an output similar to this: ```shell cds version ``` ```zsh @sap/cds-dk 9.6.1 /opt/homebrew/lib/node_modules/@sap/cds/dk npm root -l ~/cap/bookshop/node_modules npm root -g /opt/homebrew/lib/node_modules Node.js 24.12.0 /opt/homebrew/bin/node ``` ## Jumpstart Projects > Source: /docs/get-started/#jumpstart-projects ### `cds init` > Source: /docs/get-started/#cds-init Use `cds init` to jumpstart CAP projects, which creates a project root folder with a default layout as shown below: ```shell cds init bookshop cd bookshop ``` ```zsh bookshop/ # the project's root folder ├─ app/ # UI-related content ├─ srv/ # Service-related content ├─ db/ # Domain models and database-related content └─ readme.md # Project readme file ``` > [!info] Convention over configuration > CAP uses defaults for many things you'd have to configure in other frameworks. The idea is that things just work out of the box, with zero configuration. While you _can_ override these defaults, of course, you _should not_ do so, but rather stick to the defaults, for the sake of simplicity. ### `cds watch` > Source: /docs/get-started/#cds-watch We can run `cds watch` to start a server, which would respond like this: ```shell cds watch ``` ```zsh No models found in db/,srv/,app/,app/*. Waiting for some to arrive... ``` Let's feed it with a simple service definition by running that in a _secondary terminal_, which adds a simple service definition as shown below: ```shell cds add tiny-sample ``` :::code-group ```cds [srv/cat-service.cds] service CatalogService { entity Books { key ID:Integer; title:String; author:String; } } ``` ::: `cds watch` would react automatically with some output containing this: ```shell [cds] - loaded model from 1 file(s): srv/cat-service.cds [cds] - connect to db > sqlite { url: ':memory:' } [cds] - serving CatalogService { at: ['/odata/v4/catalog'] } [cds] - server listening on { url: 'http://localhost:4004' } ``` > [!tip] Served out of the box > Et voilà! Your first CAP service is up and running, with automatically bootstrapped in-memory database, and a full-fledged OData service, generically serving requests like that: http://localhost:4004/odata/v4/catalog/Books ## Grow as You Go... > Source: /docs/get-started/#grow-as-you-go When your project evolves, you'd use `cds add` to add features and facets as needed, for example, to add initial data, Java-specific setups, or deployment options, as outlined below. And finally, use `cds up` to build and deploy your project in one go. ### `cds add` > Source: /docs/get-started/#cds-add Use `cds add` to grow your project as you go: ```shell cds add data cds add nodejs cds add java ``` Use `cds add` to add deployment options: ```shell cds add hana cds add xsuaa cds add ias cds add multitenancy cds add mta cds add kyma cds add github-actions ``` ### `cds up` > Source: /docs/get-started/#cds-up Use `cds up` to build and deploy your project in one go: ```shell cds up cds up --to cf cds up --to k8s ``` ## Stay up to Date! > Source: /docs/get-started/#stay-up-to-date > [!important] Staying up to date is crucial to receive important security fixes. > In order to benefit from the latest features and improvements, as well as receiving crucial security fixes, it's of utter importance to stay up to date with latest releases of CAP. Regularly run the following commands to do so. Keep your development environment up to date: ```shell brew upgrade npm upgrade --global ``` Keep your project dependencies up to date: ```shell # within your project folder > Source: /docs/get-started/#within-your-project-folder npm upgrade ``` > Use `npm outdated` to check which dependencies are outdated before upgrading. > [!warning] Do not use pinned versions > For such upgrades to work, always **use open version ranges** in your project dependencies – with a leading caret, as in `^9.7.0`, and as shown below –, combined with [`package-lock.json`](https://docs.npmjs.com/cli/configuring-npm/package-lock-json), and [`npm ci`](https://docs.npmjs.com/cli/commands/npm-ci) for repeatable builds and deployments. ::: code-group ```jsonc [package.json] "dependencies": { "@sap/cds": "9.7.0", // DON'T use pinned versions // [!code --] "@sap/cds": "^9.7.0", // DO allow new minor versions [!code ++] ... } ``` ::: > [!tip] Automate dependency updates > Consider using tools like [Dependabot](https://docs.github.com/en/code-security/getting-started/dependabot-quickstart-guide) or [Renovate](https://www.mend.io/renovate/) to automate dependency updates for you. These tools automatically open pull requests in your Git repositories whenever new versions of your dependencies are released. They are also highly recommended for managing Maven dependencies in CAP Java projects. ## Next: Bookshop > Source: /docs/get-started/#next-bookshop Continue with [_The Bookshop Sample_](./bookshop) for a step-by-step walkthrough of the most common development tasks in CAP projects. Then explore the [_Core Concepts_](./concepts) and [_Key Features_](./features) of CAP, before going on to the other [_Learning Sources_](./learn-more) within this documentation, or outside. # The Bookshop Sample > Source: /docs/get-started/bookshop A Step-by-Step Walkthrough {.subtitle} Follow along as we build a simple bookshop application step-by-step, to gain hands-on experience with the most common tasks as an application developer, core concepts and best practices of CAP. {.abstract} ## Jumpstarting Projects > Source: /docs/get-started/bookshop#jumpstarting-projects With the [initial setup](./index#initial-setup) for CAP in place, start a project using [`cds init`](./#cds-init), which creates a project folder with standard structure as shown below. ```shell cds init cap/bookshop ``` ```zsh cap/bookshop/ ├─ app/ ├─ srv/ ├─ db/ └─ readme.md ``` ::: details Optionally clone the ready-made sample ... ::: code-group ```sh [Node.js] git clone https://github.com/capire/bookshop npm install ``` ```sh [Java] git clone https://github.com/sap-samples/cloud-cap-samples-java bookshop mvn install ``` ::: Open the created project folder in [_Visual Studio Code_](https://code.visualstudio.com/): ```shell code cap/bookshop ``` Run [`cds watch`](./#cds-watch) in an [*integrated terminal*](https://code.visualstudio.com/docs/terminal/basics), to watch out for content to come: ```shell cds watch ``` ```zsh No models found in db/,srv/,app/,app/* Waiting for some to arrive.. ``` So, let's feed it with content in the sections below... ## Domain Models > Source: /docs/get-started/bookshop#domain-models We capture the core concepts of our domain in domain models, which are essentially entity-relationship models, focused on the relevant data entities of your domain. ### Entity-Relationship Models > Source: /docs/get-started/bookshop#entity-relationship-models For our _bookshop_ example, we'll define entities for _Books_, _Authors_, and _Genres_ as depicted in the following entity-relationship diagram: ![Entity-Relationship Diagram of Bookshop Domain Model with entities Books, Authors, and Genres and their associations.](assets/bookshop/domain-model.drawio.svg) {} We can capture that in a rudimentary way using CDS as follows (create a file named _schema.cds_ under folder _./db_ and add this content): ::: code-group ```cds [db/schema.cds] entity Authors { name : String; books : Association to many Books; } entity Books { title : String; author : Association to Authors; genre : Association to Genres; } entity Genres { name : String; parent : Association to Genres; } ``` ::: > [!tip] Domain models are essentially entity-relationship models > - [_Entities_](../cds/cdl#entities) represent the core concepts of your domain. > - [_Associations_](../cds/cdl#associations) express relationships between them. ### Complete Domain Model > Source: /docs/get-started/bookshop#complete-domain-model Let's enhance the rudimentary model above with some essentials, such as key elements, additional fields, and required on conditions for to-many associations. Here's the complete domain model for our bookshop application: ::: code-group ```cds [db/schema.cds] using { Currency, managed, sap } from '@sap/cds/common'; namespace sap.capire.bookshop; entity Books : managed { key ID : Integer; title : localized String; descr : localized String; author : Association to Authors; genre : Association to Genres; stock : Integer; price : Decimal; currency : Currency; } entity Authors : managed { key ID : Integer; name : String; books : Association to many Books on books.author = $self; } entity Genres : sap.common.CodeList { key ID : Integer; parent : Association to Genres; } ``` ::: ###### Focus on Domain > Source: /docs/get-started/bookshop#focus-on-domain > [!tip] Primary Focus on Domain > Strive to keep your domain models simple, concise and comprehensible, focused on the core concepts of your domain, that is, [_“Keep it simple, stupid!”_](https://en.wikipedia.org/wiki/kiss_principle). Factor out secondary concerns into separate sources, which _extend_ and _annotate_ the core models.\ > See also: [_Separation of Concerns_](#separation-of-concerns). [Learn more about _Domain Modeling_.](../guides/domain/index){ .learn-more} [Learn more about _CDS_.](../cds/){ .learn-more} ### Compile to CSN, ... > Source: /docs/get-started/bookshop#compile-to-csn- While not required, we can optionally test-compile models individually to check for validity and produce different outputs. For example, run this command in a terminal, which dumps the parsed CDS model as CSN object to stdout: ```shell cds compile db/schema.cds cds compile db/schema.cds --to json cds compile db/schema.cds --to yaml cds compile db/schema.cds --to sql ``` ![](assets/bookshop/cds-compile.drawio.svg) > [!tip] CDS models can be represented in different formats: > - [**CDL** (_Contextual Definition Language_)](../cds/cdl) is the human-friendly textual notation. > - [**CSN** (_Core Schema Notation_)](../cds/csn) is the machine-readable object notation, > - which can be serialized to JSON or YAML, > - or translated to other languages, such as SQL DDL or OData EDMX. ## Databases > Source: /docs/get-started/bookshop#databases As soon as we saved the domain model, `cds watch` reacted with additional output as shown below, which indicates that the model has been compiled, and an in-memory database has been deployed automatically: ```shell [cds] - connect to db > sqlite { url: ':memory:' } /> successfully deployed to in-memory database. ``` ![](assets/bookshop/databases.drawio.svg) ###### Inner Loop > Source: /docs/get-started/bookshop#inner-loop > [!tip] Inner-Loop Development > SQLite isn't meant for productive use, but rather for development only. > It drastically speeds up turn-around times in local inner-loop development. > Essentially it acts as a mock stand-in for the target databases we'll use in production, that is, SAP HANA. ### Compile to SQL > Source: /docs/get-started/bookshop#compile-to-sql To see what happens under the hood, we can optionally use `cds compile -2 sql` to test-compile our models to SQL, which would yield output as shown below: ```shell cds compile db/schema.cds --to sql ``` ```sql CREATE TABLE sap_capire_bookshop_Books ( ID INTEGER NOT NULL PRIMARY KEY, title NVARCHAR(255), descr NVARCHAR(2000), stock INTEGER, price DECIMAL(9, 2), author_ID INTEGER, genre_ID NVARCHAR(36), currency_code NVARCHAR(3), ... ); CREATE TABLE sap_capire_bookshop_Authors (...); CREATE TABLE sap_capire_bookshop_Genres (...); ``` ### Add Initial Data > Source: /docs/get-started/bookshop#add-initial-data With the database deployed automatically, we can now add some initial data to it. Do so by placing a few CSV files in _db/data_ like this: ```zsh db/data/ ├── sap.capire.bookshop-Authors.csv ├── sap.capire.bookshop-Books.csv └── sap.capire.bookshop-Genres.csv ``` ::: code-group ```csvc [ -Books.csv ] ID , title , author_ID , genre_ID , stock 201 , Wuthering Heights , 101 , 11 , 12 207 , Jane Eyre , 107 , 11 , 11 251 , The Raven , 150 , 16 , 333 252 , Eleonora , 150 , 15 , 555 271 , Catweazle , 170 , 13 , 22 ``` ```csvc [ -Authors.csv ] ID , name 101 , Emily Brontë 107 , Charlotte Brontë 150 , Edgar Allan Poe 170 , Richard Carpenter ``` ```csvc [ -Genres.csv ] ID , name 11 , Drama 13 , Fantasy 15 , Romance 16 , Mystery ``` ::: ::: details `cds add data` can help you with the file and record generation ```shell cds add data cds add data --records 10 ``` [Learn more in the _CLI reference_.](../tools/cds-cli#data){.learn-more} ::: ![](assets/bookshop/initial-data.drawio.svg) After you've added these files, `cds watch` restarts the server with new output, telling us that the .csv files have been detected, and filled into the database: ```log [cds] - connect to db > sqlite { url: ':memory:' } > init from db/data/sap.capire.bookshop-Authors.csv > init from db/data/sap.capire.bookshop-Books.csv > init from db/data/sap.capire.bookshop-Genres.csv /> successfully deployed to in-memory database. ``` ### Querying Data > Source: /docs/get-started/bookshop#querying-data ###### Using cds repl > Source: /docs/get-started/bookshop#using-cds-repl We can query the database using CDS Query Language (CQL), for example, in CAP's built-in REPL. Start it in a terminal as follows: ```shell cds repl ./ ``` ::: details About _cds repl_ ... The `cds repl` command boots up a minimal CAP environment in an interactive shell that allows us to enter and execute CAP JavaScript commands, with results printed to the console. It's a great way to explore and interact with our models, services, and data in an ad-hoc way. The acronym _REPL_ stands for [_Read-Eval-Print Loop_](https://en.wikipedia.org/wiki/Read–eval–print_loop), which was first coined by LISP in the late 1950s like that: `(loop (print (eval (read))))` ::: This bootstraps the CAP application within _cds repl_, and opens an interactive prompt, where we can enter and run CQL statements like this: ```js await SELECT `ID, title, genre.name as genre` .from `Books` ``` ... which would yield results like these: ```yaml [ { ID: 201, title: 'Wuthering Heights', genre: 'Drama' }, { ID: 207, title: 'Jane Eyre', genre: 'Drama' }, { ID: 251, title: 'The Raven', genre: 'Mystery' }, { ID: 252, title: 'Eleonora', genre: 'Romance' }, { ID: 271, title: 'Catweazle', genre: 'Fantasy' } ] ``` We'll see [more of querying](#querying) later on when we have services in place... ![](assets/bookshop/querying-data.drawio.svg) ## Services > Source: /docs/get-started/bookshop#services Note that `cds watch` is still waiting for more content, as indicated with this message: ```shell No service definitions found in loaded models. Waiting for some to arrive... ``` So, let's go on feeding it with service definitions ... ### Use Case-Specific Services > Source: /docs/get-started/bookshop#use-case-specific-services We add two files in folder _./srv_ with respective content as follows: ::: code-group ```cds [srv/admin-service.cds] using { sap.capire.bookshop as my } from '../db/schema'; service AdminService @(odata:'/admin') { entity Authors as projection on my.Authors; entity Books as projection on my.Books; entity Genres as projection on my.Genres; } ``` ::: ::: code-group ```cds [srv/cat-service.cds] using { sap.capire.bookshop as my } from '../db/schema'; service CatalogService @(odata:'/browse') { @readonly entity Books as projection on my.Books { *, // all fields with the following denormalizations: author.name as author, genre.name as genre, } excluding { createdBy, modifiedBy }; } ``` ::: The two services reflect different use cases, and corresponding user personas, as depicted in the illustration below. ![Two use-case-specific services: AdminService for administrators to maintain master data, and CatalogService for visitors to browse and order books.](assets/bookshop/services.drawio.svg) - **_AdminService_** is for *administrators to **maintain*** master data. It exposes all entities as-is from the domain model, allowing full CRUD access to all data. - **_CatalogService_** is for *visitors to **browse*** and order books. It serves denormalized read-only views on `Books`, with flattened fields for `author` and `genre`, to simplify browsing. Entities `Authors` and `Genres` are not exposed, nor internal admin details `createdBy` and `modifiedBy`. ###### Services as Interfaces > Source: /docs/get-started/bookshop#services-as-interfaces ###### Services as Facades > Source: /docs/get-started/bookshop#services-as-facades > [!tip] Services as Interfaces and Facades > Services constitute the **interfaces** of an application to consumers in the outside world, such as UIs or other services. They can be published as respective APIs. At the same time, they act as **facades** which handle all inbound requests, and restrict access to an application’s inner domain data. ###### Use Case-Oriented Services > Source: /docs/get-started/bookshop#use-case-oriented-services > [!tip] Use Case-Oriented Services > Always design services with respective consumers – and in case of UIs > respective end user personas – in mind. > Services can use **_denormalized views_** on underlying data, to expose only > subsets of information relevant to the respective use case. [Learn more about **Defining Services**.](../guides/services/providing-services){.learn-more} ### Served Out-of-the-Box > Source: /docs/get-started/bookshop#served-out-of-the-box This time `cds watch` reacted with additional output as shown below, which shows that the two service definitions have been compiled, and generic service providers got constructed and mounted to the listed HTTP endpoints. ```shell [cds] - serving AdminService { at: [ '/admin' ], decl: 'srv/admin-service.cds:3' } [cds] - serving CatalogService { at: [ '/browse' ], decl: 'srv/cat-service.cds:3' } [cds] - server listening on { url: 'http://localhost:4004' } [cds] - [ terminate with ^C ] ``` #### Send Requests from Browser > Source: /docs/get-started/bookshop#send-requests-from-browser We can access these endpoints through these OData URLs opened in a browser: - _[/browse/Books?$select=ID,title,genre](http://localhost:4004/browse/Books?$select=ID,title,genre)_ - _[/admin/Authors?$select=ID,name&$expand=books($select=ID,title)](http://localhost:4004/admin/Authors?$select=ID,name&$expand=books($select=ID,title))_ #### Send Requests from REST Client > Source: /docs/get-started/bookshop#send-requests-from-rest-client Alternatively, we can use the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client), which we [installed with VS Code](./index#visual-studio-code). For example, copy the following into a file _`test/requests.http`_ and send requests from there: ::: code-group ```http [test/requests.http] ### CatalogService.read Books > Source: /docs/get-started/bookshop#catalogserviceread-books GET http://localhost:4004/browse/Books? &$select=ID,title,author &$filter=contains(author,'Bro') ### AdminService.read Authors > Source: /docs/get-started/bookshop#adminserviceread-authors GET http://localhost:4004/admin/Authors? &$select=ID,name &$expand=books($select=ID,title) Authorization: Basic alice: ``` ::: ::: details `cds add http` can help you with the file creation: ```shell cds add http ``` [Learn more in the _CLI reference_.](../tools/cds-cli#http){.learn-more} ::: > [!tip] Served automatically by generic providers > Simple service definitions in CDS are all we need to serve full-fledged OData services. Behind the scenes, built-in generic providers handle all the heavy lifting for us, such as parsing OData requests, and translating them into appropriate SQL queries executed against a primary database, and returning results as OData responses. [Learn more about **Generic Providers**.](../guides/services/providing-services){.learn-more} ![](assets/bookshop/served-ootb.drawio.svg) ### Compile to EDMX > Source: /docs/get-started/bookshop#compile-to-edmx We can optionally also compile service definitions explicitly, for example to [OData EDMX metadata documents](https://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part3-csdl.html): ```shell cds compile srv/cat-service.cds --to edmx ``` Essentially, this invokes what happened automatically behind the scenes in the previous steps. While we don't really need such explicit compile steps, you can do this to test correctness on the model level, for example. ## Querying > Source: /docs/get-started/bookshop#querying Now that we have a deployed SQLite database, filled with some initial data, as well as services to serve requests from the outside, we can send queries to them, based on [CDS Query Language (CQL)](../cds/cql) and the OData protocol. ### Querying Primary Database > Source: /docs/get-started/bookshop#querying-primary-database In the _Databases_ section above, we already saw how to [run queries in `cds repl`](#querying-data), so let's revisit that here in more detail. Start `cds repl`, and bootstrap the app from the current project within: ```shell cds repl ./ ``` Run the following query via [`cds.ql`](../node.js/cds-ql), which would yield the results shown below: ```js await SELECT `ID, title, genre.name as genre` .from `Books` ``` ```yaml [ { ID: 201, title: 'Wuthering Heights', genre: 'Drama' }, { ID: 207, title: 'Jane Eyre', genre: 'Drama' }, { ID: 251, title: 'The Raven', genre: 'Mystery' }, { ID: 252, title: 'Eleonora', genre: 'Romance' }, { ID: 271, title: 'Catweazle', genre: 'Fantasy' } ] ``` We can also run deeply nested queries along to-many associations or compositions, for example, to get authors along with their written books like this: ```js await SELECT.from `Authors { ID, name, books { ID, title, genre.name as genre } }` ``` ```yaml [ { ID: 101, name: 'Emily Brontë', books: [ { ID: 201, title: 'Wuthering Heights', genre: 'Drama' } ]}, { ID: 107, name: 'Charlotte Brontë', books: [ { ID: 207, title: 'Jane Eyre', genre: 'Drama' } ]}, { ID: 150, name: 'Edgar Allan Poe', books: [ { ID: 251, title: 'The Raven', genre: 'Mystery' }, { ID: 252, title: 'Eleonora', genre: 'Romance' } ]}, { ID: 170, name: 'Richard Carpenter', books: [ { ID: 271, title: 'Catweazle', genre: 'Fantasy' } ]} ] ``` > [!tip] CDS Query Language (CQL) > [CQL](../cds/cql) is a high-level query language, similar to SQL, but adapted to CDS concepts, in particular associations, by [path expressions](../cds/cql#path-expressions) and [nested projections](../cds/cql#postfix-projections). It can be used in different CAP runtimes and services to capture and execute queries in a conceptual way, largely agnostic to the underlying database. ### Querying App Services > Source: /docs/get-started/bookshop#querying-app-services We can send CQL queries to CAP services in very similar ways as we did before against the database. Still in the REPL, we can access the defined services using `cds.connect`: ```js const CatalogService = await cds.connect.to ('CatalogService') const AdminService = await cds.connect.to ('AdminService') ``` And send queries to them like this (which would yield the same results as before): ```js await CatalogService .read `ID, title, genre` .from `Books` await AdminService .read `Authors { ID, name, books { ID, title, genre.name as genre } }` ``` ###### Pushed down to DB 1 > Source: /docs/get-started/bookshop#pushed-down-to-db-1 > [!tip] Pushed down to Database > CAP services push down queries to the primary database whenever possible, to leverage its full power and performance for data-intensive operations. They basically just delegate all queries to the database services as shown below.\ > See also: [_Pushed down to Database_](#pushed-down-to-db-2) in the _Constraints_ section. ![](assets/bookshop/pushdown.drawio.svg) ::: details Details: How queries are delegated to database ... Assumed we got an inbound query like that: ```js let query = SELECT `ID, title` .from `Books` ``` When we run it against a service like this: ```js const CatalogService = await cds.connect.to ('CatalogService') await CatalogService.run (query) ``` It is delegated to the primary database like so: ```js const db = await cds.connect.to ('db') // the primary database await db.run (query) ``` ::: > [!tip] CAP Services support Querying > In essence, all CAP services support querying out-of-the-box, by uniform APIs, centered on method `srv.run(query)`. Actually, as shown above, database clients in CAP are CAP services themselves, which can be consumed and queried in the very same way, using the same APIs. ### CAP-level Integration > Source: /docs/get-started/bookshop#cap-level-integration We can also send such OData requests programmatically, for example, doing the very same in `cds repl` [as before](#querying-app-services) for local services, just now with the CAP server and the REPL started _in two separate terminals_: 1. Start the bookshop server in terminal 1: ```shell cds watch ``` 2. Start the REPL in terminal 2: ```shell cds repl ``` 3. Within the REPL, run this to load remote service bindings: ```js [within cds repl] await cds.service.bindings ``` ::: details About _cds.service.bindings_ ... The `cds.service.bindings` command fetches the service bindings from a running CAP server instance in another process, and makes them available in the current process, for example, within `cds repl`. ```js [cds] - using bindings from: { registry: '~/.cds-services.json' } Bindings { provides: { AdminService: { endpoints: { odata: '/admin' }, server: 5472 }, CatalogService: { endpoints: { odata: '/browse' }, server: 5472 } }, servers: { '5472': { root: '~/cap/samples/bookshop', url: 'http://localhost:4004' } } } ``` ::: With that in place, we can connect to the remote services, and send queries to them [in the very same way as before](#querying-app-services), and _as if they were local_: ```js const CatalogService = await cds.connect.to ('CatalogService') const AdminService = await cds.connect.to ('AdminService') ``` ```js await CatalogService .read `ID, title, genre` .from `Books` await AdminService .read `Authors { ID, name, books { ID, title, genre.name as genre } }` ``` ![CAP-level service integration with two scenarios: Local services where Consumer connects to Service via CQL, and Remote services where Consumer connects to Proxy via CQL, Proxy connects to Protocol Adapter via OData, and Protocol Adapter connects to Service via CQL.](../guides/integration/assets/remoting.drawio.svg) ###### CAP-level Service Integration > Source: /docs/get-started/bookshop#cap-level-service-integration ###### Calesi > Source: /docs/get-started/bookshop#calesi > [!tip] CAP-level Service Integration ('Calesi') > CAP services can be consumed from other CAP applications, using the same uniform, and protocol-agnostic APIs as for local services – that is, **_as if they were local_**. This is accomplished by the service instances returned by `cds.connect` being remote proxies, which automatically translate all requests into protocol-specific ones, sent to remote services. Thereby taking care of all connectivity, remote communication, marshalling of data, as well as generic resilience. ## Serving UIs > Source: /docs/get-started/bookshop#serving-uis ### Generic *index.html* > Source: /docs/get-started/bookshop#generic-indexhtml Unless replaced by a custom `index.html` in the `app/` folder, CAP serves a generic welcome page at the root of the server. Open __ in your browser to view the generated _index.html_ page: ![Generic welcome page generated by CAP that list all endpoints. ](assets/bookshop/index-html.png){} Explore the listed endpoints and links to access the OData services in action. The _Fiori preview_ links work with generic Fiori backends, that give you a glimpse of Fiori UIs. ### SAP Fiori UIs > Source: /docs/get-started/bookshop#sap-fiori-uis CAP provides out-of-the-box support for SAP Fiori UIs, for example, with respect to SAP Fiori annotations and advanced features such as search, value helps and SAP Fiori Draft. ![The bookshop catalog service in an SAP Fiori UI.](assets/bookshop/fiori-app.png) [Learn more about **Serving Fiori UIs**.](../guides/uis/fiori){.learn-more} ### Vue.js UIs > Source: /docs/get-started/bookshop#vuejs-uis ###### Vuejs UIs > Source: /docs/get-started/bookshop#vuejs-uis-1 Besides Fiori UIs, CAP services can be consumed from any UI frontends using standard AJAX requests. For example, you can [find a simple Vue.js app in the GitHub repo](https://github.com/capire/bookshop/tree/main/app/vue), which demonstrates browsing and ordering books using OData requests to the `CatalogService` API we defined above. ![The bookshop catalog service in a simple Vue.js UI.](assets/bookshop/vue-app.png){} ## Custom Logic > Source: /docs/get-started/bookshop#custom-logic While the generic providers serve most CRUD requests out of the box, you can add content to deal with the specific domain logic of your application, which often involves input validation, as well as more complex business logic. - Use [declarative constraints](#declarative-constraints) for most input validation cases, which are enforced by generic runtimes automatically. - Add [custom event handlers in Node.js](#custom-handlers-in-nodejs) or [in Java](#custom-handlers-in-java) to CAP services for more complex programmatic logic, such as modifying response data, or handling [custom actions](#custom-actions). > [!note] Choosing between Node.js and Java > The latter is the first time in this guide where you need to choose between Node.js and Java as your CAP runtime. > You can pick either of them, depending on your team's skillset and other boundary conditions, and add respective configuration using `cds add nodejs` or `cds add java` explained below. ### Declarative Constraints > Source: /docs/get-started/bookshop#declarative-constraints Custom logic frequently deals with input validation. We can accomplish that by annotating entities that need input validation with [`@assert`](../guides/services/constraints) annotations, which get enforced by generic runtimes automatically. We do so in a separate `.cds` file next to the one containing the respective service definition: ::: code-group ```cds [srv/admin-constraints.cds] using { AdminService } from './admin-service.cds'; annotate AdminService.Books with { title @mandatory; author @assert: (case when not exists author then 'Specified Author does not exist' end); genre @mandatory @assert: (case when not exists genre then 'Specified Genre does not exist' end); price @assert.range: [1,111]; // 1 ... 111 inclusive stock @assert.range: [(0),_]; // positive numbers only } ``` ::: ###### Pushed down to DB 2 > Source: /docs/get-started/bookshop#pushed-down-to-db-2 > [!tip] Pushed down to Database > Instead of reading data from the database into the application for validation, the constraints are evaluated by single queries sent to the database. This way we reduce overhead, and leverage the full power and performance of the underlying database. ###### Separation of Concerns > Source: /docs/get-started/bookshop#separation-of-concerns > [!tip] Separation of Concerns > As constraints are not the core concepts themselves, but rather rules to govern their valid use, we chose to place them in a separate file, instead of polluting the service definition they apply to. This way we factor out secondary concerns to keep base definitions clean, concise and comprehensible. \ > See also: [_Focus on Domain_](#focus-on-domain). ### Custom Handlers in Node.js > Source: /docs/get-started/bookshop#custom-handlers-in-nodejs Prepare your project for custom coding in Node.js by adding the respective facet: ```shell cds add nodejs npm install ``` Put implementations for services into equally named _.js_ files placed next to a service definition's _.cds_ file, for example: ```zsh ├─ srv/ │ ├─ cat-service.cds # [!code focus] │ └─ cat-service.js # [!code focus] └─ ... ``` ::: code-group ```js [srv/cat-service.js] const cds = require('@sap/cds') class CatalogService extends cds.ApplicationService { init() { // After READ handler on Books to add discount info this.after ('READ', 'Books', results => results.forEach (book => { if (book.stock > 111) book.title += ` -- 11% discount!` })) return super.init() }} module.exports = { CatalogService } ``` ```js [srv/cat-service.mjs] import cds from '@sap/cds' export class CatalogService extends cds.ApplicationService { init() { // After READ handler on Books to add discount info this.after ('READ', 'Books', results => results.forEach (book => { if (book.stock > 111) book.title += ` -- 11% discount!` })) return super.init() }} ``` ::: [You can use `cds add handler` to assist you creating such custom code.](../tools/cds-cli#handler){.learn-more} [Learn more about service implementations in Node.js.](../node.js/core-services#implementing-services){.learn-more} ![](assets/bookshop/event-handlers.drawio.svg) > [!tip] On / Before / After Hooks > Event handlers can intercept any CRUD event, as well as custom Actions and Functions. They can be registered for different phases of request processing, such as: **_on_**, that is _instead of_ the default processing, **_before_** the default processing, or **_after_** it. ### Custom Handlers in Java > Source: /docs/get-started/bookshop#custom-handlers-in-java Prepare your project for custom coding in Java by adding the respective facet, and use the [CAP Java variant of `cds watch`](../java/developing-applications/running#cds-watch) to start the server going forward: ```shell cds add java mvn install ``` ```shell mvn cds:watch ``` In CAP Java, service implementations go into subclasses of `EventHandler`, annotated with `@Component` and `@ServiceName`, and the respective event handlers are methods within these classes annotated with `@On`, `@Before`, or `@After`, depending on the desired interception phase: ::: code-group ```java [srv/src/main/java/sap/capire/bookshop/CatalogServiceHandler.java] package sap.capire.bookshop; import java.util.List; import org.springframework.stereotype.Component; import com.sap.cds.services.cds.CqnService; import com.sap.cds.services.handler.EventHandler; import com.sap.cds.services.handler.annotations.After; import com.sap.cds.services.handler.annotations.ServiceName; import cds.gen.catalogservice.Books; import cds.gen.catalogservice.Books_; import cds.gen.catalogservice.CatalogService_; @Component // [!code focus] @ServiceName(CatalogService_.CDS_NAME) // [!code focus] public class CatalogServiceHandler implements EventHandler { // [!code focus] // After READ handler on Books to add discount info @After(event = CqnService.EVENT_READ, entity = Books_.CDS_NAME) // [!code focus] public void addDiscountIfApplicable (List books) { // [!code focus] for (Books book : books) { // [!code focus] if (book.getStock() != null && book.getStock() > 111) // [!code focus] book.setTitle (book.getTitle() + " -- 11% discount!"); // [!code focus] } // [!code focus] } // [!code focus] } ``` ::: [Learn more about event handler classes in Java.](../java/event-handlers/index#handlerclasses){.learn-more} ### Custom Actions > Source: /docs/get-started/bookshop#custom-actions Besides standard CRUD operations, CAP services can also define custom actions and functions in their service definitions. Let's extend our `CatalogService` [from above](#use-case-specific-services) with a custom action to submit book orders like that: ::: code-group ```cds [srv/cat-service.cds] extend service CatalogService with { @requires: 'authenticated-user' action submitOrder ( book: Books:ID, quantity : Integer ); } ``` ::: While you **_can_** add custom handlers for standard CRUD events, you **_have to_** do so for custom actions defined in your service definitions, as they cannot be handled by generic providers. We do so like that: ::: code-group ```js [srv/cat-service.js] // Action handler for submitOrder this.on ('submitOrder', async req => { let { book:id, quantity } = req.data let affected = await UPDATE (Books,id) .with `stock = stock - ${quantity}` .where `stock >= ${quantity}` if (!affected) req.error `${quantity} exceeds stock for book #${id}` }) ``` ::: We can test that by adding this to the [_`test/requests.http`_ file we created earlier](#send-requests-from-rest-client): ::: code-group ```http [test/requests.http] ### CatalogService.submitOrder() > Source: /docs/get-started/bookshop#catalogservicesubmitorder POST http://localhost:4004/browse/submitOrder Content-Type: application/json Authorization: Basic bob: { "book": 201, "quantity": 3 } ``` ::: Send that request repeatedly until stock is depleted and an according error returned. ## Summary > Source: /docs/get-started/bookshop#summary We have now built a simple bookshop application step-by-step, thereby following a subset of typical CAP development workflows as depicted in the illustration below. ![](../guides/playbook.drawio.svg) Thereby we touched upon some best practices of CAP, such as: ::: tip [Inner-Loop Development](#inner-loop) ::: ::: tip [Focus on Domain](#focus-on-domain) ::: ::: tip [Separation of Concerns](#separation-of-concerns) ::: ::: tip [Use Case-Oriented Services](#use-case-oriented-services) ::: ::: tip [Served Out-of-the-Box](#served-out-of-the-box) ::: ::: tip [Pushdown to Database](#pushed-down-to-db-1) ::: Learn more about these practices and guiding principles in the [_Core Concepts_](./concepts) guide following next, and the [_Key Features_](./features) guide thereafter. After that, go ahead and explore further on your own in the respective deep dive guides in the [_Develop_ section](../guides/). # Core Concepts of CAP > Source: /docs/get-started/concepts Cloud Scale by Design {.subtitle} ## Introduction > Source: /docs/get-started/concepts#introduction ### Primary Building Blocks > Source: /docs/get-started/concepts#primary-building-blocks The CAP framework features a mix of proven and broadly adopted open-source and SAP technologies. The following figure depicts CAP's place and focus in a stack architecture. ![Vertically CAP services are placed between database and UI. Horizontally, CDS fuels CAP services and is closer to the core than, for example, toolkits and IDEs. Also shown horizontally is the integration into various platform services.](./assets/concepts/architecture.drawio.svg){} The major building blocks are as follows: - [**Core Data Services** (CDS)](../cds/) — CAP's universal modeling language, and the very backbone of everything; used to capture domain knowledge, generating database schemas, translating to and from various API languages, and most important: fueling generic runtimes to automatically serve request out of the box. - [**Service Runtimes**](../guides/services/providing-services) for [Node.js](../node.js/) and [Java](../java/) — providing the core frameworks for services, generic providers to serve requests automatically, database support for SAP HANA, SQLite, and PostgreSQL, and protocol adapters for REST, OData, GraphQL, ... - [**Platform Integrations**](../plugins/) — providing CAP-level service interfaces (*'[Calesi](#the-calesi-pattern)'*) to cloud platform services in platform-agnostic ways, as much as possible. Some of these are provided out of the box, others as plugins. - [**Command-Line Interface** (CLI)](../tools/) — the Swiss army knife on the tools and development kit front, complemented by integrations and support in [*SAP Build Code*](https://www.sap.com/germany/products/technology-platform/developer-tools.html), *Visual Studio Code*, *IntelliJ*, and *Eclipse*. In addition, there's a fast-growing number of [plugins](../plugins/) contributed by open-source and inner-source [communities](/resources/index#public-resources) that enhance CAP in various ways, and integrate with additional tools and environments; the [*Calesi* plugins](features#the-calesi-effect) are among them. ### Models fuel Runtimes > Source: /docs/get-started/concepts#models-fuel-runtimes CDS models play a prevalent role in CAP applications. They're ultimately used to fuel generic runtimes to automatically serve requests, without any coding for custom implementations required. ![Models fuel Generic Services](./assets/concepts/fueling-services.drawio.svg){} CAP runtimes bootstrap *Generic Service Providers* for services defined in service models. They use the information at runtime to translate incoming requests from a querying protocol, such as OData, into SQL queries sent to the database. :::tip Models fuel Runtimes CAP uses the captured declarative information about data and services to **automatically serve requests**, including complex deep queries, with expands, where clauses and order by, aggregations, and so forth... ::: ### Concepts Overview > Source: /docs/get-started/concepts#concepts-overview The following sections provide an overview of the core concepts and design principles of CAP. The following illustration is an attempt to show all concepts, how they relate to each other, and to introduce the terminology. ![Service models declare service interfaces, events, facades, and services. Service interfaces are published as APIs and are consumed by clients. Clients send requests which trigger events. Services are implemented in service providers, react on events, and act as facades. Facades are inferred to service interfaces and are views on domain models. Service providers are implemented through event handlers which handle events. Also, service providers read/write data which has been declared in domain models.](./assets/concepts/key-concepts.drawio.svg){} Start reading the diagram from the _Service Models_ bubble in the middle, then follow the arrows to the other concepts. We dive into each of these concepts in the following sections, starting with _Domain Models_, the other grey bubble in the previous illustration. ## Domain Models > Source: /docs/get-started/concepts#domain-models [CDS](../cds/index) is CAP's universal modeling language to declaratively capture knowledge about an application's domain. Data models capture the *static* aspects of a domain, using the widely used technique of [*entity-relationship modeling*](https://en.wikipedia.org/wiki/Entity–relationship_model#:~:text=An%20entity–relationship%20model%20(or,instances%20of%20those%20entity%20types).). For example, a simple domain model as illustrated in this ER diagram: ![bookshop-erm.drawio](./assets/bookshop/domain-model.drawio.svg) In a first iteration, it would look like this in CDS, with some fields added: ::: code-group ```cds [Domain Data Model] entity Authors { name : String; books : Association to many Books; } entity Books { title : String; author : Association to Authors; genre : Association to Genres; } entity Genres { name : String; parent : Association to Genres; } ``` ::: ### Definition Language (CDL) > Source: /docs/get-started/concepts#definition-language-cdl We use CDS's [*Conceptual Definition Language (CDL)*](../cds/cdl) as a *human-readable* way to express CDS models. Think of it as a *concise*, and more *expressive* derivate of [SQL DDL](https://wikipedia.org/wiki/Data_definition_language). For processing at runtime CDS models are compiled into a *machine-readable* plain object notation, called *CSN*, which stands for [*Core Schema Notation (CSN)*](../cds/csn). For deployment to databases, CSN models are translated into native SQL DDL. Supported databases are [*SQLite*](../guides/databases/sqlite) and *[H2](../guides/databases/h2)* for development, and [_SAP HANA_](../guides/databases/hana) and [_PostgreSQL_](../guides/databases/postgres) for production. ![cdl-csn.drawio](./assets/concepts/cdl-csn.drawio.svg) Refer to *[On the Nature of Models](../cds/models)* in the CDS reference docs. {.learn-more} ### Associations > Source: /docs/get-started/concepts#associations Approached from an SQL angle, CDS adds the concepts of (managed) *[Associations](../cds/cdl#associations)*, and [path expressions](../cds/cql#path-expressions) linked to that, which greatly increases the expressiveness of domain data models. For example, we can write queries, and hence declare views like that: ```cds [Using Associations] entity EnglishBooks as select from Books where author.country.code = 'GB'; ``` This is an even more compact version, using *[infix filters](../cds/cql#with-infix-filters)* and *navigation*. ```cds entity EnglishBooks as select from Authors[country.code='GB']:books; ``` ::: details See how that would look like in SQL... From a plain SQL perspective, think of *Associations* as the like of 'forward-declared joins', as becomes apparent in the following SQL equivalents of the preceding view definitions. Path expressions in `where` clauses become *INNER JOINs*: ```sql CREATE VIEW EnglishBooks AS SELECT * FROM Books -- for Association Books:author: INNER JOIN Authors as author ON author.ID = Books.author_ID -- for Association Authors:country: INNER JOIN Countries as country ON country.code = author.country_code -- the actual filter condition: WHERE country.code = 'GB'; ``` Path expressions in *infix filters* become *SEMI JOINs*, for example, using `IN`: ```sql CREATE VIEW EnglishBooks AS SELECT * FROM Books -- for Association Books:author: WHERE Books.author_ID IN (SELECT ID from Authors as author -- for Association Authors:country: WHERE author.country_code IN (SELECT code from Countries as country -- the actual filter condition: WHERE country.code = 'GB'; ) ) ``` ... same with `EXISTS`, which is faster with some databases: ```sql CREATE VIEW EnglishBooks AS SELECT * FROM Books -- for Association Books:author: WHERE EXISTS (SELECT 1 from Authors as author WHERE author.ID = Books.author_ID -- for Association Authors:country: AND EXISTS (SELECT 1 from Countries as country WHERE country.code = author.country_code -- the actual filter condition: AND country.code = 'GB'; ) ) ``` ::: ### Aspects > Source: /docs/get-started/concepts#aspects A distinctive feature of CDS is its intrinsic support for [_Aspect-oriented Modeling_](../cds/aspects), which allows to factor out separate concerns into separate files. It also allows everyone to adapt and extend everything anytime, including reuse definitions you don't own, but have imported to your models. ::: code-group ```cds [Separation of Concerns] // All authorization rules go in here, the domain models are kept clean using { Books } from './my/clean/schema.cds'; annotate Books with @restrict: [{ grant:'WRITE', to:'admin' }]; ``` ```cds [Verticalization] // Everyone can extend any definitions, also ones they don't own: using { sap.common.Countries } from '@sap/cds/common'; extend Countries with { county: String } // for UK, ... ``` ```cds [Customization] // SaaS customers can do the same for their private usage: using { Books } from '@capire/bookshop'; extend Books with { ISBN: String } ``` :::
:::tip Key features & qualities CDS greatly promotes **Focus on Domain** by a *concise* and *comprehensible* language. Intrinsic support for *aspect-oriented modeling* fosters **Separation of Concerns**, as well as **Extensibility** in customization, verticalization, and composition scenarios. ::: ## Services > Source: /docs/get-started/concepts#services Services are the most central concept in CAP when it comes to an application's behavior. They're declared in CDS, frequently as views on underlying data, and implemented by services providers in the CAP runtimes. This ultimately establishes a **Service-centric Paradigm** which manifests in these **key design principles**: - **Every** active thing is a **service** → _yours, and framework-provided ones_{.dimmed} - Services establish **interfaces** → *declared in service models*{.dimmed} - Services react to **events** → *in sync and async ones*{.dimmed} - Services run **queries** → *pushed down to database*{.dimmed} - Services are **agnostic** → *platforms and protocols*{.dimmed} - Services are **stateless** → *process passive data*{.dimmed} ![Key Design Principles](./assets/concepts/paradigm.drawio.svg) :::tip Design principles and benefits The design principles - and adherence to them - are crucial for the key features & benefits. ::: ### Services as Interfaces > Source: /docs/get-started/concepts#services-as-interfaces Service models capture the *behavioral* aspects of an application. In its simplest form a service definition, focusing on the *interface* only, could look like that: ::: code-group ```cds [Service Definition in CDS] service BookshopService { entity Books : cuid { title: String; author: Association to Authors } entity Authors :cuid { name: String; } action submitOrder ( book: UUID, quantity: Integer ); } ``` ::: ### Services as Facades > Source: /docs/get-started/concepts#services-as-facades Most frequently, services expose denormalized views of underlying domain models. They act as facades to an application's core domain data. The service interface results from the _inferred_ element structures of the given projections. For example, if we take the *bookshop* domain model as a basis, we could define a service that exposes a flattened view on books with authors names as follows (note and click on the *⇒ Inferred Interface* tab): ::: code-group ```cds [Service as Facade] using { sap.capire.bookshop as underlying } from '../db/schema'; service CatalogService { @readonly entity ListOfBooks as projection on underlying.Books { ID, title, author.name as author // flattened } } ``` ```cds [⇒   Inferred Interface] service CatalogService { @readonly entity ListOfBooks { key ID : UUID; title : String; author : String, // flattened authors.name } } ``` [Learn more about `as projection on` in the **Querying** section below](#querying). {.learn-more} ::: ::: tip **Use Case-Oriented Services** The previous example follows the recommended best practice of a *[use case-oriented service](../guides/services/providing-services#use-case-oriented-services)* which is specialized on *one* specific use case and group of users. Learn more about that in the [Providing Services](../guides/services/providing-services) guide. ::: ### Service Providers > Source: /docs/get-started/concepts#service-providers As we'll learn in the next chapter after this, service providers, that is the implementations of services, react to events, such as a request from a client, by registering respective event handlers. At the end of the day, a service implementation is **the sum of all event handlers** registered with this service. [More about service implementations through *Event Handlers* in the next chapter](#events) {.learn-more} ### Not Microservices > Source: /docs/get-started/concepts#not-microservices Don't confuse CAP services with Microservices: - **CAP services** are modular software components, while ... - **Microservices** are deployment units. CAP services are important for how you *design* and *implement* your applications in clean and modularized ways on a fine-granular use case-oriented level. The primary focus of Microservices is on how to cut your whole application into independent coarse-grained(!) deployment units, to release and scale them independently. ## Events > Source: /docs/get-started/concepts#events While services are the most important concept for models and runtime, events are equally, if not more, important to the runtime. CAP has a *ubiquitous* notion of events: they show up everywhere, and everything is an event, and everything happening at runtime is in reaction to events. We complement our [*Service-centric Paradigm*](#services) by these additional **design principles**: - **Everything** happening at runtime is triggered by / in reaction to **events** - **Providers** subscribe to, and *handle* events, as their implementations - **Observers** subscribe to, and *listen* to events 'from the outside' - Events can be of ***local*** or ***remote*** origin, and be... - Delivered via ***synchronous*** or ***asynchronous*** channels ### Event Handlers > Source: /docs/get-started/concepts#event-handlers Services react to events by registering *event handlers*. ![event-handlers.drawio](./assets/concepts/event-handlers.drawio.svg) This is an example of that in Node.js: ```js class BookshopService extends cds.ApplicationService { init() { const { Books } = this.entities this.before ('UPDATE', Books, req => validate (req.data)) this.after ('READ', Books, books => ... ) this.on ('SubmitOrder', req => this.emit ('BookOrdered',req.data)) }} ``` You can also register *generic* handlers, acting on classes of similar events: ```js this.before ('READ','*', ...) // for READ requests to all entities this.before ('*','Books', ...) // for all requests to Books this.before ('*', ...) // for all requests served by this srv ``` ::: info What constitutes a service implementation? The service's implementation consists of all event handlers registered with it. ::: ### Event Listeners > Source: /docs/get-started/concepts#event-listeners The way we register event handlers that *implement* a service looks similar to how we register similar handlers for the purpose of just *listening* to what happens with other services. At the end of the day, the difference is only to *whom* we register event listeners. ::: code-group ```js [Service Provider] class SomeServiceProvider { async init() { this.on ('SomeEvent', req => { ... }) }} ``` ```js [Observer] class Observer { async init() { const that = await cds.connect.to ('SomeService') that.on ('SomeEvent', req => { ... }) }} ``` ::: ::: info Service provider and observer Everyone/everything can register event handlers with a given service. This is not limited to the service itself, as its implementation, but also includes *observers* or *interceptors* listening to events 'from the outside'. ::: ### Sync / Async > Source: /docs/get-started/concepts#sync--async From an event handler's perspective, there's close to no difference between *synchronous requests* received from client like UIs, and *asynchronous event messages* coming in from respective message queues. The arrival of both, or either of which, at the service's interface is an event, to which we can subscribe to and react in the same uniform way, thus blurring the lines between the synchronous and the asynchronous world. ![events.drawio](./assets/concepts/events.drawio.svg) Handling synchronous requests vs asynchronous event messages: ::: code-group ```js [Handling sync Requests] class CatalogService { async init() { this.on ('SubmitOrder', req => { // sync action request const { book, quantity } = req.data // process it... }) }} ``` ```js [Handling async Events] class AnotherService { async init() { const cats = await cds.connect.to ('CatalogService') cats.on ('BookOrdered', msg => { // async event message const { book, quantity } = msg.data // process it... }) }} ``` ::: Same applies to whether we *send* a request or *emit* an asynchronous event: ```js await cats.send ('SubmitOrder', { book:201, quantity:1 }) await this.emit ('BookOrdered', { book:201, quantity:1 }) ``` ### Local / Remote > Source: /docs/get-started/concepts#local--remote Services cannot only be used and called remotely, but also locally, within the same process. The way we connect to and interact with *local* services is the same as for *remote* ones, via whatever protocol: ```js const local_or_remote = await cds.connect.to('SomeService') await local_or_remote.send ('SomeRequest', {...data}) await local_or_remote.read ('SomeEntity').where({ID:4711}) ``` Same applies to the way we subscribe to and react to incoming events: ```js this.on ('SomeRequest', req => {/* process req.data */}) this.on ('READ','SomeEntity', req => {/* process req.query */}) ``` > [!note] > > The way we *connect* to and *consume* services, as well as the way we *listen* and *react* to events, and hence *implement* services, are *agnostic* to whether we deal with *local* or *remote* services, as well as to whatever *protocols* are used.
→ see also [*Agnostic by Design*](#agnostic-by-design) ## Data > Source: /docs/get-started/concepts#data All data processed and served by CAP services is *passive*, and represented by *plain simple* data structures as much as possible. In Node.js it's plain JavaScript record objects, in Java it's hash maps. This is of utmost importance for the reasons set out in the following sections. ![passive-data.drawio](./assets/concepts/passive-data.drawio.svg) ### Extensible Data > Source: /docs/get-started/concepts#extensible-data Extensibility, in particular in a SaaS context, allows customers to tailor a SaaS application to their needs by adding extension fields. These fields are not known at design time but need to be served by your services, potentially through all interfaces. With CAP's combination of dynamic querying and passive data this is intrinsically covered and extension fields look and feel no different than pre-defined fields. For example, an extension like that can automatically be served by CAP: ```cds extend Books with { some_extension_field : String; } ``` > [!warning] > > In contrast to that, common *DAOs*, *DTOs*, *Repositories*, or *Active Records* approaches which use static classes can't transport such extension data, not known at the time these classes are defined. Additional means would be required, which is not the case for CAP. ### Queried Data > Source: /docs/get-started/concepts#queried-data As detailed out in the next chapter, querying allows service clients to ask exactly for the data they need, instead of always reading full data records, only to display a list of books titles. For example, querying allows that: ```js let books = await GET `Books { ID, title, author.name as author }` ``` While a static DAO/DTO-based approach would look like that: ```js let books = await GET `Books` // always read in a SELECT * fashion ``` In effect, when querying is used the shape of records in result sets vary very much, even in denormalized ways, which is hardly possible to achieve with static access or transfer objects. ### Passive Data > Source: /docs/get-started/concepts#passive-data As, for the previously mentioned reasons, we can't use static classes to represent data at runtime, there's also no reasonable way to add any behavior to data objects. So in consequence, all data has to be passive, and hence all logic, such as for validation or field control 'determinations' has to go somewhere else → into event handlers. > [!tip] > > Adhering to the principle of passive data also has other positive effects. For example: > > **(1)** Passive data can be easily cached in content delivery networks.   **(2)** Passive data is more lightweight than active objects.   **(3)** Passive data is *immutable* → which allows to apply parallelization as known from functional programming. ## Querying > Source: /docs/get-started/concepts#querying As a matter of fact, business applications tend to be *data-centric*. That is, the majority of operations deal with the discipline of reading and writing data in various ways. Over the decades, querying, as known from SQL, as well as from web protocols like OData or GraphQL, became the prevalent and most successful way for this discipline. ### Query Language (CQL) > Source: /docs/get-started/concepts#query-language-cql As already introduced in the [*Domain Models*](#domain-models) section, CAP uses queries in CDS models, for example to declare service interfaces by projections on underlying entities, here's an excerpt of what was mentioned earlier: ```cds entity ListOfBooks as projection on underlying.Books { ID, title, author.name as author } ``` We use [CDS's *Conceptual Query Language (CQL)*](../cds/cql) to write queries in a human-readable way. For reasons of familiarity, CQL is designed as a derivate of SQL, but used in CAP independent of SQL and databases. For example to derive new types as projections on others, or sending OData or GraphQL queries to remote services. Here's a rough comparison of [CQL](../cds/cql) with [GraphQL](http://graphql.org), [OData](https://www.odata.org), and [SQL](https://en.wikipedia.org/wiki/SQL): | Feature | CQL | GraphQL | OData | SQL | | ------------------ | :-----: | :-------: | :-----: | :-----: | | CRUD | ✓ | ✓ | ✓ | ✓ | | Flat Projections | ✓ | ✓ | ✓ | ✓ | | Nested Projections | ✓ | ✓ | ✓ | | | Navigation | ✓ | (✓) | ✓ | | | Filtering | ✓ | | ✓ | ✓ | | Sorting | ✓ | | ✓ | ✓ | | Pagination | ✓ | | ✓ | ✓ | | Aggregation | ✓ | | ✓ | ✓ | | Denormalization | ✓ | | | ✓ | | Native SQL | ✓ | | | ✓ | As apparent from this comparison, we can regard CQL as a superset of the other query languages, which enables us to translate from and to all of them. ### Queries at Runtime > Source: /docs/get-started/concepts#queries-at-runtime CAP also uses queries at runtime: an OData or GraphQL request is essentially a query which arrives at a service interface. Respective protocol adapters translate these into *machine-readable* runtime representations of CAP queries (→ see [*Core Query Notation, CQN*](../cds/cqn)), which are then forwarded to and processed by target services. Here's an example, including CQL over http: ::: code-group ```sql [CQL] SELECT from Books { ID, title, author { name }} ``` ```graphql [CQL /http] GET Books { ID, title, author { name }} ``` ```graphql [GraphQL] POST query { Books { ID, title, author { name } } } ``` ```http [OData] GET Books?$select=ID,title&$expand=author($select=name) ``` ```js [⇒  CAP Query (in CQN)] { SELECT: { from: {ref:['Books']}, columns: [ 'ID', 'title', {ref:['author']}, expand:[ 'name' ] }] }} ``` ::: Queries can also be created programmatically at runtime, for example to send queries to a database. For that we're using *human-readable* language bindings, which in turn create CQN objects behind the scenes. For example, like that in Node.js (both creating the same CQN object as described earlier): ::: code-group ```js [Using TTL] let books = await SELECT `from Books { ID, title, author { name } }` ``` ```js [Using Fluent API] let books = await SELECT.from (Books, b => { b.ID, b.title, b.author (a => a.name) }) ``` ::: ### Push-Down to Databases > Source: /docs/get-started/concepts#push-down-to-databases The CAP runtimes automatically translate incoming queries from the protocol-specific query language to CQN and then to native SQL, which is finally sent to underlying databases. The idea is to push down queries to where the data is, and execute them there with best query optimization and late materialization. ![cql-cqn.drawio](./assets/concepts/cql-cqn.drawio.svg) CAP queries are **first-class** objects with **late materialization**. They're captured in CQN, kept in standard program variables, passed along as method arguments, are transformed and combined with other queries, translated to other target query languages, and finally sent to their targets for execution. This process is similar to the role of functions as first-class objects in functional programming languages. ## Agnostic by Design > Source: /docs/get-started/concepts#agnostic-by-design In [Introduction - What is CAP](features) we learned that your domain models, as well as the services, and their implementations are **agnostic to protocols**, as well whether they're connected to and consume other services **locally or remotely**. In this chapter, we complement this by CAP-level integration of platform services and vendor-independent database support. So, in total, and in effect, we learn: > [!tip] Your domain models and application logic stays... > > - Agnostic to *Local vs Remote* > - Agnostic to *Protocols* > - Agnostic to *Databases* > - Agnostic to *Platform Services* and low-level *Technologies* > > **This is *the* key enabling quality** for several major benefits and value propositions of CAP, such as [*Fast Inner Loops*](features#fast-inner-loops), [*Agnostic Services*](features#agnostic-microservices), [*Late-cut Microservices*](features#late-cut-microservices), and several more... ### Hexagonal Architecture > Source: /docs/get-started/concepts#hexagonal-architecture The *[Hexagonal Architecture](https://en.wikipedia.org/wiki/hexagonal_architecture_(software))* (also known as *Ports and Adapters Architecture/Pattern*) as first proposed by Alistair Cockburn in 2005, is quite famous and fancied these days (rightly so). He introduced it back then with the following opening statement and illustration: *"Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases"* {} ![Hexagonal Architecture original illustration by Alistair Cockburn](assets/concepts/hexagonal-archritecture-origin.png) We can translate that to these objectives in our world of cloud-based business applications: > [!tip] Objectives of Hexagonal Architecture > > - Your *Application* (→ the inner hexagon) should stay ***agnostic*** to *"the outside"* > - Thereby allowing to replace *"the outside"* met in production by *mocked* variants > - To reduce complexity and speed up turnaround times at *development*, and in *tests* > > -> See also: [*Inner-Loop Development & Tests*](features#fast-inner-loops) > In contrast to that, if (you think) you are doing Hexagonal Architecture, but still find yourself trapped in a slow and expensive always-connected development experience, you might have missed a point... → the *Why* and *What*, not *How*. #### CAP as an implementation of Hexagonal Architecture > Source: /docs/get-started/concepts#cap-as-an-implementation-of-hexagonal-architecture CAP's [agnostic design principles](#agnostic-by-design) are very much in line with the goals of Hexagonal Architecture, and actually give you exactly what these are aiming for: as your applications greatly stay *agnostic* to protocols, and other low-level details, which could lock them in to one specific execution environment, they can be "*developed and tested in isolation*", which in fact is one of CAP's [key value propositions](features#fast-inner-loops). Moreover, they become [*resilient* to disrupting changes](features#minimized-lock-ins) in "the outside". Not only do we address the very same goals, we can also identify several symmetries in the way we address and achieve these goals as follows: | Hexagonal Architecture | CAP | | ---------------------- | ------------------------------------------------------------ | | "The Outside" | Remote *Clients* of Services (inbound)
Databases, Platform Services (outbound) | | Adapters | Protocol ***Adapters*** (inbound + outbound),
Framework Services (outbound) | | Ports | Service ***Interfaces*** + Events (inbound + outbound) | | Application Model | Use-case ***Services*** + Event Handlers | | Domain Model | Domain ***Entities*** (w/ essential invariants) |
> [!tip] > > CAP is very much in line with both, the intent and goals of Hexagonal Architecture, as well as with the fundamental concepts. Actually, CAP *is an implementation* of Hexagonal Architecture, in particular with respect to the [*Adapters*](#protocol-adapters) in the outer hexagon, but also regarding [*Application Models*](#application-domain) and [*(Core) Domain Models*](#application-domain) in the inner hexagon. ### Application Domain > Source: /docs/get-started/concepts#application-domain Looking at the things in the inner hexagon, many protagonists distinct between *application model* and *domain model* living in there. In his initial post about [*Hexagonal Architecture*](https://wiki.c2.com/?HexagonalArchitecture) in the in [*c2 wiki*](https://wiki.c2.com) Cockburn already highlighted that as follows in plain text: ​ *OUTSIDE <-> transformer <--> ( **application** <-> **domain** )* {} ::: details Background from MVC and *Four Layers Architecture* ... That distinction didn't come by surprise to the patterns community in c2, as Cockburn introduced his proposal as a *"symmetric"* evolution of the [*Four Layers Architecture*](https://wiki.c2.com/?FourLayerArchitecture) by Kyle Brown, which in turn is an evolution of the [*Model View Controller*](https://wiki.c2.com/?ModelViewController) pattern, invented by Trygve Reenskaug et al. at Xerox PARC. The first MVC implementations in [*Smalltalk-80*](https://en.wikipedia.org/wiki/Smalltalk) already introduced the notion of an *[Application Model](https://wiki.c2.com/?ApplicationModel)* which acts as a *mediator* between use case-oriented application logic, and the core [*Domain Model*](https://wiki.c2.com/?DomainModel) classes, which primarily represent an application's data objects, with only the most central invariants carved in stone. Yet, **both are agnostic** to wire protocols or ['UI widgetry'](https://wiki.c2.com/?FourLayerArchitecture) → the latter being covered and abstracted from by *Views* and *Controllers* in MVC. ::: #### See Also... > Source: /docs/get-started/concepts#see-also - The [*Model Model View Controller*](https://wiki.c2.com/?ModelModelViewController) pattern in c2 wiki, in which Randy Stafford points out the need for such twofold models: *"... there have always been two kinds of model: [DomainModel](https://wiki.c2.com/?DomainModel), and [ApplicationModel](https://wiki.c2.com/?ApplicationModel)."* {.indent} - [*Hexagonal Architecture and DDD (Domain Driven Design)*](https://www.happycoders.eu/software-craftsmanship/hexagonal-architecture/#hexagonal-architecture-and-ddd-domain-driven-design) by Sven Woltmann, a great end-to-end introduction to the topic, which probably has the best, and most correct illustrations, like this one: ![Hexagonal architecture and DDD (Domain Driven Design)](https://www.happycoders.eu/wp-content/uploads/2023/01/hexagonal-architecture-ddd-domain-driven-design-600x484.png){} #### Entities ⇒ Core Domain Model > Source: /docs/get-started/concepts#entities--core-domain-model Your core domain model is largely covered by CDS-declared entities, enriched with invariant assertions, which are deployed to databases and automatically served by generic service providers out of the box. Even enterprise aspects like common code lists, localized data, or temporal data are simple to add and served out of the box as well. #### Services ⇒ Application Model > Source: /docs/get-started/concepts#services--application-model Your application models are your services, also served automatically by generic providers, complemented with your domain-specific application logic you added in custom event handlers. The services are completely agnostic to inbound and outbound protocols: they react on events in agnostic ways, and use other services in equally agnostic ways — including framework-provided ones, like database services or messaging services. > [!tip] > > Your ***Core Domain Model*** is largely captured in respective [CDS data models](#domain-models), including annotations for invariants, and served automatically by CAP's generic providers. > > Your ***Application Model*** are CAP services, which are [also declared in CDS](#services) and served by generic providers, complemented with your domain-specific [**custom event handlers**](#event-handlers). ### Protocol Adapters > Source: /docs/get-started/concepts#protocol-adapters Behind the scene - that is, in the **outer hexagon** containing stuff, you as an application developer should not see - the CAP runtime employs Protocol Adapters, which translate requests from (and to) low-level protocols like HTTP, REST, OData, GraphQL, ... to protocol-agnostic CAP requests and queries for inbound and outbound communication. --> ***Inbound*** Communication : Requests your application *receives*. --> ***Outbound*** Communication : Requests your application *sends* to other services. In effect your service implementations stay agnostic to (wire) protocols, which allows you to exchange protocols, replace targets by mocks, do fast inner loop development in airplane mode, ... even change topologies from a monolith to micro services and vice versa late in time. ![protocol-adapters.drawio](./assets/concepts/protocol-adapters.drawio.svg) The inbound and outbound adapters (and the framework services) effectively provide your inner core with the ***ports*** to the outside world, which always provide the same, hence *agnostic* style of API (indicated by the green arrows used in the previous graphic), as already introduced in [Local /Remote](#local--remote). Inbound: ```js this.on ('SomeEvent', msg => {/* process msg.data */}) this.on ('SomeRequest', req => {/* process req.data */}) this.on ('READ','SomeEntity', req => {/* process req.query */}) ``` Outbound: ```js const any = await cds.connect.to('SomeService') await any.emit ('SomeEvent', {...data}) await any.send ('SomeRequest', {...data}) await any.read ('SomeEntity').where({ID:4711}) ``` > In the latter, `any` can be any service your application needs to talk to. Local application services, remote services, CAP-based and non-CAP-based ones, as well as framework-provided services, such as database services, or messaging services → more on that in the next section... ### Framework Services > Source: /docs/get-started/concepts#framework-services In the figure above we see boxes for *Framework Services* and *Database Services*. Both are CAP framework-provided services, which — following our [guiding principle](#services) of *"Every active thing in CAP is a CAP service"* — are implemented as CAP services itself, and hence are also consumed via the same agnostic API style, as any other CAP service. Overall, this is the class hierarchy implemented in the CAP runtimes: ![service-classes.drawio](./assets/concepts/service-classes.drawio.svg) The *RemoteService* box at the bottom is a CAP service proxy for remote services, which in turn used the outbound *Protocol Adapters* behind the scenes to translate outgoing requests to the target wire protocols. The *DatabaseService* subclasses provide implementations for the different databases, thereby trying to provide a consistent, portable usage, without falling into a common denominator syndrome pit. Same for the *MessagingServices*. ## Intrinsic Extensibility > Source: /docs/get-started/concepts#intrinsic-extensibility SaaS customers of CAP applications use the very same techniques as any developer can use to adapt given models or service implementations to their needs. That applies to both, models and service implementations. ### Extending Models > Source: /docs/get-started/concepts#extending-models Everyone can extend every model definition: SaaS customers can add extension fields or new entities to respective definitions of as SaaS application's models. In the same way, you can extend any reuse definition that you might consume from reuse packages, including the reuse models shipped with CAP itself. For example: ```cds using { User, managed } from '@sap/cds/common'; extend managed with { ChangeNotes : Composition of many { key timestamp : DateTime; author : User; note : String(1000); } } ``` This would extend the common reuse type `managed` obtained from `@sap/cds/common` to not only capture latest modifications, but a history of commented changes, with all entities inheriting from that aspect, own or reused ones, receiving this enhancement automatically. > [!tip] > > Not only can your SaaS customers extend *your* definitions, but also you can extend any definitions that you *reuse* to adapt it to your needs. Adapting widely used reuse definitions, as in this example, has the advantage that you reach many existing usages. [Learn more about these options in the CDS guide about *Aspect-oriented Modeling*](../cds/aspects). {.learn-more} ### Extension Logic > Source: /docs/get-started/concepts#extension-logic As introduced in the section on [*Event Listeners*](#event-listeners) above, everyone can add event handlers to every service. Similar to aspect-oriented modeling, this allows to extend reuse services. For example, assuming you're using a reuse package that provides a service to manage reviews, as show-cased in the [*capire/reviews*](https://github.com/capire/reviews) package. And whenever a new review is added you want to do something in addition. To accomplish this, simply add a respective event handler to the reuse service like so: ```js const ReviewsService = await cds.connect.to('ReviewsService') ReviewsService.after ('CREATE', 'Reviews', req => { // do something in addition... }) ``` As a service provider you can also introduce explicitly defined business-level extension points, instead of allowing your clients to hook in to your technical event. For example as the owner of the reviews service, you could add and event like that to your service definition: ```cds service ReviewsService { ... event ReviewAdded { subject : ReviewedSubject; title : String; message : String; reviewer : User; } } ``` And in your implementation you would emit such events like so: ```js class ReviewsService { init() { this.after ('CREATE','Reviews', req => this.emit('ReviewAdded', req.data)) }} ``` With that your clients can hook in to that extension point like that: ```js const ReviewsService = await cds.connect.to('ReviewsService') ReviewsService.on ('ReviewAdded', msg => { // do something in addition... }) ``` ### Extensible Framework > Source: /docs/get-started/concepts#extensible-framework As stated in the introduction: "*Every active thing is a Service*". This also applies to all framework features and services, like databases, messaging, remote proxies, MTX services, and so on. And as everyone can add event handlers to every service, you can also add event handlers to framework services, and thereby extend the core framework. For example, you could extend CAP's primary **database service** like this: ```js cds.db .before ('*', req => { console.log (req.event, req.target.name) }) ``` In the same way you could add handlers to **remote service proxies**: ```js const proxy = await cds.connect.to ('SomeRemoteService') proxy.on ('READ', 'Something', req => { // handle that remote call yourself }) proxy.before ('READ', '*', req => { // modify requests before they go out }) proxy.after ('READ', '*', result => { // post-process recieved responses }) ``` ## The Calesi Pattern > Source: /docs/get-started/concepts#the-calesi-pattern 'Calesi' stands for CAP-level Service Interfaces, and refers to the increasing numbers of BTP platform services which offer a CAP-level client library. These drastically reduce the boilerplate code applications would have to write. For example, adding attachments required thousands of lines of code, caring for the UI, streaming of large data, size limiting, malware scanning, multitenancy, and so forth... after we provided the [Attachments plugin](../plugins/index#attachments), all an application needs to do now is to add that line to an entity: ```cds entity Foo { //... attachments : Composition of many Attachments; // [!code focus] } ``` Whenever you have to integrate external services, you should follow the Calesi patterns. For example, let's take an audit logging use case: Data privacy regulations require to write audit logs whenever personal data is modified. 1. **Declare the service interface** — provide a CAP service that encapsulates outbound communication with the audit log service. Start by defining the respective service interface in CDS: ```cds service AuditLogService { event PersonalDataModified : LogEntry { subject : DataSubject; changes : many { field : String; old : String; new : String; }; tenant : Tenant; user : User; timestamp : DateTime: } } ``` 2. **Implement a mock variant** — Add a first service implementation: one for mocked usage during development: ```js class AuditLogService {init(){ this.on('PersonalDataModified', msg => { console.log('Received audit log message', red.data) }) }} ``` > [!tip] > > With that, you already fulfilled a few goals and guidelines from Hexagonal Architecture: The interface offered to your clients is agnostic and follows CAP's uniform service API style. Your consumers can use this mock implementation at development to speed up their [inner loop development](features#fast-inner-loops) phases. 3. **Provide the real impl** — Start working on the 'real' implementation that translates received audit log messages into outbound calls to the real audit log service. > [!note] > > You bought yourself some time for doing that, as your clients already got a working mock solution, which they can use for their development. 4. **Plug and play** —Add profile-aware configuration presets, so your consumers don't need to do any configuration at all: ```js { cds: { requires: { 'audit-log': { "[development]": { impl: ".../audit-log-mock.js" }, "[production]": { impl: ".../the-real-audit-log-srv.js" }, } } } } ``` 5. **Served automatically?** — Check if you could automate things even more instead of having your use your service programmatically. For example, we could introduce an annotation *@PersonalData*, and write audit log entries automatically whenever an entity or element is tagged with that: ```js :line-numbers cds.on('served', async services => { const auditlog = await cds.connect.to('AuditLog') for (let each of services) { for (let e of each.entities) if (e['@PersonalData']) { each.on('UPDATE',e, auditlog.emit('PersonalDataModified', {...})) } } }) ``` That example was an *outbound* communication use case. Basically, we encapsulate outbound channels with CAP Services, as done in CAP for messaging service interfaces and database services. For *inbound* integrations, we would create an adapter, that is, a service endpoint which translates incoming messages into CAP event messages which it forwards to CAP services. With that, the actual service provider implementation is again a protocol-agnostic CAP service, which could as well be called locally, for example in development and tests. > [!tip] > > Essentially, the 'Calesi' pattern is about encapsulating any external communication within a CAP-service-based interface, so that the actual consumption and/or implementation benefits from the related advantages, such as an agnostic consumption, intrinsic extensibility, automatic mocking, and so on. # Introduction to CAP > Source: /docs/get-started/features Value Propositions {.subtitle} ## What is CAP? > Source: /docs/get-started/features#what-is-cap The _Cloud Application Programming Model_ (CAP) is a framework of languages, libraries, and tools for building *enterprise-grade* cloud applications. It guides developers along a *golden path* of **proven best practices**, which are **served out of the box** by generic providers cloud-natively, thereby relieving application developers from tedious recurring tasks. In effect, CAP-based projects benefit from a primary **focus on domain**, with close collaboration of developers and domain experts, **rapid development** at **minimized costs**, as well as **avoiding technical debts** by eliminating exposure to, and lock-ins to volatile low-level technologies. Someone once said: "CAP is like ABAP for the non-ABAP world" {.quote} ... which is not completely true, of course
... ABAP is much older \:-) ## Jumpstart & Grow As You Go... > Source: /docs/get-started/features#jumpstart--grow-as-you-go ###### grow-as-you-go > Source: /docs/get-started/features#grow-as-you-go ### Jumpstarting Projects > Source: /docs/get-started/features#jumpstarting-projects To get started with CAP, there's only a [minimalistic initial setup](./index) required. Starting a project is a matter of seconds. No tedious long lasting platform onboarding ceremonies are required; instead you can (and should): - Start new CAP projects within seconds. - Create functional apps with full-fledged servers within minutes. - Without prior onboarding to, or being connected to, the cloud. ```sh cds init cds watch ``` > [!tip] > > Following the principle of *convention over configuration*, CAP uses built-in configuration presets and defaults for different profiles. For the development profile, there's all set up for jumpstart development. In parallel, ops teams could set up the cloud, to be ready for first deployments later in time. ### Growing as You Go... > Source: /docs/get-started/features#growing-as-you-go Add things only when you need them or when you know more. Avoid any premature decisions or up-front overhead. For example, typical CAP projects adopt an *iterative* and *evolutionary* workflow like that: 1. **jumpstart a project** → no premature decisions made at that stage, just the name. 2. **rapidly create** fully functional first prototypes or proof-of-concept versions. 3. work in **fast inner loops** in airplane mode, and only occasionally go hybrid. 4. anytime **add new features** like Fiori UIs, message queues, different databases, etc. 5. do a first **ad-hoc deployment** to the cloud some days later 6. set up your **CI/CD pipelines** some weeks later 7. switch on **multitenancy** and **extensibility** for SaaS apps before going live 8. optionally cut out some **micro services** only if necessary and months later earliest ```sh cds add hana,redis,mta,helm,mtx,multitenancy,extensibility... ``` > [!tip] > > Avoid futile up-front setups and overhead and rather **get started rapidly**, having a first prototype up and running as fast as possible... by doing so, you might even find out soon that this product idea you or somebody else had doesn't work out anyways, so rather stop early ... ### Fast Inner Loops > Source: /docs/get-started/features#fast-inner-loops Most of your development happens in inner loops, where developers would **code**, **run**, and **test** in **fast iteration**. However, at least in mediocre cloud-based development approaches, this is slowed down drastically, for example, by the need to always be connected to platform services, up to the need to always deploy to the cloud to see and test the effects of recent changes. ![inner-loop](./assets/features/inner-loop.png){} CAP applications are [**agnostic by design**](concepts#agnostic-by-design), which allows to stay in fast inner loops by using local mock variants as stand-ins for many platform services and features, thereby eliminating the need to always connect to or deploy to the cloud; developers can stay in fast inner loops, without connection to cloud – aka. ***airplane*** mode development. Only when necessary, they can test in ***hybrid*** mode or do ad-hoc deployments to the cloud. CAP provides mocked variants for several platform services out of the box, which are used automatically through default configuration presets in ***development*** profile, while the real services are automatically used in ***production*** profile. Examples are: | Platform Service | Development | Production | | ---------------- | -------------------- | -------------------------------- | | Database | SQLite, H2 in-memory | SAP HANA, PostgreSQL | | Authentication | Mocked Auth | SAP Identity Services | | App Gateway | None | SAP App Router | | Messaging | File-based Queues | SAP Cloud Appl. Event Hub, Kafka, Redis, ... | > [!tip] > > CAP's agnostic design, in combination with the local mock variants provided out of the box, not only retains **fast turnarounds** in inner loops, it also **reduces complexity**, and makes development **resilient** against unavailable platform services → thus promoting **maximized speed** at **minimized costs**. ### Agnostic Microservices > Source: /docs/get-started/features#agnostic-microservices CAP's thorough [agnostic design](concepts#agnostic-by-design) not only allows to swap local mock variants as stand-ins for productive platform services, it also allows to do the same for your application services. Assumed you plan for a microservices architecture, the team developing microservice `A` would always have a dependency to the availability of microservice `B`, which they need to connect to, at least in a hybrid setup, worst case even ending up in the need to always have both deployed to the cloud. With CAP, you can (and should) instead just run both services in the same local process at development, basically by using `B` as a plain old library in `A`, and only deploy them to separate microservices in production, **without having to touch your models or code** (given `A` uses `B` through public APIs, which should always be the case anyways). ![modulith](./assets/features/modulith.png){} ![late-cut-microservices](./assets/features/late-cut-microservices.png){} If service `A` and `B` are developed in different runtimes, for example, Node.js and Java, you can't run them in the same process. But even then you can (and should) leverage CAP's ability to easily serve a service generically based on a service definition in CDS. So during development, `A` would use a mocked variant of `B` served automatically by CAP's generic providers. ### Late-cut Microservices > Source: /docs/get-started/features#late-cut-microservices You can (and should) also leverage the offered options to have CAP services co-deployed in a single *modulithic* process to delay the decision of whether and how to cut your application into microservices to a later phase of your project, when you know more about where to actually do the right cuts in the right way. In general, we always propose that approach: 1. **Avoid** premature cuts into microservices → ends up in lots of pain without gains 2. **Go for** a *modulith* approach instead → with CAP services for modularization 3. Cut into separate microservices **later on** → only when you really need to > [!tip] > > - **CAP services** are your primary means for modularization > - **Microservices** are **deployment units**. > - **Valid** reasons for microservices are: > 1. need to scale things differently > 2. different runtimes, for example, Node.js vs Java > 3. loosely coupled, coarse-grained subsystems with separate lifecycles > - **False** reasons are: distributed development, modularization, isolation, ... → there are well established and proven better ways to address these things, without the pain which comes with microservices. ### Parallelized Workflows > Source: /docs/get-started/features#parallelized-workflows As shown in the [*Bookshop by capire*](./bookshop) walkthrough, a simple service definition in CDS is all we need to run a full-fledged REST, or OData, or GraphQL server There are more options to parallelize workflows. Fueled by service definition is all that is required to get a full-fledged REST or OData service **served out of the box** by generic providers. So, projects could spawn two teams in parallel: one working on the frontend using automatically served backends, while the other one works on the actual implementations of the backend part. ## Proven Best Practices > Source: /docs/get-started/features#proven-best-practices ### Served Out Of The Box > Source: /docs/get-started/features#served-out-of-the-box The CAP runtimes in Node.js and Java provide many generic implementations for recurring tasks and best practices, distilled from proven SAP applications. This is a list of the most common tasks covered by the core frameworks: - [Serving CRUD Requests](../guides/services/served-ootb) - [Serving Nested Documents](../guides/services/served-ootb#deep-reads-and-writes) - [Serving (Fiori) Drafts](../guides/uis/fiori#draft-support) - [Serving Media Data](../guides/services/media-data) - [Searching Data](../guides/services/served-ootb#searching-data) - [Pagination](../guides/services/served-ootb#implicit-pagination) - [Sorting](../guides/services/served-ootb#implicit-sorting) - [Authentication](../node.js/authentication) - [Authorization](../guides/security/authorization) - [Localization / i18n](../guides/uis/i18n) - [Basic Input Validation](../guides/services/constraints) - [Auto-generated Keys](../guides/services/served-ootb#auto-generated-keys) - [Concurrency Control](../guides/services/served-ootb#concurrency-control)
> [!tip] > > This set of automatically served requests and covered related requirements, means that CAP's generic providers automatically serve the vast majority, if not all of the requests showing up in your applications, without you having to code anything for that, except for true custom domain logic. [See also the *Features Overview*](feature-matrix) {.learn-more} ### Enterprise Best Practices > Source: /docs/get-started/features#enterprise-best-practices On top of the common request-serving related things handled by CAP's generic providers, we provide out of the box solutions for these higher-level topic fields: - [Common Reuse Types & Aspects](../cds/common) - [Managed Data](../guides/domain/index#managed-data) - [Localized Data](../guides/uis/localized-data) - [Temporal Data](../guides/domain/temporal-data) - [Data Federation](https://github.com/SAP-samples/teched2022-AD265/wiki) → hands-on tutorial; capire guide in the making... - [Verticalization & Extensibility](../guides/extensibility/index)
> [!tip] > > These best practice solutions mostly stem from close collaborations with as well as contributions by real, successful projects and SAP products, and from ABAP. Which means they've been proven in many years of adoption and real business use. ### The 'Calesi' Effect > Source: /docs/get-started/features#the-calesi-effect '**Calesi**' stands for "**CA**P-**le**vel **S**ervice **I**ntegrations" as well as for an initiative we started late 2023 by rolling out the *CAP Plugins* technique, which promotes plugins and add-ons contributions not only by the CAP team, but also by - **SAP BTP technology units** and service teams (beyond CAP team) - **SAP application teams** - **Partners** & **Customers**, as well as - **Contributors** from the CAP community That initiative happened to be successful, and gave a boost to a steadily **growing ecosystem** around CAP with an active **inner source** and **open source** community on the one hand side. On the other hand, it resulted into an impressive collection of production-level add-ons. Here are some highlights **maintained by SAP teams**: - [GraphQL Adapter](../plugins/index#graphql-adapter) - [OData V2 Adapter](../plugins/index#odata-v2-proxy) - [WebSockets Adapter](../plugins/index#websocket) - [UI5 Dev Server](../plugins/index#ui5-dev-server) - [Open Telemetry → SAP Cloud Logging, Dynatrace, ...](../plugins/index#telemetry) - [Attachments → SAP Object Store /S3](../plugins/index#attachments) - [Attachments → SAP Document Management Service](../plugins/index#@cap-js/sdm) - [Messaging → SAP Cloud Application Event Hub](../plugins/index#event-hub) - [Change Tracking](../plugins/index#change-tracking) - [Notifications](../plugins/index#notifications) - [Audit Logging → SAP Audit Logging](../plugins/index#audit-logging) - [Personal Data Management → SAP DPI Services](../guides/security/data-privacy) - [Open Resource Discovery (ORD)](../plugins/index#ord-open-resource-discovery) > [!tip] > > This is just a subset and a snapshot of the growing number of plugins.
Find more in the [***CAP Plugins***](../plugins/index) page. ### Intrinsic Extensibility > Source: /docs/get-started/features#intrinsic-extensibility-underconstruction SaaS customers, verticalization partners, or your teams can... - Add/override annotations, translations, initial data - Add extension fields, entities, relationships - Add custom logic → in-app + side-by-side - Bundle and share that as reuse extension packages - Feature-toggle such pre-built extension packages per tenant All of these tasks are done in [the same way as you do in your own projects](concepts#intrinsic-extensibility): - Using the same techniques of CDS Aspects and Event Handlers - Including adaption and extensions of reuse types/models - Including extensions to framework-provided services And all of that is available out of the box, that is, without you having to create extension points. You would want to restrict who can extend what, though. ### Cloud-Native by Design > Source: /docs/get-started/features#cloud-native-by-design CAP's [service-centric paradigm](concepts#services) is designed from the ground up for cloud-scale enterprise applications. Its core design principles of flyweight, stateless services processing passive, immutable data, complemented by an intrinsic, ubiquitous [events-based](concepts#events) processing model greatly promote scalability and resilience. On top of that, several built-in facilities address many things to care about in cloud-based apps out of the box, such as: - **Multitenancy** → tenant *isolation* at runtime; *deploy*, *subscribe*, *update* handled by MTX - **Extensibility** → for customers to tailor SaaS apps to their needs → [see Intrinsic...](#intrinsic-extensibility) - **Security** → CAP+plugins do authentications, certificates, mTLS, ... - **Scalability** → by stateless services, passive data, messaging, ... - **Resilience** → by messaging, tx outbox, outboxed audit logging, ... - **Observability** → by logging + telemetry integrated to BTP services
> [!tip] > > Application developers don't have to and **should not have to care** about these complex non-functional requirements. Instead they should [focus on domain](#focus-on-domain), that is, their functional requirements, as much as possible. > [!caution] > > Many of these crucial cloud qualities are of complex and critical nature, for example, **multitenancy**, **isolation** and **security**, but also scalability and resilience isn't that easy to do right → it's a **high risk** to assume each application developer in each project is doing everything in the right ways ### Open _and_ Opinionated > Source: /docs/get-started/features#open-and-opinionated That might sound like a contradiction, but it isn't: While CAP certainly gives *opinionated* guidance, we do so without sacrificing openness and flexibility. At the end of the day, you stay in control of which tools or technologies to choose, or which architecture patterns to follow as depicted in the following table. | CAP is *Opinionated* in... | CAP is *Open* as... | | ------------------------------------------------------------ | ------------------------------------------------------------ | | **Platform-agnostic APIs** to avoid lock-ins to low-level stuff. | All abstractions follow a glass-box pattern that allows unrestricted access to lower-level things, if necessary | | **Best practices**, served out of the box by generic providers | You're free to do things your way in [custom handlers](../guides/services/custom-code), ... while CAP simply tries to get the tedious tasks out of your way. | | **Out-of-the-box support** for
**[SAP Fiori](https://developers.sap.com/topics/ui-development.html)** and **[SAP HANA](https://developers.sap.com/topics/hana.html)** | You can also choose other UI technologies, like [Vue.js](./bookshop#vuejs-uis). Other databases are supported as well. | | **Tools support** in [VS Code](../tools/cds-editors#vscode). | Everything in CAP can be done using the [`@sap/cds-dk`](../tools/cds-cli) CLI and any editor or IDE of your choice. |
> [!tip] > > And most important: As CAP itself is designed as an open framework, everything what's not covered by CAP today can be solved in application projects, in specific custom code, or by [generic handlers](concepts#extensible-framework) ... or by [plugins](../plugins/index) that you could build and contribute.
⇒ **Contributions *are* welcome!** ## Focus on Domain > Source: /docs/get-started/features#focus-on-domain CAP places **primary focus on domain**, by capturing _domain knowledge_ and _intent_ instead of imperative coding — that means, _What, not How_ — which promotes the following: - Close collaboration of _developers_ and _domain experts_ in domain modeling. - _Out-of-the-box_ implementations for _best practices_ and recurring tasks. - _Platform-agnostic_ approach to _avoid lock-ins_, hence _protecting investments_. ### Conceptual Modeling by CDS > Source: /docs/get-started/features#conceptual-modeling-by-cds-underconstruction ### Domain-Driven Design > Source: /docs/get-started/features#domain-driven-design-underconstruction ### Rapid Development > Source: /docs/get-started/features#rapid-development-underconstruction ### Minimal Distraction > Source: /docs/get-started/features#minimal-distraction-underconstruction ## Avoid Technical Debt > Source: /docs/get-started/features#avoid-technical-debt There are several definitions of technical debt found in media which all boil down to: Technical debt arises when speed of delivery is prioritized over quality.
The results must later be revised, thoroughly refactored, or completely rebuilt. {.quote} So, how could CAP help to avoid, or reduce the risks of piling up technical debt? ### Less Code → Less Mistakes > Source: /docs/get-started/features#less-code--less-mistakes Every line of code not written is free of errors. {.quote} Moreover: - Relieving app dev teams from overly technical disciplines not only saves efforts and time, it also avoids **severe mistakes** which can be made in these fields, for example, in tenant isolation and security. - **Best practices** reproduce proven solution patterns to recurring tasks, found and refined in successful application projects; your peers. - Having them **served out of the box** paves the path for their adoption, and hence reduces the likelihood of picking anti patterns instead. ### Single Points to Fix > Source: /docs/get-started/features#single-points-to-fix Of course, we also make mistakes and errors in CAP, but ... - We can fix them centrally and all CAP users benefit from that immediately. - Those bugs are frequently found and fixed by your peers in crime, before you even encounter them yourselves. - And this effect increases with steadily growing adoption of CAP that we see, ... - And with the open culture we established successfully, for example, **open issue reports** in GitHub, that is, the standard out there, instead of private support tickets — a relict of the past. ### Minimized Lock-Ins > Source: /docs/get-started/features#minimized-lock-ins Keeping pace with a rapidly changing world of volatile cloud technologies and platforms is a major challenge, as today's technologies that might soon become obsolete. CAP avoids such lock-ins and shields application developers from low-level things like: - **Authentication** and **Authorization**, incl. things like Certificates, mTLS, OAuth, ... - **Service Bindings** like K8s secrets, VCAP_SERVICES, ... - **Multitenancy**-related things, especially w.r.t. tenant isolation - **Messaging** protocols or brokers such as AMQP, MQTT, Webhooks, Kafka, Redis, ... - **Networking** protocols such as HTTP, gRCP, OData, GraphQL, SOAP, RFC, ... - **Audit Logging** → use the *Calesi* variant, which provides ultimate resilience - **Logs**, **Traces**, **Metrics** → CAP does that behind the scenes + provides *Calesi* variants - **Transaction Management** → CAP manages all transactions → don't mess with that! > [!tip] > > CAP not only abstracts these things at scale, but also does most things automatically in the background. In addition, it allows us to provide various implementations that encourage *Evolution w/o Disruption*, as well as fully functional mocks used in development. > [!caution] > > Things get dangerous when application developers have to deal with low-level security-related things like authentication, certificates, tenant isolation, and so on. Whenever this happens, it's a clear sign that something is seriously wrong. ## What about AI? > Source: /docs/get-started/features#what-about-ai-underconstruction - AI provides tremendous boosts to productivity → for example: - **Coding Assists** → for example, by [GitHub Copilot](https://github.com/features/copilot) in `.cds`, `.js`, even `.md` sources - **Code Analysis** → detecting bad practices → guiding to [best practices](concepts) - **Code Generation** → for example, for tests, test data, ... - **Project Scaffolding** → for quick head starts - **Search & Learning Assists** → like SAP Joule, ... - But this doesn't replace the need for **Human Intelligence**! - There's a different between a GPT-generated one-off thesis and long-lived enterprise software, which needs to adapt and scale to new requirements. By **embracing and advocating standard tooling** like VS Code and GitHub Actions, CAP ensures its projects are day-one beneficiaries of new AI features rolled out there. **CAP itself** is a major contribution to AI → its simple, clear concepts, uniform ways to implement and consume services make it easier for AIs and humans alike to reason about the system. Its openness and public visibility helped influence the very first AI models — even the original ChatGPT knew some CAP, and its baked-in knowledge of it has since improved dramatically. # Learning Sources > Source: /docs/get-started/learn-more Capire, Samples, Tutorials, Podcasts, ... {.subtitle} ## The _capire_ Documentation > Source: /docs/get-started/learn-more#the-capire-documentation This documentation — named _'capire'_, italian for understand — is the official documentation for CAP. It's organized as follows:
Section Description
    Guides that walk you through the most common tasks in CAP-based development and deployment.
    Reference documentation for these respective areas.
    Curated list of plugins for CAP.
    Release notes and release schedule.
    About support channels, community, ...
#### Callouts and Alerts > Source: /docs/get-started/learn-more#callouts-and-alerts We use [GitHub-flavored alerts](https://vitepress.dev/guide/markdown#github-flavored-alerts) to highlight important information in our documentation. Here are the different types of alerts or callouts you may encounter: > [!info] > Useful information that users should know, less important than notes. > [!note] > Useful information that users should know even when skimming content. > [!tip] > Helpful advice for doing things better or more easily. > [!important] > Key information users need to know to achieve their goal. > [!warning] > Urgent info that needs immediate attention to avoid problems. > [!caution] > Advises about risks or negative outcomes of certain actions. > [!danger] > Advises about risks or negative outcomes of certain actions. ## The _capire_ Samples > Source: /docs/get-started/learn-more#the-capire-samples The _capire_ samples at https://github.com/capire are the official and curated collection of samples for the SAP Cloud Application Programming Model, maintained by the CAP team. [![Screenshot of the capire GitHub organization page with sample repositories](assets/learn-more/capire-samples.png){}](https://github.com/capire) ## Featured Samples > Source: /docs/get-started/learn-more#featured-samples ![](assets/learn-more/poetry-slam.drawio.svg){} ### Partner Reference App > Source: /docs/get-started/learn-more#partner-reference-app The Partner Reference Application provides a “golden path” for SaaS providers on SAP Business Technology Platform (SAP BTP), featuring: - centralized identity and access management, - a common launchpad, - cross-application front-end navigation, - and secure back-channel integration. You also find the bill of materials and a sizing example. This addresses the question "Which BTP resources do I need to subscribe to and in what quantities?" and serves as a basis for cost calculation. ![](assets/learn-more/star-wars.png){} ### Star Wars App > Source: /docs/get-started/learn-more#star-wars-app SWAPI - the Star Wars API, a CAP-based adaptation of [swapi.dev](https://swapi.dev), a Python-based app that exposed data from the Star Wars movies. The many bi-directional, many-to-many relationships with the data provide a good basis for an SAP Cloud Application Programming Model and Fiori Draft UI sample. {.indent} ![](assets/learn-more/SuSaaS.png){} ### BTP SuSaaS App > Source: /docs/get-started/learn-more#btp-susaas-app The Sustainable SaaS (SuSaaS) sample application has been built in a partner collaboration to help interested developers, partners, and customers in developing multitenant Software as a Service applications using CAP and deploying them to the SAP Business Technology Platform (SAP BTP). ## The *qmacro* Series > Source: /docs/get-started/learn-more#the-qmacro-series ![](assets/learn-more/qmacro.png){} SAP Developer Advocate [DJ Adams](https://qmacro.org) has compiled a vast number of learning resources around CAP, published under the umbrella of _qmacro_, which are most recommended to both beginners and advanced users of CAP: ### Videos > Source: /docs/get-started/learn-more#videos - [The Art and Science of CAP](https://www.youtube.com/playlist?list=PL6RpkC85SLQAe45xlhIfhTYB9G0mdRVjI) (with Daniel Hutzel) - [Under the hood: CDS Expressions in CAP](https://www.youtube.com/playlist?list=PL6RpkC85SLQCEU8XcyqnA5wYEZGxMPm6B) (with Patrice Bender) - [Expert sessions: Getting started with CAP Node.js](https://www.youtube.com/playlist?list=PL6RpkC85SLQDxW_6INTtprrvZ3WiXT8u5) (with Daniel Schlachter) - [Back to basics: CAP Node.js](https://www.youtube.com/playlist?list=PL6RpkC85SLQBHPdfHQ0Ry2TMdsT-muECx) - [Back to basics: Managed associations in CAP](https://www.youtube.com/playlist?list=PL6RpkC85SLQCSm1JSRzeBE-BlkygKRAAF) - [Good to know: CAP Node.js](https://www.youtube.com/playlist?list=PL6RpkC85SLQDZ18v94otZSJJrpcNkPPV9) - [How things work: CAP Node.js plugins](https://www.youtube.com/playlist?list=PL6RpkC85SLQDwzbi9eVuMStRlpVMBqidQ) - [Hands-on SAP Dev general live stream series](https://www.youtube.com/playlist?list=PL6RpkC85SLQABOpzhd7WI-hMpy99PxUo0) - [Did you know?](https://qmacro.org/blog/posts/2026/02/06/series-of-did-you-know-videos/) featuring CAP topics ### Blog post series > Source: /docs/get-started/learn-more#blog-post-series - [The Art and Science of CAP](https://qmacro.org/blog/posts/2024/12/06/the-art-and-science-of-cap/) - [Local-first dev with CAP Node.js](https://qmacro.org/blog/posts/2026/05/11/local-first-dev-with-cap-node-js/) - [Under the hood: CDS Expressions in CAP](https://qmacro.org/blog/posts/2025/12/09/a-new-hands-on-sap-dev-mini-series-on-the-core-expression-language-in-cds/) - [Modules, modularity & reuse in CDS models](https://qmacro.org/blog/posts/2026/01/01/modules-modularity-and-reuse-in-cds-models/) - [CAP Node.js Plugins](https://qmacro.org/blog/posts/2024/12/30/cap-node-js-plugins/) - [All posts tagged with 'cap'](https://qmacro.org/tags/cap/) ### Selected individual articles > Source: /docs/get-started/learn-more#selected-individual-articles - [Shift left with CAP](https://qmacro.org/blog/posts/2026/02/09/shift-left-with-cap/) - [Five reasons to use CAP](https://qmacro.org/blog/posts/2024/11/07/five-reasons-to-use-cap/) - [CAP service authentication at design time and in production](https://qmacro.org/blog/posts/2026/06/19/cap-service-authentication-at-design-time-and-in-production/) - [Flattening the hierarchy with mixins](https://qmacro.org/blog/posts/2024/11/08/flattening-the-hierarchy-with-mixins/) - [A reCAP intro to the cds REPL](https://qmacro.org/blog/posts/2025/07/21/a-recap-intro-to-the-cds-repl/) - [A deep dive into OData and CDS annotations](https://qmacro.org/blog/posts/2023/03/10/a-deep-dive-into-odata-and-cds-annotations/) - [Using @capire modules from GitHub Packages](https://qmacro.org/blog/posts/2025/10/12/using-capire-modules-from-github-packages/) - [Modelling contained-in relationships with compositions in CDS](https://qmacro.org/blog/posts/2025/10/14/modelling-contained-in-relationships-with-compositions-in-cds/) - [A simple exploration of status transition flows in CAP](https://qmacro.org/blog/posts/2025/12/08/a-simple-exploration-of-status-transition-flows-in-cap/) ### Workshop exercise content > Source: /docs/get-started/learn-more#workshop-exercise-content - [Service integration with SAP Cloud Application Programming Model](https://github.com/SAP-samples/cap-service-integration-codejam) - [A hands-on tour of CAP](https://github.com/SAP-samples/cap-tour-hands-on/) - [Stay cool, stay local: CAP local development workshop](https://github.com/SAP-samples/cap-local-development-workshop) - [Hands-on with CAP CDS](https://github.com/SAP-samples/cap-cds-hands-on) ### Miscellaneous > Source: /docs/get-started/learn-more#miscellaneous - [The 'capref' collection – Axioms, Best Practices and Features](https://github.com/qmacro/capref/tree/main#readme) - [Integrating an external API into a CAP service](https://youtu.be/T_rjax3VY2E) ## SAP Learning Sources > Source: /docs/get-started/learn-more#sap-learning-sources - [SAP Learning Courses](https://learning.sap.com/courses?lsc_product=SAP+Cloud+Application+Programming+Model) - [SAP BTP Developers Guide](https://help.sap.com/docs/btp/btp-developers-guide/btp-developers-guide) - [Tutorials featured in there](https://help.sap.com/docs/btp/btp-developers-guide/tutorials-for-sap-cloud-application-programming-model) - [SAP Discovery Center Missions](https://discovery-center.cloud.sap/missionCatalog/?search=cap&product=32) ## Hands-Ons & CodeJams > Source: /docs/get-started/learn-more#hands-ons--codejams - [TechEd 2023 Hands-On AD264 – Build Extensions with CAP](https://github.com/SAP-samples/teched2023-AD264/) - [TechEd 2022 Hands-On AD264 – Verticalization, Customization, Composition](https://github.com/SAP-archive/teched2022-AD264) ## Blog Posts & Other Material > Source: /docs/get-started/learn-more#blog-posts--other-material - [Hybrid Testing and Alternative DBs](https://youtu.be/vqub4vJbZX8?si=j5ZkPR6vPb59iBBy)
by Thomas Jung - [Consume External Services](https://youtu.be/rWQFbXFEr1M)
by Thomas Jung - [Building a CAP app in 60 min](https://youtu.be/zoJ7umKZKB4)
by Martin Stenzig - [Surviving and Thriving with the SAP Cloud Application Programming Model](https://community.sap.com/t5/tag/CAPTricks/tg-p/board-id/technology-blog-sap)
by Max Streifeneder (2023) - [Multitenant SaaS applications on SAP BTP using CAP? Tried-and-True!](https://community.sap.com/t5/technology-blogs-by-sap/multitenant-saas-applications-on-sap-btp-using-cap-tried-and-true/ba-p/13541907)
by Martin Frick (2022) # Getting Help > Source: /docs/get-started/get-help Support Channels & Troubleshooting FAQs {.subtitle}
| To... | External | |-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------| | Ask Questions / Get Answers | [SAP Community](https://community.sap.com/t5/c-khhcw49343/SAP+Cloud+Application+Programming+Model/pd-p/9f13aee1-834c-4105-8e43-ee442775e5ce) | | Create issues / bug reports | [SAP Support Portal](https://support.sap.com) | | File feature requests | [SAP Influence Portal](https://influence.sap.com/sap/ino/#/campaign/2280) |
> [!tip] > If you encounter issues, check the Troubleshooting FAQs below before posting questions or creating issues in the support channels. ## Setup > Source: /docs/get-started/get-help#setup ### Can't start VS Code from Command Line on macOS > Source: /docs/get-started/get-help#cant-start-vs-code-from-command-line-on-macos To start VS Code via the `code` CLI, users on macOS must first run a command (*Shell Command: Install 'code' command in PATH*) to add the VS Code executable to the `PATH` environment variable. Read VS Code's [macOS setup guide](https://code.visualstudio.com/docs/setup/mac) for help. ### Check the Node.js version > Source: /docs/get-started/get-help#check-the-nodejs-version Run the latest LTS version of Node.js (even numbers: 22, 24). Avoid odd versions, as some modules with native parts may not install. Check version with: ```sh node -v ``` If you encounter an error like "_Node.js v1... or higher is required for `@sap/cds ...`._" on server startup, upgrade to the indicated version at the minimum, or even better, the most recent LTS version. For [Cloud Foundry](https://docs.cloudfoundry.org/buildpacks/node/index.html#runtime), use the `engines` field in _package.json_. [Learn more about the release schedule of **Node.js**.](https://github.com/nodejs/release#release-schedule/){.learn-more} [Learn about ways to install **Node.js**.](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm){.learn-more} ### Check access permissions on macOS or Linux > Source: /docs/get-started/get-help#check-access-permissions-on-macos-or-linux If you get error messages like `Error: EACCES: permission denied, mkdir '/usr/local/...'` when installing a global module like `@sap/cds-dk`, configure `npm` to use a different directory for global modules: ```sh mkdir ~/.npm-global ; npm set prefix '~/.npm-global' export PATH=~/.npm-global/bin:$PATH ``` Also add the last line to your user profile, for example, `~/.profile`, so that future shell sessions have changed `PATH` as well. [Learn more about other ways to handle this **error**.](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally){.learn-more} ### Check if your environment variables are properly set on Windows > Source: /docs/get-started/get-help#check-if-your-environment-variables-are-properly-set-on-windows Global npm installations are stored in a user-specific directory on your machine. On Windows, this directory usually is: ```sh C:\Users\\AppData\Roaming\npm ``` Verify that your `PATH`-environment variable contains this path. In addition, set the variable `NODE_PATH` to:
``C:\Users\\AppData\Roaming\npm\node_modules``. ### Updating CDS Versions > Source: /docs/get-started/get-help#updating-cds-versions * Design time tools like `cds init`: Install and update `@sap/cds-dk` globally using `npm i -g @sap/cds-dk`. * Node.js runtime: Maintain the version of `@sap/cds` in the top-level _package.json_ of your application in the `dependencies` section. [Learn more about recommendations on how to manage **Node.js dependencies**.](../node.js/best-practices#dependencies){.learn-more} * CAP Java SDK: Maintain the version in the _pom.xml_ of your Java module, which is located in the root folder. In this file, modify the property `cds.services.version`. ## Node.js > Source: /docs/get-started/get-help#nodejs ### How can I start Node.js apps on different ports? > Source: /docs/get-started/get-help#how-can-i-start-nodejs-apps-on-different-ports By default, CAP Node.js servers listen on port 4004, which might be occupied if other CAP servers are running in parallel. In this case, `cds watch` offers to pick a different port. ```shell cds watch ``` ```zsh ... EADDRINUSE - port 4004 is already in use by another server process. Press Return to restart with an arbitrary port. ... ``` Ports can be explicitly set with the `PORT` environment variable, the cds.server.port = 4005 config option, or the `--port` argument to `cds serve` and `cds watch`; see `cds help watch` for more. ### Why do I lose registered event handlers? > Source: /docs/get-started/get-help#why-do-i-lose-registered-event-handlers Node.js allows extending existing services, for example in mashup scenarios. This is commonly done on bootstrap time in `cds.on('served', ...)` handlers like so: #### DO: > Source: /docs/get-started/get-help#do ```js cds.on('served', ()=>{ const { db } = cds.services db.on('before',(req)=> console.log(req.event, req.path)) }) ``` It is important to note that by Node.js `emit` are synchronous operations, so, **avoid _any_ `await` operations** in there, as that might lead to race conditions. In particular, when registering additional event handlers with a service, as shown in the snippet above, this could lead to very hard to detect and resolve issues with handler registrations. So, for example, don't do this: #### DON'T: > Source: /docs/get-started/get-help#dont ```js cds.on('served', async ()=>{ const db = await cds.connect.to('db') // DANGER: will cause race condition !!! db.on('before',(req)=> console.log(req.event, req.path)) }) ``` ### Why does my app not show up in Dynatrace? > Source: /docs/get-started/get-help#why-does-my-app-not-show-up-in-dynatrace Requirements: - App start script is `cds-serve` (not `npx cds run`) - Dependency `@dynatrace/oneagent-sdk` is in _package.json_ ### Why are requests rejected with HANA timeout errors? > Source: /docs/get-started/get-help#why-are-requests-rejected-with-hana-timeout-errors ... with error messages like these: - _Acquiring client from pool timed out_ - _ResourceRequest timed out_ Verify that the SAP HANA database is accessible in your application's environment. This includes verifying the SAP HANA is either part of or mapped to your Cloud Foundry space or Kyma cluster and the IP addresses are [in an allowed range](https://help.sap.com/docs/HANA_SERVICE_CF/cc53ad464a57404b8d453bbadbc81ceb/71eb651f84274a0cb2f2b4380df91724.html). Connectivity issues are likely the root cause if you experience this error during application startup. [Learn how to set up SAP HANA instance mappings](https://help.sap.com/docs/hana-cloud/sap-hana-cloud-administration-guide/map-sap-hana-database-to-another-environment-context){.learn-more} If you frequently get this error during normal runtime operation your database client pool settings likely don't match the application's requirements. There are two possible root causes: | | Explanation | |----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| | _Root Cause 1_ | The maximum number of database clients in the pool is reached and additional requests wait too long for the next client. | | _Root Cause 2_ | The creation of a new connection to the database takes too long. | | _Solution_ | Adapt `max` or `acquireTimeoutMillis` with more appropriate values, according to the [documentation](../node.js/databases#databaseservice-configuration). | Ensure that database transactions are either committed or rolled back. This can work in two ways: 1. Couple it to your request (this happens automatically): Once the request is succeeded, the database service commits the transaction. If there was an error in one of the handlers, the database service performs a rollback. 2. For manual transactions (for example, by writing `const tx = cds.tx()`), you need to perform the commit/rollback yourself: `await tx.commit()`/`await tx.rollback()`. If you're using [@sap/hana-client](https://www.npmjs.com/package/@sap/hana-client), verify that the environment variable [`HDB_NODEJS_THREADPOOL_SIZE`](https://help.sap.com/docs/SAP_HANA_CLIENT/f1b440ded6144a54ada97ff95dac7adf/31a8c93a574b4f8fb6a8366d2c758f21.html?version=2.11) is adjusted appropriately. This variable specifies the amount of workers that concurrently execute asynchronous method calls for different connections. ### Why are requests rejected with `431` and not logged? > Source: /docs/get-started/get-help#why-are-requests-rejected-with-431-and-not-logged | | Explanation | |--------------|----------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | `431` occurs when the size of the request headers exceeds the maximum limit configured in the Node.js HTTP server. In this case, the Node.js HTTP server rejects the request during the initial parsing phase before it reaches CAP. Therefore, the request is not logged by the application. | | _Solution_ | Inspect the request headers and check their size. If large headers are required and cannot be reduced, increase the maximum allowed HTTP header size in Node.js by setting the following environment variable `NODE_OPTIONS="--max-http-header-size=65536"` | ### Why are requests rejected with `502`? > Source: /docs/get-started/get-help#why-are-requests-rejected-with-502 ... and do not even seem to reach the application? If you have long running requests, you may experience intermittent `502` errors that are characterized by being logged by the platform's router, but not by your CAP application. In most cases, this behavior is caused by the server having just closed the TCP connection without waiting for acknowledgement, so that the platform's load balancer still considers it open and uses it to forward the request. The issue is discussed in detail in this [blog post](https://adamcrowder.net/posts/node-express-api-and-aws-alb-502/#the-502-problem) by Adam Crowder. One solution is to increase the server's `keepAliveTimeout` to above that of the respective load balancer. The following example shows how to set `keepAliveTimeout` on the [http.Server](https://nodejs.org/api/http.html#class-httpserver) created by CAP. ```js const cds = require('@sap/cds') cds.once('listening', ({ server }) => { server.keepAliveTimeout = 3 * 60 * 1000 // > 3 mins }) module.exports = cds.server ``` [Watch the video to learn more about **Best Practices for CAP Node.js Apps**.](https://www.youtube.com/watch?v=WTOOse-Flj8&t=87s){.learn-more} ### Why are requests rejected with `504`? > Source: /docs/get-started/get-help#why-are-requests-rejected-with-504 ... mostly after 30 seconds, even though the application continues processing the request? | | Explanation | |--------------|----------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | Most probably, this error is caused by the destination timeout of the App Router. | | _Solution_ | Set your own `timeout` configuration of [@sap/approuter](https://www.npmjs.com/package/@sap/approuter#destinations). | ### How to fix `no service definition found for `? > Source: /docs/get-started/get-help#how-to-fix-no-service-definition-found-for-xyz | | Explanation | |--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | Most probably, the service name in the `requires` section does not match the served service definition. | | _Solution_ | Set the `.service` property in the respective `requires` entry. See [cds.connect()](../node.js/cds-connect#cdsrequiressrvservice) for more details. | ### Why does my remote service call not work? > Source: /docs/get-started/get-help#why-does-my-remote-service-call-not-work | | Explanation | |--------------|-----------------------------------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | The destination, the remote system or the request details are not configured correctly. | | _Solution_ | To further troubleshoot the root cause, you can enable logging with environment variables `SAP_CLOUD_SDK_LOG_LEVEL=silly` and `DEBUG=remote`. | ### Why is a destination not correctly retrieved by SAP Cloud SDK? > Source: /docs/get-started/get-help#why-is-a-destination-not-correctly-retrieved-by-sap-cloud-sdk | | Explanation | |--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | If the application has a service binding with the same name as the requested destination, the SAP Cloud SDK prioritizes the service binding. This service has different endpoints than the originally targeted remote service. For more information, refer to the [SAP Cloud SDK documentation](https://sap.github.io/cloud-sdk/docs/js/features/connectivity/destinations#referencing-destinations-by-name). | | _Solution_ | Use different names for the service binding and the destination. | ### Why are type definitions for `@sap/cds` not found or incomplete? > Source: /docs/get-started/get-help#why-are-type-definitions-for-sapcds-not-found-or-incomplete | | Explanation | |----------------|-----------------------------------------------------------------------| | _Root Cause 1_ | The package `@cap-js/cds-types` is not installed. | | _Solution 1_ | Install the package as a dev dependency. | | _Root Cause 2_ | Symlink is missing. | | _Solution 2_ | Try `npm rebuild` or add `@cap-js/cds-types` in your _tsconfig.json_. | #### Install as dev dependency > Source: /docs/get-started/get-help#install-as-dev-dependency Install type definitions by adding the `typescript` facet: ::: code-group ```sh [facet] cds add typescript ``` ```sh [manually] npm i -D @cap-js/cds-types ``` ::: #### Fix missing symlink > Source: /docs/get-started/get-help#fix-missing-symlink Installing `@cap-js/cds-types` leverages VS Code's automatic type resolution mechanism by symlinking the package in `node_modules/@types/sap__cds` in a postinstall script. If you find that this symlink is missing, try `npm rebuild` to trigger the postinstall script again. If the symlink doesn't persist, explicitly configure _tsconfig.json_: ::: code-group ```json [tsconfig.json] { "compilerOptions": { "types": ["@cap-js/cds-types"], } } ``` ::: For incomplete types, report issues in [the `@cap-js/cds-types` repository](https://github.com/cap-js/cds-types/issues/new/choose). ### How to fix "`tar: Error is not recoverable: exiting now`"? > Source: /docs/get-started/get-help#how-to-fix-tar-error-is-not-recoverable-exiting-now If you get this error (for example, when building MTX resources), install the tar library for better Windows compatibility: ```sh npm add -D tar ``` On macOS and Linux, the built-in implementation continues to be used. ### How to fix "SqlError: invalid table name: Could not find table/view ..."? > Source: /docs/get-started/get-help#how-to-fix-sqlerror-invalid-table-name-could-not-find-tableview- On Windows there's a known issue, where `cds build --production` may silently fail to create the _resources.tgz_ in the MTX sidecar build output. After deployment and subscription, you can then notice the mentioned SqlError or similar error messages that point to tables/views not being available. :::warning The build log will incorrectly report the file as written. ::: To fix this on Windows, install the tar library: ```sh npm add -D tar ``` Even with this dependency added, on macOS and Linux the built-in implementation continues to be used. ### How to fix "`Error: Could not locate the bindings file. Tried: ...`" > Source: /docs/get-started/get-help#how-to-fix-error-could-not-locate-the-bindings-file-tried- You probably have `ignore-scripts` set to `true` in your npm configuration. While this is generally a good idea, it prevents certain libraries, like `better-sqlite3`, from running a required postinstall script. To solve this, you can either temporarily allow scripts and run a reinstall, or manually run the build script for the library in question. For `better-sqlite3`, run `npm run build-release` from within the _node_modules/better-sqlite3_ directory. The first line after the error message shows the relevant _node_modules_ directory. ## Java > Source: /docs/get-started/get-help#java ### How to bypass authorization checks? > Source: /docs/get-started/get-help#how-to-bypass-authorization-checks Use `privilegedUser()` when [defining](../java/event-handlers/request-contexts#defining-requestcontext) your own `RequestContext`. This introduces a user that passes all authorization restrictions. Useful when calling a restricted service through the [local service consumption API](../java/services) regardless of the original user's authorizations or in a background thread. ### Why do I get a "User should not exist" error during build time? > Source: /docs/get-started/get-help#why-do-i-get-a-user-should-not-exist-error-during-build-time | | Explanation | |--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | You've [explicitly configured a mock](../java/security#custom-mock-users) user with a name that is already used by a [preconfigured mock user](../java/security#preconfigured-mock-users). | | _Solution_ | Rename the mock user and build your project again. | ### Why do I get an "Error on server start"? > Source: /docs/get-started/get-help#why-do-i-get-an-error-on-server-start There could be a mismatch between your locally installed Node.js version and the version that is used by the `cds-maven-plugin`. The result is an error similar to the following: ```sh ❗️ ERROR on server start: ❗️ Error: The module '/home/user/....node' was compiled against a different Node.js version using ``` To fix this, either switch the Node.js version using a Node version manager, or add the Node version to your _pom.xml_ as follows: ```xml v24.14.1 ``` [Learn more about the install-node goal.](../java/assets/cds-maven-plugin-site/install-node-mojo.html){.learn-more target="_blank"} ### How can I expose custom REST APIs with CAP? > Source: /docs/get-started/get-help#how-can-i-expose-custom-rest-apis-with-cap To expose additional REST APIs not covered by CAP's protocol adapters (for example, OData V4), implement your own Spring Web MVC RestController. Common examples include CSV file uploads or custom REST endpoints. Your RestController can fully leverage CAP Java APIs. You'll typically interact with services and the database through the [local service consumption API](../java/services). Learn more: [Spring docs](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc), [Spring Boot docs](https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-spring-mvc), and this [tutorial](https://spring.io/guides/gs/serving-web-content/). ### How can I build a CAP Java application without SQL database? > Source: /docs/get-started/get-help#how-can-i-build-a-cap-java-application-without-sql-database The project skeleton generated by the CAP Java archetype adds the relevant Spring Boot and CAP Java dependencies, so that SQL database is supported by default. However, using an SQL database in CAP Java is fully optional. You can also develop CAP applications that don't use persistence at all. To remove the SQL database support, you need to exclude the JDBC-related dependencies of Spring Boot and CAP Java. This means that CAP Java won't create a Persistence Service instance. ::: tip Default Application Service event handlers delegate to Persistence Service You need to implement your own custom handlers in case you remove the SQL database support. ::: You can exclude those dependencies from the `cds-starter-spring-boot` dependency in the `srv/pom.xml`: ```xml com.sap.cds cds-starter-spring-boot com.sap.cds cds-feature-jdbc org.springframework.boot spring-boot-starter-jdbc ``` In addition you might want to remove the H2 dependency, which is included in the `srv/pom.xml` by default as well. If you don't want to exclude dependencies completely, but make sure that an in-memory H2 database **isn't** used, you can disable Spring Boot's `DataSource` auto-configuration, by annotating the `Application.java` class with `@SpringBootApplication(exclude = org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class)`. In that mode CAP Java however can still react on explicit data source configurations or database bindings. ### What to do about Maven-related errors in Eclipse's _Problems_ view? > Source: /docs/get-started/get-help#what-to-do-about-maven-related-errors-in-eclipses-problems-view - In _Problems_ view, execute _Quick fix_ from the context menu if available. If Eclipse asks you to install additional Maven Eclipse plug-ins to overcome the error, do so. - Errors like _'Plugin execution not covered by lifecycle configuration: org.codehaus.mojo:exec-maven-plugin)_ can be ignored. Do so in _Problems_ view > _Quick fix_ context menu > _Mark goal as ignored in Eclipse preferences_. - In case, there are still errors in the project, use _Maven > Update Project..._ from the project's context menu. ## OData > Source: /docs/get-started/get-help#odata ### How do I generate an OData response in Node.js for Error 404? > Source: /docs/get-started/get-help#how-do-i-generate-an-odata-response-in-nodejs-for-error-404 If your application(s) endpoints are served with OData and you want to change the standard HTML response to an OData response, adapt the following snippet to your needs and add it in your [custom _server.js_ file](../node.js/cds-server#custom-server-js). ```js let app cds.on('bootstrap', a => { app = a }) cds.on('served', () => { app.use((req, res, next) => { // > unhandled request res.status(404).json({ message: 'Not Found' }) }) }) ``` ### Why do some requests fail if I set `@odata.draft.enabled` on my entity? > Source: /docs/get-started/get-help#why-do-some-requests-fail-if-i-set-odatadraftenabled-on-my-entity The annotation `@odata.draft.enabled` is very specific to SAP Fiori elements, only some requests are allowed. For example it's forbidden to freely add `IsActiveEntity` to `$filter`, `$orderby` and other query options. The technical reason for that is that active instances and drafts are stored in two different database tables. Mixing them together is not trivial, therefore only some special cases are supported. ## SQLite > Source: /docs/get-started/get-help#sqlite ### How do I install SQLite on Windows? > Source: /docs/get-started/get-help#how-do-i-install-sqlite-on-windows * From the [SQLite page](https://sqlite.org/download.html), download the precompiled binaries for Windows `sqlite-tools-win*.zip`. * Create a folder _C:\sqlite_ and unzip the downloaded file in this folder to get the file `sqlite3.exe`. * Start using SQLite directly by opening `sqlite3.exe` from the folder _sqlite_ or from the command line window opened in _C:\sqlite_. * _Optional_: Add _C:\sqlite_ in your PATH environment variable. As soon as the configuration is active, you can start using SQLite from every location on your Windows installation. * Use the command _sqlite3_ to connect to the in-memory database: ```sh C:\sqlite>sqlite3 SQLite version ... Enter ".help" for instructions Connected to a transient in-memory database. Use ".open FILENAME" to reopen on a persistent database. sqlite> ``` If you want to test further, use _.help_ command to see all available commands in _sqlite3_. In case you want a visual interface tool to work with SQLite, you can use [SQLite Viewer](https://marketplace.visualstudio.com/items?itemName=qwtel.sqlite-viewer). It's available as an extension for VS Code and integrated in SAP Business Application Studio. ## SAP HANA > Source: /docs/get-started/get-help#sap-hana ### How to get an SAP HANA Cloud instance for SAP BTP? > Source: /docs/get-started/get-help#how-to-get-an-sap-hana-cloud-instance-for-sap-btp To configure this service in the SAP BTP cockpit on trial, refer to the [SAP HANA Cloud Onboarding Guide](https://www.sap.com/documents/2021/09/7476f8c4-f77d-0010-bca6-c68f7e60039b.html). See [SAP HANA Cloud](https://help.sap.com/docs/HANA_CLOUD) documentation or visit the [SAP HANA Cloud community](https://pages.community.sap.com/topics/hana/cloud) for more details. ::: warning HANA needs to be restarted on trial accounts On trial, your SAP HANA Cloud instance will be automatically stopped overnight, according to the server region time zone. That means you need to restart your instance every day before you start working with your trial. ::: [Learn more about SAP HANA Cloud trying out tutorials in the Tutorial Navigator.](https://developers.sap.com/mission.hana-cloud-database-get-started.html){.learn-more} ### How do I resolve deployment errors? > Source: /docs/get-started/get-help#how-do-i-resolve-deployment-errors #### Deployment fails — _Cyclic dependencies found_ or _Cycle between files_ > Source: /docs/get-started/get-help#deployment-fails--cyclic-dependencies-found-or-cycle-between-files | | Explanation | |--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | This is a known issue with older HDI/HANA versions, which are offered on trial landscapes. | | _Solution_ | Apply the workaround of adding `--treat-unmodified-as-modified` as argument to the `hdi-deploy` command in _db/package.json_. This option redeploys files, even if they haven't changed. If you're the owner of the SAP HANA installation, ask for an upgrade of the SAP HANA instance. | #### Deployment fails — _Version incompatibility_ > Source: /docs/get-started/get-help#deployment-fails--version-incompatibility | | Explanation | |--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | An error like `Version incompatibility for the ... build plugin: "2.0.x" (installed) is incompatible with "2.0.y" (requested)` indicates that your project demands a higher version of SAP HANA than what is available in your org/space on SAP BTP, Cloud Foundry environment. The error might not occur on other landscapes for the same project. | | _Solution_ | Lower the version in file `db/src/.hdiconfig` to the one given in the error message. If you're the owner of the SAP HANA installation, ask for an upgrade of the SAP HANA instance. | #### Deployment fails - _unable to get local issuer certificate_ > Source: /docs/get-started/get-help#deployment-fails---unable-to-get-local-issuer-certificate + _Could not connect to any host... - unable to get local issuer certificate_ + MTX sidecar crashes with HTTP error _429 (Too Many Requests)_ | | Explanation | |--------------|--------------------------------| | _Root Cause_ | A change of SAP's root certificate from _DigiCert Global Root CA_ to _DigiCert TLS RSA4096 Root G5_ leads to deployment failures because older certificates get rejected by too old SAP HANA driver versions and/or older service bindings in SAP HANA Cloud. | | _Solution_ | For Node.js applications, update the `hdb` driver to the latest version. [See SAP note 3397584](https://me.sap.com/notes/3397584) for details. See the [SAP HANA blog post](https://community.sap.com/t5/technology-blog-posts-by-sap/action-required-update-your-certificate-trust-stores-for-enhanced-sap-hana/ba-p/14332703) for the broader context. | #### Deployment fails — _Cannot create certificate store_ > Source: /docs/get-started/get-help#deployment-fails--cannot-create-certificate-store | | Explanation | |--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | If you deploy to SAP HANA from a local Windows machine, this error might occur if the SAP CommonCryptoLib isn't installed on this machine. | | _Solution_ | To install it, follow these [instructions](https://help.sap.com/docs/SAP_DATA_SERVICES/e54136ab6a4a43e6a370265bf0a2d744/c049e28431ee4e8280cd6f5d1a8937d8.html). If this doesn't solve the problem, also set the environment variables as [described here](https://help.sap.com/docs/SAP_HANA_PLATFORM/e7e79e15f5284474b965872bf0fa3d63/463d3ceeb7404eca8762dfe74e9cff62.html). | #### Deployment fails — > Source: /docs/get-started/get-help#deployment-fails- + _Failed to get connection for database_ + _Connection failed (RTE:[300015] SSL certificate validation failed_ + _Cannot create SSL engine: Received invalid SSL Record Header_ | | Explanation | |--------------|----------------------------------------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | Your SAP HANA Cloud instance is stopped. | | _Solution_ | [Start your SAP HANA Cloud instance.](https://help.sap.com/docs/HANA_CLOUD/9ae9104a46f74a6583ce5182e7fb20cb/fe8cbc3a13b4425990880bac3a5d50d9.html) | #### Deployment fails — SSL certificate validation failed: error code: 337047686 > Source: /docs/get-started/get-help#deployment-fails--ssl-certificate-validation-failed-error-code-337047686 | | Explanation | |--------------|-------------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | The `@sap/hana-client` can't verify the certificate because of missing system toolchain dependencies. | | _Solution_ | Make sure [`ca-certificates`](https://packages.ubuntu.com/focal/ca-certificates) is installed on your Docker container. | #### Deployment fails — _Cannot create SSL engine: Received invalid SSL Record Header_ > Source: /docs/get-started/get-help#deployment-fails--cannot-create-ssl-engine-received-invalid-ssl-record-header | | Explanation | |--------------|----------------------------------------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | Your SAP HANA Cloud instance is stopped. | | _Solution_ | [Start your SAP HANA Cloud instance.](https://help.sap.com/docs/HANA_CLOUD/9ae9104a46f74a6583ce5182e7fb20cb/fe8cbc3a13b4425990880bac3a5d50d9.html) | #### Deployment fails — _Error: HDI make failed_ > Source: /docs/get-started/get-help#deployment-fails--error-hdi-make-failed | | Explanation | |--------------|--------------------------------------------------------------------------------------| | _Root Cause_ | Your configuration isn't properly set. | | _Solution_ | Configure your project as described in [Using Databases](../guides/databases/index). | #### Deployment fails — _Connection failed (RTE:[89008] Socket closed by peer_ > Source: /docs/get-started/get-help#deployment-fails--connection-failed-rte89008-socket-closed-by-peer #### Hybrid testing connectivity issue — _ResourceRequest timed out_ {} > Source: /docs/get-started/get-help#hybrid-testing-connectivity-issue--resourcerequest-timed-out- | | Explanation | |--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | Your IP isn't part of the filtering you configured when you created an SAP HANA Cloud instance. This error can also happen if you exceed the [maximum number of simultaneous connections to SAP HANA Cloud (1000)](https://help.sap.com/docs/HANA_CLOUD_DATABASE/c1d3f60099654ecfb3fe36ac93c121bb/20a760537519101497e3cfe07b348f3c.html). | | _Solution_ | Configure your SAP HANA Cloud instance [to accept your IP](https://help.sap.com/docs/HANA_SERVICE_CF/cc53ad464a57404b8d453bbadbc81ceb/71eb651f84274a0cb2f2b4380df91724.html). If configured correctly, check if the number of database connections are exceeded. Make sure your [pool configuration](../node.js/databases#pool) does not allow more than 1000 connections. |
#### Deployment fails — _... build plugin for file suffix "hdbmigrationtable" [8210015]_ > Source: /docs/get-started/get-help#deployment-fails---build-plugin-for-file-suffix-hdbmigrationtable-8210015 | | Explanation | |--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | Your project configuration is missing some configuration in your _.hdiconfig_ file. | | _Solution_ | Use `cds add hana` to add the needed configuration to your project. Or maintain the _hdbmigrationtable_ plugin in your _.hdiconfig_ file manually: `"hdbmigrationtable": { "plugin_name": "com.sap.hana.di.table.migration" }` | #### Deployment fails — _In USING declarations only main artifacts can be accessed, not sub artifacts of \_ > Source: /docs/get-started/get-help#deployment-fails--in-using-declarations-only-main-artifacts-can-be-accessed-not-sub-artifacts-of-name This error occurs if all of the following applies: + You [added native SAP HANA objects](../guides/databases/hana-native#add-native-objects) to your CAP model. + You used deploy format `hdbcds`. + You didn't use the default naming mode `plain`. | | Explanation | |--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | The name/prefix of the native SAP HANA object collides with a name/prefix in the CAP CDS model. | | _Solution_ | Change the name of the native SAP HANA object so that it doesn't start with the name given in the error message and doesn't start with any other prefix that occurs in the CAP CDS model. If you can't change the name of the SAP HANA object, because it already exists, define a synonym for the object. The name of the synonym must follow the naming rule to avoid collisions (root cause). | #### Deployment fails — _The include_filter definitions ... use key values that are not disjunct_ > Source: /docs/get-started/get-help#deployment-fails--the-includefilter-definitions--use-key-values-that-are-not-disjunct | | Explanation | |--------------|----------------------------| | _Root Cause_ | You have changed from data files like `xxx_texts.csv` to `xxx_texts_de.csv`. | | _Solution_ | Add entries in `undeploy.json`. | If you've already deployed your application using translation files _without_ language key like `xxx_texts.csv` and now want to use language-specific translation files like `xxx_texts_de.csv`, you have to **undeploy the existing translation files**. Add the corresponding file entries, for example ```json [ ... "src/gen/data/xxx_texts.hdbtabledata", "src/gen/data/xxx_texts.csv" ] ``` to your _undeploy.json_. Otherwise, you will get a deployment error similar to this one: ``` The include_filter definitions in the table import files .../xxx_texts.hdbtabledata and .../xxx_texts_de.hdbtabledata use key values that are not disjunct; .../xxx_texts.hdbtabledata defines no include_filters which prohibits other imports from importing into the same table. ``` ### Why is removed sample _.csv_ deployed and overwriting existing data? > Source: /docs/get-started/get-help#why-is-removed-sample-csv-deployed-and-overwriting-existing-data | | Explanation | |--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | _Root Cause_ | SAP HANA still claims exclusive ownership of the data that was once deployed through `hdbtabledata` artifacts, even though the CSV files are now deleted in your project. | | _Solution_ | Add an _undeploy.json_ file to the root of your database module (the _db_ folder by default). This file defines the files **and data** to be deleted. See section [HDI Delta Deployment and Undeploy Allow List](https://help.sap.com/docs/HANA_CLOUD_DATABASE/c2b99f19e9264c4d9ae9221b22f6f589/ebb0a1d1d41e4ab0a06ea951717e7d3d.html) for more details. | #### How do I keep existing data? > Source: /docs/get-started/get-help#how-do-i-keep-existing-data If you want to keep the data from _.csv_ files and data you've already added, apply [SAP Note 2922271](https://me.sap.com/notes/2922271). Depending on whether you have a single-tenant or multi-tenant application, see the following details for how to set the `path_parameter` and `undeploy` parameters: :::details Single-tenant applications {open} Use the _db/undeploy.json_ file as given in the SAP note. The _package.json_ file that is mentioned in the SAP note is located in the _db/_ folder. - If you don't find a _db/package.json_ file, use _gen/db/package.json_ (created by `cds build`) as a template and copy it to _db/package.json_. - After the modification, run `cds build --production` and verify your changes have been copied to _gen/db/package.json_. - Don't modify _gen/db/package.json_ as it is overwritten on every build. ::: :::details Multi-tenant applications Instead of configuring the static deployer application in _db/package.json_, use environment variable [`HDI_DEPLOY_OPTIONS`](https://help.sap.com/docs/SAP_HANA_PLATFORM/4505d0bdaf4948449b7f7379d24d0f0d/a4bbc2dd8a20442387dc7b706e8d3070.html), the `cds` configuration in _package.json_, or add the options to the model update request as `hdi` parameter: CDS configuration for [Deployment Service](../guides/multitenancy/mtxs#deployment-config) ```json "cds.xt.DeploymentService": { "hdi": { "deploy": { "undeploy": [ "src/gen/data/my.bookshop-Books.hdbtabledata" ], "path_parameter": { "src/gen/data/my.bookshop-Books.hdbtabledata:skip_data_deletion": "true" } }, ... } } ``` Options in [Saas Provisioning Service upgrade API](../guides/multitenancy/mtxs#example-usage-1) call payload ```json { "tenants": ["*"], "_": { "hdi": { "deploy": { "undeploy": [ "src/gen/data/my.bookshop-Books.hdbtabledata" ], "path_parameter": { "src/gen/data/my.bookshop-Books.hdbtabledata:skip_data_deletion": "true" } } } } } ``` ::: After you have successfully deployed these changes to all affected HDI (tenant) containers (in all spaces, accounts etc.), you can remove the configuration again. ### How can a table function access the logged in user? > Source: /docs/get-started/get-help#how-can-a-table-function-access-the-logged-in-user The _cds runtime_ sets the session variable `APPLICATIONUSER`. This should always reflect the logged in user. Do not use a `XS_` prefix. ## MTXS > Source: /docs/get-started/get-help#mtxs ### Why is my MTX sidecar is killed with 'Exit status 137'? > Source: /docs/get-started/get-help#why-is-my-mtx-sidecar-is-killed-with-exit-status-137 In this case, the process was killed by a `SIGKILL` signal, typically because it exceeded its resource limits, for example memory or CPU, causing the container platform to terminate it. ::: tip Distinguish extensibility and non-extensibility scenarios While out-of-memory issues are more common, with **extensibility enabled** you’re more likely to run into CPU bottlenecks due to expensive compilations that need to be performed at (MTX) runtime. ::: MTX uses **four parallel workers** by default to perform tenant upgrades. If your project exceeds a certain complexity threshold, you might run into these resource bottlenecks. We advise you to **follow this algorithm** to mitigate resource overload: 1. **Decrease your model complexity**: Ask yourself, is your current domain model a good compression of your business domain? Decreasing complexity here will have positive trickle-down effects, including tenant upgrade performance. 2. **Increase resources (scale up)**: Increase the RAM assigned to your MTX sidecar or upgrade task. This is typically done in deployment resources like _mta.yaml_ (Cloud Foundry) or _values.yaml_ (Kyma). [Learn more about database upgrade task configuration](../guides/multitenancy/#update-database-schema){.learn-more} ::: info In Cloud Foundry, CPU shares scale with memory As there is no way to increase CPU independently from memory, your memory configuration might be a bottleneck even if the process is killed due to CPU spikes. ::: 3. **Decrease workers in async MTX operations**: When scaling up resources is no longer feasible, you can run with fewer parallel migrations: ```jsonc "cds": { "requires": { "multitenancy": { "jobs": { "workerSize": 3 // default: 4 } } } } ``` > This won't affect application runtime performance. 4. **Increase the number of MTX sidecars (scale out)**: To compensate for eventual performance losses from **3.**, distribute the work across multiple sidecars. ### How do I get detailed SAP HANA deployment logs > Source: /docs/get-started/get-help#how-do-i-get-detailed-sap-hana-deployment-logs The deployment logs are part of the [application logs](#cflogs-recent). To avoid problems with the logging infrastructure, the default detail level of the deployment logs is limited to logs printed to `stderr`. To get more details, you need to increase the log level by setting the environment variable `DEBUG=deploy`. ### Why do I get 'Extensions exist, but extensibility is disabled'? > Source: /docs/get-started/get-help#why-do-i-get-extensions-exist-but-extensibility-is-disabled This message indicates that extensions exist, but the application is not configured for extensibility. To avoid accidental data loss from removing existing extensions from the database, the upgrade is blocked. ::: danger If data loss is acceptable `cds.requires.['cds.xt.DeploymentService'].upgrade.skipExtensionCheck = true` in your CDS configuration enables you to skip this check. ::: ### Why does `cds login` fail with a 401 error? > Source: /docs/get-started/get-help#why-does-cds-login-fail-with-a-401-error See [How to configure your App Router](../guides/extensibility/customization#app-router) to verify your setup. [Find the documentation on `cds login`](../guides/extensibility/customization#cds-login){.learn-more} ### Why does my subscription fail with "Subaccount verification failed" > Source: /docs/get-started/get-help#why-does-my-subscription-fail-with-subaccount-verification-failed When using HANA TMS v2, the message "Subaccount verification failed" indicates that you are trying to create a tenant container for a HANA tenant that was created in a different subaccount. Most probably, you are using the same `hana_tenant_prefix` and `tenant_id` as another application that has been deployed in another subaccount. See how to [handle HANA tenants with HANA TMS v2](../guides/multitenancy/index.md#handle-sap-hana-tenants) to avoid this situation. ## BTP > Source: /docs/get-started/get-help#btp ### How do I get an account on the SAP Business Technology Platform? > Source: /docs/get-started/get-help#how-do-i-get-an-account-on-the-sap-business-technology-platform For a start, create your [Trial Account](https://account.hanatrial.ondemand.com/).
## MTA > Source: /docs/get-started/get-help#mta ### Why does my MTA build fail with _package-lock.json_ issues? > Source: /docs/get-started/get-help#why-does-my-mta-build-fail-with-package-lockjson-issues If `mbt build` fails with `The 'npm ci' command can only install with an existing package-lock.json`, this means that such a file is missing in your project. - Create the _package-lock.json_ file with a regular [`npm update`](https://docs.npmjs.com/cli/v8/commands/npm-update) command. - If the file was not created, make sure to enable it with `npm config set package-lock true` and repeat the previous command. > The _package-lock.json_ should be added to version control. Make sure that _.gitignore_ does __not__ contain it. The purpose of _package-lock.json_ is to pin your project's dependencies to allow for reproducible builds. [Learn more about dependency management in Node.js.](../node.js/best-practices#dependencies){.learn-more} ### Why does my MTA build fail for other reasons? > Source: /docs/get-started/get-help#why-does-my-mta-build-fail-for-other-reasons - Make sure to use the latest version of the [Cloud MTA Build Tool (MBT)](https://sap.github.io/cloud-mta-build-tool/). - Consult the [Cloud MTA Build Tool documentation](https://sap.github.io/cloud-mta-build-tool/usage/) for further information, for example, on the available tool options. ### How can I define the build order between MTA modules? > Source: /docs/get-started/get-help#how-can-i-define-the-build-order-between-mta-modules By default, the Cloud MTA Build Tool executes module builds in parallel. If you want to enforce a specific build order, for example, because one module build relies on the outcome of another one, check the [Configuring build order](https://sap.github.io/cloud-mta-build-tool/configuration/#configuring-build-order) section in the tool documentation. ### How do I undeploy an MTA? > Source: /docs/get-started/get-help#how-do-i-undeploy-an-mta `cf undeploy ` deletes an MTA (use `cf mtas` to find the MTA ID). Use `--delete-services`, `--delete-service-keys` and `--delete-service-brokers` parameters to also wipe services, service keys, or service brokers. ::: danger This also deletes the HDI containers with the application data. ::: ### How can I reduce MTA archive size during development? > Source: /docs/get-started/get-help#how-can-i-reduce-mta-archive-size-during-development You can reduce MTA archive sizes, and thereby speedup deployments, by omitting `node_module` folders. First, add a file `less.mtaext` with the following content: ::: code-group ```yaml [less.mtaext] _schema-version: '3.1' ID: bookshop-small extends: capire.bookshop modules: - name: bookshop-srv build-parameters: ignore: ["node_modules/"] ``` ::: Now you can build the archive with: ```sh mbt build -t gen --mtar mta.tar -e less.mtaext ``` ::: warning Not recommended for production deployments - For test deployments during _development_. For _production_ deployments, self-contained archives are preferrable. - If all your dependencies are available in _public_ registries like npmjs.org or Maven Central. Dependencies from _corporate_ registries are not resolvable in this mode. ::: ## Cloud Foundry > Source: /docs/get-started/get-help#cloud-foundry ### How do I get logs from my application in Cloud Foundry? > Source: /docs/get-started/get-help#how-do-i-get-logs-from-my-application-in-cloud-foundry You can use the Cloud Foundry CLI to retrieve recent logs: ```sh cf logs --recent ``` ::: tip Stream logs to your terminal If you omit the option `--recent`, you can run this command in parallel to your deployment and see the logs as they come in. ::: ### How do I resolve errors with the `cf` CLI? > Source: /docs/get-started/get-help#how-do-i-resolve-errors-with-the-cf-cli #### Installation fails — _mkdir ... The system cannot find the path specified_ > Source: /docs/get-started/get-help#installation-fails--mkdir--the-system-cannot-find-the-path-specified This is a known [issue](https://github.com/cloudfoundry/docs-cf-cli/issues/57) on Windows. The fix is to set the `HOMEDRIVE` environment variable to `C:`. In any `cmd` shell session, you can do so with `SET HOMEDRIVE=C:`
Also, make sure to persist the variable for future sessions in the system preferences. See [How do I set my system variables in Windows](https://superuser.com/questions/949560/how-do-i-set-system-environment-variables-in-windows-10) for more details. #### `cf` commands fail — _Error writing config_ > Source: /docs/get-started/get-help#cf-commands-fail--error-writing-config This is the same issue as with the installation error above. ### Why does my app deployment fail with "No space left on device"? > Source: /docs/get-started/get-help#why-does-my-app-deployment-fail-with-no-space-left-on-device If on deployment to Cloud Foundry, a module crashes with the error message `Cannot mkdir: No space left on device` then the solution is to adjust the space available to that module in the `mta.yaml` file. Adjust the `disk-quota` parameter. ```sh parameters: disk-quota: 512M memory: 256M ``` [Learn more about this error in KBA 3310683](https://userapps.support.sap.com/sap/support/knowledge/en/3310683){.learn-more} ### Why do I get "404 Not Found: Requested route does not exist"? > Source: /docs/get-started/get-help#why-do-i-get-404-not-found-requested-route-does-not-exist In order to send a request to an app, it must be associated with a route. Please see [Cloud Foundry Documentation -> Routes](https://docs.cloudfoundry.org/devguide/deploy-apps/routes-domains.html#routes) for details. As this is done automatically by default, the process is mostly transparent for developers. If you receive an error response `404 Not Found: Requested route ('') does not exist`, this can have two reasons: 1. The route really does not exist or is not bound to an app. You can check this in SAP BTP cockpit either in the app details view or in the list of routes in the Cloud Foundry space. 2. The app (or all app instances, in case of horizontal scale-out) failed the readiness check. Please see [Health Checks](../guides/deploy/health-checks.md) and [Using Cloud Foundry health checks](https://docs.cloudfoundry.org/devguide/deploy-apps/healthchecks.html) for details on how to set up the check. ::: details Troubleshoot using the Cloud Foundry CLI ```sh cf apps # -> list all apps cf app # -> get details on your app, incl. state and routes cf app --guid # -> get your app's guid cf curl "/v3/processes//stats" # -> list of processes (one per app instance) with property "routable" # indicating whether the most recent readiness check was successful ``` See [cf curl](https://cli.cloudfoundry.org/en-US/v7/curl.html) and [The process stats object](https://v3-apidocs.cloudfoundry.org/version/3.184.0/index.html#the-process-stats-object) for details on how to use the CLI. ::: ### Why do I get "_404 Cannot GET /_"? > Source: /docs/get-started/get-help#why-do-i-get-404-cannot-get- For security reasons, the **index page is not served in production** in [Node.js](../node.js/cds-server#toggle-generic-index-page) and [Java](../java/developing-applications/configuring#production-profile). If you try to access your backend URL, you will therefore see a _404 Cannot GET /_ error. ::: warning This also means you **cannot use the `/` path as a health status indicator**. See the [_Health Checks_](../guides/deploy/health-checks) guide for the correct paths. ::: Only if absolutely required and you understand the security implications to your application, you can enable this page in your deployment. Learn more about enabling generic index page in [Java](../java/developing-applications/properties#cds-indexpage) and in [Node.js](../node.js/cds-server#toggle-generic-index-page).{.learn-more} ## Kyma / K8s > Source: /docs/get-started/get-help#kyma--k8s ### Why do I get "package.json and package-lock.json aren't in sync"? > Source: /docs/get-started/get-help#why-do-i-get-packagejson-and-package-lockjson-arent-in-sync Run `npm i --package-lock-only` to update the _package-lock.json_ and re-run `cds up`.
# The CAP Cookbook > Source: /docs/guides/ Recipes for CAP Development { .subtitle} The following figure illustrates a walkthrough of the most prominent tasks during development of CAP-based projects. The guides contained in this section provide details and instructions about each. ![](playbook.drawio.svg) {} [ Domain Modeling ](domain/index) : Most projects start with capturing the essential objects of their domain in a respective domain model. Find here an introduction to the basics of domain modeling with CDS, complemented with recommended best practices. [ Services & APIs ](services/index.md ) : Services are the central building block to expose and consume functionality in CAP applications. This guide provides an introduction to defining, implementing, and consuming services. [ Serving UIs ](uis/index.md) : CAP provides out-of-the-box support for SAP Fiori elements front ends. [ Protocols ](protocols/index.md) : CAP supports multiple protocols to expose and consume services. This guide provides an overview of the supported protocols and their characteristics. [ Databases ](databases/index.md) : These guides provide instructions on how to use databases with CAP applications. Out of the box-support is provided for SAP HANA, SQLite, H2 (Java only), and PostgreSQL. [ Integration ](integration/index.md) : The guides in this section covers the various CAP-level service integration and data federation patterns, as well as platform capabilities available to your CAP projects. [ Events & Messaging ](events/index.md) : CAP provides intrinsic support for emitting and receiving events. This is complemented by Messaging Services connecting to message brokers to exchange event messages across remote services. [ Security & Data Privacy ](security/index.md) : This guide teaches how to how to develop, deploy and operate CAP applications in a secure way. [ Extensibility ](extensibility/index.md) : Learn here about intrinsic capabilities to extend your applications in verticalization and customization scenarios. # Domain Modeling > Source: /docs/guides/domain/ Domain Models capture the static, data-related aspects of a problem domain in terms of entity-relationship models. They serve as the basis for *[persistence models](../databases/index)* deployed to databases as well as for *[service definitions](../services/providing-services)*. ## Introduction > Source: /docs/guides/domain/#introduction ### Capture Intent — *What, not How!* > Source: /docs/guides/domain/#capture-intent--what-not-how CDS focuses on *conceptual modelling*: we want to capture intent, not imperative implementations — that is: What, not How. Not only does that keep domain models concise and comprehensible, it also allows us to provide optimized generic implementations. For example, given an entity definition like that: ```cds using { cuid, managed } from '@sap/cds/common'; entity Books : cuid, managed { title : localized String; descr : localized String; author : Association to Authors; } ``` In that model we used the [pre-defined aspects](../../cds/common) `cuid` and `managed`, as well as the [qualifier `localized`](../uis/localized-data#declaring-localized-data) to capture generic aspects. We also used [managed associations](#associations). In all these cases, we focus on capturing our intent, while leaving it to generic implementations to provide best-possible implementations. ### Entity-Relationship Modeling > Source: /docs/guides/domain/#entity-relationship-modeling Entity-Relationship Modelling (ERM) is likely the most widely known and applied conceptual modelling technique for data-centric applications. It is also one of the foundations for CDS. Assume we had been given this requirement: > _"We want to create a bookshop allowing users to browse **Books** and **Authors**, and navigate from Books to Authors and vice versa. Books are classified by **Genre**"._ Using CDS, we would translate that into an initial domain model as follows: ```cds using { cuid } from '@sap/cds/common'; entity Books : cuid { title : String; descr : String; genre : Genre; author : Association to Authors; } entity Authors : cuid { name : String; books : Association to many Books on books.author = $self; } type Genre : String enum { Mystery; Fiction; Drama; } ``` ### Aspect-oriented Modeling > Source: /docs/guides/domain/#aspect-oriented-modeling CDS Aspects and Annotations provide powerful means for **separation of concerns**. This greatly helps to keep our core domain model clean, while putting secondary concerns into separate files and model fragments. → Find details in chapter [Aspects](#aspects) below. ### Fuelling Generic Providers > Source: /docs/guides/domain/#fuelling-generic-providers As depicted in the illustration below, domain models serve as the sources for persistence models, deployed to databases, as well as the underlying model for services acting as API facades to access data. ![This graphic is explained in the accompanying text.](cds-fueling-generic-providers.drawio.svg) The more we succeeded in capturing intent over imperative implementations, the more we can provide optimized generic implementations. ### Domain-Driven Design > Source: /docs/guides/domain/#domain-driven-design ::: tip CAP shares these goals and approaches with [Domain-driven Design](https://en.wikipedia.org/wiki/Domain-driven_design): 1. Placing projects' primary **focus on the core domain** 2. Close collaboration of **developers** and **domain experts** 3. Iteratively refining **domain knowledge** ::: We use CDS as our ubiquitous modelling language, with CDS Aspects giving us the means to separate core domain aspects from generic aspects. CDS's human-readable nature fosters collaboration of developers and domain experts. As CDS models are used to fuel generic providers — the database as well as application services — we ensure the models are applied in the implementation. And as coding is minimized we can more easily refine and revise our models, without having to refactor large boilerplate codebases. ## Best Practices > Source: /docs/guides/domain/#best-practices ### Keep it Simple, Stupid > Source: /docs/guides/domain/#keep-it-simple-stupid Domain modeling is a means to an end; your clients and consumers are the ones who have to understand and work with your models the most, much more than you as their creator. Keep that in mind and understand the task of domain modeling as a service to others. ::: tip **Keep models *concise* and *comprehensible*** As said in the *["Keep it simple, stupid!"](https://en.wikipedia.org/w/index.php?title=KISS_principle&oldid=992997588)* Wikipedia entry: *"... most systems work best if they're kept simple rather than made complicated; therefore, [simplicity](https://en.wikipedia.org/wiki/Simplicity) should be a key goal in [design](https://en.wikipedia.org/wiki/Design), and unnecessary complexity should be avoided."* ::: ::: warning **Avoid overly abstract models** Even though domain models should abstract from technical implementations, don't overstress this and balance it with ease of adoption. For example if the vast majority of your clients use relational databases, don't try to overly abstract from that, as that would have all suffer from common denominator syndromes. ::: #### Prefer Flat Models > Source: /docs/guides/domain/#prefer-flat-models While CDS provides great support, you should always think twice before using structured types. Some technologies you or your customers use might not integrate with those out of the box. Moreover, flat structures are easier to understand and consume. ##### **Good:** > Source: /docs/guides/domain/#good ```cds entity Contacts { isCompany : Boolean; company : String; title : String; firstname : String; lastname : String; } ``` ##### **Bad:** > Source: /docs/guides/domain/#bad ```cds entity Contacts { isCompany : Boolean; companyData : CompanyDetails; personData : PersonDetails; } type CompanyDetails { name : String; } type PersonDetails { titles : AcademicTitles; name : PersonName; } type PersonName : { first : String; last : String; } type AcademicTitles : { primary : String; secondary : String; } ``` ### Separation of Concerns > Source: /docs/guides/domain/#separation-of-concerns As highlighted with a few samples in the chapter above, always strive to keep your core domain model clean, concise and comprehensible. CDS Aspects help you to do so, by decomposing models and definitions into separate files with potentially different life cycles, contributed by different _people_. We strongly recommend to make use of that as much as possible. ### Naming Conventions > Source: /docs/guides/domain/#naming-conventions We recommend adopting the following simple naming conventions as commonly used in many communities, for example, Java, JavaScript, C, SQL, etc. To easily distinguish type / entity names from elements names we recommend to... ::: tip Capitalize *Type / Entity* Names * Start **_entity_** and **_type_** names with capital letters — for example, `Authors` * Start **_elements_** with a lowercase letter — for example, `name` ::: As entities represent not only data types, but also data sets, from which we can read from, we recommend following common SQL convention: ::: tip Pluralize *Entity* Names * Use **plural** form for **_entities_** — for example, `Authors` * Use **singular** form for **_types_** — for example, `Genre` ::: In general always prefer conciseness, comprehensibility and readability, and avoid overly lengthy names, probably dictated by overly strict systematics: ::: tip Prefer *Concise* Names - Don't repeat contexts → for example `Authors.name` instead of `Authors.authorName` - Prefer one-word names → for example `address` instead of `addressInformation` - Use `ID` for technical primary keys → see also [Use Canonic Primary Keys](#prefer-canonic-keys) ::: ## Core Concepts > Source: /docs/guides/domain/#core-concepts ### Namespaces > Source: /docs/guides/domain/#namespaces You can use [namespaces](../../cds/cdl#namespaces) to get to unique names without bloating your code with fully qualified names. For example: ```cds namespace foo.bar; entity Boo {} entity Moo : Boo {} ``` ... is equivalent to: ```cds entity foo.bar.Boo {} entity foo.bar.Moo : foo.bar.Boo {} ``` Note: - **Namespaces are just prefixes** — which are automatically applied to all relevant names in a file. Beyond this there's nothing special about them. - **Namespaces are optional** — use namespaces if your models might be reused in other projects; otherwise, you can go without namespaces. - The **reverse domain name** approach works well for choosing namespaces. ::: warning Avoid names that could change Don't use short-lived ingredients in namespaces, or names in general, such as your current organization's name, or project code names. ::: ### Domain Entities > Source: /docs/guides/domain/#domain-entities Entities represent a domain's data. When translated to persistence models, especially relational ones, entities become tables. Entity definitions essentially declare structured types with named and typed elements, plus the [primary key](#primary-keys) elements used to identify entries. ```cds entity name { key element1 : Type; element2 : Type; ... } ``` [Learn more about entity definitions.](../../cds/cdl#entities--type-definitions){.learn-more} #### Views / Projections > Source: /docs/guides/domain/#views--projections Borrowing powerful view building from SQL, we can declare entities as (denormalized) views on other entities: ```cds entity ProjectedEntity as select from BaseEntity { element1, element2 as name, /*...*/ }; ``` [Learn more about views and projections.](../../cds/cdl#views--projections){.learn-more} ### Primary Keys > Source: /docs/guides/domain/#primary-keys Use the keyword `key` to signify one or more elements that form an entity's primary key: ```cds entity Books { key ID : UUID; // [!code focus] ... } ``` ##### Do: > Source: /docs/guides/domain/#do - [Prefer ***simple***, ***technical*** primary keys](#prefer-simple-technical-keys) - [Prefer ***canonic*** primary keys](#prefer-canonic-keys) - [Prefer ***UUIDs*** for primary keys](#prefer-uuids-for-keys) ##### Don't: > Source: /docs/guides/domain/#dont - Don't use binary data as keys! - [Don't interpret UUIDs!](#dont-interpret-uuids) #### Prefer Simple, Technical Keys > Source: /docs/guides/domain/#prefer-simple-technical-keys While you can use arbitrary combinations of fields as primary keys, keep in mind that primary keys are frequently used in joins all over the place. And the more fields there are to compare for a join the more you'll suffer from poor performance. So prefer primary keys consisting of single fields only. Moreover, primary keys should be immutable, that means once assigned on creation of a record they should not change subsequently, as that would break references you might have handed out. Think of them as a fingerprint of a record. #### Prefer Canonic Keys > Source: /docs/guides/domain/#prefer-canonic-keys We recommend using canonically named and typed primary keys, as promoted [by aspect `cuid` from @sap/cds/common](../../cds/common#aspect-cuid). ```cds // @sap/cds/common aspect cuid { key ID : UUID } ``` ```cds using { cuid } from '@sap/cds/common'; entity Books : cuid { ... } entity Authors : cuid { ... } ``` This eases the implementation of generic functions that can apply the same ways of addressing instances across different types of entities. #### Prefer UUIDs for Keys > Source: /docs/guides/domain/#prefer-uuids-for-keys While UUIDs certainly come with an overhead and a performance penalty when looking at single databases, they have several advantages when we consider the total bill. So, you can avoid [the evil of premature optimization](https://wiki.c2.com/?PrematureOptimization) by at least considering these points: * **UUIDs are universal** — that means that they're unique across every system in the world, while sequences are only unique in the source system's boundaries. Whenever you want to exchange data with other systems you'd anyways add something to make your records 'universally' addressable. * **UUIDs allow distributed seeds** — for example, in clients. In contrast, database sequences or other sequential generators always need a central service, for example, a single database instance and schema. This becomes even more a problem in distributed landscape topologies. * **Database sequences are hard to guess** — assume that you want to insert a _SalesOrder_ with three _SalesOrderItems_ in one transaction. INSERT _SalesOrder_ will automatically get a new ID from the sequence. How would you get this new ID in order to use it for the foreign keys in subsequent INSERTs of the _SalesOrderItems_? * **Auto-filled primary keys** — primary key elements with type UUID are automatically filled by generic service providers in Java and Node.js upon INSERT. ::: tip Prefer UUIDs for Keys Use DB sequences only if you really deal with high data volumes. Otherwise, prefer UUIDs. ::: You can also have semantic primary keys such as order numbers constructed by customer name+date, etc. And if so, they usually range between UUIDs and DB sequences with respect to the pros and cons listed above. #### Don't Interpret UUIDs! > Source: /docs/guides/domain/#dont-interpret-uuids It is an unfortunate anti pattern to validate UUIDs, such as for compliance to [RFC 4122](https://tools.ietf.org/html/rfc4122). This not only means useless processing, it also impedes integration with existing data sources. For example, ABAP's [**GUID_32s**](https://www.sapdatasheet.org/abap/dtel/guid_32.html) are uppercase without hyphens. **UUIDs are unique opaque values!** — The only assumption required and allowed is that UUIDs are unique so that they can be used for lookups and compared by equality — nothing else! It's the task of the UUID generator to ensure uniqueness, not the task of subsequent processors! On the same note, converting UUID values obtained as strings from the database into binary representations such as `java.lang.UUID`, only to render them back to strings in responses to HTTP requests, is useless overhead. ::: warning In summary: * Avoid unnecessary assumptions, for example, about uppercase or lowercase * Avoid useless conversions, for example, from strings to binary and back * Avoid useless validations of UUID formats, for example, about hyphens ::: [See also: Mapping UUIDs to OData](../protocols/odata#override-type-mapping) {.learn-more} [See also: Mapping UUIDs to SQL](../databases/hana-native#mapping-uuids-to-sql) {.learn-more} ### Data Types > Source: /docs/guides/domain/#data-types #### Standard Built-in Types > Source: /docs/guides/domain/#standard-built-in-types CDS comes with a small set of built-in types: - `UUID`, - `Boolean`, - `Date`, `Time`, `DateTime`, `Timestamp` - `Integer`, `UInt8`, `Int16`, `Int32`, `Int64` - `Double`, `Decimal` - `String`, `LargeString` - `Binary`, `LargeBinary` [See list of **Built-in Types** in the CDS reference docs.](../../cds/types){.learn-more} #### Common Reuse Types > Source: /docs/guides/domain/#common-reuse-types In addition, a set of common reuse types and aspects is provided with package [_`@sap/cds/common`_](../../cds/common), such as: - Types `Country`, `Currency`, `Language` with corresponding value list entities - Aspects `cuid`, `managed`, `temporal` For example, usage is as simple as this: ```cds using { Country, managed } from '@sap/cds/common'; entity Addresses : managed { //> using reuse aspect street : String; town : String; country : Country; //> using reuse type } ``` [Learn more about reuse types provided by _`@sap/cds/common`_.](../../cds/common){.learn-more} ::: tip **Use common reuse types and aspects**... ... to keep models concise, and benefitting from improved interoperability, proven best practices, and out-of-the-box support through generic implementations in CAP runtimes. ::: #### Custom-defined Types > Source: /docs/guides/domain/#custom-defined-types Declare custom-defined types to increase semantic expressiveness of your models, or to share details and annotations as follows: ```cds type User : String; //> merely for increasing expressiveness type Genre : String enum { Mystery; Fiction; ... } type DayOfWeek : Number @assert.range:[1,7]; ``` #### Use Custom Types Reasonably > Source: /docs/guides/domain/#use-custom-types-reasonably Avoid overly excessive use of custom-defined types. They're valuable when you have a decent **reuse ratio**. Without reuse, your models just become harder to read and understand, as one always has to look up respective type definitions, as in the following example: ```cds using { sap.capire.bookshop.types } from './types'; namespace sap.capire.bookshop; entity Books { key ID : types.BookID; name : types.BookName; descr : types.BookDescr; ... } ``` ```cds // types.cds namespace sap.capire.bookshop.types; type BookID : UUID; type BookName : String; type BookDescr : String; ``` ### Associations > Source: /docs/guides/domain/#associations Use _Associations_ to capture relationships between entities. ```cds entity Books { ... author : Association to Authors; //> to one } entity Authors { ... books : Association to many Books on books.author = $self; } ``` [Learn more about Associations in the _CDS Language Reference_.](../../cds/cdl#associations){ .learn-more} #### Managed :1 Associations > Source: /docs/guides/domain/#managed-1-associations The association `Books:author` in the sample above is a so-called *managed* association, with foreign key columns and on conditions added automatically behind the scenes. ```cds entity Books { ... author : Association to Authors; } ``` In contrast to that we could also use *unmanaged* associations with all foreign keys and on conditions specified manually: ```cds entity Books { ... author : Association to Authors on author.ID = author_ID; author_ID : type of Authors:ID; } ``` > Note: To-many associations are unmanaged by nature as we always have to specify an on condition. Reason for that is that backlink associations or foreign keys cannot be guessed reliably. ::: tip Prefer managed associations For the sake of conciseness and comprehensibility of your models always prefer *managed Associations* for to-one associations. ::: #### To-Many Associations > Source: /docs/guides/domain/#to-many-associations Simply add the `many` qualifier keyword to indicate a to-many cardinality: ```cds entity Authors { ... books : Association to many Books; } ``` If your models are meant to target APIs, this is all that is required. When targeting databases though, we need to add an `on` condition, like so: ```cds entity Authors { ... books : Association to many Books on books.author = $self; } ``` > The `on` condition can either compare a backlink association to `$self`, or a backlink foreign key to the own primary key, for example `books.author.ID = ID`. #### Many-to-Many Associations > Source: /docs/guides/domain/#many-to-many-associations CDS currently doesn't provide dedicated support for _many-to-many_ associations. Unless we add some, you have to resolve _many-to-many_ associations into two _one-to-many_ associations using a link entity to connect both. For example: ```cds entity Projects { ... members : Composition of many Members on members.project = $self; } entity Users { ... projects : Composition of many Members on projects.user = $self; } entity Members: cuid { // link table project : Association to Projects; user : Association to Users; } ``` We can use [_Compositions of Aspects_](#composition-of-aspects) to reduce noise a bit: ```cds entity Projects { ... members : Composition of many { key user : Association to Users }; } entity Users { ... projects : Composition of many Projects.members on projects.user = $self; } ``` Behind the scenes the equivalent of the model above would be generated, with the link table called `Projects.members` and the backlink association to `Projects` in there called `up_`. Consider that for SAP Fiori elements 'project' and 'user' shall not be keys, even if their combination is unique, because as keys those fields can't be edited on the UI. In this case a different key is required, for example a UUID, and the unique constraint for `project` and `user` can be expressed via `@assert.unique`. ### Compositions > Source: /docs/guides/domain/#compositions Compositions represent contained-in relationships. CAP runtimes provide these special treatments to Compositions out of the box: - **Deep Insert / Update** automatically filling in document structures - **Cascaded Delete** is when deleting Composition roots - **Composition** targets are **auto-exposed** in service interfaces #### Modeling Document Structures > Source: /docs/guides/domain/#modeling-document-structures Compositions are used to model document structures. For example, in the following definition of `Orders`, the `Orders:Items` composition refers to the `OrderItems` entity, with the entries of the latter being fully dependent objects of `Orders`. ```cds entity Orders { ... Items : Composition of many OrderItems on Items.parent = $self; } entity OrderItems { // to be accessed through Orders only key parent : Association to Orders; key pos : Integer; quantity : Integer; } ``` [Learn more about Compositions in the _CDS Language Reference_.](../../cds/cdl#compositions){ .learn-more} #### Composition of Aspects > Source: /docs/guides/domain/#composition-of-aspects We can use anonymous inline aspects to rewrite the above with less noise as follows: ```cds entity Orders { ... Items : Composition of many { key pos : Integer; quantity : Integer; }; } ``` [Learn more about Compositions of Aspects in the _CDS Language Reference_.](../../cds/cdl#managed-compositions){ .learn-more} Behind the scenes this will add an entity named `Orders.Items` with a backlink association named `up_`, so effectively generating the same model as above. You can annotate the inline composition with UI annotations as follows: ```cds annotate Orders.Items with @( UI.LineItem : [ {Value: pos}, {Value: quantity}, ], ); ``` ## Aspects > Source: /docs/guides/domain/#aspects CDS's [Aspects](../../cds/cdl#aspects) provide powerful mechanisms to separate concerns. It allows decomposing models and definitions into separate files with potentially different life cycles, contributed by different _people_. The basic mechanism use the `extend` or `annotate` directives to add secondary aspects to a core domain entity like so: ```cds extend Books with { someAdditionalField : String; } ``` ```cds annotate Books with @some.entity.level.annotations { title @some.field.level.annotations; }; ``` Variants of this allow declaring and applying **named aspects** like so: ```cds aspect NamedAspect { someAdditionalField : String } extend Books with NamedAspect; ``` We can also apply named aspects as **includes** in an inheritance-like syntax: ```cds entity Books : NamedAspect { ... } ``` [Learn more about the usage of aspects in the _Aspect-oriented Modeling_ section.](../../cds/aspects).{ .learn-more} ::: tip Consumers always see effective models The separation into aspects is fully transparent to consumers. ::: ### Authorization > Source: /docs/guides/domain/#authorization CAP supports out-of-the-box authorization by annotating services and entities with `@requires` and `@restrict` annotations like that: ```cds entity Books @(restrict: [ { grant: 'READ', to: 'authenticated-user' }, { grant: 'CREATE', to: 'content-maintainer' }, { grant: 'UPDATE', to: 'content-maintainer' }, { grant: 'DELETE', to: 'admin' }, ]) { ... } ``` To avoid polluting our core domain model with the generic aspect of authorization, we can use aspects to separate concerns, putting the authorization annotations into a separate file, maintained by security experts like so: ```cds // core domain model in schema.cds entity Books { ... } entity Authors { ... } ``` ```cds // authorization model using { Books, Authors } from './schema.cds'; annotate Books with @restrict: [ { grant: 'READ', to: 'authenticated-user' }, { grant: 'CREATE', to: 'content-maintainer' }, { grant: 'UPDATE', to: 'content-maintainer' }, { grant: 'DELETE', to: 'admin' }, ]; annotate Authors with @restrict: [ ... ]; ``` ### Fiori Annotations > Source: /docs/guides/domain/#fiori-annotations Similarly to authorization annotations we would frequently add annotations which are related to UIs, starting with `@title` annotations used for field or column labels in UIs, or specific Fiori annotations in `@UI`, `@Common`, etc. vocabularies. Also here we strongly recommend to keep the core domain models clean of that, but put such annotation into respective frontend models: ```cds // core domain model in db/schema.cds entity Books : cuid { ... } entity Authors : cuid { ... } ``` ```cds // common annotations in app/common.cds using { sap.capire.bookshop as my } from '../db/schema'; annotate my.Books with { ID @title: '{i18n>ID}'; title @title: '{i18n>Title}'; genre @title: '{i18n>Genre}' @Common: { Text: genre.name, TextArrangement: #TextOnly }; author @title: '{i18n>Author}' @Common: { Text: author.name, TextArrangement: #TextOnly }; price @title: '{i18n>Price}' @Measures.ISOCurrency : currency_code; descr @title: '{i18n>Description}' @UI.MultiLineText; } ``` ```cds // Specific UI Annotations for Fiori Object & List Pages using { sap.capire.bookshop as my } from '../db/schema'; annotate my.Books with @( Common.SemanticKey : [ID], UI: { Identification : [{ Value: title }], SelectionFields : [ ID, author_ID, price, currency_code ], LineItem : [ { Value: ID, Label: '{i18n>Title}' }, { Value: author.ID, Label: '{i18n>Author}' }, { Value: genre.name }, { Value: stock }, { Value: price }, { Value: currency.symbol }, ] } ) { ID @Common: { SemanticObject : 'Books', Text: title, TextArrangement : #TextOnly }; author @ValueList.entity: 'Authors'; }; ``` ### Localized Data > Source: /docs/guides/domain/#localized-data Business applications frequently need localized data, for example to display books titles and descriptions in the user's preferred language. With CDS we simply use the `localized` qualifier to tag respective text fields in your as follows. #### **Do:** > Source: /docs/guides/domain/#do-1 ```cds entity Books { ... title : localized String; descr : localized String; } ``` #### **Don't:** > Source: /docs/guides/domain/#dont-1 In contrast to that, this is what you would have to do without CAP's `localized` support: ```cds entity Books { key ID : UUID; title : String; descr : String; texts : Composition of many Books.texts on texts.book = $self; ... } entity Books.texts { key locale : Locale; key ID : UUID; title : String; descr : String; } ``` Essentially, this is also what CAP generates behind the scenes, plus many more things to ease working with localized data and serving it out of the box. ::: tip `localized` keeps models comprehensible By generating `.texts` entities and associations behind the scenes, CAP's **out-of-the-box support** for `localized` data avoids polluting your models with doubled numbers of entities, and detrimental effects on comprehensibility. ::: [Learn more in the **Localized Data** guide.](../uis/localized-data){.learn-more} ## Managed Data > Source: /docs/guides/domain/#managed-data ### `@cds.on.insert` > Source: /docs/guides/domain/#cdsoninsert ### `@cds.on.update` > Source: /docs/guides/domain/#cdsonupdate Use the annotations `@cds.on.insert` and `@cds.on.update` to signify elements to be auto-filled by the generic handlers upon insert and update. For example, you could add fields to track who created and updated data records and when: ```cds entity Foo { //... createdAt : Timestamp @cds.on.insert: $now; createdBy : User @cds.on.insert: $user; modifiedAt : Timestamp @cds.on.insert: $now @cds.on.update: $now; modifiedBy : User @cds.on.insert: $user @cds.on.update: $user; } ``` [Learn more about pseudo variables `$now` and `$user` below.](#pseudo-variables ){.learn-more} These **rules** apply: - Data *cannot* be filled in from external clients → payloads are cleansed - Data *can* be filled in from custom handlers or from `.csv` files ::: details Note the differences to [defaults](../../cds/cdl#default-values)... ... for example, given this model: ```cds entity Foo { //... managed : Timestamp @cds.on.insert: $now; defaulted : Timestamp default $now; } ``` While both behave identical for database-level `INSERT`s, they differ for `CREATE` requests on higher-level service providers: Values for `managed` in the request payload will be ignored, while provided values for `default` will be written to the database. ::: ::: tip In Essence: Managed data fields are filled in automatically and are write-protected for external clients. ::: ::: warning Limitations In case of `UPSERT` operations, the handlers for `@cds.on.update` are executed, but not the ones for `@cds.on.insert`. ::: ### Aspect _`managed`_ {} > Source: /docs/guides/domain/#aspect-managed- You can also use the [pre-defined aspect `managed`](../../cds/common#aspect-managed) from [@sap/cds/common](../../cds/common) to get the very same as by the definition above: ```cds using { managed } from '@sap/cds/common'; entity Foo : managed { /*...*/ } ``` [Learn more about `@sap/cds/common`.](../../cds/common){.learn-more} With this we keep our core domain model clean and comprehensible. ## Pseudo Variables > Source: /docs/guides/domain/#pseudo-variables The pseudo variables used in the annotations above are resolved as follows: - `$now` is replaced by the current server time (in UTC) - `$user` is the current user's ID as obtained from the authentication middleware - `$user.` is replaced by the value of the respective attribute of the current user - `$uuid` is replaced by a version 4 UUID [Learn more about **Authentication** in Node.js.](../../node.js/authentication){.learn-more} [Learn more about **Authentication** in Java.](../../java/security#authentication){.learn-more} # Temporal Data > Source: /docs/guides/domain/temporal-data CAP provides out-of-the-box support for declaring and serving date-effective entities with application-controlled validity, in particular to serve as-of-now and time-travel queries. Temporal data allows you to maintain information relating to past, present, and future application time. Built-in support for temporal data follows the general principle of CDS to capture intent with models while staying conceptual, concise, and comprehensive, and minimizing pollution by technical artifacts. > For an introduction to this topic, see [Temporal database](https://en.wikipedia.org/w/index.php?title=Temporal_database&oldid=911558203) (Wikipedia) and [Temporal features in SQL:2011](https://files.ifi.uzh.ch/dbtg/ndbs/HS17/SQL2011.pdf). ## Starting with 'Timeless' Models > Source: /docs/guides/domain/temporal-data#starting-with-timeless-models For the following explanation, let's start with a base model to manage employees and their work assignments, which is free of any traces of temporal data management. ### Timeless Model > Source: /docs/guides/domain/temporal-data#timeless-model ::: code-group ```cds [timeless-model.cds] namespace com.acme.hr; using { com.acme.common.Persons } from './common'; entity Employees : Persons { jobs : Composition of many WorkAssignments on jobs.empl=$self; job1 : Association to one /*of*/ WorkAssignments; } entity WorkAssignments { key ID : UUID; role : String(111); empl : Association to Employees; dept : Association to Departments; } entity Departments { key ID : UUID; name : String(111); head : Association to Employees; members : Association to many Employees on members.jobs.dept = $self; } ``` ::: > An employee can have several work assignments at the same time. > Each work assignment links to one department. ### Timeless Data > Source: /docs/guides/domain/temporal-data#timeless-data A set of sample data entries for this model, which only captures the latest state, can look like this: ![Alice has the job a a developer and consultant. Bob is a builder. Alice works in her roles for the departments core development and app development. Bob's work assignment is linked to the construction department.](./timeless-data.drawio.svg) > Italic titles indicate to-one associations; actual names of the respective foreign key columns in SQL are `job1_ID`, `empl_ID`, and `dept_ID`. ## Declaring Temporal Entities > Source: /docs/guides/domain/temporal-data#declaring-temporal-entities _Temporal Entities_ represent _logical_ records of information for which we track changes over time by recording each change as individual _time slices_ in the database with valid from/to boundaries. For example, we could track the changes of Alice's primary work assignment _WA1_ over time: ![Alice progressed from developer to senior developer to architect.](./time-slices.drawio.svg) ::: tip Validity periods are expected to be **non-overlapping** and **closed-open** intervals; same as in SQL:2011. ::: ### Using Annotations `@cds.valid.from/to` > Source: /docs/guides/domain/temporal-data#using-annotations-cdsvalidfromto To track temporal data, just add a pair of date/time elements to the respective entities annotated with `@cds.valid.from/to`, as follows: ```cds entity WorkAssignments { //... start : Date @cds.valid.from; end : Date @cds.valid.to; } ``` ::: tip The annotation pair `@cds.valid.from/to` actually triggers the built-in mechanisms for [serving temporal data](#serving-temporal-data). It specifies which elements form the **application-time** period, similar to SQL:2011. ::: ### Using Common Aspect `temporal` > Source: /docs/guides/domain/temporal-data#using-common-aspect-temporal Alternatively, use the predefined aspect [`temporal`](../../cds/common#aspect-temporal) to declare temporal entities: ```cds using { temporal } from '@sap/cds/common'; entity WorkAssignments : temporal {/*...*/} ``` Aspect [`temporal`](../../cds/common#aspect-temporal) is defined in _[@sap/cds/common](../../cds/common)_ as follows: ```cds aspect temporal { validFrom : Timestamp @cds.valid.from; validTo : Timestamp @cds.valid.to; } ``` ### Separate Temporal Details > Source: /docs/guides/domain/temporal-data#separate-temporal-details The previous samples would turn the whole _WorkAssignment_ entity into a temporal one. Frequently though, only some parts of an entity are temporal, while others stay timeless. You can reflect this by separating temporal elements from non-temporal ones: ```cds entity WorkAssignments { // non-temporal head entity key ID : UUID; empl : Association to Employees; details : Composition of WorkDetails on details.ID = $self.ID; } entity WorkDetails : temporal { // temporal details entity key ID : UUID; // logical record ID role : String(111); dept : Association to Departments; } ``` The data situation would change as follows: ![Alice has two work assignments. Her first work assignment is stable but the roles in this assignment change over time. She progressed from developer to senior developer to architect. Each role has specific validity defined.](./temporal-details.drawio.svg) ## Serving Temporal Data > Source: /docs/guides/domain/temporal-data#serving-temporal-data We expose the entities from the following timeless model in a service as follows: ::: code-group ```cds [service.cds] using { com.acme.hr } from './temporal-model'; service HRService { entity Employees as projection on hr.Employees; entity WorkAssignments as projection on hr.WorkAssignments; entity Departments as projection on hr.Departments; } ``` ::: > You can omit composed entities like _WorkAssignments_ from the service, as they would get [auto-exposed](../services/providing-services#auto-exposed-entities) automatically.
## Reading Temporal Data > Source: /docs/guides/domain/temporal-data#reading-temporal-data ### As-of-now Queries > Source: /docs/guides/domain/temporal-data#as-of-now-queries READ requests without specifying any temporal query parameter will automatically return data valid _as of now_. For example, assumed the following OData query to read all employees with their current work assignments is processed on March 2019: ```http GET Employees? $expand=jobs($select=role&$expand=dept($select=name)) ``` The values of `$valid`, and so also the respective session variables, would be set to, for example: | | | | |--------------|----------------------------------|----------------------------| | `$valid.from` = | _session_context('valid-from')_= | _2019-03-08T22:11:00Z_ | | `$valid.to` = | _session_context('valid-to')_ = | _2019-03-08T22:11:00.001Z_ | The result set would be: ```json [ { "ID": "E1", "name": "Alice", "jobs": [ { "role": "Architect", "dept": {"name": "Core Development"}}, { "role": "Consultant", "dept": {"name": "App Development"}} ]}, { "ID": "E2", "name": "Bob", "jobs": [ { "role": "Builder", "dept": {"name": "Construction"}} ]} ] ``` ### Time-Travel Queries > Source: /docs/guides/domain/temporal-data#time-travel-queries We can run the same OData query as in the previous sample to read a snapshot data as valid on January 1, 2017 using the `sap-valid-at` query parameter: ```http GET Employees?sap-valid-at=date'2017-01-01' $expand=jobs($select=role&$expand=dept($select=name)) ``` The values of `$valid` and hence the respective session variables would be set to, for example: | | | | |--------------|----------------------------------|----------------------------| | `$valid.from` = | _session_context('valid-from')_= | _2017-01-01T00:00:00Z_ | | `$valid.to` = | _session_context('valid-to')_ = | _2017-01-01T00:00:00.001Z_ | The result set would be: ```json [ { "ID": "E1", "name": "Alice", "jobs": [ { "role": "Developer", "dept": {"name": "Core Development"}}, { "role": "Consultant", "dept": {"name": "App Development"}} ]}, ... ] ``` ::: warning Time-travel queries aren't supported on SQLite due to the lack of *session_context* variables. ::: ### Time-Period Queries > Source: /docs/guides/domain/temporal-data#time-period-queries We can run the same OData query as in the previous sample to read all history of data as valid since 2016 using the `sap-valid-from` query parameter: ```http GET Employees?sap-valid-from=date'2016-01-01' $expand=jobs($select=role&$expand=dept($select=name)) ``` The result set would be: ```json [ { "ID": "E1", "name": "Alice", "jobs": [ { "role": "Developer", "dept": {"name": "App Development"}}, { "role": "Developer", "dept": {"name": "Core Development"}}, { "role": "Senior Developer", "dept": {"name": "Core Development"}}, { "role": "Consultant", "dept": {"name": "App Development"}} ]}, ... ] ``` > You would add `validFrom` in such time-period queries, for example: ```http GET Employees?sap-valid-from=date'2016-01-01' $expand=jobs($select=validFrom,role,dept/name) ``` ::: warning Time-series queries aren't supported on SQLite due to the lack of *session_context* variables. ::: ::: tip Writing temporal data must be done in custom handlers. ::: ### Transitive Temporal Data > Source: /docs/guides/domain/temporal-data#transitive-temporal-data The basic techniques and built-in support for reading temporal data serves all possible use cases with respect to as-of-now and time-travel queries. Special care has to be taken though if time-period queries transitively expand across two or more temporal data entities. As an example, assume that both, _WorkAssignments_ and _Departments_ are temporal: ```cds using { temporal } from '@sap/cds/common'; entity WorkAssignments : temporal {/*...*/ dept : Association to Departments; } entity Departments : temporal {/*...*/} ``` When reading employees with all history since 2016, for example: ```http GET Employees?sap-valid-from=date'2016-01-01' $expand=jobs( $select=validFrom,role&$expand=dept( $select=validFrom,name ) ) ``` The results for `Alice` would be: ```json [ { "ID": "E1", "name": "Alice", "jobs": [ { "validFrom":"2014-01-01", "role": "Developer", "dept": [ {"validFrom":"2013-04-01", "name": "App Development"} ]}, { "validFrom":"2017-01-01", "role": "Consultant", "dept": [ {"validFrom":"2013-04-01", "name": "App Development"} ]}, { "validFrom":"2017-01-01", "role": "Developer", "dept": [ {"validFrom":"2014-01-01", "name": "Tech Platform Dev"}, {"validFrom":"2017-07-01", "name": "Core Development"} ]}, { "validFrom":"2017-04-01", "role": "Senior Developer", "dept": [ {"validFrom":"2014-01-01", "name": "Tech Platform Dev"}, {"validFrom":"2017-07-01", "name": "Core Development"} ]}, { "validFrom":"2018-09-15", "role": "Architect", "dept": [ {"validFrom":"2014-01-01", "name": "Tech Platform Dev"}, {"validFrom":"2017-07-01", "name": "Core Development"} ]} ]}, ... ] ``` That is, all-time slices for changes to departments since 2016 are repeated for each time slice of work assignments in that time frame, which is a confusing and redundant piece of information. You can fix this by adding an alternative association to departments as follows: ```cds using { temporal } from '@sap/cds/common'; entity WorkAssignments : temporal {/*...*/ dept : Association to Departments; dept1 : Association to Departments on dept1.id = dept.id and dept1.validFrom <= validFrom and validFrom < dept1.validTo; } entity Departments : temporal {/*...*/} ``` ## Primary Keys of Time Slices > Source: /docs/guides/domain/temporal-data#primary-keys-of-time-slices While timeless entities are uniquely identified by the declared primary `key` — we call that the _conceptual_ key in CDS — time slices are uniquely identified by _the conceptual `key` **+** `validFrom`_. In effect the SQL DDL statement for the _WorkAssignments_ would look like this: ```sql CREATE TABLE com_acme_hr_WorkAssignments ( ID : nvarchar(36), validFrom : timestamp, validTo : timestamp, -- ... PRIMARY KEY ( ID, validFrom ) ) ``` In contrast to that, the exposed API preserves the timeless view, to easily serve as-of-now and time-travel queries out of the box [as described above](#serving-temporal-data): ```xml ... ``` Reading an explicit time slice can look like this: ```sql SELECT from WorkAssignments WHERE ID='WA1' and validFrom='2017-01-01' ``` Similarly, referring to individual time slices by an association: ```cds entity SomeSnapshotEntity { //... workAssignment : Association to WorkAssignments { ID, validFrom } } ```
# Providing and Consuming Services > Source: /docs/guides/services/ [Defining Provided Services](providing-services.md) : Learn how to define services in CDS, expose projections on domain models, and declare the data entities and operations your services provide. [Served Out-of-the-Box](served-ootb.md) : Discover the generic service providers that automatically handle CRUD operations, search, pagination, and input validation without custom code. [Status Flows](status-flows.md) : Model and validate status transitions in a controlled and reliable way, eliminating the need for extensive custom coding. [Constraints](constraints.md) : Express validation conditions declaratively using CXL expressions that are validated automatically whenever data is written. [Custom Code](custom-code.md) : Add custom event handlers and service implementations for use cases that require domain-specific logic beyond generic capabilities. [Custom Actions](custom-actions.md) : Define and implement domain-specific operations as custom actions and functions in addition to common CRUD operations. [Serving Media Data](media-data.md) : Store and stream media and binary data either in the database or external repositories using OData V4's media resource support. # Define Provided Services > Source: /docs/guides/services/providing-services Services are defined in CDS using the `service` construct. A service definition declares the data entities and operations it serves, typically exposing projections on underlying domain model entities. {.abstract} ## Services as APIs > Source: /docs/guides/services/providing-services#services-as-apis In its most basic form, a service definition simply declares the data entities and operations it serves. For example: ```cds service BookshopService { entity Books { key ID : UUID; title : String; author : Association to Authors; } entity Authors { key ID : UUID; name : String; books : Association to many Books on books.author = $self; } action submitOrder (book : Books:ID, quantity : Integer); } ``` This definition effectively defines the API served by `BookshopService`. ![This graphic is explained in the accompanying text.](./assets/service-apis.drawio.svg) Simple service definitions like that are all we need to run full-fledged servers out of the box, served by CAP's generic runtimes, without any implementation coding required. ## Services as Facades > Source: /docs/guides/services/providing-services#services-as-facades In contrast to the all-in-one definition above, services usually expose views, aka projections, on underlying domain model entities: ```cds using { sap.capire.bookshop as my } from '../db/schema'; service BookshopService { entity Books as projection on my.Books; entity Authors as projection on my.Authors; action submitOrder (book : Books:ID, quantity : Integer); } ``` This way, services become facades to encapsulated domain data, exposing different aspects tailored to respective use cases. ![This graphic is explained in the accompanying text.](./assets/service-as-facades.drawio.svg) ## Denormalized Views > Source: /docs/guides/services/providing-services#denormalized-views Instead of exposing access to underlying data in a 1:1 fashion, services frequently expose denormalized views, tailored to specific use cases. For example, the following service definition, undiscloses information about maintainers from end users and also [marks the entities as `@readonly`](constraints#readonly): ```cds using { sap.capire.bookshop as my } from '../db/schema'; /** For serving end users */ service CatalogService @(path:'/browse') { /** For displaying lists of Books */ @readonly entity ListOfBooks as projection on Books excluding { descr }; /** For display in details pages */ @readonly entity Books as projection on my.Books { *, author.name as author } excluding { createdBy, modifiedBy }; } ``` [Learn more about **CQL** the language used for `projections`.](../../cds/cql){.learn-more} [See also: Use Case-Oriented Services!](#use-case-oriented-services){.learn-more} [Find above sources in **capire/bookshop**.](https://github.com/capire/bookshop/blob/main/srv/cat-service.cds){ .learn-more} ## Auto-Exposed Entities > Source: /docs/guides/services/providing-services#auto-exposed-entities Annotate entities with `@cds.autoexpose` to automatically include them in services containing entities with Association referencing to them. For example, this is commonly done for code list entities in order to serve Value Lists dropdowns on UIs: ```cds service Zoo { entity Foo { //... code : Association to SomeCodeList; } } @cds.autoexpose entity SomeCodeList {...} ``` [Learn more about Auto-Exposed Entities in the CDS reference docs.](../../cds/cdl#auto-expose){.learn-more} ## Redirected Associations > Source: /docs/guides/services/providing-services#redirected-associations When exposing related entities, associations are automatically redirected. This ensures that clients can navigate between projected entities as expected. For example: ```cds service AdminService { entity Books as projection on my.Books; entity Authors as projection on my.Authors; //> AdminService.Authors.books refers to AdminService.Books } ``` [Learn more about Redirected Associations in the CDS reference docs.](../../cds/cdl#auto-redirect){.learn-more} ## Use Case-oriented Services > Source: /docs/guides/services/providing-services#use-case-oriented-services We strongly recommend designing your services for single use cases. Services in CAP are cheap, so there's no need to save on them. #### **DON'T:** Single Services Exposing All Entities 1:1 > Source: /docs/guides/services/providing-services#dont-single-services-exposing-all-entities-11 The anti-pattern to that are single services exposing all underlying entities in your app in a 1:1 fashion. While that may save you some thoughts in the beginning, it's likely that it will result in lots of headaches in the long run: * They open huge entry doors to your clients with only few restrictions * Individual use-cases aren't reflected in your API design * You have to add numerous checks on a per-request basis... * Which have to reflect on the actual use cases in complex and expensive evaluations #### **DO:** One Service Per Use Case > Source: /docs/guides/services/providing-services#do-one-service-per-use-case For example, let's assume that we have a domain model defining *Books* and *Authors* more or less as above, and then we add *Orders*. We could define the following services: ```cds using { my.domain as my } from './db/schema'; ``` ```cds /** Serves end users browsing books and place orders */ service CatalogService { @readonly entity Books as select from my.Books { ID, title, author.name as author }; @requires: 'authenticated-user' @insertonly entity Orders as projection on my.Orders; } ``` ```cds /** Serves registered users managing their account and their orders */ @requires: 'authenticated-user' service UsersService { @restrict: [{ grant: 'READ', where: 'buyer = $user' }] // limit to own ones @readonly entity Orders as projection on my.Orders; action cancelOrder ( ID:Orders.ID, reason:String ); } ``` ```cds /** Serves administrators managing everything */ @requires: 'authenticated-user' service AdminService { entity Books as projection on my.Books; entity Authors as projection on my.Authors; entity Orders as projection on my.Orders; } ``` These services serve different use cases and are tailored for each. Note, for example, that we intentionally don't expose the `Authors` entity to end users. # Generic Service Providers > Source: /docs/guides/services/served-ootb Served Out-of-the-Box {.subtitle} ## Introduction > Source: /docs/guides/services/served-ootb#introduction The CAP runtimes for [Node.js](../../node.js/index) and [Java](../../java/index) provide a wealth of generic implementations, which serve most requests automatically, with out-of-the-box solutions to recurring tasks such as search, pagination, or input validation — the majority of this guide focuses on these generic features. In effect, a service definition [as introduced above](providing-services) is all we need to run a full-fledged server out of the box. The need for coding reduces to real custom logic specific to a project's domain → section [Custom Logic](custom-code) picks that up. ## Serving CRUD Requests > Source: /docs/guides/services/served-ootb#serving-crud-requests The CAP runtimes for [Node.js](../../node.js/index) and [Java](../../java/index) provide generic handlers, which automatically serve all CRUD requests to entities for CDS-modelled services on top of a default [primary database](../databases/index). This comprises read and write operations like that: * `GET /Books/201` → reading single data entities * `GET /Books?...` → reading data entity sets with advanced query options * `POST /Books {....}` → creating new data entities * `PUT/PATCH /Books/201 {...}` → updating data entities * `DELETE /Books/201` → deleting data entities
::: warning No filtering and sorting for virtual elements CAP runtimes delegate filtering and sorting to the database. Therefore filtering and sorting is not available for `virtual` elements. ::: ## Deep Reads and Writes > Source: /docs/guides/services/served-ootb#deep-reads-and-writes CDS and the runtimes have advanced support for modeling and serving document-oriented data. The runtimes provide generic handlers for serving deeply nested document structures out of the box as documented in here. ### Deep `READ` > Source: /docs/guides/services/served-ootb#deep-read You can read deeply nested documents by *expanding* along associations or compositions. For example, like this in OData: :::code-group ```http GET .../Orders?$expand=header($expand=items) ``` ```js[cds.ql] SELECT.from ('Orders', o => { o.ID, o.title, o.header (h => { h.ID, h.status, h.items('*') }) }) ``` [Learn more about `cds.ql`](../../node.js/cds-ql){.learn-more} ::: Both would return an array of nested structures as follows: ```js [{ ID:1, title: 'first order', header: { // to-one ID:2, status: 'open', items: [{ // to-many ID:3, description: 'first order item' },{ ID:4, description: 'second order item' }] } }, ... ] ``` ### Deep `INSERT` > Source: /docs/guides/services/served-ootb#deep-insert Create a parent entity along with child entities in a single operation, for example, like that: :::code-group ```http POST .../Orders { ID:1, title: 'new order', header: { // to-one ID:2, status: 'open', items: [{ // to-many ID:3, description: 'child of child entity' },{ ID:4, description: 'another child of child entity' }] } } ``` ::: Note that Associations and Compositions are handled differently in (deep) inserts and updates: - Compositions → runtime **deeply creates or updates** entries in target entities - Associations → runtime **fills in foreign keys** to *existing* target entries For example, the following request would create a new `Book` with a *reference* to an existing `Author`, with `{ID:12}` being the foreign key value filled in for association `author`: ```http POST .../Books { ID:121, title: 'Jane Eyre', author: {ID:12} } ``` ### Deep `UPDATE` > Source: /docs/guides/services/served-ootb#deep-update Deep `UPDATE` of the deeply nested documents look very similar to deep `INSERT`: :::code-group ```http PUT .../Orders/1 { title: 'changed title of existing order', header: { ID:2, items: [{ ID:3, description: 'modified child of child entity' },{ ID:5, description: 'new child of child entity' }] }] } ``` ::: Depending on existing data, child entities will be created, updated, or deleted as follows: - entries existing on the database, but not in the payload, are deleted → for example, `ID:4` - entries existing on the database, and in the payload are updated → for example, `ID:3` - entries not existing on the database are created → for example, `ID:5` **`PUT` vs `PATCH`** — Omitted fields get reset to `default` values or `null` in case of `PUT` requests; they are left untouched for `PATCH` requests. Omitted compositions have no effect, whether during `PATCH` or during `PUT`. That is, to delete all children, the payload must specify `null` or `[]`, respectively, for the to-one or to-many composition. ### Deep `DELETE` > Source: /docs/guides/services/served-ootb#deep-delete Deleting a root of a composition hierarchy results in a cascaded delete of all nested children. :::code-group ```sql DELETE .../Orders/1 -- would also delete all headers and items ``` ::: ### Limitations > Source: /docs/guides/services/served-ootb#limitations Note that deep `WRITE` operations are only supported out of the box if the following conditions are met: 1. The on-condition of the composition only uses comparison predicates with an `=` operator. 2. The predicates are only connected with the logical operator `AND`. 3. The operands are references or `$self`. CAP Java also supports pseudo variables like `$user.locale`. ```cds entity Orders { key ID : UUID; title : String; Items : Composition of many OrderItems on substring(title, 0, 1) <= 'F' or Items.pos > 12; // [!code --] Items : Composition of many OrderItems on Items.order = $self; // [!code ++] } entity OrderItems { key order : Association to Orders; key pos : Integer; descr: String; } ``` ## Auto-Generated Keys > Source: /docs/guides/services/served-ootb#auto-generated-keys On `CREATE` operations, `key` elements of type `UUID` are filled in automatically. In addition, on deep inserts and upserts, respective foreign keys of newly created nested objects are filled in accordingly. For example, given a model like that: ```cds entity Orders { key ID : UUID; title : String; Items : Composition of many OrderItems on Items.order = $self; } entity OrderItems { key order : Association to Orders; key pos : Integer; descr: String; } ``` When creating a new `Order` with nested `OrderItems` like that: ```js POST .../Orders { title: 'Order #1', Items: [ { pos:1, descr: 'Item #1' }, { pos:2, descr: 'Item #2' } ] } ``` CAP runtimes will automatically fill in `Orders.ID` with a new uuid, as well as the nested `OrderItems.order.ID` referring to the parent. ## Searching Data > Source: /docs/guides/services/served-ootb#searching-data CAP runtimes provide out-of-the-box support for advanced search of a given text in all textual elements of an entity including nested entities along composition hierarchies. A typical search request looks like that: ```js GET .../Books?$search=Heights ``` That would basically search for occurrences of `"Heights"` in all text fields of Books, that is, in `title` and `descr` using database-specific `contains` operations (for example, using `like '%Heights%'` in standard SQL). ### The `@cds.search` Annotation > Source: /docs/guides/services/served-ootb#the-cdssearch-annotation By default search is limited to the elements of type `String` of an entity that aren't [calculated](../../cds/cdl#calculated-elements) or [virtual](../../cds/cdl#virtual-elements). Yet, sometimes you may want to deviate from this default and specify a different set of searchable elements, or to extend the search to associated entities. Use the `@cds.search` annotation to do so. The general usage is: ```cds @cds.search: { element1, // included element2 : true, // included element3 : false, // excluded assoc1, // extend to searchable elements in target entity assoc2.elementA // extend to a specific element in target entity } entity E { } ``` [Learn more about the syntax of annotations.](../../cds/cdl#annotations){.learn-more} ### Including Fields > Source: /docs/guides/services/served-ootb#including-fields ```cds @cds.search: { title } entity Books { ... } ``` Searches the `title` element only. #### Extend Search to *Associated* Entities > Source: /docs/guides/services/served-ootb#extend-search-to-associated-entities ```cds @cds.search: { author } entity Books { ... } @cds.search: { biography: false } entity Authors { ... } ``` Searches all elements of the `Books` entity, as well as all searchable elements of the associated `Authors` entity. Which elements of the associated entity are searchable is determined by the `@cds.search` annotation on the associated entity. So, from `Authors`, all elements of type `String` are searched but `biography` is excluded. #### Extend to Individual Elements in Associated Entities > Source: /docs/guides/services/served-ootb#extend-to-individual-elements-in-associated-entities ```cds @cds.search: { author.name } entity Books { ... } ``` Searches only in the element `name` of the associated `Authors` entity. ### Excluding Fields > Source: /docs/guides/services/served-ootb#excluding-fields ```cds @cds.search: { isbn: false } entity Books { ... } ``` Searches all elements of type `String` excluding the element `isbn`, which leaves the `title` and `descr` elements to be searched. ::: tip You can explicitly annotate calculated elements to make them searchable, even though they aren't searchable by default. The virtual elements won't be searchable even if they're explicitly annotated. ::: ### The `@Common.Text` Annotation > Source: /docs/guides/services/served-ootb#the-commontext-annotation If an entity has an element annotated with the `@Common.Text` annotation, then the property that holds the display text is added to the list of searchable elements (see exception below). For example, with the following model, the list of searchable elements for `Books` is `title` and `author.name`: ```cds entity Books : cuid { title : String; @Common.Text : author.name author : Association to Author; } entity Author : cuid { name : String; } ``` ::: warning `@cds.search` takes precedence over `@Common.Text` As a result, `@Common.Text` is ignored as soon as `@cds.search` defines anything in including mode. Only if you exclusively exclude properties using `@cds-search`, the `@Common.Text` is kept. ::: To illustrate the above: - `@cds.search: { title: false }` on `Books` would only exclude properties, so `author.name` would still be searched. - `@cds.search: { title }` on `Books` defines an include list, so `author.name` is not searched. In this mode, `@cds.search` is expected to include all properties that should be searched. Hence, `author.name` would need to be added to `@cds.search` itself: `@cds.search: { title, author.name }`. ### Fuzzy Search on SAP HANA Cloud > Source: /docs/guides/services/served-ootb#fuzzy-search-on-sap-hana-cloud > Prerequisite: For CAP Java, you need to run in [`HEX` optimization mode](../../java/cqn-services/persistence-services#sql-optimization-mode) on SAP HANA Cloud and enable cds.sql.hana.search.fuzzy = true Fuzzy search is a fault-tolerant search feature of SAP HANA Cloud, which returns records even if the search term contains additional characters, is missing characters, or has typographical errors. You can configure the fuzziness in the range `[0.0, 1.0]`. The value 1.0 enforces exact search. - Java: cds.sql.hana.search.fuzzinessThreshold = 0.8 - Node.js:cds.hana.fuzzy = 0.7(1) (1) If set to `false`, fuzzy search is disabled and falls back to a case insensitive substring search. Override the fuzziness for elements, using the `@Search.fuzzinessThreshold` annotation: ```cds entity Books { @Search.fuzzinessThreshold: 0.7 title : String; } ``` The relevance of a search match depends on the weight of the element causing the match. By default, all [searchable elements](#cds-search) have equal weight. To adjust the weight of an element, use the `@Search.ranking` annotation. Allowed values are HIGH, MEDIUM (default), and LOW: ```cds entity Books { @Search.ranking: HIGH title : String; @Search.ranking: LOW publisherName : String; } ``` ::: tip Wildcards in search terms When using wildcards in search terms, an *exact pattern search* is performed. Supported wildcards are '*' matching zero or more characters and '?' matching a single character. You can escape wildcards using '\\'. ::: ## Pagination & Sorting > Source: /docs/guides/services/served-ootb#pagination--sorting ### Implicit Pagination > Source: /docs/guides/services/served-ootb#implicit-pagination By default, the generic handlers for READ requests automatically **truncate** result sets to a size of 1,000 records max. If there are more entries available, a link is added to the response allowing clients to fetch the next page of records. The OData response body for truncated result sets contains a `nextLink` as follows: ```http GET .../Books >{ value: [ {... first record ...}, {... second record ...}, ... ], @odata.nextLink: "Books?$skiptoken=1000" } ``` To retrieve the next page of records from the server, the client would use this `nextLink` in a follow-up request, like so: ```http GET .../Books?$skiptoken=1000 ``` On firing this query, you get the second set of 1,000 records with a link to the next page, and so on, until the last page is returned, with the response not containing a `nextLink`. ::: warning Per OData specification for [Server Side Paging](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_ServerDrivenPaging), the value of the `nextLink` returned by the server must not be interpreted or changed by the clients. ::: ### Reliable Pagination > Source: /docs/guides/services/served-ootb#reliable-pagination > Note: This feature is available only for OData V4 endpoints. Using a numeric skip token based on the values of `$skip` and `$top` can result in duplicate or missing rows if the entity set is modified between the calls. _Reliable Pagination_ avoids this inconsistency by generating a skip token based on the values of the last row of a page. The reliable pagination is available with following limitations: - Results of functions or arithmetic expressions can't be used in the `$orderby` option (explicit ordering). - The elements used in the `$orderby` of the request must be of simple type. - All elements used in `$orderby` must also be included in the `$select` option, if it's set. - Complex [concatenations](../protocols/odata#concat) of result sets aren't supported. ::: warning Don't use reliable pagination if an entity set is sorted by elements that contain sensitive information, the skip token could reveal the values of these elements. ::: The feature can be enabled with the following [configuration options](../../node.js/cds-env#project-settings) set to `true`: - Java: cds.query.limit.reliablePaging.enabled: true - Node.js: cds.query.limit.reliablePaging: true ### Paging Limits > Source: /docs/guides/services/served-ootb#paging-limits You can configure default and maximum page size limits in your [project configuration](../../node.js/cds-env#project-settings) as follows: ```json "cds": { "query": { "limit": { "default": 20, //> no default "max": 100 //> default 1000 } } } ``` - The **maximum limit** defines the maximum number of items that can get retrieved, regardless of `$top`. - The **default limit** defines the number of items that are retrieved if no `$top` was specified. #### Annotation `@cds.query.limit` > Source: /docs/guides/services/served-ootb#annotation-cdsquerylimit You can override the defaults by applying the `@cds.query.limit` annotation on the service or entity level, as follows: ```cds @cds.query.limit: { default?, max? } | Number ``` The limit definitions for `CatalogService` and `AdminService` in the following example are equivalent. ```cds @cds.query.limit.default: 20 @cds.query.limit.max: 100 service CatalogService { // ... } @cds.query.limit: { default: 20, max: 100 } service AdminService { // ... } ``` `@cds.query.limit` can be used as shorthand if no default limit needs to be specified at the same level. ```cds @cds.query.limit: 100 service CatalogService { entity Books as projection on my.Books; //> pages at 100 @cds.query.limit: 20 entity Authors as projection on my.Authors; //> pages at 20 } service AdminService { entity Books as projection on my.Books; //> pages at 1000 (default) } ``` #### Precedence > Source: /docs/guides/services/served-ootb#precedence The closest limit applies, that means, an entity-level limit overrides that of its service, and a service-level limit overrides the global setting. The value `0` disables the respective limit at the respective level. ```cds @cds.query.limit.default: 20 service CatalogService { @cds.query.limit.max: 100 entity Books as projection on my.Books; //> default = 20 (from CatalogService), max = 100 @cds.query.limit: 0 entity Authors as projection on my.Authors; //> no default, max = 1,000 (from environment) } ``` ### Implicit Sorting > Source: /docs/guides/services/served-ootb#implicit-sorting Paging requires implied sorting, otherwise records might be skipped accidentally when reading follow-up pages. By default the entity's primary key is used as a sort criterion. For example, given a service definition like this: ```cds service CatalogService { entity Books as projection on my.Books; } ``` The SQL query executed in response to incoming requests to Books will be enhanced with an additional order-by clause as follows: ```sql SELECT ... from my_Books ORDER BY ID; -- default: order by the entity's primary key ``` If the request specifies a sort order, for example, `GET .../Books?$orderby=author`, both are applied as follows: ```sql SELECT ... from my_Books ORDER BY author, -- request-specific order has precedence ID; -- default order still applied in addition ``` We can also define a default order when serving books as follows: ```cds service CatalogService { entity Books as projection on my.Books order by title asc; } ``` Now, the resulting order by clauses are as follows for `GET .../Books`: ```sql SELECT ... from my_Books ORDER BY title asc, -- from entity definition ID; -- default order still applied in addition ``` ... and for `GET .../Books?$orderby=author`: ```sql SELECT ... from my_Books ORDER BY author, -- request-specific order has precedence title asc, -- from entity definition ID; -- default order still applied in addition ``` ## Concurrency Control > Source: /docs/guides/services/served-ootb#concurrency-control CAP runtimes support different ways to avoid lost-update situations as documented in the following. Use _optimistic locking_ to _detect_ concurrent modification of data _across requests_. The implementation relies on [ETags](#etag). Use _pessimistic locking_ to _protect_ data from concurrent modification by concurrent _transactions_. CAP leverages database locks for [pessimistic locking](#select-for-update). ### Conflict Detection Using ETags > Source: /docs/guides/services/served-ootb#conflict-detection-using-etags The CAP runtimes support optimistic concurrency control and caching techniques using ETags. An ETag identifies a specific version of a resource found at a URL. Enable ETags by adding the `@odata.etag` annotation to an element to be used to calculate an ETag value as follows: ```cds using { managed } from '@sap/cds/common'; entity Foo : managed {...} annotate Foo with { modifiedAt @odata.etag } ``` > The value of an ETag element should uniquely change with each update per row. > The `modifiedAt` element from the [pre-defined `managed` aspect](../../cds/common#aspect-managed) is a good candidate, as this is automatically updated. > You could also use update counters or UUIDs, which are recalculated on each update. You use ETags when updating, deleting, or invoking the action bound to an entity by using the ETag value in an `If-Match` or `If-None-Match` header. The following examples represent typical requests and responses: ```http POST Employees { ID:111, name:'Name' } > 201 Created {'@odata.etag': 'W/"2000-01-01T01:10:10.100Z"',...} //> Got new ETag to be used for subsequent requests... ``` ```http GET Employees/111 If-None-Match: "2000-01-01T01:10:10.100Z" > 304 Not Modified // Record was not changed ``` ```http GET Employees/111 If-Match: "2000-01-01T01:10:10.100Z" > 412 Precondition Failed // Record was changed by another user ``` ```http UPDATE Employees/111 If-Match: "2000-01-01T01:10:10.100Z" > 200 Ok {'@odata.etag': 'W/"2000-02-02T02:20:20.200Z"',...} //> Got new ETag to be used for subsequent requests... ``` ```http UPDATE Employees/111 If-Match: "2000-02-02T02:20:20.200Z" > 412 Precondition Failed // Record was modified by another user ``` ```http DELETE Employees/111 If-Match: "2000-02-02T02:20:20.200Z" > 412 Precondition Failed // Record was modified by another user ``` If the ETag validation detects a conflict, the request typically needs to be retried by the client. Hence, optimistic concurrency should be used if conflicts occur rarely. ### Pessimistic Locking > Source: /docs/guides/services/served-ootb#pessimistic-locking _Pessimistic locking_ allows you to lock the selected records so that other transactions are blocked from changing the records in any way. Use _exclusive_ locks when reading entity data with the _intention to update_ it in the same transaction and you want to prevent the data to be locked or updated in a concurrent transaction. Use _shared_ locks if you only need to prevent the entity data to be locked exclusively by an update in a concurrent transaction or by a read operation with lock mode _exclusive_. Non-locking read operations or read operations with lock mode _shared_ are not prevented. The records are locked until the end of the transaction by commit or rollback statement. Here's an overview table: | State | Select Without Lock | Select With Shared Lock | Select With Exclusive Lock/Update | | --------------- | ----------------------- | -------------------------- | ------------------------------------- | | not locked | passes | passes | passes | | shared lock | passes | passes | waits | | exclusive lock | passes | waits | waits | [Learn more about using the `SELECT ... FOR UPDATE` statement in the Node.js runtime.](../../node.js/cds-ql#forupdate){.learn-more} [Learn more about using the `Select.lock()` method in the Java runtime.](../../java/working-with-cql/query-api#write-lock){.learn-more} ::: warning Restrictions - Pessimistic locking is supported for domain entities (DB table rows). The locking is not possible for projections and views. - Pessimistic locking is not supported by SQLite. H2 supports exclusive locks only. ::: # Status-Transition Flows > Source: /docs/guides/services/status-flows Status-transition flows ensure transitions are explicitly modeled, validated, and executed in a controlled and reliable way, thereby eliminating the need for extensive custom coding. – _Status: _ {.abstract} ::: details Enabling it for CAP Java In CAP Node.js support for flows is built-in and available out of the box. For CAP Java, it is provided by the feature [cds-feature-flow](https://central.sonatype.com/artifact/com.sap.cds/cds-feature-flow). Enable it by adding this dependency to your _srv/pom.xml_ file: ```xml com.sap.cds cds-feature-flow runtime ``` ::: ## Modeling Status Flows > Source: /docs/guides/services/status-flows#modeling-status-flows The following example is taken from the [@capire/xtravels](https://github.com/capire/xtravels) sample application, in which we want to model a status flow for travel requests as depicted below: ![A flow diagram showing three status states connected by arrows. The leftmost oval contains the word Open. An arrow labeled accept points from Open to an oval containing Accepted at the top right. Another arrow labeled reject points from Open to an oval containing Canceled at the bottom right.](./assets/xtravels-flow-simple.svg) We can easily model this flow in CDS as follows: ::: code-group ```cds [srv/travel-flows.cds] using { TravelService } from './travel-service'; annotate TravelService.Travels with @flow.status: Status actions { acceptTravel @from: [ #Open ] @to: #Accepted; rejectTravel @from: [ #Open ] @to: #Rejected; deductDiscount @from: [ #Open ]; // restricted to #Open travels } ``` [See full source code](https://github.com/capire/xtravels/tree/main/srv/travel-flows.cds){.learn-more} ::: In essence we model status flows using three annotations: - `@flow.status` designates the **status** element for an entity to be flow-controlled. - `@from` and `@to` define valid entry states and target states for **transitions**, which are implemented by **bound** actions. ### @flow.status: element > Source: /docs/guides/services/status-flows#flowstatus-element Annotation `@flow.status` is an entity-level annotation that identifies the status element for which to establish a status-transition flow. This designated status element is expected to be an `enum`, with enum symbols representing the various states of the entity. For example: ```cds entity Travels { // ... @readonly Status : TravelStatusCode default 'O'; } ``` ```cds type TravelStatusCode : String enum { Open = 'O'; Accepted = 'A'; Rejected = 'X'; }; ``` Alternatively, the status element can also be an association to a code list entity with a _single_ enum element named `code`, which in turn is an `enum` as outlined above: ```cds entity Travels { // ... @readonly Status : Association to TravelStatus default 'O'; } ``` ```cds entity TravelStatus { key code : TravelStatusCode; // name has to be 'code' description : localized String; } ``` > [!tip] Combine with @readonly and default > > Consider making the status element `@readonly` to prevent clients from setting or modifying them in unmanaged ways. In addition, a `default` value can be specified for new entries. ### @from: entry state > Source: /docs/guides/services/status-flows#from-entry-state In an entity with a designated `@flow.status` element, add the `@from` annotation to a bound action of that entity to define the valid entry states for that action, either as a single value or an array of values: ```cds @from: [#Open] action acceptTravel(); @from: #Open action acceptTravel(); // equivalent ``` Use the enum symbols defined with the designated status elements such as `#Open` in the example above. You can also use the raw values such as `'O'`, but using enum symbols is recommended for better readability. When the action is invoked, the current state of the entity is validated against the states defined in `@from`. If the current state does not match any of the defined states, the action execution is rejected. ### @to: target state > Source: /docs/guides/services/status-flows#to-target-state Add the `@to` annotation to a bound action of a flow-controlled entity to define the target state for that action, either as a single value or the special value `$flow.previous`: ```cds @from: [#Open] @to: #Accepted action acceptTravel(); ``` Use the enum symbols defined with the designated status elements such as `#Open` in the example above. You can also use the raw values such as `'O'`, but using enum symbols is recommended for better readability. At runtime, after the action execution, the status element of the entity is automatically updated to the target state defined in `@to`. ### @to: $flow.previous > Source: /docs/guides/services/status-flows#to-flowprevious Use the target state `$flow.previous` to return a previous state from a current state that can be reached via different routes. If present in a flow CAP framework will automatically track the sequence of states entered. The following example introduces a `Blocked` state with two possible previous states, `Open` and `InReview`, and an `unblock` action that restores the previous state. ![The graphic is explained in the accompanying text.](./assets/xtravels-flow-previous.svg) ::: code-group ```cds [srv/flow-previous.cds] annotate TravelService.Travels with @flow.status: Status actions { acceptTravel @from: #InReview @to: #Accepted; rejectTravel @from: #InReview @to: #Rejected; deductDiscount @from: #Open; reviewTravel @from: #Open @to: #InReview; // [!code highlight] reopenTravel @from: #InReview @to: #Open; // [!code highlight] blockTravel @from: [#Open, #InReview] @to: #Blocked; // [!code highlight] unblockTravel @from: #Blocked @to: $flow.previous; // [!code highlight] } ``` ::: [See sample in _@capire/xtravels_.](https://github.com/capire/xtravels/tree/main/xmpls/flow) {.learn-more} ## Served Out-of-the-Box > Source: /docs/guides/services/status-flows#served-out-of-the-box ### By Generic Handlers > Source: /docs/guides/services/status-flows#by-generic-handlers The need for custom code is greatly reduced when using status-transition flows, as CAP automatically provides generic handlers for the common flow operations: validation of entry states before action execution, and updating the status to the target state after action execution. - Based on the `@from` annotation, a generic handler validates that the entity is in a valid entry state - the current state must match one of the states specified in `@from`. If validation fails, the request returns a `409 Conflict` HTTP status code with an appropriate error message. - Based on the `@to` annotation, a generic handler automatically updates the entity's status to the target state. ### To Fiori UIs > Source: /docs/guides/services/status-flows#to-fiori-uis When using SAP Fiori elements, status-transition flows are automatically recognized and supported in the generated UIs. UI annotations to enable/disable respective buttons and to refresh displayed data are automatically generated for UI5 as shown below: ```xml in/Status_code ``` ```xml in/Status_code O ``` ## Adding Custom Handlers > Source: /docs/guides/services/status-flows#adding-custom-handlers While many use cases are covered by the generic handlers, you can add custom handlers for the actions, as usual. For example: - Add `before` handlers for additional validations before entering a transition. - Add `after` handlers for conditional target states. ## Current Limitations > Source: /docs/guides/services/status-flows#current-limitations Following are are some current limitations of status-transition flows, which we plan to address in future releases: 1. Status-transition flows also work with draft-enabled entities, however when in draft state all actions are disabled. Thus, status transitions can only be performed on active entities. 2. CRUD and DRAFT operations can't be restricted by status-transition flows today. Only bound actions can be flow-controlled. # Declarative Constraints > Source: /docs/guides/services/constraints Declarative constraints allow you to express data validity conditions using [CDS Expression Language (CXL)](../../cds/cxl.md) that are enforced automatically whenever data is written. This greatly reduces the need for extensive custom code for input validation. {.abstract} > [!note] > Don't confuse constraints as discussed in here with [database constraints](../databases/cdl-to-ddl#database-constraints). Declarative constraints are meant for domain-specific input validation with error messages meant to be shown to end users, while database constraints are meant to prevent data corruption due to programming error, with error messages not intended for end users. ## Introduction > Source: /docs/guides/services/constraints#introduction Use annotations like `@assert` and `@mandatory` to declaratively add constraints for the primary purpose of input validation. Add them to the elements of the entities exposed by respective services, which accept input to be validated. ### Constraints Annotations > Source: /docs/guides/services/constraints#constraints-annotations Following is an excerpt from the [`@capire/xtravels`](https://github.com/capire/xtravels/tree/main/srv/travel-constraints.cds) sample: ::: code-group ```cds [srv/travel-constraints.cds] using { TravelService } from './travel-service'; annotate TravelService.Travels with { Description @assert: (case when length(Description) < 3 then 'Description too short' end); Agency @mandatory @assert: (case when not exists Agency then 'Agency does not exist' end); Customer @assert: (case when Customer is null then 'Customer must be specified' when not exists Customer then 'Customer does not exist' end); BeginDate @mandatory @assert: (case when BeginDate > EndDate then 'ASSERT_BEGINDATE_BEFORE_ENDDATE' when exists Bookings [Flight.date < Travel.BeginDate] then 'ASSERT_BOOKINGS_IN_TRAVEL_PERIOD' end); BookingFee @assert: (case when BookingFee < 0 then 'ASSERT_BOOKING_FEE_NON_NEGATIVE' end); } ``` ::: > [!tip] > **BEST PRACTICES** applied here > > **Separation of Concerns** – always put secondary concerns, such as constraints in this case, into separate files as in the example, instead of polluting your core service definitions. > > **Concise and comprehensible** – in contrast to imperative coding, constraints expressed in expression languages as shown here are easy to read and understand. > > **Fueling AI** – Not the least, this also fuels AI-based approaches: AIs can easily generate such constraints, and you as a developer using such AIs can easily validate what was generated. ### Served Out-of-the-Box > Source: /docs/guides/services/constraints#served-out-of-the-box The constraints are enforced automatically by the CAP runtimes on any input, and if failures occur, the request is ultimately rejected and the transaction rolled back. Some of the checks, for example, the static `@mandatory` checks, are validated directly on the input data, while the ones specified with `@assert:(\)` are collected into a query and **pushed down to the database** for execution. This in turn means, that first the respective `INSERT`s and `UPDATE`s are sent to the database, followed by the validation query. ::: details Behind the scenes... The automatically compiled and executed validation query would look like that (in [CQL](../../cds/cql)) for the constraints from the sample above: ```sql SELECT from TravelService.Travels { (case when length(Description) < 3 then 'Description too short' end) as Description, (case when not exists Agency then 'Agency does not exist' end) as Agency, (case when Customer is null then 'Customer must be specified' when not exists Customer then 'Customer does not exist' end) as Customer, (case when BeginDate > EndDate then 'ASSERT_BEGINDATE_BEFORE_ENDDATE' when exists Bookings [Flight.date < Travel.BeginDate] then 'ASSERT_BOOKINGS_IN_TRAVEL_PERIOD' end) as BeginDate, (case when BookingFee < 0 then 'ASSERT_BOOKING_FEE_NON_NEGATIVE' end) as BookingFee, } ``` ::: > [!tip] > **BEST PRACTICES** applied here > > **Push down to the database** is a general principle applied in CAP. Applied to input validation with declarative constraints it means that instead of reading a lot of related data into the service layer to do the checks there, we push down the respective checks to where the data is (in the database). > > **What, not how!** – This in turn boils down to the even more general principle that we share with functional programming: tell us *what* to do (= *intentional*), not how (= *imperative*), because then generic runtimes can apply advanced optimized ways to execute things, which is impossible with imperative code. ### Served to Fiori UIs > Source: /docs/guides/services/constraints#served-to-fiori-uis For Fiori UIs as clients the error messages will be automatically be equiped with relevant `target` properties to attach them to the respective fields on the UIs. For example a Fiori UI for the sample above, would display returned errors like that: ![image-20251219115646302](./assets/fiori-errors.png) ::: details Behind the scenes ... A sample response for such errors displayed in Fiori UIs would look like that: ```json { "@odata.context": "$metadata#Travels/$entity", "ID": 4132, "DraftMessages": [ { "target": "/Travels(ID=4132,IsActiveEntity=false)/EndDate", // [!code focus] "numericSeverity": 4, "@Common.numericSeverity": 4, "message": "Alle Buchungen müssen innerhalb des Reisezeitraums liegen", "code": "ASSERT_BOOKINGS_IN_TRAVEL_PERIOD" }, { "target": "/Travels(ID=4132,IsActiveEntity=false)/Customer_ID", // [!code focus] "numericSeverity": 4, "@Common.numericSeverity": 4, "message": "Customer does not exist", "code": "400" }, { "target": "/Travels(ID=4132,IsActiveEntity=false)/Bookings(Travel_ID=4132,Pos=1,IsActiveEntity=false)/Flight_date", // [!code focus] "numericSeverity": 4, "@Common.numericSeverity": 4, "message": "Das Flugdatum dieser Buchung liegt nicht innerhalb des Reisezeitraums", "code": "ASSERT_BOOKING_IN_TRAVEL_PERIOD" } ], "IsActiveEntity": false } ``` ::: ## Input Validation > Source: /docs/guides/services/constraints#input-validation Use annotations like `@assert` and `@mandatory` to declaratively add constraints for the primary purpose of input validation. Add them to the elements of the entities exposed by respective services, which accept input to be validated. ### `@assert:` *(constraint)* > Source: /docs/guides/services/constraints#assert-constraint Annotate an element with `@assert: ()` to specify checks to be applied on respective input and errors to be raised if they fail. The `` are standard SQL `case` expressions with one or more `when` branches, as shown in this example: ```cds annotate TravelService.Travels with { Description @assert: (case // [!code focus] when Description is null then 'Description must be specified' // [!code focus] when trim(Description) = '' then 'Description must not be empty' // [!code focus] when length(Description) < 3 then 'Description too short' // [!code focus] end); // [!code focus] } ``` [Refer to _Expressions as Annotation Values_ for details on syntax.](../../cds/cdl.md#expressions-as-annotation-values) {.learn-more} Conditions can also **refer to other data elements** in the same entity as shown in this example which validated input for `BeginDate` with the related `EndDate`: ```cds annotate TravelService.Travels with { BeginDate @assert: (case // [!code focus] when BeginDate > EndDate then 'Begin date must be before end date' // [!code focus] end); // [!code focus] } ``` We can also use **path expressions** to compare with data from **associated** entities. For example, this one is from another annotation on `TravelService.Bookings` in the [`@capire/xtravels`](https://github.com/capire/xtravels/tree/main/srv/travel-constraints.cds) sample, that checks if all currencies specified in the list of bookings match the currency chosen in the travel header, refered to by the `Travel` association: ```cds annotate TravelService.Bookings with { Currency @assert: (case // [!code focus] when Currency != Travel.Currency then 'Currencies must match' // [!code focus] end); // [!code focus] } ``` We can also do checks with sets of related data using path expressions which navigate along **to-many associations** or compositions, combined with SQL's `exists` quantifier, and optional [infix filters](../../cds/cql#with-infix-filters), as shown in this example: ```cds annotate TravelService.Travels with { BeginDate @assert: (case // [!code focus] when exists Bookings [Flight.date < Travel.BeginDate] // [!code focus] then 'All bookings must be within travel period' // [!code focus] end); // [!code focus] } ``` ### `@assert.format` > Source: /docs/guides/services/constraints#assertformat Allows you to specify a regular expression string (in ECMA 262 format in CAP Node.js and java.util.regex.Pattern format in CAP Java) that all string input must match. ```cds entity Foo { bar : String @assert.format: '[a-z]ear'; } ``` ### `@assert.range` > Source: /docs/guides/services/constraints#assertrange Allows you to specify `[ min, max ]` ranges for elements with ordinal types — that is, numeric or date/time types. For `enum` elements, `true` can be specified to restrict all input to the defined enum values. ```cds entity Foo { bar : Integer @assert.range: [ 0, 3 ]; boo : Decimal @assert.range: [ 2.1, 10.25 ]; car : DateTime @assert.range: ['2018-10-31', '2019-01-15']; zoo : String @assert.range enum { high; medium; low; }; } ``` By default, specified `[min,max]` ranges are interpreted as closed intervals, that means, the performed checks are `min ≤ input ≤ max`. You can also specify open intervals by wrapping the *min* and/or *max* values into parentheses like that: ```cds @assert.range: [(0),100] // 0 < input ≤ 100 @assert.range: [0,(100)] // 0 ≤ input < 100 @assert.range: [(0),(100)] // 0 < input < 100 ``` In addition, you can use an underscore `_` to represent *Infinity* like that: ```cds @assert.range: [(0),_] // positive numbers only, _ means +Infinity here @assert.range: [_,(0)] // negative number only, _ means -Infinity here ``` > Basically values wrapped in parentheses _`(x)`_ can be read as _excluding `x`_ for *min* or *max*. Note that the underscore `_` doesn't have to be wrapped into parentheses, as by definition no number can be equal to *Infinity* . Support for open intervals and infinity is available for CAP Node.js since `@sap/cds` version **8.5** and in CAP Java since version **3.5.0**. ### `@assert.target` > Source: /docs/guides/services/constraints#asserttarget Annotate a [managed to-one association](../../cds/cdl#managed-associations) with `@assert.target` to check whether the target entity referenced by the association (the reference's target) exists for a given input. ```cds entity Books { key ID : UUID; title : String; author : Association to Authors @assert.target; } entity Authors { key ID : UUID; name : String; books : Association to many Books on books.author = $self; } ``` You can check whether multiple targets exist in the same transaction. For example, in the `Books` entity, you could annotate one or more managed to-one associations with the `@assert.target` annotation. However, it is assumed that dependent values were inserted before the current transaction. For example, in a deep create scenario, when creating a book, checking whether an associated author exists that was created as part of the same deep create transaction isn't supported, in this case, you will get an error. The `@assert.target` check constraint is meant to **validate user input** and not to ensure referential integrity. Therefore only `CREATE`, and `UPDATE` events are supported (`DELETE` events are not supported). To ensure that every non-null foreign key in a table has a corresponding primary key in the associated/referenced target table (ensure referential integrity), the [`@assert.integrity`](../databases/cdl-to-ddl#database-constraints) constraint must be used instead. If the reference's target doesn't exist, an HTTP response (error message) is provided to HTTP client applications and logged to stdout in debug mode. The HTTP response body's content adheres to the standard OData specification for an error [response body](https://docs.oasis-open.org/odata/odata-json-format/v4.01/cs01/odata-json-format-v4.01-cs01.html#sec_ErrorResponse). ```http POST Books HTTP/1.1 Accept: application/json;odata.metadata=minimal Prefer: return=minimal Content-Type: application/json;charset=UTF-8 {"author_ID": "796e274a-c3de-4584-9de2-3ffd7d42d646"} ``` **HTTP Response** ```http HTTP/1.1 400 Bad Request odata-version: 4.0 content-type: application/json;odata.metadata=minimal {"error": { "@Common.numericSeverity": 4, "code": "400", "message": "Value doesn't exist", "target": "author_ID" }} ``` ::: tip In contrast to the `@assert.integrity` constraint, whose check is performed on the underlying database layer, the `@assert.target` check constraint is performed on the application service layer before the custom application handlers are called. ::: ::: warning Cross-service checks are not supported. It is expected that the associated entities are defined in the same service. ::: ::: warning The `@assert.target` check constraint relies on database locks to ensure accurate results in concurrent scenarios. However, locking is a database-specific feature, and some databases don't permit to lock certain kinds of objects. On SAP HANA, for example, views with joins or unions can't be locked. Do not use `@assert.target` on such artifacts/entities. ::: ### `@mandatory` > Source: /docs/guides/services/constraints#mandatory Elements marked with `@mandatory` are checked for missing and empty input and respective requests are rejected. ```cds service Sue { entity Books { key ID : UUID; title : String @mandatory; } } ``` In addition to server-side input validation as introduced above, this adds a corresponding `@FieldControl` annotation to the EDMX so that OData / Fiori clients would enforce a valid entry, thereby avoiding unnecessary request roundtrips: ```xml ```
### `@readonly` > Source: /docs/guides/services/constraints#readonly Elements annotated with `@readonly`, as well as [_calculated elements_](../../cds/cdl#calculated-elements), are protected against write operations. That is, if a CREATE or UPDATE operation specifies values for such fields, these values are **silently ignored**. By default [`virtual` elements](../../cds/cdl#virtual-elements) are also _calculated_. ::: tip The same applies for fields with the [OData Annotations](../protocols/odata#annotations) `@FieldControl.ReadOnly` (static), `@Core.Computed`, or `@Core.Immutable` (the latter only on UPDATEs). ::: ::: warning Not allowed on keys Do not use the `@readonly` annotation on keys in all variants. :::
## Error Messages > Source: /docs/guides/services/constraints#error-messages ### Custom Messages > Source: /docs/guides/services/constraints#custom-messages For `@assert: ()` annotations you always specify custom error messages, specific to the individual checks: ```cds annotate TravelService.Travels with { Description @assert: (case // [!code focus] when Description is null then 'Description must be specified' // [!code focus] when trim(Description) = '' then 'Description must not be empty' // [!code focus] when length(Description) < 3 then 'Description too short' // [!code focus] end); // [!code focus] } ``` The annotations `@assert.range`, `@assert.format`, and `@mandatory` also support custom error messages, just not as elegant, as the above: Use the annotation `@.message` to specify a custom error message: ```cds entity Person : cuid { name : String; @assert.format: '/^\S+@\S+\.\S+$/' @assert.format.message: 'Provide a valid email address' email : String; @assert.range: [(0),_] @assert.range.message: '{i18n>person-age}' age : Int16; } ``` Note: The above can also be written like that: ```cds entity Person : cuid { name : String; @assert.format: { $value: '/^\S+@\S+\.\S+$/', message: 'Provide a valid email address' } email : String; @assert.range: { $value: [(0),_], message: '{i18n>person-age}' } age : Int16; } ``` ### Localized Messages > Source: /docs/guides/services/constraints#localized-messages Whenever you specify an error message with the annotations above, that is, in the `then` part of an `@assert: ()` or in `@mandatory.message`, `@assert.format.message`, or `@assert.range.message`, you can either specify a plain text, or a [I18n text bundle key](../uis/i18n#externalizing-texts-bundles). Actually, we saw this already in the [sample in the introduction](#introduction): ::: code-group ```cds [srv/travel-constraints.cds] using { TravelService } from './travel-service'; annotate TravelService.Travels with { Description @assert: (case when length(Description) < 3 then 'Description too short' // [!code focus] end); Agency @mandatory @assert: (case when not exists Agency then 'Agency does not exist' // [!code focus] end); BeginDate @mandatory @assert: (case when BeginDate > EndDate then 'ASSERT_BEGINDATE_BEFORE_ENDDATE' // [!code focus] when exists Bookings [Flight.date < Travel.BeginDate] then 'ASSERT_BOOKINGS_IN_TRAVEL_PERIOD' // [!code focus] end); BookingFee @assert: (case when BookingFee < 0 then 'ASSERT_BOOKING_FEE_NON_NEGATIVE' // [!code focus] end); } ``` ::: If you use a message key, the message is automatically looked up in the message bundle of the service with the current user's preferred locale. [Learn more about localized messages.](../uis/i18n){.learn-more} ## Field Control > Source: /docs/guides/services/constraints#field-control Declarative constraints can also be used to do field control in Fiori UIs, that is, to add visual indicators to mandatory or readonly fields, or to hide fields. In particular, CAP automatically adds respective OData annotations to generated EDMX $metadata documents for the CDS listed below. ### `@mandatory` > Source: /docs/guides/services/constraints#mandatory-1 Currently only static `@mandatory` annotations are supported for field control in Fiori UIs. They result in the addition of the following OData annotation to the EDMX $metadata: ```xml ``` ### `@readonly` > Source: /docs/guides/services/constraints#readonly-1 Currently only static `@readonly` annotations are supported for field control in Fiori UIs. They result in the addition of the following OData annotation to the EDMX $metadata: ```xml ``` ### `@UI.Hidden` > Source: /docs/guides/services/constraints#uihidden Use the `@UI.Hidden` annotation to hide fields in Fiori UIs. You can also use it with expressions as values, for example like that: ```cds @UI.Hidden: (status <> 'visible') ``` [Learn more about that in the *OData guide*](../protocols/odata#expression-annotations) {.learn-more} ## Invariant Constraints > Source: /docs/guides/services/constraints#invariant-constraints Annotations in general are propagated from underlying entities to views on top. This also applies to the annotations like `@assert` and `@mandatory` introduced in here, which can be used to declare invariant constraints on base entities, which are then inherited to and hence enforced on all interface views on top. Picking up the [sample from the introduction](#introduction) again, we could extract some of the constraints and add them to the `sap.capire.travels.Travels` entity from the domain model, with is the underlying entity of `TravelService.Travels`: ::: code-group ```cds [srv/travel-invariants.cds] using { sap.capire.travels.Travels } from '../db/schema'; annotate Travels with { Description @assert: (case when length(Description) < 3 then 'Description too short' end); Customer @assert: (case when Customer is null then 'Customer must be specified' when not exists Customer then 'Customer does not exist' end); } ``` ::: And this works fine for these constraints in this example. However, it may be dangerous if you do that for constraints which refer to other fields, as views on top might not expose these fields. This would immediately lead to compiler errors. Note also, that even though you might think you know all your views, and ensure all related fields are included in all views, somebody that you never meet, builds a new view on top of one of your entity. Hence always **adhere to this strict rule**: > [!danger] > > Only add invariant constraints to underlying entities that **do not refer to other elements**! # Custom Event Handlers > Source: /docs/guides/services/custom-code As most use cases are covered by [generic runtimes](served-ootb), the need for custom coding is greatly reduced. Nevertheless, there are still numerous cases where custom code is required. And the CAP runtimes provide a flexible and powerful event handler mechanism for you to add custom code at any point during request processing. {.abstract} > [!tip] Always prefer declarative techniques > Consider first whether your custom logic can be addressed using declarative techniques, like [status flows](./status-flows) or [declarative constraints](./constraints), before resorting over to programmatic and hence imperative options. ## Custom Service Providers > Source: /docs/guides/services/custom-code#custom-service-providers **In Node.js**, the easiest way to add custom implementations for services is through equally named _.js_ files placed next to a service definition's _.cds_ file: ```sh ./srv - cat-service.cds # service definitions - cat-service.js # service implementation ... ``` [Learn more about providing service implementations in Node.js.](../../node.js/core-services#implementing-services){.learn-more} **In Java**, you'd assign `EventHandler` classes using dependency injection as follows: ```Java @Component @ServiceName("org.acme.Foo") public class FooServiceImpl implements EventHandler {...} ``` [Learn more about Event Handler classes in Java.](../../java/event-handlers/index#handlerclasses){.learn-more} ## Custom Event Handlers > Source: /docs/guides/services/custom-code#custom-event-handlers Within your custom implementations, you can register event handlers like that: ::: code-group ```js [Node.js] module.exports = function (){ this.on ('submitOrder', (req)=>{...}) //> custom actions this.on ('CREATE',`Books`, (req)=>{...}) this.before ('UPDATE',`*`, (req)=>{...}) this.after ('READ',`Books`, (books)=>{...}) } ``` ```Java @Component @ServiceName("BookshopService") public class BookshopServiceImpl implements EventHandler { @On(event="submitOrder") public void onSubmitOrder (EventContext req) {...} @On(event="CREATE", entity="Books") public void onCreateBooks (EventContext req) {...} @Before(event="UPDATE", entity="*") public void onUpdate (EventContext req) {...} @After(event="READ", entity="Books") public void onReadBooks (EventContext req) {...} } ``` ::: [Learn more about **adding event handlers in Node.js**.](../../node.js/core-services#srv-on-before-after){.learn-more} [Learn more about **adding event handlers in Java**.](../../java/event-handlers/index#handlerclasses){.learn-more} ## Hooks: `on`, `before`, `after` > Source: /docs/guides/services/custom-code#hooks-on-before-after In essence, event handlers are functions/method registered to be called when a certain event occurs, with the event being a custom operation, like `submitOrder`, or a CRUD operation on a certain entity, like `READ Books`; in general following this scheme: - `` , `` , `[]` → handler function CAP allows to plug in event handlers to these different hooks, that is phases during processing a certain event: - `on` handlers run _instead of_ the generic/default handlers. - `before` handlers run _before_ the `on` handlers - `after` handlers run _after_ the `on` handlers, and get the result set as input `on` handlers form an *interceptor* stack: the topmost handler getting called by the framework. The implementation of this handler is in control whether to delegate to default handlers down the stack or not. `before` and `after` handlers are *listeners*: all registered listeners are invoked in parallel. If one vetoes / throws an error the request fails. ## Within Event Handlers > Source: /docs/guides/services/custom-code#within-event-handlers Event handlers all get a uniform _Request_/_Event Message_ context object as their primary argument, which, among others, provides access to the following information: - The `event` name — that is, a CRUD method name, or a custom-defined one - The `target` entity, if any - The `query` in [CQN](../../cds/cqn) format, for CRUD requests - The `data` payload - The `user`, if identified/authenticated - The `tenant` using your SaaS application, if enabled [Learn more about **implementing event handlers in Node.js**.](../../node.js/events#cds-request){.learn-more} [Learn more about **implementing event handlers in Java**.](../../java/event-handlers/index#eventcontext){.learn-more} # Custom Actions and Functions > Source: /docs/guides/services/custom-actions ## Defining Custom Actions > Source: /docs/guides/services/custom-actions#defining-custom-actions In addition to common CRUD operations, you can declare domain-specific custom operations as shown below. These custom operations always need custom implementations in corresponding events handlers. You can define `actions` and `functions` in CDS models like that: ```cds service Sue { // unbound actions & functions function sum (x:Integer, y:Integer) returns Integer; function stock (id : Foo:ID) returns Integer; action add (x:Integer, to: Integer) returns Integer; // bound actions & functions entity Foo { key ID:Integer } actions { function getStock() returns Integer; action order (x:Integer) returns Integer; // bound to the collection and not a specific instance of Foo action customCreate (in: many $self, x: String) returns Foo; // All parameters are optional by default, unless marked with `not null`: action discard (reason: String not null); } } ``` [Learn more about modeling actions and functions in CDS.](../../cds/cdl#actions){.learn-more} ## Kinds of Actions > Source: /docs/guides/services/custom-actions#kinds-of-actions The differentiation between *Actions* and *Functions* as well as *bound* and *unbound* stems from the OData specifications, and in essence is as follows: - **Actions** modify data in the server - **Functions** retrieve data - **Unbound** actions/functions are like plain unbound functions in JavaScript. - **Bound** actions/functions always receive the bound entity's primary key as implicit first argument, similar to `this` pointers in Java or JavaScript. The exception are bound actions to collections, which are bound against the collection and not a specific instance of the entity. An example use case are custom create actions for the SAP Fiori elements UI. ## Implementing Actions > Source: /docs/guides/services/custom-actions#implementing-actions In general, implement actions or functions like that: ```js module.exports = function Sue(){ this.on('sum', ({data:{x,y}}) => x+y) this.on('add', ({data:{x,to}}) => stocks[to] += x) this.on('stock', ({data:{id}}) => stocks[id]) this.on('getStock','Foo', ({params:[id]}) => stocks[id]) this.on('order','Foo', ({params:[id],data:{x}}) => stocks[id] -= x) } ``` Event handlers for actions or functions are very similar to those for CRUD events, with the name of the action/function replacing the name of the CRUD operations. No entity is specific for unbound actions/functions. **Method-style Implementations** in Node.js, you can alternatively implement actions and functions using conventional JavaScript methods with subclasses of `cds.Service`: ```js module.exports = class Sue extends cds.Service { sum(x,y) { return x+y } add(x,to) { return stocks[to] += x } stock(id) { return stocks[id] } getStock(Foo,id) { return stocks[id] } order(Foo,id,x) { return stocks[id] -= x } } ``` ## Calling Actions / Functions > Source: /docs/guides/services/custom-actions#calling-actions--functions **HTTP Requests** to call the actions/function declared above look like that: ```js GET .../sue/sum(x=1,y=2) // unbound function GET .../sue/stock(id=2) // unbound function POST .../sue/add {"x":11,"to":2} // unbound action GET .../sue/Foo(2)/Sue.getStock() // bound function POST .../sue/Foo(2)/Sue.order {"x":3} // bound action ``` > Note: You always need to add the `()` for functions, even if no arguments are required. The OData standard specifies that bound actions/functions need to be prefixed with the service's name. In the previous example, entity `Foo` has a bound action `order`. That action must be called via `/Foo(2)/Sue.order` instead of simply `/Foo(2)/order`. > For convenience, you may: > - Call bound actions/functions without prefixing them with the service name. > - Omit the `()` if no parameter is required. > - Use query options to provide function parameters like `sue/sum?x=1&y=2`
**Programmatic** usage via **generic APIs** for Node.js: For unbound actions and functions: ```ts async function srv.send ( event : string | { event, data?, headers?: object }, data? : object | any ) return : result of this.dispatch(req) ``` For bound actions and functions: ```ts async function srv.send ( event : string | { event, entity, data?, params?: array of object, headers?: object }, entity : string, data? : object | any ) return : result of this.dispatch(req) ``` - `event` is a name of a custom action or function - `entity` is a name of an entity - `params` are keys of the entity instance Programmatic usage would look like this for Node.js: ```js const srv = await cds.connect.to('Sue') // unbound actions/functions await srv.send('sum',{x:1,y:2}) await srv.send('stock',{id:2}) await srv.send('add',{x:11,to:2}) // actions/functions bound to collection await srv.send('getStock','Foo',{id:2}) // for actions/functions bound to entity instance, use this syntax await srv.send({ event: 'order', entity: 'Foo', data: {x:3}, params: [{id:2}]}) ``` > Note: Always pass the target entity name as second argument for bound actions/functions.
**Programmatic** usage via **typed API** — Node.js automatically equips generated service instances with specific methods matching the definitions of actions/functions found in the services' model. This allows convenient usage like that: ```js const srv = await cds.connect.to(Sue) // unbound actions/functions srv.sum(1,2) srv.stock(2) srv.add(11,2) // bound actions/functions srv.getStock('Foo',2) srv.order('Foo',2,3) ``` > Note: Even with that typed APIs, always pass the target entity name as second argument for bound actions/functions. # Serving Media Data > Source: /docs/guides/services/media-data CAP provides out-of-the-box support for serving media and other binary data. Media data can be stored either directly in the database or in an external repository, and can be streamed to and from clients. CAP uses OData V4's support for media resources to handle media data.{.abstract} ## Annotating Media Elements > Source: /docs/guides/services/media-data#annotating-media-elements You can use the following annotations in the service model to indicate that an element in an entity contains media data. `@Core.MediaType` : Indicates that the element contains media data (directly or using a redirect). The value of this annotation is either a string with the contained MIME type (as shown in the first example), or is a path to the element that contains the MIME type (as shown in the second example). `@Core.IsMediaType` : Indicates that the element contains a MIME type. The `@Core.MediaType` annotation of another element can reference this element. `@Core.IsURL @Core.MediaType` : Indicates that the element contains a URL pointing to the media data (redirect scenario). `@Core.ContentDisposition.Filename` : Indicates that the element is expected to be displayed as an attachment, that is downloaded and saved locally. The value of this annotation is a path to the element that contains the Filename (as shown in the fourth example ). `@Core.ContentDisposition.Type` : Can be used to instruct the browser to display the element inline, even if `@Core.ContentDisposition.Filename` is specified, by setting to `inline` (see the fifth example). If omitted, the behavior is `@Core.ContentDisposition.Type: 'attachment'`. [Learn more how to enable stream support in SAP Fiori elements.](https://ui5.sap.com/#/topic/b236d32d48b74304887b3dd5163548c1){.learn-more} The following examples show these annotations in action: 1. Media data is stored in a database with a fixed media type `image/png`: ```cds entity Books { //... image : LargeBinary @Core.MediaType: 'image/png'; } ``` 2. Media data is stored in a database with a _variable_ media type: ```cds entity Books { //... image : LargeBinary @Core.MediaType: imageType; imageType : String @Core.IsMediaType; } ``` 3. Media data is stored in an external repository: ```cds entity Books { //... imageUrl : String @Core.IsURL @Core.MediaType: imageType; imageType : String @Core.IsMediaType; } ``` 4. Content disposition data is stored in a database with a _variable_ disposition: ```cds entity Authors { //... image : LargeBinary @Core.MediaType: imageType @Core.ContentDisposition.Filename: fileName; fileName : String; } ``` 5. The image shall have the suggested file name but be displayed inline nevertheless: ```cds entity Authors { //... image : LargeBinary @Core.MediaType: imageType @Core.ContentDisposition.Filename: fileName @Core.ContentDisposition.Type: 'inline'; fileName : String; } ``` [Learn more about the syntax of annotations.](../../cds/cdl#annotations){.learn-more} ::: warning In case you rename the properties holding the media type or content disposition information in a projection, you need to update the annotation's value as well. ::: ## Reading Media Resources > Source: /docs/guides/services/media-data#reading-media-resources Read media data using `GET` requests of the form `/Entity()/mediaProperty`: ```cds GET ../Books(201)/image > Content-Type: application/octet-stream ``` > The response's `Content-Type` header is typically `application/octet-stream`. > Although allowed by [RFC 2231](https://datatracker.ietf.org/doc/html/rfc2231), Node.js does not support line breaks in HTTP headers. Hence, make sure you remove any line breaks from your `@Core.IsMediaType` content. Read media data with `@Core.ContentDisposition.Filename` in the model: ```cds GET ../Authors(201)/image > Content-Disposition: 'attachment; filename="foo.jpg"' ``` > The media data is streamed automatically. [Learn more about returning a custom streaming object (Node.js - beta).](../../node.js/best-practices#custom-streaming-beta){.learn-more} ## Creating a Media Resource > Source: /docs/guides/services/media-data#creating-a-media-resource As a first step, create an entity without media data using a POST request to the entity. After creating the entity, you can insert a media property using the PUT method. The MIME type is passed in the `Content-Type` header. Here are some sample requests: ```cds POST ../Books Content-Type: application/json { } ``` ```cds PUT ../Books(201)/image Content-Type: image/png ``` > The media data is streamed automatically. ## Updating Media Resources > Source: /docs/guides/services/media-data#updating-media-resources The media data for an entity can be updated using the PUT method: ```cds PUT ../Books(201)/image Content-Type: image/png ``` > The media data is streamed automatically. ## Deleting Media Resources > Source: /docs/guides/services/media-data#deleting-media-resources One option is to delete the complete entity, including all media data: ```http DELETE ../Books(201) ``` Alternatively, you can delete a media data element individually: ```http DELETE ../Books(201)/image ``` ## Using External Resources > Source: /docs/guides/services/media-data#using-external-resources The following are requests and responses for the entity containing redirected media data from the third example, "Media data is stored in an external repository". > This format is used by OData-Version: 4.0. To be changed in OData-Version: 4.01. ```cds GET: ../Books(201) >{ ... image@odata.mediaReadLink: "http://other-server/image.jpeg", image@odata.mediaContentType: "image/jpeg", imageType: "image/jpeg" } ``` ## Conventions & Limitations > Source: /docs/guides/services/media-data#conventions--limitations #### General Conventions > Source: /docs/guides/services/media-data#general-conventions - Binary data in payloads must be a Base64 encoded string. - Binary data in URLs must have the format `binary''`. For example: ```http GET $filter=ID eq binary'Q0FQIE5vZGUuanM=' ``` #### Node.js Runtime Conventions and Limitations > Source: /docs/guides/services/media-data#nodejs-runtime-conventions-and-limitations - The usage of binary data in some advanced constructs like the `$apply` query option and `/any()` might be limited. - On SQLite, binary strings are stored as plain strings, whereas a buffer is stored as binary data. As a result, if in a CDS query, a binary string is used to query data stored as binary, this wouldn't work. - Please note, that SQLite doesn't support streaming. That means, that LargeBinary fields are read as a whole (not in chunks) and stored in memory, which can impact performance. - SAP HANA Database Client for Node.js (HDB) and SAP HANA Client for Node.js (`@sap/hana-client`) packages handle binary data differently. For example, HDB automatically converts binary strings into binary data, whereas SAP HANA Client doesn't. - In the Node.js Runtime, all binary strings are converted into binary data according to SAP HANA property types. To disable this default behavior, you can set the environment variable cds.hana.base64_to_buffer: false. # Serving UIs from CAP Applications > Source: /docs/guides/uis/ CAP does not have its own UI technology, but can be used from a frontend built with any UI framework. On top of that, there is out-of-the-box support for SAP Fiori elements, for example the Fiori annotations, and support for Fiori Drafts. This guide provides an overview of the available capabilities and points to further guides with more detailed information. {.abstract}
[ Localization / I18n ](i18n) : Guides you through the steps to internationalize your application to provide localized versions with respect to both localized UIs as well as localized data. [ Localized Data ](localized-data) : This guide extends the localization/i18n of static content, such as labels or messages, to serve localized versions of actual application data. [ SAP Fiori UIs ](fiori) : This guide explains how to serve SAP Fiori UIs from CAP applications, including the usage of Fiori annotations and Fiori drafts. [ Vue.js + React.js ](vue-react) : This guide explains how to serve Vue.js or React UIs with a CAP backend. # Localization, i18n > Source: /docs/guides/uis/i18n Guides you through the steps to internationalize your application to provide localized versions with respect to both Localized Models as well as Localized Data. _'Localization'_ is a means to adapting your app to the languages of specific target markets. This guide focuses on static texts such as labels. See [CDS](../../cds/index.md) and [Localized Data](./localized-data) for information about how to manage and serve actual payload data in different translations. ## Externalizing Texts Bundles > Source: /docs/guides/uis/i18n#externalizing-texts-bundles All you have to do to internationalize your models is to externalize all of your literal texts to text bundles and refer to the respective keys from your models as annotation values. Here is a sample of a model and the corresponding bundle. ::: code-group ```cds [srv/my-service.cds] service Bookshop { entity Books @( UI.HeaderInfo: { Title.Label: '{i18n>Book}', TypeName: '{i18n>Book}', TypeNamePlural: '{i18n>Books}', }, ){/*...*/} } ``` ::: ::: code-group ```properties [_i18n/i18n.properties] Book = Book Books = Books foo = Foo ``` ::: > You can define the keys of your properties entries. [Learn more about annotations in CSN.](../../cds/csn#annotations){ .learn-more} Then you can translate the texts in localized bundles, each with a language/locale code appended to its name, for example: ```sh _i18n/ i18n.properties # dev main → 'default fallback' i18n_en.properties # English → 'default language' i18n_de.properties # German i18n_zh_TW.properties # Traditional Chinese ... ``` ## Where to Place Text Bundles? > Source: /docs/guides/uis/i18n#where-to-place-text-bundles Recommendation is to put your properties files in a folder named `_i18n` in the root of your project, as in this example: ```zsh bookshop/ ├─ _i18n/ │ ├─ i18n_en.properties │ ├─ i18n_de.properties │ ├─ i18n_fr.properties │ └─ i18n.properties │ ... ``` By default, text bundles are fetched from folders named *_i18n* or *i18n* in the neighborhood of models, that is, all folders that contain `.cds` sources or parent folders thereof. For example, given the following project layout and sources: ```zsh bookshop/ ├─ app/ │ ├─ browse/ │ │ └─ fiori.cds │ ├─ common.cds │ └─ index.cds ├─ srv/ │ ├─ admin-service.cds │ └─ cat-service.cds ├─ db/ │ └─ schema.cds └─ readme.md ``` We will be loading i18n bundles from all of these locations, if existing: ```zsh bookshop/app/browse/_i18n bookshop/app/_i18n bookshop/srv/_i18n bookshop/db/_i18n bookshop/_i18n ``` [Learn more about the underlying machinery in the reference docs for `cds.i18n`](../../node.js/cds-i18n){.learn-more} ## CSV-Based Text Bundles > Source: /docs/guides/uis/i18n#csv-based-text-bundles For smaller projects you can use CSV files instead of _.properties_ files, which you can easily edit in _Excel_, _Numbers_, etc. The format is as follows: | key | en | de | zh_CN | ... | | --- | --- | --- | --- | --- | | Book | Book | Buch | ... | | Books | Books | Bücher | ... | | ... | With this CSV source: ```csv key,en,de,zh_CN,... Book,Book,Buch,... Books,Books,Bücher,... ... ``` ## Merging Algorithm > Source: /docs/guides/uis/i18n#merging-algorithm Each localized model is constructed by applying: 1. The _default fallback_ bundle (that is, *i18n.properties*), then ... 2. The _default language_ bundle (usually *i18n_en.properties*), then ... 3. The requested bundle (for example, *i18n_de.properties*) In that order. So, the complete stack of overlaid models for the given example would look like this (higher ones override lower ones): | Source | Content | |:--- |:--- | | *_i18n/i18n_de.properties* | specific language bundle | | *_i18n/i18n_en.properties* | default language bundle | | *_i18n/i18n.properties* | default fallback bundle | | *srv/my-service.cds* | service definition | | *db/schema.cds* | underlying data model | ::: tip Set default language The _default language_ is usually `en` but can be overridden by configuring cds.i18n.default_language in your project's _package.json_. ::: ## Merging Reuse Bundles > Source: /docs/guides/uis/i18n#merging-reuse-bundles If your application is [importing models from a reuse package](../integration/reuse-and-compose), that package comes with its own language bundles for localization. These are applied upon import, so they can be overridden in your models as well as in your language bundles and their translations. For example, assuming that your data model imports from a _foundation_ package, then the overall stack of overlays would look like this: | Source | |:--- | | *./_i18n/i18n_de.properties* | | *./_i18n/i18n_en.properties* | | *./_i18n/i18n.properties* | | *./srv/my-service.cds* | | *./db/schema.cds* | | *foundation/_i18n/i18n_de.properties* | | *foundation/_i18n/i18n_en.properties* | | *foundation/_i18n/i18n.properties* | | *foundation/index.cds* | | *foundation/\.cds* | | *foundation/\.cds* | | ... | ## Determining User Locales > Source: /docs/guides/uis/i18n#determining-user-locales Upon incoming requests at runtime, the user's preferred language is determined as follows: 1. Read the preferred language from the first of: 1. The value of the `sap-locale` URL parameter, if present. 2. The value of the `sap-language` URL parameter, but only if it's `1Q`, `2Q` or `3Q` as described below. 3. The first entry from the request's `Accept-Language` header. 2. Narrow to normalized locales as described below. ::: tip Differences between Node.js and Java runtimes CAP Node.js accepts formats following the available standards of POSIX and RFC 1766, and transforms them into normalized locales. CAP Java only accepts language codes following the standard of RFC 1766 (or [IETF's BCP 47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt)). ::: ## Normalized Locales > Source: /docs/guides/uis/i18n#normalized-locales To reduce the number of required translations, most determined locales are normalized by narrowing them to their main language codes only, for example, `en_US`, `en_CA`, `en_AU` → `en`, except for these preserved language codes: | Locale | Language | | --- | --- | | zh_CN | Chinese - China | | zh_HK | Chinese - Hong Kong, China | | zh_TW | Chinese traditional - Taiwan, China | | en_GB | English - English | | fr_CA | French - Canada | | pt_PT | Portuguese - Portugal | | es_CO | Spanish - Colombia | | es_MX | Spanish - Mexico | | en_US_x_saptrc | SAP tracing translations w/ `sap-language=1Q` | | en_US_x_sappsd | SAP pseudo translations w/ `sap-language=2Q` | | en_US_x_saprigi | Rigi language w/ `sap-language=3Q` | #### Configuring Normalized Locales > Source: /docs/guides/uis/i18n#configuring-normalized-locales For CAP Node.js, the list of preserved locales is configurable, for example in the _package.json_ file, using the configuration option cds.i18n.preserved_locales as follows: ```jsonc {"cds":{ "i18n": { "preserved_locales": [ "en_GB", "fr_CA", "pt_PT", "pt_BR", "zh_CN", "zh_HK", "zh_TW" ] } }} ``` In this example we removed `es_CO` and `es_MX` from the list, and added `pt_BR`. In CAP Java the preserved locales can be configured via the cds.locales.normalization.includeList [property](../../java/developing-applications/properties#cds-locales-normalization). ::: warning *Note:* However this list is configured, ensure to have translations for the listed locales, as the fallback language will otherwise be `en`. ::: #### Use Underscores in File Names > Source: /docs/guides/uis/i18n#use-underscores-in-file-names Due to the ambiguity regarding standards, for example, the usage of hyphens (`-`) in contrast to underscores (`_`), CAP follows the approach of the [SAP Translation Hub](https://discovery-center.cloud.sap/serviceCatalog/sap-translation-hub). Using that approach, CAP normalizes locales to **underscores** as our de facto standard. In effect, this means: - We support incoming locales as [language tags](https://www.ietf.org/rfc/bcp/bcp47.txt) using hyphens to separate sub tags 1, for example `en-GB`. - We always normalize these to underscores, which is `en_GB`. - Always use underscores in filenames, for example, `i18n_en_GB.properties` - Always use underscores when filling `LOCALE` columns of localized text tables (for example, in CSV files). 1 CAP Node.js also supports underscore separated tags, for example `en_GB`.
# Localized Data > Source: /docs/guides/uis/localized-data Localized data refers to the maintenance of different translations of textual data and automatically fetching the translations matching the users' preferred language, with per-row fallback to default languages, if the required translations aren't available. It extends the localization/i18n of static labels or messages, to serve localized versions of actual application data. {.abstract} > Find a **working sample** at . ## Declaring Localized Data > Source: /docs/guides/uis/localized-data#declaring-localized-data Use the `localized` modifier to mark entity elements that require translated texts. ```cds entity Books { key ID : UUID; title : localized String; descr : localized String; price : Decimal; currency : Currency; } ``` [Find this source also in **capire/bookshop**.](https://github.com/capire/bookshop/blob/main/db/schema.cds#L4-L8){ .learn-more} ::: warning _Restriction_ If you want to use the `localized` modifier, the entity's keys must not be associations. ::: > `localized` in entity sub elements isn't currently supported and is ignored. > This includes `localized` in structured elements and structured types. ## Behind the Scenes > Source: /docs/guides/uis/localized-data#behind-the-scenes The `cds` compiler automatically unfolds the previous definition as follows, applying the basic mechanisms of [Managed Compositions](../../cds/cdl#managed-compositions), and [Scoped Names](../../cds/cdl#scoped-names): First, a separate _Books.texts_ entity is added to hold translated texts: ```cds entity Books.texts { key locale : sap.common.Locale; key ID : UUID; //= source's primary key title : String; descr : String; } ``` [See the definition of `sap.common.Locale`.](../../cds/common#locale-type){ .learn-more} Second, the source entity is extended with associations to _Books.texts_: ```cds extend entity Books with { texts : Composition of many Books.texts on texts.ID=ID; localized : Association to Books.texts on localized.ID=ID and localized.locale = $user.locale; } ``` The composition `texts` points to all translated texts for the given entity, whereas the `localized` association points to the translated texts and is narrowed to the request's locale. Third, views are generated in SQL DDL to easily read localized texts with an equivalent fallback: ```cds entity localized.Books as select from Books {*, coalesce (localized.title, title) as title, coalesce (localized.descr, descr) as descr }; ``` ### Resolving localized texts via views > Source: /docs/guides/uis/localized-data#resolving-localized-texts-via-views As we already mentioned, the CDS compiler is already creating views that resolve the translated texts internally. Once a CDS runtime detects a request with a user locale, it uses those views instead of the table of the involved entity. Note that SQLite doesn't support locales like _SAP HANA_ does. For _SQLite_, additional views are generated for different languages. Currently those views are generated for the locales 'de' and 'fr' and the default locale is handled as 'en'. ```json "i18n": { "for_sqlite": ["en", ...] } ``` > In _package.json_ put this snippet in the `cds` block, but don't do so for _.cdsrc.json_. > For testing with SQLite: Make sure that the _Books_ table contains the English texts and that the other languages go into the _Books.texts_ table. For _H2_, you need to use the property as follows. ```json "i18n": { "for_sql": ["en", ...] } ``` ### Resolving search over localized texts at runtime > Source: /docs/guides/uis/localized-data#resolving-search-over-localized-texts-at-runtime Although the approach with the generated localized views is very convenient, it's limited on SQLite and shows suboptimal performance with large data sets on _SAP HANA_. Especially for search operations the performance penalty is very critical. Therefore, both CAP runtimes have implemented a solution targeted for search operations. If the `localized` association of your entity is present and accessible by the given CQL statement, the runtimes generate SQL statements that resolve the localized texts. This is optimized for the underlying database. When your CQL queries select entities directly there is no issue as the `localized` association is automatically accessible in an entity with localized elements. If your CQL query selects from a view, it is important that your views' projection preserves the `localized` association. The following view definitions preserve the `localized` association in the view, allowing you to optimize query execution, or for broader language support on SQLite, H2, and PostgreSQL. **Preferred -** Exclude elements that mustn't be exposed: ```cds entity OpenBookView as select from Books {*} excluding { price, currency }; ``` Include the `localized` association: ```cds entity ClosedBookView as select from Books { ID, title, descr, localized }; ``` ### Base Entities Stay Intact > Source: /docs/guides/uis/localized-data#base-entities-stay-intact In contrast to similar strategies, all texts aren't externalized but the original texts are kept in the source entity. This saves one join when reading localized texts with fallback to the original ones. ### Extending *.texts* Entities > Source: /docs/guides/uis/localized-data#extending-texts-entities It's possible to collectively extend all generated *.texts* entities by extending the aspect `sap.common.TextsAspect`, which is defined in [*common.cds*](../../cds/common#texts-aspects). For example, the aspect can be used to add an association to the `Languages` code list entity, or to add flags that help you to control the translation process. Example: ```cds extend sap.common.TextsAspect with { language : Association to sap.common.Languages on language.code = locale; } ``` The earlier description is simplified, *.texts* entities are generated with an include on `sap.common.TextsAspect`, if the aspect exists. For the *Books* entity, the generated *.texts* entity looks like: ```cds entity Books.texts : sap.common.TextsAspect { key ID : UUID; title : String; descr : String; } ``` When the include is expanded, the key element `locale` is inserted into *.texts* entities, alongside all the other elements that have been added to `sap.common.TextsAspect` via extensions. ```cds entity Books.texts { // from sap.common.TextsAspect key locale: sap.common.Locale; language : Association to sap.common.Languages on language.code = locale; // from Books key ID : UUID; title : String; descr : String; } ``` It isn't allowed to extend `sap.common.TextsAspect` with * [Managed Compositions of Aspects](../../cds/cdl#managed-compositions) * localized elements * key elements For entities that have an annotation [`@fiori.draft.enabled`](./fiori#draft-for-localized-data), the corresponding *.texts* entities also include the aspect, but the element `locale` isn't marked as a key and an element `key ID_texts : UUID` is added. ## Pseudo var `$user.locale` > Source: /docs/guides/uis/localized-data#pseudo-var-userlocale [`$user.locale`]: #user-locale As shown in the second step, the pseudo variable `$user.locale` is used to refer to the user's preferred locale and join matching translations from `.texts` tables. This pseudo variable allows expressing such queries in a database-independent way, which is realized in the service runtimes as follows: ### Determining `$user.locale` from Inbound Requests > Source: /docs/guides/uis/localized-data#determining-userlocale-from-inbound-requests The user's preferred locale is determined from request parameters, user settings, or the _accept-language_ header of inbound requests [as explained in the Localization guide](i18n#user-locale). ### Programmatic Access to `$user.locale` > Source: /docs/guides/uis/localized-data#programmatic-access-to-userlocale The resulting [normalized locale](i18n#normalized-locales) is available programmatically, in your event handlers. * Node.js: `req.locale` * Java: `eventContext.getParameterInfo().getLocale()` ### Propagating `$user.locale` to Databases > Source: /docs/guides/uis/localized-data#propagating-userlocale-to-databases [propagation]: #propagating-of-user-locale Finally, the [normalized locale](i18n#normalized-locales) is **propagated** to underlying databases using session variables, that is, `$user.locale` translates to `session_context('locale')` in native SQL of SAP HANA and most databases. Not all databases support session variables. For example, for _SQLite_ we currently would just create stand-in views for selected languages. With that, the APIs are kept stable but have restricted feature support. ## Reading Localized Data > Source: /docs/guides/uis/localized-data#reading-localized-data Given the asserted unfolding and user locales propagated to the database, you can read localized data as follows: ### In Agnostic Code > Source: /docs/guides/uis/localized-data#in-agnostic-code Read _original_ texts, that is, the ones in the originally created data entry: ```sql SELECT ID, title, descr from Books ``` ### For End Users > Source: /docs/guides/uis/localized-data#for-end-users Reading texts for end users uses the `localized` association, which requires prior [propagation] of [`$user.locale`] to the underlying database. Read _localized_ texts in the user's preferred language: ```sql SELECT ID, localized.title, localized.descr from Books ``` ### For Translation UIs > Source: /docs/guides/uis/localized-data#for-translation-uis Translation UIs read and write texts in all languages, independent from the current user's preferred one. They use the to-many `texts` association, which is independent from [`$user.locale`]. Read texts in **different** translations: ```sql SELECT ID, texts[locale='fr'].title, texts[locale='fr'].descr from Books ``` Read texts in **all** translations: ```sql SELECT ID, texts.locale, texts.title, texts.descr from Books ``` ## Serving Localized Data > Source: /docs/guides/uis/localized-data#serving-localized-data The generic handlers of the service runtimes automatically serve read requests from `localized` views. Users see all texts in their preferred language or the fallback language. [See also **Enabling Draft for Localized Data**.](./fiori#draft-for-localized-data){ .learn-more} For example, given this service definition: ```cds using { Books } from './books'; service CatalogService { entity BooksList as projection on Books { ID, title, price }; entity BooksDetails as projection on Books; entity BooksShort as projection on Books { ID, price, substr(title, 0, 10) as title : localized String(10), }; } ``` ### `localized.` Helper Views > Source: /docs/guides/uis/localized-data#localized-helper-views For each exposed entity in a service definition, and all intermediate views, a corresponding `localized.` entity is created. It has the same query clauses and all annotations, except for the `from` clause being redirected to the underlying entity's `localized.` counterpart. A helper view is only created if the corresponding entity contains at least one element with a `localized` property, or it exposes an association to an entity that is localized. You may need to cast an element if that property is not propagated, for example for expressions such as in `CatalogService.BooksShort`. ```cds using { localized.Books } from './books_localized'; entity localized.CatalogService.BooksList as SELECT from localized.Books { ID, title, price }; entity localized.CatalogService.BooksDetails as SELECT from localized.Books; entity localized.CatalogService.BooksShort as SELECT from localized.Books { ID, price, substr(title, 0, 10) as title : localized String(10), }; ``` ::: warning `localized` entities are only generated for SQL They are not part of the CSN or exposed via OData. ::: ### Read Operations > Source: /docs/guides/uis/localized-data#read-operations The generic handlers in the service framework will automatically redirect all incoming read requests to the `localized_` helper views in the SQL database, unless in SAP Fiori draft mode. The `@cds.localized: false` annotation can be used to explicitly switch off the automatic redirection to the localized views. All incoming requests to an entity annotated with `@cds.localized: false` will directly access the base entity. ```cds using { Books } from './books'; service CatalogService { @cds.localized: false //> direct access to base entity; all fields are non-localized defaults entity BooksDetails as projection on Books; } ``` In Node.js applications, for requests with an `$expand` query option on entities annotated with `@cds.localized: false`, the expanded properties are not translated. ```http // all fields from authors are non-localized defaults if BooksDetails // is annotated with `@cds.localized: false` GET /BooksDetails?$expand=authors ``` ### Write Operations > Source: /docs/guides/uis/localized-data#write-operations Since the corresponding text table is linked through composition, you can use deep inserts or upserts to fill in language-specific texts. ```http POST /Entity HTTP/1.1 Content-Type: application/json { "name": "Some name", "description": "Some description", "texts": [ {"name": "Ein Name", "description": "Eine Beschreibung", "locale": "de"} ] } ``` If you want to add a language-specific text to an existing entity, perform a `POST` request to the text table of the entity through navigation. ```http POST /Entity()/texts HTTP/1.1 Content-Type: application/json { {"name": "Ein Name", "description": "Eine Beschreibung", "locale": "de"} } ``` ### Update Operations > Source: /docs/guides/uis/localized-data#update-operations To update the language-specific texts of an entity along with the default fallback text, you can perform a deep update as a `PUT` or `PATCH` request to the entity through navigation. ```http PUT/PATCH /Entity() HTTP/1.1 Content-Type: application/json { "name": "Some new name", "description": "Some new description", "texts": [ {"name": "Ein neuer Name", "description": "Eine neue Beschreibung", "locale": "de"} ] } ``` To update a single language-specific text field, perform a `PUT` or a `PATCH` request to the entity's text field via navigation. ```http PUT/PATCH /Entity()/texts(ID=,locale='')/ HTTP/1.1 Content-Type: application/json { "name": "Ein neuer Name" } ``` ::: warning Language codes need to follow BCP 47 Accepted language codes in the `locale` property need to follow the [BCP 47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt) standard but use __underscore__ (`_`) instead of __hyphen__ (`-`), for example `en_GB`. ::: ### Delete Operations > Source: /docs/guides/uis/localized-data#delete-operations To delete a locale's language-specific texts of an entity, perform a `DELETE` request to the entity's texts table through navigation. Specify the entity's key and the locale that you want to delete. ```http DELETE /Entity()/texts(ID=,locale='') HTTP/1.1 ``` ## Nested Localized Data > Source: /docs/guides/uis/localized-data#nested-localized-data The definition of books has a `currency` element that is effectively an association to the `sap.common.Currencies` code list entity, which in turn has localized texts. Find the respective definitions in the reference docs for `@sap/cds/common`, in the section on [Common Code Lists](../../cds/common#code-lists). Upon unfolding, all associations to other entities with localized texts are automatically redirected as follows: ```cds entity localized.Currencies as select from Currencies AS c {* /*...*/}; entity localized.Books as select from Books AS p mixin { // association is redirected to localized.Currencies country : Association to localized.Currencies on country = p.country; } into {* /*...*/}; ``` Given that, nested localized data can be easily read with independent fallback logic: ```sql SELECT from localized.Books { ID, title, descr, currency.name as currency } where title like '%pen%' or currency.name like '%land%' ``` In the result sets for this query, values for `title`, `descr`, as well as the `currency` name are localized. ## Adding Initial Data > Source: /docs/guides/uis/localized-data#adding-initial-data To add initial data, two _.csv_ files are required. The first _.csv_ file, for example _Books.csv_, should contain all the data in the default language. The second file, for example _Books_texts.csv_ (please note **_texts** in the file name) should contain the translated data in all other languages your application is using. For example, _Books.csv_ can look as follows: ::: code-group ```csv [Books.csv] ID,title,descr,author_ID,stock,price,currency_code,genre_ID 201,Wuthering Heights,"Wuthering Heights, Emily Brontë's only novel ...",101,12,11.11,GBP,11 207,Jane Eyre,Jane Eyre is a novel by English writer ...,107,11,12.34,GBP,11 251,The Raven,The Raven is a narrative poem by ...,150,333,13.13,USD,16 252,Eleonora,Eleonora is a short story by ...,150,555,14,USD,16 271,Catweazle,Catweazle is a British fantasy ...,170,22,150,JPY,13 ... ``` ::: This is the corresponding _Books_texts.csv_: ::: code-group ```csv [Books_texts.csv] ID,locale,title,descr 201,de,Sturmhöhe,Sturmhöhe (Originaltitel: Wuthering Heights) ist der einzige Roman... 201,fr,Les Hauts de Hurlevent,Les Hauts de Hurlevent (titre original : Wuthering Heights)... 207,de,Jane Eyre,Jane Eyre. Eine Autobiographie (Originaltitel: Jane Eyre. An Autobiography)... 252,de,Eleonora,Eleonora ist eine Erzählung von Edgar Allan Poe. Sie wurde 1841... ... ``` ::: #### Add `ID_texts` for SAP Fiori Draft on SAP HANA > Source: /docs/guides/uis/localized-data#add-idtexts-for-sap-fiori-draft-on-sap-hana If you set `@fiori.draft.enabled`, you need to manually add the technical primary key `ID_texts` to the initial data as follows: ::: code-group ```csv{1} [Books_texts.csv] ID_texts,ID,locale,title,descr d2a65a27-9f2a-480f-bc38-84ee8ec5c13e,201,de,Sturmhöhe,Sturmhöhe (Originaltitel: Wuthering Heights) ist der einzige Roman... 8c42c706-a979-41cf-9ffe-91e6cf1383a0,201,fr,Les Hauts de Hurlevent,Les Hauts de Hurlevent (titre original : Wuthering Heights)... 9e1c4c81-dc90-4600-85b1-e9dd4bf12ce0,207,de,Jane Eyre,Jane Eyre. Eine Autobiographie (Originaltitel: Jane Eyre. An Autobiography)... 9be0524b-4cb9-4fc1-9dc2-d65b1c13cf53,252,de,Eleonora,Eleonora ist eine Erzählung von Edgar Allan Poe. Sie wurde 1841... ``` [Learn more about Enabling Draft for Localized Data.](./fiori#draft-for-localized-data){.learn-more} # Serving SAP Fiori UIs > Source: /docs/guides/uis/fiori CAP provides out-of-the-box support for SAP Fiori elements. This guide explains how to add SAP Fiori elements apps to a CAP project and how to add SAP Fiori elements annotations to service definitions. Throughout this guide, "Fiori" refers to SAP Fiori elements. [Learn more about developing SAP Fiori elements and OData V4 (since 1.84.)](https://sapui5.hana.ondemand.com/#/topic/62d3f7c2a9424864921184fd6c7002eb){.learn-more} ## Getting Started > Source: /docs/guides/uis/fiori#getting-started ### Using Fiori Previews > Source: /docs/guides/uis/fiori#using-fiori-previews ###### Fiori Preview > Source: /docs/guides/uis/fiori#fiori-preview For entities exposed via OData V4, a _Fiori preview_ link appears on the index page. It dynamically serves an SAP Fiori elements list page that allows you to quickly see the effect of annotation changes without having to create a UI application first. > [!important] Not for production > The preview is meant for quick tests and iterations during development, but not for production use, and hence automatically disabled in production. To also enable it in cloud deployments for test or demo purposes, set cds.fiori.preview:true for Node.js apps, or cds.index-page.enabled:true for Java ### Adding Fiori Apps > Source: /docs/guides/uis/fiori#adding-fiori-apps As showcased in [cap/samples](https://github.com/capire/bookstore/tree/main/app), SAP Fiori apps should be added as subfolders of the `app/` directory in a CAP project. Each subfolder constitutes an individual SAP Fiori application, with [local annotations](#fiori-annotations), _manifest.json_, etc. So, a typical folder layout would look like this: | Folder/Sub Folder | Description | |----------------------------|--------------------------------------| | `app/` | All SAP Fiori apps should go in here | |     `browse/` | SAP Fiori app for end users | |     `orders/` | SAP Fiori app for order management | |     `admin/` | SAP Fiori app for admins | |     `index.html` | For sandbox tests | | `srv/` | All services | | `db/` | Domain models and database artifacts | ::: tip Links to Fiori applications created in the `app/` folder are automatically added to the index page of your CAP application for local development. ::: ### SAP Fiori Tools > Source: /docs/guides/uis/fiori#sap-fiori-tools The SAP Fiori tools provide advanced support for [adding SAP Fiori apps](https://help.sap.com/docs/SAP_FIORI_tools/17d50220bcd848aa854c9c182d65b699/db44d45051794d778f1dd50def0fa267.html) to existing CAP projects as well as a wealth of productivity tools, for example for adding SAP Fiori annotations, or graphical modeling and editing. They can be used locally in [Visual Studio Code (VS Code)](https://marketplace.visualstudio.com/items?itemName=SAPSE.sap-ux-fiori-tools-extension-pack) or in [SAP Business Application Studio](https://help.sap.com/docs/SAP_FIORI_tools/17d50220bcd848aa854c9c182d65b699/b0110400b44748d7b844bb5977a657fa.html). ### OData Annotations Plugin > Source: /docs/guides/uis/fiori#odata-annotations-plugin The [SAP CDS language support plugin](https://marketplace.visualstudio.com/items?itemName=SAPSE.vscode-cds) includes a plugin that helps you add and edit OData annotations in CDS syntax in VS Code. It provides the following features: - Code completion - Validation against the OData vocabularies and project metadata - Navigation to the referenced annotations - Quick view of vocabulary information - Internationalization support These features are available for [OData annotations in CDS syntax](../protocols/odata#annotations) but not yet for [core data services common annotations](../../cds/annotations). The [@sap/ux-cds-odata-language-server-extension](https://www.npmjs.com/package/@sap/ux-cds-odata-language-server-extension) module requires no manual installation. The latest version is fetched automatically from [npmjs.com](https://npmjs.com), as indicated by the user preference setting **CDS > Contributions: Registry**. [Learn more about the **CDS extension for VS Code**.](https://www.youtube.com/watch?v=eY7BTzch8w0){.learn-more} ## Fiori Annotations > Source: /docs/guides/uis/fiori#fiori-annotations SAP Fiori elements apps are generic front ends that construct and render pages and controls based on annotated metadata documents. The annotations provide the semantic information needed to render that content, for example: ```cds annotate CatalogService.Books with @( UI: { SelectionFields: [ ID, price, currency_code ], LineItem: [ {Value: title}, {Value: author, Label:'{i18n>Author}'}, {Value: genre.name}, {Value: price}, {Value: currency.symbol, Label:' '}, ] } ); ``` [Find this source and many more in **capire/bookstore**.](https://github.com/capire/bookstore/tree/main/app){.learn-more target="_blank"} [Learn more about **OData Annotations in CDS**.](../protocols/odata#annotations){.learn-more} ### Where to Put Them? > Source: /docs/guides/uis/fiori#where-to-put-them Although CDS allows you to add annotations anywhere in your models, we recommend placing them in separate _.cds_ files in your _./app/*_ folders, for example, as follows. ```sh ./app #> all your Fiori annotations should go here, for example: ./admin fiori-service.cds #> annotating ../srv/admin-service.cds ./browse fiori-service.cds #> annotating ../srv/cat-service.cds services.cds #> imports ./admin/fiori-service and ./browse/fiori-service ./srv #> all service definitions should stay clean in here: admin-service.cds cat-service.cds ... ``` [See this also in **capire/bookstore**.](https://github.com/capire/bookstore/blob/main/app/services.cds){.learn-more} This recommendation follows the principles of [Conceptual Modeling](../domain/index#domain-driven-design) and [Separation of Concerns](../domain/index#separation-of-concerns). ### Prefer `@title` and `@description` > Source: /docs/guides/uis/fiori#prefer-title-and-description Influenced by the [JSON Schema](https://json-schema.org), CDS supports the [common annotations](../../cds/annotations#common-annotations) `@title` and `@description`, which are mapped to corresponding [OData annotations](../protocols/odata#annotations) as follows: | CDS | JSON Schema | OData | |----------------|---------------|---------------------| | `@title` | `title` | `@Common.Label` | | `@description` | `description` | `@Core.Description` | We recommend preferring these annotations over the OData ones in protocol-agnostic data models and service models, for example: ```cds annotate my.Books with { //... title @title: 'Book Title'; author @title: 'Author ID'; } ``` ### Prefer `@readonly`, `@mandatory`, ... > Source: /docs/guides/uis/fiori#prefer-readonly-mandatory- CDS supports `@readonly` as a common annotation, which translates to respective [OData annotations](../protocols/odata#annotations) from the `@Capabilities` vocabulary. We recommend using the former for reasons of conciseness and comprehensibility as shown in this example: ```cds @readonly entity Foo { // entity-level @readonly foo : String // element-level } ``` is equivalent to: ```cds entity Foo @(Capabilities:{ // entity-level InsertRestrictions.Insertable: false, UpdateRestrictions.Updatable: false, DeleteRestrictions.Deletable: false }) { // element-level @Core.Computed foo : String } ``` Similar recommendations apply to `@mandatory` and others → see [Common Annotations](../../cds/annotations#common-annotations). ## Simple Value Helps > Source: /docs/guides/uis/fiori#simple-value-helps In addition to supporting the standard `@Common.ValueList` annotations as defined in the [OData Vocabularies](../protocols/odata#annotations), CAP provides convenient support for Value Helps. ### `@cds.odata.valuelist` > Source: /docs/guides/uis/fiori#cdsodatavaluelist Simply add the `@cds.odata.valuelist` annotation to an entity, and all managed associations targeting this entity will automatically receive Value Lists in SAP Fiori clients. For example: ```cds @cds.odata.valuelist entity Currencies { key code ... } ``` ```cds service BookshopService { entity Books { //... currency : Association to Currencies; } } ``` This would be expanded by the compiler to the following OData annotations, in the EDMX documents generated for Fiori clients: ```xml ``` ### `@sap/cds/common` > Source: /docs/guides/uis/fiori#sapcdscommon [@sap/cds/common]: ../../cds/common The reuse types in [@sap/cds/common] already have this added to base types and entities, so all uses automatically benefit from this. This is an effective excerpt of respective definitions in `@sap/cds/common`: ```cds type Currencies : Association to sap.common.Currencies; ``` ```cds context sap.common { entity Currencies : CodeList {...}; entity CodeList { name : localized String; ... } } ``` ```cds annotate sap.common.CodeList with @( UI.Identification: [name], cds.odata.valuelist, ); ``` In effect, usages of [@sap/cds/common] stay clean of any pollution, for example: ```cds using { Currency } from '@sap/cds/common'; entity Books { //... currency : Currency; } ``` [Find this also in **capire/bookstore**.](https://github.com/capire/bookshop/blob/main/db/schema.cds){.learn-more} With that, all UIs on all services exposing `Books` will automatically receive Value Help for currencies. You can also benefit from that when [deriving your project-specific code list entities from **sap.common.CodeList**](../../cds/common#adding-own-code-lists). ## Fiori Draft Support > Source: /docs/guides/uis/fiori#fiori-draft-support
SAP Fiori uses drafts to let users save their progress while editing data and continue later without losing changes. Drafts are stored on the server and can be accessed from different devices and locations providing flexibility and convenience for users. CAP provides out-of-the-box support for drafts, making it easy to implement this functionality in your applications. > [!note] This documentation focuses on CAP only > For general information on the user experience and the technical details of drafts in SAP Fiori, refer to the [SAP Fiori Design Guidelines](https://experience.sap.com/fiori-design-web/draft-handling/) and the [SAP UI5 documentation](https://ui5.sap.com/#/topic/ed9aa41c563a44b18701529c8327db4d). ### Draft-Enabled Entities > Source: /docs/guides/uis/fiori#draft-enabled-entities All you need to do to serve an entity with draft support enabled is to annotate it with `@odata.draft.enabled`. For example, as we do in the [_capire/xtravels_](https://github.com/capire/xtravels/blob/b147a1daad27d11352e0d39b525b25ed3241c016/app/travels/capabilities.cds#L3) sample: ::: code-group ```cds [app/travels/capabilities.cds] annotate TravelService.Travels with @odata.draft.enabled; ``` ::: Behind the scenes, CAP handles everything else. Most importantly, it adds a new `.drafts` entity next to the active entity with the same elements, used to store draft data. Think of it as a shadow entity, defined like this: ::: code-group ```cds [=> generated automatically:] entity TravelService.Travels.drafts : TravelService.Travels { ... } ``` ::: You can access this entity definition from the model at runtime, for example, to add custom handlers to draft events or to access draft data. In a CAP Node.js service implementation, use the [`.drafts`](../../node.js/cds-reflect#-drafts) reference as a shortcut to access the draft entity: ```js const { Travels } = this.entities SELECT.from (Travels) //> queries active data SELECT.from (Travels.drafts) //> queries draft data ``` ### Draft Choreography > Source: /docs/guides/uis/fiori#draft-choreography With [`@odata.draft.enabled`](#draft-enabled-entities) entities in place, CAP automatically serves the Fiori draft choreography as illustrated in the following diagram: ![](./fiori-draft.drawio.svg) In essence, the draft choreography defines the following flows: - Creating drafts for **new** active entities, or for **editing** existing ones. - Filling in draft data through a series of _PATCH_ events. - **Saving** the draft back to the active entity, or **discarding** it. Drafts are isolated from any active data until they are saved/activated. When drafts are discarded, they are removed as if they never existed – with draft locks as the only exception to prevent conflicting changes. ### Draft Locks > Source: /docs/guides/uis/fiori#draft-locks Whenever a draft is created to _edit_ an active entity, this active entity is locked for any operation that could result in conflicting changes. In particular: - No other draft can be created for the same active instance. - No direct updates or deletes to the active instance are allowed. The lock is released automatically when the draft is saved, activated, or discarded. Other users can manually reclaim it after a period of inactivity, which is 15 minutes by default, but can be configured via cds.fiori.draft_lock_timeout: 1h for CAP Node.js and cds.drafts.cancellationTimeout: 1h for CAP Java, respectively. See draft lock configuration for [Node.js](../../node.js/fiori#draft-locks) or [Java](../../java/fiori-drafts#draft-lock). Draft locks are not applied when creating drafts for new entities, as there is no active entity to be locked in this case. ### Requests to Drafts > Source: /docs/guides/uis/fiori#requests-to-drafts Fiori clients send the following HTTP requests for draft operations: ```php:line-numbers [Requests to draft data] POST /Foo //> NEW POST /Foo(ID,IsActiveEntity=true)/draftEdit //> EDIT GET /Foo(ID,IsActiveEntity=false) //> READ PATCH /Foo(ID,IsActiveEntity=false) {...} //> PATCH POST /Foo(ID,IsActiveEntity=false)/draftActivate //> SAVE DELETE /Foo(ID,IsActiveEntity=false) //> DISCARD ``` The key parameter `IsActiveEntity=false` addresses draft data, with the exception of the empty POST and `draftEdit` for semantic reasons. ::: details Full HTTP requests ... The requests above are abbreviated for clarity. The actual HTTP requests include the service path, content-type headers, and JSON bodies as shown below. ```http POST /odata/v4/TravelService/Travels Content-Type: application/json {} ``` ```http POST /odata/v4/TravelService/Travels(ID=a11fb6f1-36ab-46ec-b00c-d379031e817a,IsActiveEntity=true)/draftEdit Content-Type: application/json ``` ```http PATCH /odata/v4/TravelService/Travels(ID=a11fb6f1-36ab-46ec-b00c-d379031e817a,IsActiveEntity=false) Content-Type: application/json { ... } ``` ... and so forth. ::: ### Requests to Active Data > Source: /docs/guides/uis/fiori#requests-to-active-data Add `IsActiveEntity=true` as a key parameter to your requests to address *active* data directly, bypassing potentially existing drafts (draft locks still apply), for example: ```php:line-numbers [Requests to active data] POST /Books { IsActiveEntity:true, ... } //> CREATE PATCH /Books(ID=201,IsActiveEntity=true) {...} //> UPDATE DELETE /Books(ID=201,IsActiveEntity=true) //> DELETE GET /Books(ID=201,IsActiveEntity=true) //> READ ``` ::: details Available for CAP Node.js While this was always possible in CAP Java before, it's available for CAP Node.js in the same way by default since v10. Can be disabled with cds.fiori.bypass_draft: false, which prevents bypassing the draft flow for _CREATE_ and _UPDATE_ operations entirely. ::: > [!tip] Draft locks still apply > Directly updating an active entity does **not** bypass [draft locks](#draft-locks). > If an existing draft locks the entity, direct updates are blocked to prevent lost update situations. > [!warning] Ensure validation for all entry points > Requests to active data also features partial CREATE/UPDATE requests to the root entity and its composition children. Ensure that the validations and determinations are run in all situations, not only on the root. #### Draft-agnostic Requests > Source: /docs/guides/uis/fiori#draft-agnostic-requests Taking this further, through cds.fiori.draft_new_action: true `IsActiveEntity=true` is assumed by default, so clients that are unaware of drafts or don't need to handle them can ignore all draft-specific requests and parameters: ```php:line-numbers [Draft-agnostic requests to active data] // creation of active instances POST /Foo //> CREATE GET /Foo(ID) //> READ PATCH /Foo(ID) {...} //> UPDATE DELETE /Foo(ID) //> DELETE // creation of draft instances POST /Foo/draftNew //> CREATE new draft [!code ++] ... ``` The previously used `POST /Foo` requests without an `IsActiveEntity` parameter to create new drafts is now replaced by the collection bound action `draftNew` to resolve the ambiguities with requests to active data. ::: details Available for CAP Node.js – not yet for CAP Java Draft-agnostic requests as above assume `IsActiveEntity=true` by default for all requests that don't explicitly specify it. This was possible in CAP Node.js, but not in CAP Java, which is still bound by the [*Olingo*](https://olingo.apache.org) library. For CAP Java, explicitly add `IsActiveEntity=true` as a key parameter to address active data [Learn more about Direct CRUD events in **Java**.](../../java/fiori-drafts#bypassing-draft-flow){.learn-more} ::: ### Programmatic Access > Source: /docs/guides/uis/fiori#programmatic-access You can also access draft data programmatically from custom code in JavaScript or Java. In CAP Java, add `IsActiveEntity` as a key parameter to your queries ([learn more](../../java/fiori-drafts#draftservices)): ```java {3} Select.from(FOO).where(o -> o.ID().eq(201); //> reads active data Select.from(FOO).where(o -> o.ID().eq(201) .and( //> reads draft data o.IsActiveEntity().eq(false)) ); ``` In CAP Node.js, use the [`Foo.drafts`](../../node.js/cds-reflect#-drafts) references to access draft data: ```js {3} const { Foo } = this.entities SELECT.from (Foo, 201) // reads active data only SELECT.from (Foo.drafts, 201) // reads draft data, if exists ``` Even better, use [`req.subject`](../../node.js/events#-subject), which automatically resolves to the correct entity instance - active or draft - based on the current request context. For example, in custom action handlers triggered for both active and draft data: ```js this.on ('approveTravel', req => UPDATE (req.subject) .with ({ status: 'A' })) this.on ('rejectTravel', req => UPDATE (req.subject) .with ({ status: 'X' })) ``` ### Draft Input Validation > Source: /docs/guides/uis/fiori#draft-input-validation ###### Validating Drafts > Source: /docs/guides/uis/fiori#validating-drafts During the draft phase - that is, on `PATCH` requests to draft data - all [`@assert`s](../services/constraints) are validated and error messages are returned to the client. Unlike with active entities, the draft is still created or updated even with invalid data, so users can correct it later without losing their progress. #### Custom Handlers for Draft Events > Source: /docs/guides/uis/fiori#custom-handlers-for-draft-events You can add custom handlers to draft events by referring to draft-specific events and `.drafts` entities, as shown for CAP Node.js ([learn more](../../node.js/fiori#draft-specific-events)): ```js:line-numbers const { Foo } = this.entities this.before ('NEW', Foo.drafts, ...) this.before ('EDIT', Foo, ...) //> note: refers to active entity this.before ('PATCH', Foo.drafts, ...) this.before ('SAVE', Foo.drafts, ...) this.before ('DISCARD', Foo.drafts, ...) ``` Similar in CAP Java ([learn more](../../java/fiori-drafts#editing-drafts)): ```java:line-numbers @Before (event = DraftService.EVENT_DRAFT_CREATE) @Before (event = DraftService.EVENT_DRAFT_EDIT) @Before (event = DraftService.EVENT_DRAFT_PATCH) @Before (event = DraftService.EVENT_DRAFT_SAVE) @Before (event = DraftService.EVENT_DRAFT_CANCEL) ``` #### Validation on Active Entities > Source: /docs/guides/uis/fiori#validation-on-active-entities When a draft is saved, all validations for active entities run as usual. Invalid data is rejected, so only valid data gets activated. This includes constraints such as `@assert` and `@readonly`, and all custom handlers registered to active entity events, for example: ```js const { Foo } = this.entities this.before ([ 'CREATE', 'UPDATE' ], Foo, req => {/* validate all */}) ``` > [!caution] Validate on active entities, not only on drafts > Validations on draft entities alone are not sufficient, because active entities can be updated directly, bypassing drafts. Always perform all necessary validations on active entities, not only on drafts. > Also note that updates to active entities can be partial, for example, updating only an individual `OrderItem` within an `Order` via `PATCH /Orders(1)/Items(3)`. Make sure your validation logic covers such cases. ### Persistent Messages > Source: /docs/guides/uis/fiori#persistent-messages While in draft state, error messages are automatically persisted and remain visible even after you edit other fields or navigate away from the page. CAP automatically generates corresponding side-effect annotations in the EDMX to instruct Fiori clients to fetch state messages after every `PATCH` request. You can override generated side-effect annotations per entity, for example: ```cds annotate MyService.Foo with @( Common.SideEffects #alwaysFetchMessages: false ); ``` ::: details Available and Can be disabled with cds.fiori.draft_messages: false. ::: ### Draft for Localized Data > Source: /docs/guides/uis/fiori#draft-for-localized-data Annotate the underlying base entity in the base model with `@fiori.draft.enabled` to also support drafts for [localized data](./localized-data): ```cds annotate sap.capire.bookshop.Books with @fiori.draft.enabled; ``` :::info Background SAP Fiori drafts require single keys of type `UUID`, which is not the case for [`.texts`](./localized-data#behind-the-scenes) entities, that are generated for localized data. The `@fiori.draft.enabled` annotation tells the compiler to add an additional technical primary key element named `ID_texts`. [Learn how to add initial data for such draft-enabled localized entities.](localized-data#adding-initial-data){.learn-more} ::: ![An SAP Fiori UI showing how a book is edited in the bookshop sample and that the translations tab is used for non-standard languages.](draft-for-localized-data.png){} [See it live in **capire/bookstore**.](https://github.com/capire/bookstore/blob/main/app/admin-books/fiori-service.cds#L78){.learn-more} ## Fiori Tree Views > Source: /docs/guides/uis/fiori#fiori-tree-views Following the same principle of convenience as for Value Helps, CAP provides a shortcut annotation to define hierarchies on entities with recursive associations, which are then rendered as Tree Views in SAP Fiori clients. ### Recursive Associations > Source: /docs/guides/uis/fiori#recursive-associations Hierarchies are most commonly parent-child structures created via recursive associations. For example, in the [capire/bookshop](https://github.com/capire/bookshop) sample, we have the `Genres` entity with a recursive association `parent` to itself: ::: code-group ```cds [db/schema.cds] entity Genres : cuid { //... parent : Association to Genres; } ``` ::: ### The `@hierarchy` Annotation > Source: /docs/guides/uis/fiori#the-hierarchy-annotation To get a Tree View in SAP Fiori clients, annotate the entity with `@hierarchy`, for example as we did in the Fiori Annotations of the [capire/bookstore](https://github.com/capire/bookstore) sample: ::: code-group ```cds [app/genres/fiori-service.cds] annotate AdminService.Genres with @hierarchy; ``` ::: If multiple associations can serve as the parent association, specify the one to use as the value of the `@hierarchy` annotation, for example: ```cds annotate AdminService.Genres with @hierarchy: parent; ``` ::: details Under the hood... The `@hierarchy` annotation is a shortcut for the following Fiori-level `annotate` and `extend` statements, which you would otherwise have to write manually. ```cds // declare a hierarchy with the qualifier "GenresHierarchy" annotate AdminService.Genres with @Aggregation.RecursiveHierarchy #GenresHierarchy: { NodeProperty : ID, // identifies a node, usually the key ParentNavigationProperty : parent // navigates to a node's parent }; extend AdminService.Genres with @( // The computed properties expected by Fiori to be present in hierarchy entities Hierarchy.RecursiveHierarchy #GenresHierarchy: { LimitedDescendantCount : LimitedDescendantCount, DistanceFromRoot : DistanceFromRoot, DrillState : DrillState, LimitedRank : LimitedRank }, // Disallow filtering on these properties from Fiori UIs Capabilities.FilterRestrictions.NonFilterableProperties: [ 'LimitedDescendantCount', 'DistanceFromRoot', 'DrillState', 'LimitedRank' ], // Disallow sorting on these properties from Fiori UIs Capabilities.SortRestrictions.NonSortableProperties: [ 'LimitedDescendantCount', 'DistanceFromRoot', 'DrillState', 'LimitedRank' ], ) columns { // Ensure we can query these columns from the database null as LimitedDescendantCount : Int16, null as DistanceFromRoot : Int16, null as DrillState : String, null as LimitedRank : Int16 }; ``` > Note: When naming the hierarchy qualifier, use the following pattern:
> `Hierarchy` ::: ::: tip Build hierarchies with aggregations in CAP Java Annotate a `virtual` element with `@cds.java.descendants.aggregate` and specify the aggregation expression to build hierarchy views using calculation of aggregates. ::: ### UI5 manifest Configuration > Source: /docs/guides/uis/fiori#ui5-manifest-configuration In addition, you need to configure the TreeTable in UI5's _manifest.json_ file: ```jsonc "sap.ui5": { ... "routing": { ... "targets": { ... "GenresList": { ... "options": { "settings": { ... "controlConfiguration": { "@com.sap.vocabularies.UI.v1.LineItem": { "tableSettings": { "hierarchyQualifier": "GenresHierarchy", "type": "TreeTable" } } } } } }, }, }, ``` > Note: construct the `hierarchyQualifier` with the following pattern:
> `Hierarchy` You can now start the server with `cds watch` and see the hierarchical tree view in action in the [_Browse Genres_](http://localhost:4004/fiori-apps.html#Genres-display) app. ![Fiori UI with hierarchical tree view.](hierarchical-tree-view.png) {} The compiler automatically expands the shortcut annotation `@hierarchy` to the following `annotate` and `extend` statements. ## Cache Control in Java > Source: /docs/guides/uis/fiori#cache-control-in-java CAP lets you set a [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) header with a [max-age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#max-age) directive to indicate that a response remains fresh for _n_ seconds after it was generated. In the CDS model, use the `@http.CacheControl: {maxAge: }` annotation on stream properties. The header tells caches to store the response and reuse it for subsequent requests while it is fresh. `max-age` (in seconds) specifies how long the content remains fresh before becoming stale. :::info Elapsed time since the response was generated `max-age` is the elapsed time since the response was generated on the origin server, not the time since the response was received. ::: ::: warning Only Java The Cache Control feature is currently supported only on the Java runtime. ::: ## Role-based Visibility > Source: /docs/guides/uis/fiori#role-based-visibility In addition to adding [restrictions on services, entities, and actions/functions](../security/authorization#restrictions), there are cases where you want to hide certain UI elements for specific users. You can do this using annotations such as `@UI.Hidden` or `@UI.CreateHidden` together with `$edmJson` pointing to a singleton. First, define the [singleton](../protocols/odata#singletons) in your service and annotate it with [`@cds.persistence.skip`](../databases/cdl-to-ddl#cdspersistenceskip) so that no database artifact is created: ```cds @odata.singleton @cds.persistence.skip entity Configuration { key ID: String; isAdmin : Boolean; } ``` > A key is technically not required, but omitting it may cause issues for some consumers. Then, define an `on` handler to serve the request: ```js srv.on('READ', 'Configuration', async req => { req.reply({ isAdmin: req.user.is('admin') //admin is the role, which for example is also used in @requires annotation }); }); ``` Finally, refer to the singleton in the annotation by using a [dynamic expression](../protocols/odata#dynamic-expressions): ```cds annotate service.Books with @( UI.CreateHidden : { $edmJson: {$Not: { $Path: '/CatalogService.EntityContainer/Configuration/isAdmin'} } }, UI.UpdateHidden : { $edmJson: {$Not: { $Path: '/CatalogService.EntityContainer/Configuration/isAdmin'} } }, ); ``` The Entity Container is OData-specific. It refers to the `$metadata` of the OData service, where all accessible entities are registered. :::details SAP Fiori elements also allows to not include it in the path ```cds annotate service.Books with @( UI.CreateHidden : { $edmJson: {$Not: { $Path: '/Configuration/isAdmin'} } }, UI.UpdateHidden : { $edmJson: {$Not: { $Path: '/Configuration/isAdmin'} } }, ); ``` ::: # Serving Vue.js or React > Source: /docs/guides/uis/vue-react CAP is easily integrated with [Vue.js](https://vuejs.org/) or [React](https://react.dev/). This guide explains how to set up a minimal project with a UI. > [!note] What about other UI frameworks? > Other popular UI libraries like [Svelte](https://svelte.dev/) could follow the same pattern but don't have `cds add` support for now. ## Example project > Source: /docs/guides/uis/vue-react#example-project The example here is built on a minimal CAP project: ```sh cds init bookshop --add nodejs,tiny-sample && code bookshop ``` Now simply create a Vue.js or React app in `app/catalog`: ::: code-group ```sh [Vue.js] cds add vue --into catalog ``` ```sh [React] cds add react --into catalog ``` ::: Now simply start the dev server: ```sh cds watch ``` Open http://localhost:4004 to see your running applications. ## Next Up > Source: /docs/guides/uis/vue-react#next-up You can deploy this project to Cloud Foundry or Kyma using the _SAP BTP Application Frontend_ service or a _custom App Router_ setup. Simply add _Application Frontend_ like so: ```sh cds add app-frontend ``` > When deploying your first Application Frontend service in that subaccount also make sure to subscribe to "Application Frontend Service" with plan "build-default". Also make sure to choose an authentication mode: ```sh cds add ias ``` or... ```sh cds add xsuaa ``` For the deployment, we add HANA as the production database: ```sh cds add hana ``` Afterwards, deploy your project: ```sh cds up ``` [Learn more about Cloud Foundry deployment](../deploy/to-cf#add-ui){.learn-more} [Learn more about Kyma deployment](../deploy/to-kyma.md){.learn-more} > [!tip] When using IAS, set up the Application Frontend dependency. > > Add the API exposed by your bookshop application to the Application Frontend Service in your IAS admin console: > > ![IAS Admin console](./ias-admin.png) You can use the [`@sap/appfront-cli`](https://www.npmjs.com/package/@sap/appfront-cli) package to see the links of your deployed application: ```sh acftl list ``` # CAP-Level Database Integration > Source: /docs/guides/databases/ CAP application developers [focus on their domain](../../get-started/features#focus-on-domain), while CAP takes care of all aspects of database integration. This includes translating CDS models to native persistence models, schema evolution, deployment, as well as runtime querying – all of that in a database-agnostic way. [SQLite](./sqlite) 1 in-memory databases are automatically used in inner-loop development, while in production, [SAP HANA](./hana) 2 is used by default. {.abstract} > _1 or [H2](./h2) in case of CAP Java-based projects_.\ > _2 or [PostgreSQL](./postgres) in edge cases_. ### Best Practices, Served Out of the Box > Source: /docs/guides/databases/#best-practices-served-out-of-the-box > [!tip] Served Out of the Box > The CAP framework handles all compilation to DDL automatically, for example when you run `cds watch` or `cds deploy`. You typically don't need to worry about the details unless you want to inspect or customize the generated DDL statements. The guides in this section explain how things work under the hood. If you are on a fast track, you can safely skip them to a great extent. The illustration below shows what happens automatically under the hood: - CDS models are compiled to database-native SQL/DDL statements. - These statements are deployed to the configured database. - Initial data from CSV files is loaded into the database tables. - CQL queries from CAP services are served automatically. ![Architecture diagram showing CAP database integration flow. Initial data in CSV files and CDS models in CDL and CQL format are compiled to native SQL and DDL statements, which are then deployed to a database. CAP Services query the database using CQL. Four database types are shown as supported options: SAP HANA, SQLite, H2, and PostgreSQL, all connecting to the central database component.](assets/overview.drawio.svg) > [!tip] Following the Calesi Pattern > The implementations of the CAP database layers follow the design principles of CAP-level Service Integration which means the following for database services: > - They are CAP services themselves. > - Provide database-agnostic interfaces to applications. > - Provide mocks for local development out of the box. > - Can be extended through event handlers, as any other CAP service. > [!tip] Promoting Fast Inner-Loop Development > Through the ability to easily swap production-grade databases like SAP HANA with SQLite or H2 in-memory databases during development, without any changes to CDS models nor implementations, we greatly promote inner-loop development with fast turnaround cycles, as well as speeding up test pipelines and minimizing TCD. ### Database-Independent Guides > Source: /docs/guides/databases/#database-independent-guides The following guides explain the details of CAP-level database integration, which are mostly database-agnostic, and apply to all supported databases: [ CAP-Level Database Integration ](cap-level-dbs.md) : How database-agnostic CDS models in CDL format are compiled to native DDL statements for different databases. [ CQL Compiled to SQL ](cdl-to-ddl.md) : How database-agnostic CDS queries in CQL format are compiled to native SQL statements for different databases. [ Adding Initial Data ](initial-data.md) : How to provide initial data and test data using CSV files, which are loaded into the database automatically. [ Schema Evolution ](schema-evolution.md) : How to manage schema changes with appropriate schema evolution strategies for development and production. [ Performance Guide ](performance.md) : Pointing out performance considerations, and common pitfalls. ### Database-Specific Guides > Source: /docs/guides/databases/#database-specific-guides These guides are complemented by database-specific guides that explain particularities and customizations for each supported database: [SAP HANA](hana.md), [SQLite](sqlite.md), [H2](h2.md), [PostgreSQL](postgres.md). # CAP-Level Database Support > Source: /docs/guides/databases/cap-level-dbs CAP supports a number of portable functions and operators in CQL. The compiler automatically translates these to the best-possible database-specific native SQL equivalents. You can safely use these in CDS view definitions and runtime queries expressed in CQL. {.abstract} ## Mocked Out of the Box > Source: /docs/guides/databases/cap-level-dbs#mocked-out-of-the-box When using CAP's mocked out-of-the-box database integration, these functions and operators are supported in the in-memory SQLite database used for development and testing. #### TODO: > Source: /docs/guides/databases/cap-level-dbs#todo - Mocked by in-memory SQLite or H2 databases - With SAP HANA or PostgreSQL for production - With a defined set of portable functions and operators ## Standard Operators > Source: /docs/guides/databases/cap-level-dbs#standard-operators This chapter lists standardized operators supported by CAP, and guaranteed to work across all supported databases with feature parity. You can safely use these in CDS view definitions and runtime queries expressed in CQL. The compiler translates them to the best-possible database-specific native SQL equivalents. ### Standard SQL Operators > Source: /docs/guides/databases/cap-level-dbs#standard-sql-operators Most native SQL operators are supported in CQL as-is, like these from the SQL92 standard: - Arithmetic operators: `+`, `-`, `*`, `/`, `%` - Comparison operators: `<`, `>`, `<=`, `>=`, `=`, `<>` - Logical operators: `AND`, `OR`, `NOT` - Other operators: `IN`, `LIKE`, `BETWEEN`, `IS NULL`, `IS NOT NULL`, etc. In addition, CQL provides some extended operators as described below. ### Bivalent `==` and `!=` Operators > Source: /docs/guides/databases/cap-level-dbs#bivalent--and--operators CQL supports `==` and `!=` operators as bivalent logic variants for SQL's three-valued logic `=` and `<>`. The differences are as follows: ::: code-group ```SQL [CQL's Two-Valued Logic Operators] SELECT 1 == null, 1 != null, null == null, null != null; --> false, true, true, false ``` ::: ::: code-group ```SQL [SQL's Three-Valued Logic] SELECT 1 = null, 1 <> null, null = null, null <> null; --> null, null, null, null ``` ::: In other words: - CQL's `x == null` -> `true` if `x` is `null`, otherwise `false` - CQL's `x != null` -> `false` if `x` is `null`, otherwise `true` - SQL's `x = null` -> `null` for all `x` (even if `x` is `null`) - SQL's `x <> null` -> `null` for all `x` (even if `x` is not `null`) A real-world example makes this clearer. Consider this CQL query: ```sql SELECT from Books where genre.name != 'Science Fiction'; ``` The result set includes all books where genre is not 'Science Fiction', including the ones with an unspecified genre. In contrast, using SQL's `<>` operator, the ones with unspecified genre would be excluded. The CQL behavior is consistent with common programming languages like JavaScript and Java, as well as with OData semantics. It is implemented in database by, the translation of `!=` to `IS NOT` in SQLite, or to `IS DISTINCT FROM` in standard SQL, and to an equivalent polyfill in SAP HANA. > [!tip] Prefer == and != > Prefer using `==` and `!=` in most cases to avoid unexpected `null` results. Only use `=` and `<>` if you _really_ want SQL's three-valued logic behavior. ### Ternary `?:` Operator > Source: /docs/guides/databases/cap-level-dbs#ternary--operator CQL supports the ternary conditional operator `condition ? expr1 : expr2`, similar to many programming languages like JavaScript and Java. It evaluates `condition`, and returns the value of `expr1` if `condition` is true, or the value of `expr2` otherwise. ::: code-group ```sql [CQL example] SELECT price > 100 ? 'expensive' : 'affordable' as priceCategory from Books; ``` ::: ::: code-group ```sql [=>  Compiled SQL query] SELECT CASE WHEN price > 100 THEN 'expensive' ELSE 'affordable' END as priceCategory FROM Books; ``` ::: The compiler translates this operator to the best-possible equivalent in the target database: `CASE WHEN ... THEN ... ELSE ... END` in standard SQL, or `IF(..., ..., ...)` in SAP HANA. ## Standard Functions > Source: /docs/guides/databases/cap-level-dbs#standard-functions ###### Portable Functions > Source: /docs/guides/databases/cap-level-dbs#portable-functions The following sections list standardized string, numeric, date/time, and aggregate functions supported by CAP, and guaranteed to work across all supported databases with feature parity. You can safely use these in CDS view definitions and runtime queries expressed in CQL. The compiler, and the CAP runtimes, translate them to the best-possible database-specific native SQL equivalents. > [!important] Function names are case-sensitive > The names for standardized functions must be written exactly as listed below. For example, `toUpper` is invalid, while `toupper` is valid. Differently cased names might also work if they match native functions of the specific database, but are not guaranteed to be portable -> always use the exact casing as listed. ### String Functions > Source: /docs/guides/databases/cap-level-dbs#string-functions - `concat(x,y,...)` - `length(x)` - `trim(x)` - `tolower(x)` - `toupper(x)` - `contains(x,substring)` - `startswith(x,substring)` - `endswith(x,substring)` - `indexof(x,substring)` - `substring(x,start, length)` - `matchespattern(x,pattern)` In addition to `concat()`, CAP also supports the common `||` operator for string concatenation in CQL queries, same as in SQL queries. For example, these two queries are equivalent: ```sql SELECT concat (firstName,' ',lastName) as fullName from Authors; ``` ```sql SELECT firstName || ' ' || lastName as fullName from Authors; ``` > [!important] Indexes and Substring Details > The return value of `indexof()` as well as the `start` parameter in `substring()` are zero-based index values. If the substring is not found, `indexof()` returns `-1`. If the `start` index in `substring()` is negative, it is counted from the end of the string. If the `length` parameter is omitted, the substring to the end of the string is returned. ### Numeric Functions > Source: /docs/guides/databases/cap-level-dbs#numeric-functions - `ceil(x)`, `ceiling(x)` - `floor(x)` - `round(x)` > [!warning] Non-portable round() function with more than one argument > Note that databases support `round()` functions with multiple arguments, the second parameter being the precision. If you use that option, the `round()` function may behave differently depending on the database. ### Date / Time Functions > Source: /docs/guides/databases/cap-level-dbs#date--time-functions - `date(x)` -> `yyyy-MM-dd` strings - `time(x)` -> `HH:mm:ss` strings

- `year(x)` -> integer - `month(x)` -> integer - `day(x)` -> integer - `hour(x)` -> integer - `minute(x)` -> integer - `second(x)` -> integer

- `years_between(x,y)` -> number - `months_between(x,y)` -> number - `days_between(x,y)` -> number - `seconds_between(x,y)` -> number ### Aggregate Functions > Source: /docs/guides/databases/cap-level-dbs#aggregate-functions - `avg(x)`, `average(x)` - `min(x)`, `max(x)` - `sum(x)` - `count(x)` ## Native Functions > Source: /docs/guides/databases/cap-level-dbs#native-functions In general, the CDS compiler doesn't 'understand' SQL functions but translates them to SQL _generically_ as long as they follow the standard call syntax of `fn(x,y,...)`. This allows to use all native database functions inside your CDS models, like this: ```cds SELECT from Books { ifnull (descr, title) //> using HANA's native `ifnull` function } ``` > [!warning] Native functions are less portable > Using native functions like this makes your CDS models database-specific, and thus less portable. Therefore, prefer using the [portable functions](#portable-functions) listed above whenever possible. ## Window Functions > Source: /docs/guides/databases/cap-level-dbs#window-functions [SQL window functions](https://en.wikipedia.org/wiki/Window_function_(SQL)) with `OVER` clauses are supported as well, for example: ```sql SELECT from Books { rank() over (partition by author order by price) as rank } ``` # CDL Compilation to Database-Specific DDLs > Source: /docs/guides/databases/cdl-to-ddl Databases are deployed based on the entity definitions in your CDS models. This guide explains how that works under the hood, focusing on the compilation of CDS models to database-specific artifacts like SQL `CREATE TABLE` statements for relational databases. {.abstract} [toc]:./ > [!tip] Everything Served Out of the Box > The CAP framework handles all compilation to DDL automatically, for example when you run `cds watch` or `cds deploy`. You typically don't need to worry about the details unless you want to inspect or customize the generated DDL statements. So, all information in this guide is just to explain how things work under the hood, and if you are on a fast track, you can safely skip it. ## Using `cds compile`, ... > Source: /docs/guides/databases/cdl-to-ddl#using-cds-compile- CDS compilation to database-specific DDLs is handled by the `cds compile` command, which is part of the [`cds` CLI](../../tools/cds-cli). When you run `cds deploy` or `cds watch`, this command is invoked automatically to generate the necessary DDL statements for your target database. You can also run the command manually to see the generated DDL for your models. For example, to inspect what the SQL DDL for your entire model would look like, simply run: ```shell cds compile \* --to sql ``` The asterisk (`\*`1) can be replaced with specific .cds files or folders to compile only particular parts of your model. ```shell cds compile db/schema.cds --to sql cds compile db --to sql ``` ::: details You can combine `cds compile` with other shell commands via UNIX pipes for more advanced use cases. For example, count the number of entity definitions in your models like this: ```shell cds compile \* | grep entity | wc -l ``` > 1 The backslash (`\`) before the asterisk (`*`) is used to escape it, preventing shell expansion to all files in the current directory. ::: ### Database-Specific Dialects > Source: /docs/guides/databases/cdl-to-ddl#database-specific-dialects Add the `--dialect` option to generate DDL for specific databases. For example, to see the SAP HANA-specific variant, run: ```shell cds compile \* --to sql --dialect hana ``` We can generate DDL files for different dialects in one go, and check differences between individual ones using VS Code like this: ```shell cds compile \* --to sql --dialect sqlite -o _out/c/sqlite.sql cds compile \* --to sql --dialect h2 -o _out/c/h2.sql cds compile \* --to sql --dialect hana -o _out/c/hana.sql cds compile \* --to sql --dialect postgres -o _out/c/postgres.sql ``` ```shell code --diff _out/c/sqlite.sql _out/c/h2.sql ``` > [!tip] CDS models are database-agnostic > CDS models are designed to be database-agnostic, allowing you to switch between different databases with minimal changes. The `--dialect` option helps you see how your models translate to different database-specific DDLs. \ ### Dialects by `cds env` Profiles > Source: /docs/guides/databases/cdl-to-ddl#dialects-by-cds-env-profiles The dialect is automatically inferred from your project configuration and the current profile, so you typically don't need to specify it explicitly. For example, if your project is configured to use SAP HANA in production and SQLite in development, the respective dialects will be applied automatically. Try this out using the `--profile` option: ```shell cds compile \* --to sql --profile development cds compile \* --to sql --profile production ``` ::: details Use `cds env` to check your effective configurations: ```shell cds env requires.db --profile development cds env requires.db --profile production ``` ::: > [!tip] Dialects are inferred from profiles automatically > You typically don't need to specify the `--dialect` option manually, as it is derived from your project configuration and the active profile. ### Using `cds deploy` > Source: /docs/guides/databases/cdl-to-ddl#using-cds-deploy We can use `cds deploy` to inspect the generated DDL without actually deploying it, by using the `--dry` option. This will print the ultimate DDL statements to the console instead of executing them against the database, for example: ```shell cds deploy --dry ``` This will print out the DDL for the database configured in your project for the current profile. As for `cds compile` above, let's generate DDL files for different databases in one go, and compare it to the former output like this: ```shell cds deploy --dry --to sqlite -o _out/d/sqlite.sql cds deploy --dry --to h2 -o _out/d/h2.sql cds deploy --dry --to hana -o _out/d/hana cds deploy --dry --to postgres -o _out/d/postgres.sql ``` ```shell code --diff _out/c/sqlite.sql _out/d/sqlite.sql ``` ::: code-group ```sql [cds deploy output] DROP TABLE IF EXISTS sap_capire_bookshop_Authors; -- [!code ++] DROP TABLE IF EXISTS sap_capire_bookshop_Books; -- [!code ++] DROP TABLE IF EXISTS sap_capire_bookshop_Genres; -- [!code ++] ... -- [!code ++] CREATE TABLE sap_capire_bookshop_Authors ...; CREATE TABLE sap_capire_bookshop_Books ...; CREATE TABLE sap_capire_bookshop_Genres ...; ... ``` ```sql [cds compile output] CREATE TABLE sap_capire_bookshop_Authors ...; CREATE TABLE sap_capire_bookshop_Books ...; CREATE TABLE sap_capire_bookshop_Genres ...; ... ``` ::: Essentially, `cds deploy` calls `cds compile --to sql` under the hood, but goes a step further by also considering deployment-specific aspects, like: - **Schema Evolution** – the `diff` shows additional `DROP TABLE` statements, which are a schema evolution strategy most suitable for development. For production, more sophisticated strategies are applied. Learn more about that in the [_Schema Evolution_](schema-evolution) guide. - **Database-Specific Artifacts** – for [SAP HANA](hana), the output of `cds deploy` is not a single SQL DDL script anymore; but a number of `.hdbtable`, `.hdbview`, and other so-called HDI artifacts are generated. > [!note] Ad-hoc Deployments > Without the `--dry` option, `cds deploy` would not only compile your CDS models to DDL, but would also do an ad-hoc deployment to the target database, if available. How that works is explained in more detail in the database-specific guides for [_SAP HANA_](hana), [_SQLite_](sqlite), and [_PostgreSQL_](postgres). ## CDL ⇒ DDL Translation > Source: /docs/guides/databases/cdl-to-ddl#cdl--ddl-translation The CDL-to-DDL compilation follows several general mapping principles to translate CDS constructs into database-specific artifacts, as outlined below. ### Entities ⇒ Tables / Views > Source: /docs/guides/databases/cdl-to-ddl#entities--tables--views Declared entities become tables, projected entities become views: ::: code-group ```cds [CDS Source] entity SomeEntity { ... } entity SomeView as select from SomeEntity { ... }; entity SomeProjection as projection on SomeEntity { ... }; ``` ```sql [=>   Generated DDL] CREATE TABLE SomeEntity ( ... ); CREATE VIEW SomeView AS SELECT ... FROM SomeEntity; CREATE VIEW SomeProjection AS SELECT ... FROM SomeEntity; ``` ::: > [!tip] Views are defined using CQL > Both view defined per `as projection on` and those using `as select from` are defined using CQL, which supports a broad scope of database-agnostic features. Learn more about that in the following guide: [_CQL Compilation to SQL_](cap-level-dbs). #### Qualified Names ⇒ Slugified > Source: /docs/guides/databases/cdl-to-ddl#qualified-names--slugified Entities in CDS models have fully qualified names with dots. These are converted to database-native names, by replacing dots with underscores – called 'slugification': ::: code-group ```cds [CDS Source] namespace sap.capire.bookshop; entity Books { ... } entity Books.Details { ... } ``` ::: ::: code-group ```sql[=> Generated DDL] CREATE TABLE sap_capire_bookshop_Books ( ... ); CREATE TABLE sap_capire_bookshop_Books_Details ( ... ); ``` ::: > [!tip] Guaranteed & Stable Slugification > The slugification effects are guaranteed and stable, which means that you can rely on it and use the slugified names in native SQL queries. For example, both of the following CQL queries are equivalent and will work as expected: ```js await cds.run `SELECT from sap.capire.bookshop.Books` await cds.run `SELECT from sap_capire_bookshop_Books` ``` > [!tip] > Prefer entity names like `Books.Details` over _CamelCase_ variants like `BooksDetails`. While both work equally, they show up differently in native tools of databases that don't preserve case, for example in SAP HANA: The former will show up as `BOOKS_DETAILS`, while the latter shows up as `BOOKSDETAILS`, which is harder to read. ### Types ⇒ Native Types > Source: /docs/guides/databases/cdl-to-ddl#types--native-types [CDS types](../../cds/types) are mapped to database-specific SQL types based on the target database dialect, as outlined in the table below: | CDS Type | SAP HANA | SQLite | H2 | PostgreSQL | |---------------|-----------------|-------------------|-----------------|---------------| | UUID | NVARCHAR(36) | NVARCHAR(36) | NVARCHAR(36) | NVARCHAR(36) | | String | NVARCHAR(5e3) | NVARCHAR(255) | NVARCHAR(255) | NVARCHAR(255) | | String (n) | NVARCHAR(n) | NVARCHAR(n) | NVARCHAR(n) | NVARCHAR(n) | | Boolean | BOOLEAN | BOOLEAN | BOOLEAN | BOOLEAN | | Integer | INTEGER | INTEGER | INTEGER | INTEGER | | Int16 | SMALLINT | SMALLINT | SMALLINT | SMALLINT | | Int32 | INTEGER | INTEGER | INTEGER | INTEGER | | Int64 | BIGINT | BIGINT | BIGINT | BIGINT | | UInt8 | TINYINT | TINYINT | SMALLINT | SMALLINT | | Decimal (p,s) | DECIMAL(p,s) | REAL_DECIMAL(p,s) | DECIMAL(p,s) | DECIMAL(p,s) | | Decimal | DECIMAL | REAL_DECIMAL | DECFLOAT | DECIMAL | | Double | DOUBLE | DOUBLE | DOUBLE | FLOAT8 | | DateTime | SECONDDATE | DATETIME_TEXT | TIMESTAMP(0) | TIMESTAMP | | Date | DATE | DATE_TEXT | DATE | DATE | | Time | TIME | TIME_TEXT | TIME | TIME | | Timestamp | TIMESTAMP | TIMESTAMP_TEXT | TIMESTAMP(7) | TIMESTAMP | | Binary | VARBINARY(5e3) | BINARY_BLOB(5e3) | VARBINARY(5e3) | BYTEA | | Binary (n) | VARBINARY(n) | BINARY_BLOB(n) | VARBINARY(n) | BYTEA | | LargeBinary | BLOB | BLOB | BIN. LARGE OBJ. | BYTEA | | LargeString | NCLOB | NCLOB | NCLOB | TEXT | | Map | NCLOB | JSON_TEXT | JSON | JSONB | | Vector | REAL_VECTOR | | | | [Refer to _CDS Types Documentation_ for a specification of the CDS types.](../../cds/types){.learn-more} Custom-defined types based on built-in CDS types are mapped according to their underlying base type: ::: code-group ```cds [CDS Source] entity Foo { bar : Text(44); } type Text : String(111); ``` ```sql [=>   Generated DDL] CREATE TABLE Foo ( bar NVARCHAR(44) ); ``` ::: ### Structs ⇒ Flattened > Source: /docs/guides/databases/cdl-to-ddl#structs--flattened ###### flattened-structs > Source: /docs/guides/databases/cdl-to-ddl#flattened-structs Elements with [structured types](../../cds/cdl#structured-types) are flattened into their parent entities, with the struct name used as a prefix for the contained elements: ::: code-group ```cds [CDS Source] entity Books { title : String; price : { amount : Decimal; currency : String(3); } } ``` ::: ::: code-group ```sql [=>   Generated DDL] CREATE TABLE Books ( title NVARCHAR(255), price_amount DECIMAL, price_currency NVARCHAR(3) ); ``` ::: > [!tip] Guaranteed & Stable Flattening > The flattening effects are guaranteed and stable, which means that you can rely on it and use the flattened elements in native SQL queries. For example, both of the following CQL queries are equivalent and would work as expected: ```js await cds.run `SELECT price.amount from Books` await cds.run `SELECT price_amount from Books` ``` ### Associations ⇒ JOINs > Source: /docs/guides/databases/cdl-to-ddl#associations--joins Given this CDS model with both [managed](../../cds/cdl#managed-associations) to-one and [unmanaged](../../cds/cdl#unmanaged-associations) to-many associations, as we know them from the [_@capire/bookshop_](https://github.com/capire/bookshop) sample: ```cds entity Books { ... author : Association to Authors; // managed genre : Association to Genres; // managed } entity Authors { ... books : Association to many Books on books.author = $self; } entity Genres { ... } ``` Managed associations are _unfolded_ into unmanaged ones as below:: ```cds entity Books { ... // with managed associations unfolded to: author : Association to Authors on author_ID = author.ID; author_ID : Integer; // added foreign key element genre : Association to Genres on genre_ID = genre.ID; genre_ID : Integer; // added foreign key element } entity Authors {/* as above */} entity Genres {/* as above */} ``` This unfolded model is then compiled to DDL, with unmanaged associations **skipped**: ```sql CREATE TABLE Authors (/* no columns for unmanaged assocs */... ) CREATE TABLE Books (/* no columns for unmanaged assocs */ ... author_ID INTEGER -- added foreign key column genre_ID INTEGER -- added foreign key column ); ``` ###### Associations as Forward-declared JOINs > Source: /docs/guides/databases/cdl-to-ddl#associations-as-forward-declared-joins CQL queries that use such associations, for example: ::: code-group ```sql [CQL query using associations] SELECT title, author.name, genre.name from Books /* Note: author and genre are used like table aliases */ ``` ::: Are enhanced with JOINs as per respective association definitions: ::: code-group ```sql [=>  Compiled SQL query] SELECT title, author.name, genre.name from Books --> very same as above LEFT JOIN Authors as author on author_ID = author.ID; -- [!code ++] LEFT JOIN Genres as genre on genre_ID = genre.ID; -- [!code ++] ``` ::: > [!note] Associations as 'Forward-declared' JOINs > Looking closely at the above compiled SQL code, we can regard > associations to be like _'Forward-declared' JOINs_, along these lines: > > 1. Association names `a.name` appear in queries as standard _table aliases_ > 2. _JOINs_ are added automatically as per the following construction rule: > > _JOIN `a.target` as `a.name` on `a.on`_ > {} > > 3. For _managed_ associations with unfolded on conditions: > > _JOIN `a.target` as `a.name` on `a.keys` = `a.name` . `a.target.keys`_ > {} > > where `a` is an association definition with these properties: >
`a.target` – the target entity's name >
`a.name` – the association's name >
`a.on` – the on condition of an unmanaged association >
`a.keys` – the foreign key element(s), added to the source entity >
`a.target.keys` – the target's respective (primary) key element(s) ### Calculated Elements > Source: /docs/guides/databases/cdl-to-ddl#calculated-elements [_Materialized_ calculated elements](../../cds/cdl#on-write), that is those with a trailing `stored` keyword, are translated into corresponding database columns with `GENERATED ALWAYS AS` clauses. In contrast, [_virtual_ calculated elements](../../cds/cdl#on-read) are not represented in the database schema at all, but applied at runtime by the CAP database layers when reading data from the database. ::: code-group ```cds [CDS Source] entity Orders { quantity : Integer; price : Decimal; total : Decimal = price * quantity stored; // [!code focus] gross : Decimal = total * (1+VAT); // virtual // [!code focus] } ``` ::: ::: code-group ```sql [=>   Generated DDL] CREATE TABLE Orders ( quantity INTEGER, price DECIMAL, total DECIMAL GENERATED ALWAYS AS (price * quantity) STORED -- [!code focus] ); ``` ::: [_Virtual_ calculated elements](../../cds/cdl#on-read) are applied at runtime whenever data is read from the database, for example, a CQL query like this: ::: code-group ```sql [CQL source query] SELECT total, gross from Orders; ``` ::: would be compiled to the following SQL query: ::: code-group ```sql [=>  Compiled SQL query] SELECT total, total * (1+VAT) as gross from Orders; ``` ::: ### Virtual Elements > Source: /docs/guides/databases/cdl-to-ddl#virtual-elements [Virtual elements](../../cds/cdl#virtual-elements) are not represented in the database schema at all, similar to virtual calculated elements above. They exist only at the CAP runtime layer, and are typically used to represent data coming from external services or other non-persistent sources. ### Default Values > Source: /docs/guides/databases/cdl-to-ddl#default-values You can specify default values for elements using the `default` keyword in element definitions. These defaults are translated into SQL `DEFAULT` clauses in the generated DDL, in a one-to-one manner. ::: code-group ```cds [CDS Source] entity Books { available : Boolean default true; createdAt : DateTime default current_timestamp; } ``` ```sql [=>   Generated DDL] CREATE TABLE Books ( available BOOLEAN DEFAULT true, createdAt TIMESTAMP DEFAULT current_timestamp ); ``` ::: > [!tip] Consider using @cds.on.insert instead > Instead of using `default` values, consider using the [`@cds.on.insert`](../domain/index#cds-on-insert) annotation, which provides more flexibility and is more tuned for typical application scenarios. ### Invalid Names > Source: /docs/guides/databases/cdl-to-ddl#invalid-names When you use names in your CDS models that conflict with reserved words of underlying databases, or names that contain non-ASCII characters, special characters, or spaces, these names are considered invalid in many databases. CAP escapes these names in the generated DDL and all queries sent to the database. For example, the following is a valid CDS model with database-invalid named elements. The generated DDL escapes them with double quotes: ::: code-group ```cds [CDS Source] entity BadNames { ![a name] : String; // invalid whitespaces ![drôle] : String; // invalid diacritics ![select] : String; // reserved word in SQL and CDS group : String; // reserved word in SQL } ``` ```sql [=>   Generated DDL] CREATE TABLE BadNames ( "a name" NVARCHAR(255), "drôle" NVARCHAR(255), "select" NVARCHAR(255), "group" NVARCHAR(255) ); ``` ::: However, even though CAP allows this, and handles all accesses correctly, it is strongly discouraged to use such names in your CDS models, as that may lead to unexpected issues in several scenarios, not in control of CAP, such as native SQL queries, third-party tools, or integration with non-CAP applications. > [!warning] DON'T use Database-Invalid Names! > It's **strongly discouraged** to use names that contain non-ASCII characters, or conflict with database reserved words. Even more avoid [delimited names](../../cds/cdl#keywords--identifiers) in CDS models in the first place, as that impacts readability of your models. ###### reserved-words > Source: /docs/guides/databases/cdl-to-ddl#reserved-words > [!tip] Lists of Reserved Words > Check out the reserved words for the databases you are targeting: \ > [_SAP HANA_](https://help.sap.com/docs/HANA_CLOUD_DATABASE/c1d3f60099654ecfb3fe36ac93c121bb/28bcd6af3eb6437892719f7c27a8a285.html) > , [_SQLite_](https://www.sqlite.org/lang_keywords.html) > , [_H2_](https://www.h2database.com/html/advanced.html#keywords) > , [_PostgreSQL_](https://www.postgresql.org/docs/current/sql-keywords-appendix.html) ## Keys, Constraints > Source: /docs/guides/databases/cdl-to-ddl#keys-constraints ###### Database Constraints > Source: /docs/guides/databases/cdl-to-ddl#database-constraints CAP supports the generation of various database constraints based on CDS model definitions, as outlined below. ::: warning Don't use for end user-facing input validation Database constraints are meant to protect against data corruption due to programming errors, and are not meant for application-level input validation. If a constraint violation occurs, the error messages coming from the database aren't standardized by the runtimes but presented as-is. ::: ### Primary Key Constraints > Source: /docs/guides/databases/cdl-to-ddl#primary-key-constraints The compiler translates primary keys defined in CDS entities into SQL `PRIMARY KEY` constraints in the generated DDL. For example: ::: code-group ```cds [CDS Source] entity OrderItems { key order: Association to Orders; key pos: Integer; ... } ``` ::: ::: code-group ```sql [=>   Generated DDL] CREATE TABLE OrderItems ( order_ID NVARCHAR(36), pos INTEGER, ... PRIMARY KEY (order_ID, pos) -- [!code focus] ); ``` ::: ### Not Null Constraints > Source: /docs/guides/databases/cdl-to-ddl#not-null-constraints You can specify that a column's value must not be `NULL` by adding the [`not null` constraint](../../cds/cdl#null-values) to the element, for example: ```cds entity Books { ... title: String not null; } ``` > [!tip] Consider using @mandatory instead > Instead of, or in addition to using database-level `not null` constraints, consider using the [`@mandatory`](../services/constraints#mandatory) annotation, which provides more flexibility and is more tuned for typical application scenarios. ### Unique Constraints > Source: /docs/guides/databases/cdl-to-ddl#unique-constraints Annotate an entity with `@assert.unique.`, to express one or more, named uniqueness checks on combination of columns. These will be translated to SQL `UNIQUE` constraints in the generated DDL. For example, given an entity definition like this: ```cds entity OrderItems { ... order : Association to Orders; product : Association to Products; } ``` Use `@assert.unique` to ensure that each product appears only once per order: ```cds [CDS Source] annotate OrderItems with @assert.unique.product: [ order, product ]; ``` Which would translate to the following SQL `UNIQUE` constraint in the generated DDL: ```sql [=>   Generated DDL] CREATE TABLE OrderItems ( ... CONSTRAINT OrderItems_products UNIQUE (order_ID, product_ID) -- [!code focus] ); ``` Multiple named unique constraints per entity are supported, for example: ```cds [CDS Source] annotate OrderItems with @assert.unique.product: [ order, product ]; annotate OrderItems with @assert.unique.someOtherConstraint: [ ... ]; ``` - The `` name in `@assert.unique.` becomes the name of the database constraint. - The argument is expected to be an array of flat [element references](../../cds/cdl#annotation-values) referring to elements in the entity. These elements may have the following types: - scalar types - `String`, `Integer`, and so on - structured types – **not** elements _within_ structs. - _managed_ associations – **not** _unmanaged_ associations. - In case of structs, all [flattened columns](#flattened-structs) stemming from it will be included. Similarly, for managed associations: all foreign key columns will be included. ::: tip Primary Keys are Unique Constraints You don't need to specify `@assert.unique` constraints for the [primary keys](#primary-key-constraints) of an entity as these are automatically secured by a SQL `PRIMARY KEY` constraint, which enforces uniqueness. ::: ### Foreign Key Constraints > Source: /docs/guides/databases/cdl-to-ddl#foreign-key-constraints [managed to-one associations]: ../../cds/cdl#managed-to-one-associations For [managed to-one associations], CAP can automatically generate foreign key constraints in the database. Switch this on globally with config option cds.features.assert_integrity = db. With this flag switched on, `FOREIGN KEY` constraints are automatically added to `CREATE TABLE` statements for [managed to-one associations] like this: ::: code-group ```cds [CDS Source] entity Books { author : Association to Authors; } ``` ::: ::: code-group ```sql [=>   Generated DDL] CREATE TABLE Books ( ... ID INTEGER NOT NULL, author_ID INTEGER, -- added foreign key field CONSTRAINT Books_author -- added foreign key constraint FOREIGN KEY(author_ID) REFERENCES Authors(ID) ON UPDATE RESTRICT ON DELETE RESTRICT VALIDATED ENFORCED INITIALLY DEFERRED ) ``` ::: > [!tip] Consider using @assert.target instead > Database constraints are meant to protect against data corruption due to programming errors. Prefer using the [`@assert.target`](../services/constraints#asserttarget) for application-level input validation, which is more tuned for typical application scenarios, with error messages tailored for end users. #### `ON DELETE CASCADE` > Source: /docs/guides/databases/cdl-to-ddl#on-delete-cascade Think of the example above: a book can still exist, even if its author is deleted. However, this differs for existential relationships like the one between a book and its pages: a page cannot exist without a corresponding book. In such cases, you want the pages to be automatically deleted when the book (parent) is deleted. Typically such existential relationships are modeled in CDS using [compositions](../../cds/cdl#compositions). That is why for managed **backlink** associations (those used in a composition's on-condition via `$self = .`), the foreign key constraints are generated with `ON DELETE CASCADE` by default, instead of `ON DELETE RESTRICT`: ::: code-group ```cds [CDS Source] entity Books { key ID : Integer; pages: Composition of many Pages on pages.book = $self; } entity Pages { key number: Integer; key book: Association to Books; // → ON DELETE CASCADE } ``` ::: ::: code-group ```sql [=> Generated DDL] … ALTER TABLE Pages ADD CONSTRAINT c__Pages_book FOREIGN KEY(book_ID) REFERENCES Books(ID) ON UPDATE RESTRICT ON DELETE CASCADE VALIDATED ENFORCED INITIALLY DEFERRED; ``` ::: #### Skipping with `@assert.integrity:false` > Source: /docs/guides/databases/cdl-to-ddl#skipping-with-assertintegrityfalse You can skip foreign key constraint generation for specific associations by annotating them with `@assert.integrity:false`, for example: ```cds entity Books { author : Association to Authors @assert.integrity:false; } ``` #### Deferred Enforcement > Source: /docs/guides/databases/cdl-to-ddl#deferred-enforcement Referential integrity is enforced at the time of transaction commit. This uses the database's [deferred foreign key constraints](https://www.sqlite.org/foreignkeys.html), which are supported by most relational databases, including SAP HANA, SQLite, and PostgreSQL. However, H2 does not support deferred constraints: > [!note] Database constraints are not supported for H2 ## Customizing Options > Source: /docs/guides/databases/cdl-to-ddl#customizing-options You can customize the generated DDL using specific CDS annotations, as outlined below. ### @cds.persistence.skip > Source: /docs/guides/databases/cdl-to-ddl#cdspersistenceskip Annotate an entity with `@cds.persistence.skip` to indicate that this entity should be skipped from generated DDL scripts, and also no SQL views to be generated on top of it: ::: code-group ```cds [CDS Source] entity Foo {...} entity Bar as select from Foo; annotate Foo with @cds.persistence.skip; ``` ```sql [=>   Generated DDL] CREATE TABLE Foo ( ... )); -- skipped [!code --] CREATE VIEW Bar AS SELECT ... FROM Foo; -- skipped [!code --] ``` ::: ### @cds.persistence.exists > Source: /docs/guides/databases/cdl-to-ddl#cdspersistenceexists Annotate an entity with `@cds.persistence.exists` to indicate that this entity should be skipped from generated DDL scripts. In contrast to `@cds.persistence.skip` a database table or view is expected to exist, so we can and will generate SQL views on top. ::: code-group ```cds [CDS Source] entity Foo {...} entity Bar as select from Foo; annotate Foo with @cds.persistence.exists; ``` ```sql [=>   Generated DDL] CREATE TABLE Foo ( ... )); -- skipped, but expected to exist [!code --] CREATE VIEW Bar AS SELECT ... FROM Foo; -- generated as usual ``` ::: ::: details On SAP HANA ... When using `@cds.persistence.exists` for ... - User-defined functions (UDFs), annotate it with `@cds.persistence.udf` in addition. - Calculation views, annotate it with `@cds.persistence.calcview` in addition. See [Calculated Views and User-Defined Functions](./hana-native#calculated-views-and-user-defined-functions) for more details. ::: ### @cds.persistence.table > Source: /docs/guides/databases/cdl-to-ddl#cdspersistencetable Annotate an view entity with `@cds.persistence.table` to create a table with the effective signature of the view definition instead of an SQL view. ::: code-group ```cds [CDS Source] entity Foo { key ID : Integer; tag : String; foo : Timestamp; } entity Bar as select from Foo { ID, tag, true as bar : Boolean; }; annotate Bar with @cds.persistence.table; ``` ```sql [=>   Generated DDL] CREATE TABLE Foo ( ID INTEGER, tag NVARCHAR(255), foo TIMESTAMP ); CREATE TABLE Bar ( ID INTEGER, tag NVARCHAR(255), bar BOOLEAN ); -- [!code ++] CREATE VIEW Bar AS SELECT ... FROM Foo; -- skipped [!code --] ``` ::: > [!note] Irrelevant parts are ignored > All parts of the view definition not relevant for the signature, such as `where`, `group by`, `having`, `order by`, or `limit`, are ignored. > [!tip] Use Case: Replica Caching Tables A common use case for this annotation is to create projections on entities from imported APIs, so-called _consumption views_, and at the same time use them as replica cache tables. ### `@sql.prepend / append` > Source: /docs/guides/databases/cdl-to-ddl#sqlprepend--append Annotate entities or elements with `@sql.prepend` and `@sql.append` to add native SQL clauses before or after the generated SQL output. ::: code-group ```cds [CDS Source] entity Books { ..., title: String } entity ListOfBooks as select from Books { ... }; annotate Books:title with @sql.append: 'FUZZY SEARCH INDEX ON'; annotate Books with @sql.append: ```sql GROUP TYPE foo GROUP SUBTYPE bar ```; annotate ListOfBooks with @sql.append: 'WITH DDL ONLY'; ``` ```sql [=>   Generated DDL] CREATE TABLE Books ( ..., title NVARCHAR(100) FUZZY SEARCH INDEX ON ) GROUP TYPE foo GROUP SUBTYPE bar; CREATE VIEW ListOfBooks AS SELECT ... FROM Books WITH DDL ONLY; ``` ::: - Values for the annotations must be [string literals](../../cds/cdl#literals) or [multiline string literals](../../cds/cdl#multiline-literals). - `@sql.prepend` is only supported for entities translating to tables. It can't be used with views or with elements. > [!note] Note for SAP HANA > Ensure to read [Schema Evolution Support of Native Database Clauses](hana#schema-evolution-native-db-clauses) if you plan to use these annotations in combination with [`@cds.persistence.journal`](hana#enabling-hdbmigrationtable-generation). > [!caution] > The content of these annotations is inserted as-is into the generated DDL statements without any validation or other processing by the compiler. Use this feature with caution, as incorrect SQL clauses may lead to deployment failures or runtime errors. You're responsible to ensure that the resulting statement is valid and doesn't negatively impact your database or your application. **We don't provide support for problems caused by using this feature.** #### Column vs Row Tables on SAP HANA > Source: /docs/guides/databases/cdl-to-ddl#column-vs-row-tables-on-sap-hana CAP creates columnar tables by default on SAP HANA, which is accomplished by an implicit `@sql.prepend:'COLUMN'` applied to all entities translating to tables. You can override this by using `@sql.prepend:'ROW'` to create a row table instead. ::: warning Whenever you use `@sql.prepend`, the default `@sql.prepend:'COLUMN'` is overridden. ::: ## Database-Specific Models > Source: /docs/guides/databases/cdl-to-ddl#database-specific-models All the above translations are designed to be portable across different SQL databases supported by CAP. However, there may be scenarios where you need to add database-specific definitions. You can achieve this by using database-specific subfolders in your `./db` folder, and configuring your project to use these sub-models based on the target database as follows: 1. Add database-specific models in respective subfolders of `./db`: ::: code-group ```cds [db/sqlite/native.cds] using { AdminService } from '@capire/bookshop'; extend projection AdminService.Authors with { strftime('%Y',dateOfDeath)-strftime('%Y',dateOfBirth) as age : Integer } ``` ```cds [db/hana/native.cds] using { AdminService } from '@capire/bookshop'; extend projection AdminService.Authors with { YEARS_BETWEEN(dateOfBirth, dateOfDeath) as age : Integer } ``` ::: 2. Add [profile-specific configuration](../../node.js/cds-env#profiles) to use these database-specific extensions: ```json { "cds": { "requires": { "db": { "[development]": { "model": "db/sqlite" }, "[production]": { "model": "db/hana" } } }}} ``` Find that sample also in [@capire/bookstore](https://github.com/capire/bookstore/tree/main/db). # Adding Initial Data > Source: /docs/guides/databases/initial-data You can add `.csv` files to fill your database with initial data and test data. The runtime automatically loads these files whenever you bootstrap a database, run `cds watch` in development, or deploy and upgrade for production. {.abstract} ## Using `cds add data` > Source: /docs/guides/databases/initial-data#using-cds-add-data Use `cds add data` to generate empty `.csv` files, with column headers based on the entities in your CDS model. For example, if you run this in the [*@capire/bookshop*](../../get-started/bookshop) sample project, you can see the following output: ```shell cds add data ``` ```zsh Adding facet: data creating db/data/sap.capire.bookshop-Authors.csv creating db/data/sap.capire.bookshop-Books.csv creating db/data/sap.capire.bookshop-Books.texts.csv creating db/data/sap.capire.bookshop-Genres.csv creating db/data/sap.capire.bookshop-Genres.texts.csv Successfully added features to your project ``` By default, the command generates the files in the _db/data_ folder of your project structure: ```zsh cap/bookshop/ ├── db/ │ ├── data/ │ │ └── # generated .csv files ... ``` You can also specify other folders by providing the `--out` option, for example: ```shell cds add data -o test/data ``` Each file contains a CSV header line reflecting the structure of the entity to which it corresponds. ## Editing `.csv` Files > Source: /docs/guides/databases/initial-data#editing-csv-files Fill these files with initial data by editing them in your preferred editor. For example, the content of _db/data/sap.capire.bookshop-Books.csv_ looks like this: ::: code-group ```csvc [db/data/sap.capire.bookshop-Books.csv] ID,title,author_ID,stock 201,Wuthering Heights,101,12 207,Jane Eyre,107,11 251,The Raven,150,333 252,Eleonora,150,555 271,Catweazle,170,22 ``` ::: You can also use `cds add data` with the `--records` option to add generic data: ```shell cds add data --records 10 ``` > [!tip] AI-Generated Data > For more realistic domain-specific data, you can use AI tools, like CoPilot in VS Code, to assist you in that. This works particularly well for small to medium-sized datasets. Moreover, CDS models provide domain models that are easy to reason about, with strong typing information, meaning that AIs can generate better fitting data samples. The following table shows common rules that apply to text content in `.csv` files: | Condition | Handling | |------------------------------------------------------------------|-----------------------------| | If texts contain commas, or line breaks, or trailing whitespaces | -> enclose in double quotes | | If texts contain double quotes | -> escape by doubling them | | Numeric content should be treated as texts | -> enclose in double quotes | | Boolean values should be treated as text | -> enclose in double quotes | ## Initial vs Test Data > Source: /docs/guides/databases/initial-data#initial-vs-test-data You need to distinguish between _(real) initial data_ meant for production (configuration, code lists) and _test data_ meant for development and testing purposes only. CAP supports this by organizing CSV files in two locations: ```zsh cap/bookshop/ ├── db/ │ ├── data/ │ │ └── # .csv files for initial data │ ... ├── test/ │ ├── data/ │ │ └── # .csv files for test data ... ``` The following table describes the purpose and deployment scope of each location: | Location | Purpose | Deployed... | |-----------------|---------------------|----------------------| | **`db/data`** | (real) initial data | always, dev and prod | | **`test/data`** | test data | in development only | ::: details Bookshop data is actually test data... Note that the initial data provided in the [_@capire/bookshop_](../../get-started/bookshop) sample is actually test data, and hence we would typically place it in the _test/data_ folder. But for simplicity, it's placed in _db/data_, also because the whole purpose of that project is to be a _sample_. :::: > [!danger] Don't let users modify productive initial data > Otherwise this [data might get overridden on SAP HANA](./hana#csv-data-gets-overridden). ### Custom Folders > Source: /docs/guides/databases/initial-data#custom-folders You can also configure other folders to read data from in different profiles. Use config option cds.requires.db.data to do so. The default configuration is as follows, which you can override in your `package.json` or `.cdsrc.yaml` file as appropriate: ::: code-group ```json [package.json] "cds": { "requires": { "db": { "[development]": { "data": [ "db/data", "test/data" ] }, "[production]": { "data": [ "db/data" ] } } } } ``` ```yaml [.cdsrc.yaml] cds: requires: db: '[development]': { data: [ db/data, test/data ] } '[production]': { data: [ db/data ] } ``` ::: Use `cds env` to check which configuration is active in your current profile, for example: ```shell cds env requires.db.data --profile development ``` ## Next to `.cds` Files > Source: /docs/guides/databases/initial-data#next-to-cds-files In addition to the [configured](#custom-folders) folders for initial data and test data (that is, `db/data` and `test/data` by default), you can place `.csv` files into `data` folders anywhere next to your CDS model source files. For example: ```zsh myproject/ ├── db/ │ ├── data/*.csv │ └── schema.cds ├── srv/ │ ├── data/*.csv │ └── some-service.cds ... ``` The runtime automatically loads all `db/data/*.csv` and `srv/data/*.csv` files located in a `data` folder next to `.cds` model sources. This is especially useful for remote service definitions imported with `cds import` (by default into `srv/external/`), allowing you to serve mock data for external services. ## From Reuse Packages > Source: /docs/guides/databases/initial-data#from-reuse-packages The [_in-the-neighborhood-of-models_](#next-to-cds-files) technique enables reuse packages that provide initial data. An example of such a content reuse package is [*@capire/common*](https://github.com/capire/common), which showcases how to provide ISO reuse data for `Countries`, `Currencies`, and `Languages` code lists, as defined in [`@sap/cds/common`](../../cds/common). It essentially consists of these files as content: ```zsh @capire/common ├── cds-plugin.js ├── data │ ├── sap.common-Countries.csv │ ├── sap.common-Countries_texts.csv │ ├── sap.common-Currencies.csv │ ├── sap.common-Currencies_texts.csv │ ├── sap.common-Languages.csv │ └── sap.common-Languages_texts.csv ├── index.cds └── package.json ``` Install such packages via `npm` or `mvn`. Their content will reside under `node_modules` folders or Maven `target` folders. When packages include reuse models (like `index.cds` above), the runtime automatically discovers and loads CSV files from the adjacent `data` folder into the consuming application's database. > [!tip] How to 'enable' reuse packages > > The `@capire/common` package uses the `cds-plugin.js` technique, to provide plug-and-play configuration [in its `package.json`](https://github.com/capire/common/blob/bc92ae532d902022bf6c1dff70cce892fd0fd350/package.json#L9-L16) like that: > > ```json > "cds": { > "requires": { > "@capire/common/data": { > "model": "@capire/common" > } > } > } > ``` > > Without such plugin configuration, add a `using from '@capire/common';` clause to one of your CDS files. ## Plug-and-Play Reuse > Source: /docs/guides/databases/initial-data#plug-and-play-reuse You can consume reuse packages, such as the `@capire/common` one described above, in any CAP project by simply installing them via `npm` or `mvn`, as shown in the [`@capire/bookstore`](https://github.com/capire/bookstore) sample project: ```json [package.json] { "name": "@capire/bookstore", "version": "1.2.3", "dependencies": { "@capire/common": "^1.0.0", // [!code focus] "@sap/cds": "..." } } ``` When you run `cds watch`, the runtime automatically picks up the `@capire/common/index.cds` model file and all CSV files in its `data` folder, as shown in the following output: ```zsh [cds] - loaded model from 27 file(s): app/services.cds srv/mashup.cds node_modules/@capire/common/index.cds # [!code focus] ... ``` ```zsh [cds] - connect to db > sqlite { url: ':memory:' } > init from node_modules/@capire/common/data/sap.common-Languages_texts.csv # [!code focus] > init from node_modules/@capire/common/data/sap.common-Languages.csv # [!code focus] > init from node_modules/@capire/common/data/sap.common-Currencies_texts.csv # [!code focus] > init from node_modules/@capire/common/data/sap.common-Currencies.csv # [!code focus] > ... /> successfully deployed to in-memory database. ``` > [!tip] Installation is sufficient > The great thing with plug-and-play configuration is that a mere `npm install` in a consuming project suffices to have the `@capire/common/index.cds` file loaded together with the application’s models, and hence also all data from accompanying `.csv` files. # Using SAP HANA Cloud for Production > Source: /docs/guides/databases/hana [toc]:./ [SAP HANA Cloud](https://www.sap.com/products/technology-platform/hana.html) is supported as the CAP standard database and recommended for productive use with full support for schema evolution and multitenancy. > [!note] Supported SAP HANA versions and variants > CAP isn't validated with variants other than _SAP HANA Cloud_, like _SAP HANA Database as a Service_ or _SAP HANA (on premise)_. > > CAP's database services are validated against the latest maintained QRC version of SAP HANA Cloud. It's not guaranteed that outdated versions are fully functional with the latest database services. > > [See the official SAP HANA Cloud documentation for its maintenance strategy.](https://help.sap.com/docs/HANA_CLOUD_CN/1f64fe39189f4176bf659e737d62222a/6ced4d164e234b74aa9bea82435ce9a8.html){.learn-more} ## Setup & Configuration > Source: /docs/guides/databases/hana#setup--configuration To use SAP HANA Cloud for production, add a dependency to the _package.json_ for Node.js or to the _pom.xml_ for a CAP Java application: ::: code-group ```sh [Shell/Bash] npm add @cap-js/hana ``` ```xml [pom.xml] com.sap.cds cds-feature-hana runtime ``` ::: ::: details Using other SAP HANA drivers... Package `@cap-js/hana` uses the [`hdb`](https://www.npmjs.com/package/hdb) driver by default. You can override that by running [`npm add @sap/hana-client`](https://www.npmjs.com/package/@sap/hana-client), thereby adding it to your package dependencies, which then takes precedence over the default driver. ::: :::details In CAP Java ... The [modules](../../java/developing-applications/building#standard-modules) `cds-starter-cloudfoundry` and `cds-starter-k8s` include `cds-feature-hana`. The datasource for SAP HANA is then auto-configured based on available service bindings of type *service-manager* and *hana*. [Learn more about the configuration of an SAP HANA Cloud Database](../../java/cqn-services/persistence-services#sap-hana){ .learn-more} ::: ::: tip Prefer `cds add` ... as documented in the [deployment guide](../deploy/to-cf#1-sap-hana-database), which also does the equivalent of `npm add @cap-js/hana` but in addition cares for updating `mta.yaml` and other deployment resources. ::: ## Running `cds build` > Source: /docs/guides/databases/hana#running-cds-build Deployment to SAP HANA is done via the [SAP HANA Deployment Infrastructure (HDI)](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-developer-guide-for-cloud-foundry-multitarget-applications-sap-business-app-studio/sap-hdi-deployer?). Use `cds build` to generate all necessary deployable HDI artifacts. For example, run this in [capire/bookshop](https://github.com/capire/bookshop): ```sh cds build --for hana ``` Which should display this log output: ```log [cds] - done > wrote output to: gen/db/init.js gen/db/package.json gen/db/src/gen/.hdiconfig gen/db/src/gen/.hdinamespace gen/db/src/gen/AdminService.Authors.hdbview gen/db/src/gen/AdminService.Books.hdbview gen/db/src/gen/AdminService.Books_texts.hdbview gen/db/src/gen/AdminService.Currencies.hdbview gen/db/src/gen/AdminService.Currencies_texts.hdbview gen/db/src/gen/AdminService.Genres.hdbview gen/db/src/gen/AdminService.Genres_texts.hdbview gen/db/src/gen/CatalogService.Books.hdbview gen/db/src/gen/CatalogService.Books_texts.hdbview gen/db/src/gen/CatalogService.Currencies.hdbview gen/db/src/gen/CatalogService.Currencies_texts.hdbview gen/db/src/gen/CatalogService.Genres.hdbview gen/db/src/gen/CatalogService.Genres_texts.hdbview gen/db/src/gen/CatalogService.ListOfBooks.hdbview gen/db/src/gen/data/sap.capire.bookshop-Authors.csv gen/db/src/gen/data/sap.capire.bookshop-Authors.hdbtabledata gen/db/src/gen/data/sap.capire.bookshop-Books.csv gen/db/src/gen/data/sap.capire.bookshop-Books.hdbtabledata gen/db/src/gen/data/sap.capire.bookshop-Books.texts.csv gen/db/src/gen/data/sap.capire.bookshop-Books.texts.hdbtabledata gen/db/src/gen/data/sap.capire.bookshop-Genres.csv gen/db/src/gen/data/sap.capire.bookshop-Genres.hdbtabledata gen/db/src/gen/localized.AdminService.Authors.hdbview gen/db/src/gen/localized.AdminService.Books.hdbview gen/db/src/gen/localized.AdminService.Currencies.hdbview gen/db/src/gen/localized.AdminService.Genres.hdbview gen/db/src/gen/localized.CatalogService.Books.hdbview gen/db/src/gen/localized.CatalogService.Currencies.hdbview gen/db/src/gen/localized.CatalogService.Genres.hdbview gen/db/src/gen/localized.CatalogService.ListOfBooks.hdbview gen/db/src/gen/localized.sap.capire.bookshop.Authors.hdbview gen/db/src/gen/localized.sap.capire.bookshop.Books.hdbview gen/db/src/gen/localized.sap.capire.bookshop.Genres.hdbview gen/db/src/gen/localized.sap.common.Currencies.hdbview gen/db/src/gen/sap.capire.bookshop.Authors.hdbtable gen/db/src/gen/sap.capire.bookshop.Books.hdbtable gen/db/src/gen/sap.capire.bookshop.Books_author.hdbconstraint gen/db/src/gen/sap.capire.bookshop.Books_currency.hdbconstraint gen/db/src/gen/sap.capire.bookshop.Books_foo.hdbconstraint gen/db/src/gen/sap.capire.bookshop.Books_genre.hdbconstraint gen/db/src/gen/sap.capire.bookshop.Books_texts.hdbtable gen/db/src/gen/sap.capire.bookshop.Genres.hdbtable gen/db/src/gen/sap.capire.bookshop.Genres_parent.hdbconstraint gen/db/src/gen/sap.capire.bookshop.Genres_texts.hdbtable gen/db/src/gen/sap.common.Currencies.hdbtable gen/db/src/gen/sap.common.Currencies_texts.hdbtable ``` ### Generated HDI Artifacts > Source: /docs/guides/databases/hana#generated-hdi-artifacts As we see from the log output `cds build` generates these [deployment artifacts as expected by HDI](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-deployment-infrastructure-hdi-reference/sap-hdi-artifact-types-and-build-plug-ins-reference?), based on CDS models and .csv files provided in your projects: - `.hdbtable` files for entities - `.hdbview` files for views / projections - `.hdbconstraint` files for database constraints - `.hdbtabledata` files for CSV content - a few technical files required by HDI, such as [`.hdinamespace`](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-developer-guide-for-cloud-foundry-multitarget-applications-sap-business-app-studio/sap-hdi-name-space-configuration-syntax?version=2024_1_QRC&q=hdinamespace) and [`.hdiconfig`](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-developer-guide-for-cloud-foundry-multitarget-applications-sap-business-app-studio/sap-hdi-container-configuration-file?) ### Custom HDI Artifacts > Source: /docs/guides/databases/hana#custom-hdi-artifacts In addition to the generated HDI artifacts, you can add custom ones by adding according files to folder `db/src`. For example, let's add an index for Books titles... 1. Add a file `db/src/sap.capire.bookshop.Books.hdbindex` and fill it with this content: ::: code-group ```sql [db/src/sap.capire.bookshop.Books.hdbindex] INDEX sap_capire_bookshop_Books_title_index ON sap_capire_bookshop_Books (title) ``` ::: 2. Run cds build again → this time you should see this additional line in the log output: ```log [cds] - done > wrote output to: [...] gen/db/src/sap.capire.bookshop.Books.hdbindex // [!code focus] ``` > [!note] Use folder `db/src` here because `src` is HDI's default source folder. [Learn more about HDI Design-Time Resources and Build Plug-ins](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-developer-guide-for-cloud-foundry-multitarget-applications-sap-business-app-studio/hdi-design-time-resources-and-build-plug-ins?){.learn-more} ## Deploying to SAP HANA > Source: /docs/guides/databases/hana#deploying-to-sap-hana There are two ways to include SAP HANA in your setup: Use SAP HANA in a [hybrid mode](#cds-deploy-hana), meaning running your services locally and connecting to your database in the cloud, or running your [whole application](../deploy/index.md) on SAP Business Technology Platform. This is possible either in trial accounts or in productive accounts. To make the following configuration steps work, we assume that you've provisioned, set up, and started, for example, your SAP HANA Cloud instance in the [trial environment](https://cockpit.hanatrial.ondemand.com). If you need to prepare your SAP HANA first, see [How to Get an SAP HANA Cloud Instance for SAP Business Technology Platform, Cloud Foundry environment](../../get-started/get-help#get-hana) to learn about your options. ### Prepare for Production > Source: /docs/guides/databases/hana#prepare-for-production To prepare the project, execute: ```sh cds add hana --for hybrid ``` This configures deployment for SAP HANA. The configuration is added to a `[hybrid]` profile in your _package.json_. ::: tip The profile `hybrid` relates to [the hybrid testing](../../tools/cds-bind) scenario If you want to prepare your project for production and use the profile `production`, read the [Deploy to Cloud](../deploy/index.md) guide. ::: No further configuration is necessary for Node.js. For Java, see the [Use SAP HANA as the Database for a CAP Java Application](https://developers.sap.com/tutorials/cp-cap-java-hana-db.html#880cf07a-1788-4fda-b6dd-b5a6e5259625) tutorial for the rest of the configuration. ### Using `cds deploy` for Ad-Hoc Deployments > Source: /docs/guides/databases/hana#using-cds-deploy-for-ad-hoc-deployments `cds deploy` lets you deploy _just the database parts_ of the project to an SAP HANA instance. The server application (the Node.js or Java part) still runs locally and connects to the remote database instance, allowing for fast development roundtrips. Make sure that you're [logged in to Cloud Foundry](../deploy/to-cf#build-and-deploy) with the correct target, that is, org and space. Then in the project root folder, just execute: ```sh cds deploy --to hana ``` Or run it with a profile as follows: ```sh cds deploy --to hana --profile hybrid ``` Based on these profile settings, `cds deploy` executes `cds build` and also resolves additionally binding information. If a corresponding binding exists, its service name and service key are used. The development profile is used by default.
In more detail cds deploy does the following... * Compiles the CDS model to SAP HANA files (usually in _gen/db_, or _db/src/gen_) * Generates _[.hdbtabledata](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-deployment-infrastructure-hdi-reference/table-data-hdbtabledata)_ files for the CSV files in the project. If a _[.hdbtabledata](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-deployment-infrastructure-hdi-reference/table-data-hdbtabledata)_ file is already present next to the CSV files, no new file is generated. * Creates a Cloud Foundry service of type `hdi-shared`, which creates an HDI container. Also, you can explicitly specify the name like so: `cds deploy --to hana:`. * Starts `@sap/hdi-deploy` locally. If you need a tunnel to access the database, you can specify its address with `--tunnel-address `. * Stores the binding information with profile `hybrid` in the _.cdsrc-private.json_ file of your project. You can use a different profile with parameter `--for`. With this information, `cds watch`/`run` can fetch the SAP HANA credentials at runtime, so that the server can connect to it.
To connect to and run with your SAP HANA Cloud instance, use: ::: code-group ```sh [Node.js] cds watch --profile hybrid ``` ```sh [Java] mvn cds:watch ``` ::: [Learn more about hybrid testing using service bindings to Cloud services.](../../tools/cds-bind#run-with-service-bindings){.learn-more} [Learn more about the deployment using HDI.](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-developer-guide-for-cloud-foundry-multitarget-applications-sap-business-app-studio/sap-hdi-deployer?){.learn-more} [See the troubleshooting guide if you run into issues.](../../get-started/get-help#hana){.learn-more} #### Configuring `cds deploy` > Source: /docs/guides/databases/hana#configuring-cds-deploy When using `cds deploy --to hana`, you can specify the service name and logon information in several ways. In the default mode, the service name and service key either come from the environment variable `VCAP_SERVICES` or are defaulted from the project name, for example, `myproject-db` with `myproject-db-key`. Service instances and key either exist and will be used, or otherwise they're created. ##### `--to hana:myservice` > Source: /docs/guides/databases/hana#--to-hanamyservice This overwrites any information coming from environment variables. The service name `myservice` is used and the current Cloud Foundry client logon information is taken to connect to the system. ##### `--vcap-file someEnvFile.json` > Source: /docs/guides/databases/hana#--vcap-file-someenvfilejson This takes the logon information and the service name from the `someEnvFile.json` file and overwrite any environment variable that is already set. ##### `--to hana:myservice --vcap-file someEnvFile.json` > Source: /docs/guides/databases/hana#--to-hanamyservice---vcap-file-someenvfilejson This is equivalent to `cds deploy --to hana:myservice` and ignores information coming from `--vcap-file`. A warning is printed after deploying. ### Using `cf deploy` or `cf push` > Source: /docs/guides/databases/hana#using-cf-deploy-or-cf-push See the [Deploying to Cloud](../deploy/index.md) guide for information about how to deploy the complete application to SAP Business Technology Platform, including a dedicated deployer application for the SAP HANA database. ## Native SAP HANA Features > Source: /docs/guides/databases/hana#native-sap-hana-features The HANA Service provides dedicated support for native SAP HANA features as follows. ### Geospatial Functions > Source: /docs/guides/databases/hana#geospatial-functions CDS supports the special syntax for SAP HANA geospatial functions: ```cds entity Geo as select from Foo { geoColumn.ST_Area() as area : Decimal, new ST_Point(2.25, 3.41).ST_X() as x : Decimal }; ``` *Learn more in the [SAP HANA Spatial Reference](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-spatial-reference/accessing-and-manipulating-spatial-data?).*{.learn-more} ### Spatial Grid Generators > Source: /docs/guides/databases/hana#spatial-grid-generators SAP HANA Spatial has some built-in [grid generator table functions](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-spatial-reference/grid-generators?). To use them in a CDS model, first define corresponding facade entities in CDS. Example for function `ST_SquareGrid`: ```cds @cds.persistence.exists entity ST_SquareGrid(size: Double, geometry: hana.ST_GEOMETRY) { geom: hana.ST_GEOMETRY; i: Integer; j: Integer; } ``` Then the function can be called, parameters have to be passed by name: ```cds entity V as select from ST_SquareGrid(size: 1.0, geometry: ST_GeomFromWkt('Point(1.5 -2.5)')) { geom, i, j }; ``` ### Functions Without Arguments > Source: /docs/guides/databases/hana#functions-without-arguments SAP HANA allows to omit the parentheses for functions that don't expect arguments. For example: ```cds entity Books { key ID : UUID; } entity CatalogService.Books as select from Books { ID, current_timestamp }; ``` Some of which are well-known standard functions like `current_timestamp` in the previous example, which can be written without parentheses in CDS models. However, there are many that aren't known to the compiler, for example: - `current_connection` - `current_schema` - `current_transaction_isolation_level` - `current_utcdate` - `current_utctime` - `current_utctimestamp` - `sysuuid` To use these in CDS models, add parentheses `()`: ```cds entity Books { key ID : UUID; } entity CatalogService.Books as select from Books { ID, current_timestamp, sysuuid() as sysid // [!code focus] }; ``` [Learn more on SAP HANA functions.](https://help.sap.com/docs/SAP_HANA_PLATFORM/4fe29514fd584807ac9f2a04f6754767/20a61f29751910149f99f0300dd95cd9.html){.learn-more} ### Regex Functions > Source: /docs/guides/databases/hana#regex-functions CDS supports SAP HANA Regex functions (`locate_regexpr`, `occurrences_regexpr`, `replace_regexpr`, and `substring_regexpr`), and SAP HANA aggregate functions with an additional `order by` clause in the argument list. Example: ```sql locate_regexpr(pattern in name from 5) first_value(name order by price desc) ``` Restriction: `COLLATE` isn't supported. For other functions, where the syntax isn't supported by the compiler (for example, `xmltable(...)`), a native _.hdbview_ can be used. See [Using Native SAP HANA Artifacts](./hana-native) for more details. ## HDI Schema Evolution > Source: /docs/guides/databases/hana#hdi-schema-evolution CAP supports database schema updates by detecting changes to the CDS model when executing the CDS build. If the underlying database offers built-in schema migration techniques, compatible changes can be applied to the database without any data loss or the need for additional migration logic. Incompatible changes like deletions are also detected, but require manual resolution, as they would lead to data loss. | Change | Detected Automatically | Applied Automatically | | ---------------------------------- | :--------------------: | :-------------------: | | Adding fields | **Yes** | **Yes** | | Deleting fields | **Yes** | No | | Renaming fields | n/a 1 | No | | Changing datatype of fields | **Yes** | No | | Changing type parameters | **Yes** | **Yes** | | Changing associations/compositions | **Yes** | No 2 | | Renaming associations/compositions | n/a 1 | No | | Renaming entities | n/a | No | > 1 Rename field or association operations aren't detected as such. Instead, corresponding ADD and DROP statements are rendered requiring manual resolution activities. > > 2 Changing targets may lead to renamed foreign keys. Possibly hard to detect data integrity issues due to non-matching foreign key values if target key names remain the same (for example "ID"). ::: warning No support for incompatible schema changes Currently there's no framework support for incompatible schema changes that require scripted data migration steps (like changing field constraints NULL > NOT NULL). However, `cds build` detects those changes and renders them as non-executable statements, requesting the user to take manual resolution steps. We recommend avoiding those changes in productive environments. ::: ### Schema Evolution and Multitenancy/Extensibility > Source: /docs/guides/databases/hana#schema-evolution-and-multitenancyextensibility There's full support for schema evolution when the _cds-mtxs_ library is used for multitenancy handling. It ensures that all schema changes during base-model upgrades are rolled out to the tenant databases. ::: warning No tenant extensibility Tenant-specific extensibility using the _cds-mtxs_ library isn't supported yet. You can't activate extensions on entities annotated with `@cds.persistence.journal`. ::: ### Schema Updates with SAP HANA > Source: /docs/guides/databases/hana#schema-updates-with-sap-hana All schema updates in SAP HANA are applied using SAP HANA Deployment Infrastructure (HDI) design-time artifacts, which are auto-generated during CDS build execution. Schema updates using _.hdbtable_ deployments are a challenge for tables with large data volume. Schema changes with _.hdbtable_ are applied using temporary table generation to preserve the data. As this could lead to long deployment times, the support for _.hdbmigrationtable_ artifact generation has been added. The [Migration Table artifact type](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-deployment-infrastructure-hdi-reference/migration-tables-hdbmigrationtable?version=2024_1_QRC) uses explicit versioning and migration tasks. Modifications of the database table are explicitly specified in the design-time file and carried out on the database table exactly as specified. This saves the cost of an internal table-copy operation. When a new version of an already existing table is deployed, HDI performs the migration steps that haven't been applied. #### Deploy Artifact Transitions as Supported by HDI > Source: /docs/guides/databases/hana#deploy-artifact-transitions-as-supported-by-hdi | Target format /
Current format | hdbcds

| hdbtable

| hdbmigrationtable

| |------------------------------------|:--------------:|:----------------:|:-------------------------:| | hdbcds | | yes | n/a | | hdbtable | n/a | | yes | | hdbmigrationtable | n/a | Yes | | > [!note] No direct transition from .hdbcds to .hdbmigrationtable > Direct migration from _.hdbcds_ to _.hdbmigrationtable_ isn't supported by HDI. A deployment using _.hdbtable_ is required up front. > > During the transition from _.hdbtable_ to _.hdbmigrationtable_ you have to deploy version=1 of the _.hdbmigrationtable_ artifact, which must not include any migration steps. > > [Learn more in the **Enhance Project Configuration for SAP HANA Cloud** section.](#configure-hana){.learn-more} HDI supports the _hdbcds → hdbtable → hdbmigrationtable_ migration flow without data loss. Even going back from _.hdbmigrationtable_ to _.hdbtable_ is possible. Keep in mind that you lose the migration history in this case. For all transitions you want to execute in HDI, you need to specify an undeploy allowlist as described in [HDI Delta Deployment and Undeploy Allow List](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-developer-guide-for-cloud-foundry-multitarget-applications-sap-business-app-studio/hdi-delta-deployment-and-undeploy-allow-list?) in the SAP HANA documentation. :::tip Moving From _.hdbcds_ To _.hdbtable_ There a migration guide providing you step-by-step instructions for making the switch. [Learn more about Moving From _.hdbcds_ To _.hdbtable_](../../cds/compiler/hdbcds-to-hdbtable){.learn-more} ::: #### Enabling hdbmigrationtable Generation for Selected Entities During `cds build` > Source: /docs/guides/databases/hana#enabling-hdbmigrationtable-generation-for-selected-entities-during-cds-build If you're migrating your already deployed scenario to _.hdbmigrationtable_ deployment, consider the remarks in [Deploy Artifact Transitions as Supported by HDI](#deploy-artifact-transitions). By default, all entities are still compiled to _.hdbtable_ and you only selectively choose the entities for which you want to build _.hdbmigrationtable_ by annotating them with `@cds.persistence.journal`. Example: ```cds namespace data.model; @cds.persistence.journal entity LargeBook { key id : Integer; title : String(100); content : LargeString; } ``` `cds build` generates _.hdbmigrationtable_ source files for annotated entities as well as a _last-dev/csn.json_ source file representing the CDS model state of the last build. > [!note] .hdbmigrationtable files are source files. > These source files **must** be checked into the version control system. Subsequent model changes are applied automatically as respective migration versions including the required schema update statements to accomplish the new target state. There are cases where you have to resolve or refactor the generated statements, like for reducing field lengths. As they can't be executed without data loss (for example, `String(100)` -> `String(50)`), the required migration steps are only added as comments for you to process explicitly. Example: ```txt >>>> Manual resolution required - DROP statements causing data loss are disabled >>>> by default. >>>> You may either: >>>> uncomment statements to allow incompatible changes, or >>>> refactor statements, e.g. replace DROP/ADD by single RENAME statement >>>> After manual resolution delete all lines starting with >>>>> -- ALTER TABLE my_bookshop_Books DROP (title); -- ALTER TABLE my_bookshop_Books ADD (title NVARCHAR(50)); ``` Changing the type of a field causes `cds build` to create a corresponding ALTER TABLE statement. [Data type conversion rules](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/data-type-conversion?) are applied by the SAP HANA database as part of the deployment step. This may cause the deployment to fail if the column contents can't be converted to the new format. Examples: 1. Changing the type of a field from String to Integer may cause tenant updates to fail if existing content can't be converted. 2. Changing the type of a field from Decimal to Integer can succeed, but decimal places are truncated. Conversion fails if the content exceeds the maximum Integer length. We recommend keeping _.hdbtable_ deployment for entities where you expect low data volume. Every _.hdbmigrationtable_ artifact becomes part of your versioned source code, creating a new migration version on every model change/build cycle. In turn, each such migration can require manual resolution. You can switch large-volume tables to _.hdbmigrationtable_ at any time, keeping in mind that the existing _.hdbtable_ design-time artifact needs to be undeployed. When choosing to use _.hdbmigrationtable_ for an entity with [localized elements](../uis/localized-data#localized-data) or [compositions of aspects](../../cds/cdl#managed-compositions), the generated `.texts` and composition child entities are automatically handled via _.hdbmigrationtable_, too. If this is not desired, annotate these generated entities with `@cds.persistence.journal: false`. `cds build` performs rudimentary checks on generated _.hdmigrationtable_ files: - `cds build` fails if inconsistencies are encountered between the generated _.hdbmigrationtable_ files and the _last-dev/csn.json_ model state. For example, the last migration version not matching the table version is such an inconsistency. - `cds build` fails if manual resolution comments starting with `>>>>>` exist in one of the generated _.hdbmigrationtable_ files. This ensures that manual resolution is performed before deployment. > [!tip] Stick to hdbtable during (early) development. > Sticking to _.hdbtable_ for the actual application development phase avoids lots of initial migration versions that would need to be applied to the database schema. ### Native Database Clauses > Source: /docs/guides/databases/hana#native-database-clauses Not all clauses supported by SQL can directly be written in CDL syntax. To use native database clauses also in a CAP CDS model, you can provide arbitrary SQL snippets with the annotations [`@sql.prepend` and `@sql.append`](cdl-to-ddl#sqlprepend--append). In this section, we're focusing on schema evolution specific details. Schema evolution requires that any changes are applied by corresponding ALTER statements. See [ALTER TABLE statement reference](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/alter-table-statement-data-definition?version=2024_1_QRC) for more information. A new migration version is generated whenever an `@sql.append` or `@sql.prepend` annotation is added, changed, or removed. ALTER statements define the individual changes that create the final database schema. This schema has to match the schema defined by the TABLE statement in the _.hdbmigrationtable_ artifact. Please note that the compiler doesn't evaluate or process these SQL snippets. Any snippet is taken as is and inserted into the TABLE statement and the corresponding ALTER statement. The deployment fails in case of syntax errors. CDS Model: ```cds @cds.persistence.journal @sql.append: 'PERSISTENT MEMORY ON' entity E { ..., @sql.append: 'FUZZY SEARCH INDEX ON' text: String(100); } ``` Result in hdbmigrationtable file: ```sql == version=2 COLUMN TABLE E ( ..., text NVARCHAR(100) FUZZY SEARCH INDEX ON ) PERSISTENT MEMORY ON == migration=2 ALTER TABLE E PERSISTENT MEMORY ON; ALTER TABLE E ALTER (text NVARCHAR(100) FUZZY SEARCH INDEX ON); ``` It's important to understand that during deployment new migration versions will be applied on the existing database schema. If the resulting schema doesn't match the schema as defined by the TABLE statement, deployment fails and any changes are rolled-back. In consequence, when removing or replacing an existing `@sql.append` annotation, the original ALTER statements need to be undone. As the required statements can't automatically be determined, manual resolution is required. The `cds build` generates comments starting with `>>>>` in order to provide some guidance and enforce manual resolution. Generated file with comments: ```txt == migration=3 >>>>> Manual resolution required - insert ALTER statement(s) as described below. >>>>> After manual resolution delete all lines starting with >>>>> >>>>> Insert ALTER statement for: annotation @sql.append of artifact E has been removed (previous value: "PERSISTENT MEMORY ON") >>>>> Insert ALTER statement for: annotation @sql.append of element E:text has been removed (previous value: "FUZZY SEARCH INDEX ON") ``` Manually resolved file: ```sql == migration=3 ALTER TABLE E PERSISTENT MEMORY DEFAULT; ALTER TABLE E ALTER (text NVARCHAR(100) FUZZY SEARCH INDEX OFF); ``` Appending text to an existing annotation is possible without manual resolution. A valid ALTER statement will be generated in this case. For example, appending the `NOT NULL` column constraint to an existing `FUZZY SEARCH INDEX ON` annotation generates the following statement: ```sql ALTER TABLE E ALTER (text NVARCHAR(100) FUZZY SEARCH INDEX ON NOT NULL); ``` ::: warning You can use `@sql.append` to partition your table initially, but you can't subsequently change the partitions using schema evolution techniques as altering partitions isn't supported yet. ::: ### Advanced Options > Source: /docs/guides/databases/hana#advanced-options The following CDS configuration options are supported to manage _.hdbmigrationtable_ generation. ::: warning This hasn't been finalized yet. ::: ```js { "hana" : { "journal": { "enable-drop": false, "change-mode": "alter" // "drop" }, // ... } } ``` The `"enable-drop"` option determines whether incompatible model changes are rendered as is (`true`) or manual resolution is required (`false`). The default value is `false`. The `change-mode` option determines whether `ALTER TABLE ... ALTER` (`"alter"`) or `ALTER TABLE ... DROP` (`"drop"`) statements are rendered for data type related changes. To ensure that any kind of model change can be successfully deployed to the database, you can switch the `"change-mode"` to `"drop"`, keeping in mind that any existing data will be deleted for the corresponding column. See [hdbmigrationtable Generation](#enabling-hdbmigrationtable-generation) for more details. The default value is `"alter"`. ## Caveats > Source: /docs/guides/databases/hana#caveats ### CSV Data Gets Overridden > Source: /docs/guides/databases/hana#csv-data-gets-overridden HDI deploys CSV data as _.hdbtabledata_ and assumes exclusive ownership of the data. It's **overridden with the next application deployment**; hence: > [!danger] Do not let application users modify initial data > Only use CSV files for configuration data that can't be changed by application users but only through new deployments of your application. There must be no application endpoint through which it can be modified. > > Yet, if you need to support initial data with user changes, you can use the `include_filter` option that _[.hdbtabledata](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-deployment-infrastructure-hdi-reference/table-data-hdbtabledata)_ offers. ### Undeploying Artifacts > Source: /docs/guides/databases/hana#undeploying-artifacts As documented in the [HDI Deployer docs](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-developer-guide-for-cloud-foundry-multitarget-applications-sap-business-app-studio/hdi-delta-deployment-and-undeploy-allow-list?), an HDI deployment by default never deletes artifacts. So, if you remove an entity or CSV files, the respective tables, and content remain in the database. By default, `cds add hana` creates an `undeploy.json` like this: ::: code-group ```json [db/undeploy.json] [ "src/gen/**/*.hdbview", "src/gen/**/*.hdbindex", "src/gen/**/*.hdbconstraint", "src/gen/**/*_drafts.hdbtable", "src/gen/**/*.hdbcalculationview" ] ``` ::: If you need to remove deployed CSV files, also add this entry: ::: code-group ```json [db/undeploy.json] [ [...] "src/gen/**/*.hdbtabledata" ] ``` ::: *See this [troubleshooting](../../get-started/get-help#hana-csv) entry for more information.*{.learn-more} > [!danger] Never undeploy hdbtable files > Never have entries for _tables_ in _undeploy.json_, as this leads to data loss for all tables that are not part of the deploy set. > ::: code-group > ```json [db/undeploy.json] > "src/...hdbtable" // [!code error] data loss! > ``` > ::: ### System Limits > Source: /docs/guides/databases/hana#system-limits All limitations for the SAP HANA Cloud database can be found in the [SAP Help Portal](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/system-limitations). ### Native Associations > Source: /docs/guides/databases/hana#native-associations In previous CAP releases, CDS associations were by default reflected in SAP HANA database tables and views by _Native HANA Associations_ (HANA SQL clause `WITH ASSOCIATIONS`). But the presence of such native associations significantly increases (re-)deploy times: They need to be validated in the HDI deployment, and they can introduce indirect dependencies between other objects, which can trigger other unnecessary revalidations or even unnecessary drop/create of indexes. As CAP doesn't need these native associations, by default no native HANA associations are created anymore starting with CAP 9. In the unlikely case that you need native HANA associations because you explicitly use them in other native HANA objects or in custom code, you can switch them back on with cds.sql.native_hana_associations = true. ::: warning Initial full table migration Be aware that the first deployment after this **configuration change may take longer**. For each entity with associations, the respective database object is touched (DROP/CREATE for views, full table migration via shadow table and data copy for tables). ::: # Using Native SAP HANA Artifacts > Source: /docs/guides/databases/hana-native Create or use an existing database object (table, view, table function, calculation view) and make use of it in your CDS model, for instance for exposing it in an OData service. ## Introduction > Source: /docs/guides/databases/hana-native#introduction To leverage CAP in combination with native SAP HANA artifacts, it is important to understand HDI containers. An HDI container is a database schema controlled by HDI. This has implications on the handling and you need to know [the way HDI works](https://help.sap.com/docs/HANA_CLOUD_DATABASE/c2cc2e43458d4abda6788049c58143dc/4077972509f5437c85d6a03e01509417.html), to fully understand the approach described in this advanced guide. ## Adding Native SAP HANA Objects > Source: /docs/guides/databases/hana-native#adding-native-sap-hana-objects You create a new database object or there's an existing object (table, view, table function, calculation view) and you want to use it in your CDS model, for instance for exposing it in an OData service. ### Add Existing SAP HANA Objects from Other HDI Containers > Source: /docs/guides/databases/hana-native#add-existing-sap-hana-objects-from-other-hdi-containers To access database artifacts residing in other HDI containers, you need the permissions granted for that container and you need to introduce them into your own container using synonyms. This synonym establishes a link between both needed HDI containers. The _.hdbsynonym_ file you create for this, [is a native SAP HANA object in your project](#create-native-sap-hana-objects). ::: tip Synonyms can be used to rename database objects. ::: ### Create Native SAP HANA Objects > Source: /docs/guides/databases/hana-native#create-native-sap-hana-objects To create SAP HANA native tables or use SAP HANA native features, use the folder _db/src_ at design time and build, for example, your _.hdbtable_ or _.hdbsynonym_ files. This folder stays untouched during the `cds` build and the content is copied over to the _gen/db/src_ folder during the build. Use this process for all tables and features that can't be modeled using _CDS_. ::: tip You can use "mapping views" (_.hdbview_) to rename database objects and column names, when you make the object known to CDS. ::: ## Make the Object Known to CDS > Source: /docs/guides/databases/hana-native#make-the-object-known-to-cds * Define an entity that matches the signature of the newly designed or already existing database object. * Add the annotation `@cds.persistence.exists` to tell CDS that this object already exists on the database and must not be generated. This entity then serves as a facade for the database object and can be used in the model like a regular entity. In the following, we refer to this entity as __facade entity__. Steps to match the signature of a database object in a facade entity: * Choose a name for the facade entity, which is identical to the resulting database name of the existing database object. * Choose the names of the facade entity's elements, which are identical to the resulting database names of the existing database object's column names. * After applying the CDS-to-DB type mapping, check that the types of the facade entity's elements match the types of the database object's columns. * For a view, table function, or calculation view with parameters, check that the parameter names and types match, too. Functions with table-like input parameters are not supported. > Note: If a field of that entity is defined as `not null` and you want to disable its runtime check, you can add `@assert.notNull: false`. This is important if you want to use, for example [SAP HANA history tables](https://help.sap.com/docs/SAP_HANA_PLATFORM/6b94445c94ae495c83a19646e7c3fd56/d0b2c5142a19405fb912f71782cd0a84.html). As a result, the database name is defined by the name of the entity or its elements, after applying the SQL name mapping. We can distinguish two types of names - __plain__ and __quoted__. |Naming Mode |Description | |---------|---------| |Plain (default) | When using __plain names__, the database name is converted to uppercase and all dots are replaced by underscores. This conversion is the default behavior. If a database name is all in uppercase and is a regular SQL identifier, then it's possible to construct a corresponding name in the CDS model that matches this name. | |Quoted | If the existing database name also contains lower-case characters or characters that can't occur in regular SQL identifiers, it's not possible to choose a name in the CDS model that matches this name. Let's call such a database name "quoted", as the only possibility to create such a name is to quote it in the CREATE statement. In this case, it's necessary to introduce an additional database object (a synonym or a view) on top of the existing database object and construct the facade entity for this newly introduced mapping object. [Find here troubleshooting related to SAP HANA.](../../get-started/get-help#hana){.learn-more} ### Tables and Views Without Parameters > Source: /docs/guides/databases/hana-native#tables-and-views-without-parameters As the approach described here only depends on the signature of the existing database object, it applies to: * Database tables * SQL views without parameters * Table functions without parameters * Calculation views without parameters For simplicity, we only talk about tables in this section, but everything applies to the other mentioned objects in the same way. #### Plain Names > Source: /docs/guides/databases/hana-native#plain-names Assume that all names in the existing database table are plain names. Define the facade entity in such a way that the resulting database names match those of the table. ::: code-group ```sql [existing-table-without-params-plain.hdbtable] COLUMN TABLE DATA_MODEL_BOOKSHOP_BOOKS ( ID integer, THE_TITLE nvarchar(100), primary key ( ID ) ) ``` ::: ::: code-group ```cds [facade-entity-without-params.cds] namespace data.model; context Bookshop { @cds.persistence.exists entity Books { key id : Integer; the_title : String(100); } } ``` ::: #### Quoted Table Name, Plain Column Names > Source: /docs/guides/databases/hana-native#quoted-table-name-plain-column-names Assume that the name of the existing table contains lower case characters, ".", and "::". It isn't possible to define a CDS name that is mapped to this name. So, we introduce a synonym that maps `data.model::Bookshop.Books` to `DATA_MODEL_BOOKSHOP_BOOKS`. ::: code-group ```sql [existing-table-quoted-table-name.hdbtable] COLUMN TABLE "data.model::Bookshop.Books" ( ID integer, THE_TITLE nvarchar(100), primary key ( ID ) ) ``` ::: ::: code-group ```json [existing-table-quoted-table-name.hdbsynonym] { "DATA_MODEL_BOOKSHOP_BOOKS" : { "target": { "object" : "data.model::Bookshop.Books" } } } ``` ::: Now, define a facade entity in CDS. The table columns have plain names and thus need no mapping. ::: code-group ```cds [facade-entity-without-params.cds] namespace data.model; context Bookshop { @cds.persistence.exists entity Books { key id : Integer; the_title : String(100); } } ``` ::: #### Quoted Table Name, Quoted Column Names > Source: /docs/guides/databases/hana-native#quoted-table-name-quoted-column-names Assume that the table name and column names are quoted names. There, a synonym isn't sufficient, because it can't map the column names. In this case, put a "mapping" view on top of the existing table that maps all the names to plain ones: ::: code-group ```sql [existing-table-quoted-names.hdbtable] COLUMN TABLE "data.model::Bookshop.Books" ( "id" integer, "the.title" nvarchar(100), primary key ( "id" ) ) ``` ::: ::: code-group ```sql [mapping-view-quoted-names-2.hdbview] VIEW DATA_MODEL_BOOKSHOP_BOOKS AS SELECT "id" AS ID, "the.title" AS THE_TITLE FROM "data.model::Bookshop.Books" ``` ::: Now, define a facade entity in CDS. ::: code-group ```cds [facade-entity-without-params.cds] namespace data.model; context Bookshop { @cds.persistence.exists entity Books { key id : Integer; the_title : String(100); } } ``` ::: ### Views with Parameters > Source: /docs/guides/databases/hana-native#views-with-parameters Assume that the existing database object is an SQL view with parameters, a table function with parameters, or a calculation view with parameters. Parameters can be added to the CDS model starting with `@sap/cds 3.4.1` / `@sap/cds-compiler 1.7.1`. #### Plain Names > Source: /docs/guides/databases/hana-native#plain-names-1 Assume that all names are plain. You can directly define the facade entity with parameters. ::: code-group ```sql [existing-view-with-params-plain.hdbview] VIEW DATA_MODEL_BOOKSHOP_BOOKINFO (in AUTHOR nvarchar(100)) AS SELECT ID, 'The book: ' || THE_TITLE || ' and the author ' || :AUTHOR AS BOOK_AUTHOR_INFO FROM DATA_MODEL_BOOKSHOP_BOOKS; ``` ::: ::: code-group ```cds [facade-entity-with-params.cds] namespace data.model; context Bookshop { @cds.persistence.exists entity Bookinfo (AUTHOR : String(100)) { key id : Integer; book_author_info : String(100); } } ``` ::: #### Quoted Names > Source: /docs/guides/databases/hana-native#quoted-names Assume the SQL view with parameters has quoted names. Put a mapping view on top of the existing one that maps all the names, except the parameter names, to plain ones. > Note: Names of parameters in SQL views and in table functions can't be quoted. ::: code-group ```sql [existing-view-quoted-names.hdbview] VIEW "data.model.Bookshop.Bookinfo" (in AUTHOR nvarchar(10)) AS SELECT ID AS "id", 'The book: ' || THE_TITLE || ' and the author ' || :AUTHOR AS "book.author.info" FROM DATA_MODEL_BOOKSHOP_BOOKS; ``` ::: ::: code-group ```sql [mapping-view-quoted-names.hdbview] VIEW DATA_MODEL_BOOKSHOP_BOOKINFO (in AUTHOR nvarchar(10)) AS SELECT "id" AS ID, "book.author.info" AS BOOK_AUTHOR_INFO FROM "data.model.Bookshop.Bookinfo"(AUTHOR => :AUTHOR) ``` ::: Now, define a facade entity with parameters. ::: code-group ```cds [facade-entity-with-params.cds] namespace data.model; context Bookshop { @cds.persistence.exists entity Bookinfo (AUTHOR : String(100)) { key id : Integer; book_author_info : String(100); } } ``` ::: In contrast to SQL views or table functions, the names of calculation view parameters can be quoted, too. The following is the definition of a calculation view `data.model.bookshop.CalcBooks` with elements `id`, `the.title`, and `calculated`, and in addition there's a parameter `Param`. ::: code-group ```xml [existing-calc-view-quoted.hdbcalculationview] DATA_MODEL_BOOKSHOP_BOOKS "id"+$$Param$$ ``` ::: For this calculation view, put a mapping view on top that maps all the names, except the parameter names, to plain ones. Note the weird syntax for passing parameters to a calculation view. ::: code-group ```sql [mapping-calc-view-quoted.hdbview] VIEW DATA_MODEL_BOOKSHOP_CALCBOOKS (in PARAM nvarchar(10)) AS SELECT "id" AS ID, "the.title" AS THE_TITLE, "calculated" AS CALCULATED FROM "data.model.bookshop.CalcBooks"(placeholder."$$Param$$" => :PARAM) ``` ::: Define the facade entity in CDS. ::: code-group ```cds [facade-entity-calc-view.cds] namespace data.model; context Bookshop { @cds.persistence.exists entity CalcBooks (PARAM : String(10)) { key id : Integer; the_title : String(100); calculated : Integer; } } ``` ::: #### Default Values of View Parameters > Source: /docs/guides/databases/hana-native#default-values-of-view-parameters To set the default value of a parameter in a view, use the `default` keyword. This value will be evaluated at runtime and used as a fallback value in case if no other value was provided by the client. ::: code-group ```cds [facade-entity-with-def-val-params.cds] namespace data.model; context Bookshop { @cds.persistence.exists entity Bookinfo (AUTHOR : String(100) default 'Unknown') { key id : Integer; book_author_info : String(100); } } ``` ::: ### Calculated Views and User-Defined Functions > Source: /docs/guides/databases/hana-native#calculated-views-and-user-defined-functions Calculated view parameters need to be rendered as `PLACEHOLDER."$$$$"`. User-defined function parameters are rendered as ordinary parameters. If a user-defined function has an empty or no parameter list, it still must be called with an empty parameter list '()'. This is the trigger for SAP HANA to execute the function instead of searching for a regular view, which eventually doesn't exist. Calculated views without parameters are called with no parameter list. To produce the correct SQL view statement, use `@cds.persistence.exists` and one of the following annotations at the facade entity as a hint for the code generation: + `@cds.persistence.udf` to specify that the facade entity represents a user-defined function + `@cds.persistence.calcview` to specify that the facade entity represents a calculation view Have a look at the following CDS sample and the generated view: ::: code-group ```cds [facade-entity-existing-calc-view.cds] @cds.persistence.exists @cds.persistence.calcview entity AddressCalcView (USERID: Integer) { key id: Integer; }; view WeUseAddressCalcView as select from AddressCalcView(USERID: 4711); @cds.persistence.exists @cds.persistence.udf entity AddressUDF { key id: Integer; }; view WeUseAddressUDF as select from AddressUDF; ``` ::: ::: code-group ```sql [mapping-calc-view.hdbview] VIEW WeUseAddressCalcView AS SELECT AddressCalcView_0.id FROM AddressCalcView(PLACEHOLDER."$$USERID$$" => 4711) AS AddressCalcView_0; VIEW WeUseAddressUDF AS SELECT AddressUDF_0.id FROM AddressUDF() AS AddressUDF_0; ``` ::: ## Associations and Compositions > Source: /docs/guides/databases/hana-native#associations-and-compositions This section describes how associations and compositions to artifacts with `@cds.persistence.skip/exists` are treated during the generation of the database model with `forHana`. ##### `@cds.persistence.skip` > Source: /docs/guides/databases/hana-native#cdspersistenceskip Denotes that the artifact isn't available in the database but eventually implemented by custom code. No association can point to a nonexisting database object and no query can be executed against such a nonexisting source. As `@cds.persistence.skip` is *propagated*, projections also don't become part of the database schema. All association definitions to nonexisting database objects are removed from the defining database objects and all usages of such associations produce an error. The following sample would throw an error: ```cds entity Orders { key id: Integer; orderName: String; items: Composition of Items on $self = items.parent; }; @cds.persistence.skip entity Items { key id: Integer; name: String; parent: Association to Orders; }; view OrdersView as select from Orders { id, orderName, items.name }; view ItemSelection as select from Items; ``` The view `Orders` will be rejected with an error message as `items.name` isn't resolvable to a valid JOIN expression and view `ItemSelection` is effectively annotated with `@cds.persistence.skip`. ##### `@cds.persistence.exists` > Source: /docs/guides/databases/hana-native#cdspersistenceexists Denotes that there already exists a native database object, which should be used during runtime. The CDS artifact merely acts as a *proxy* artifact, representing the signature of the native database artifact in the CDS model. Since the database object really exists, it's *indirectly* possible to associate these native database objects. Associations to artifacts annotated with `@cds.persistence.exists` are removed from the defining database objects and all usages of such associations produce an error, as the following example shows: ```cds entity Orders { key id: Integer; orderName: String; items: Composition of Item on $self = items.parent; }; @cds.persistence.exists entity Items { key id: Integer; name: String; parent: Association to Orders; }; view OrdersView as select from Orders { id, orderName, items.name }; entity ItemSelection as projection on Items; ``` However, as the annotation `@cds.persistence.exists` __isn't__ propagated, this allows using such proxy artifacts as query sources and to be valid association targets. The example can now be rewritten to: ```cds entity Orders { key id: Integer; orderName: String; items: Composition of ItemSelection on $self = items.parent; // <--- compose ItemSelection instead Items }; @cds.persistence.exists entity Items { key id: Integer; name: String; parent: Association to Orders; }; view OrdersView as select from Orders { id, orderName, items.name // <--- is transformable into a valid JOIN expression }; entity ItemSelection as projection on Items; ``` By composing `ItemSelection` instead of `Items`, it's possible to use this composition in `Orders`. ## SAP HANA-Specific Data Types > Source: /docs/guides/databases/hana-native#sap-hana-specific-data-types The following SAP HANA-specific data types are primarily intended for porting existing SAP HANA CDS models into the CAP domain if the old SAP HANA types must be preserved in the existing database tables. If you're starting from scratch, these types shouldn't be used but only the [predefined CDS types](../../cds/types). | CDS Type | Arguments / Remarks | SQL | OData (V4) | | --- | --- |--- | --- | | `hana.SMALLINT` | | _SMALLINT_ | _Edm.Int16_ | | `hana.TINYINT` | | _TINYINT_ | _Edm.Byte_ | | `hana.SMALLDECIMAL` | | _SMALLDECIMAL_ | _Edm.Decimal_ | | `hana.REAL` | | _REAL_ | _Edm.Single_ | | `hana.CHAR` | ( `length` ) | _CHAR_ | _Edm.String_ | | `hana.NCHAR` | ( `length` ) | _NCHAR_ | _Edm.String_ | | `hana.VARCHAR`| ( `length` ) | _VARCHAR_ | _Edm.String_ | | `hana.CLOB` | | _CLOB_ | _Edm.String_ | | `hana.BINARY` | ( `length` ) | _BINARY_ | _Edm.Binary_ | | `hana.ST_POINT`|( `srid` ) (1)| `ST_POINT` | _Edm.GeometryPoint_ | | `hana.ST_GEOMETRY`|( `srid` ) (1) | `ST_GEOMETRY` | _Edm.Geometry_ | > (1) Optional, default: 0
### Mapping UUIDs to SQL > Source: /docs/guides/databases/hana-native#mapping-uuids-to-sql By default, `cds` maps UUIDs to `nvarchar(36)` in SQL databases. The length is to accommodate representations with hyphens as well as any other representations. The choice of a string type over a raw/binary type is in line with this recommendation from SAP HANA: > If the client side needs to work with the UUID, VARBINARY would lead to CAST operations or binary array handling at the client side. Here **NVARCHAR would be the data type of choice** to avoid handling binary arrays on the client side. ### Example Index > Source: /docs/guides/databases/hana-native#example-index | What | Database Object | Mapping Object | Facade Entity | | --- | --- | --- | --- | | | | | data.model.Bookshop... | | table | DATA_MODEL_BOOKSHOP_BOOKS | n/a | Books | | table | data.model::Bookshop.Books | DATA_MODEL_BOOKSHOP_BOOKS | Books | | view with param | DATA_MODEL_BOOKSHOP_BOOKINFO | n/a | Bookinfo | | view with param | data.model.Bookshop.Bookinfo | DATA_MODEL_BOOKSHOP_BOOKINFO | Bookinfo | | cv with param | data.model.bookshop.CalcBooks | DATA_MODEL_BOOKSHOP_CALCBOOKS | CalcBooks | # Using SQLite for Development > Source: /docs/guides/databases/sqlite CAP provides extensive support for [SQLite](https://www.sqlite.org/index.html), which allows projects to speed up development by magnitudes at minimized costs. We strongly recommend using this option as much as possible during development and testing. ## Setup for SQLite > Source: /docs/guides/databases/sqlite#setup-for-sqlite ### Using `cds add sqlite` > Source: /docs/guides/databases/sqlite#using-cds-add-sqlite Run this to set up SQLite in your CAP project: ```sh cds add sqlite ``` Essentially this is doing the following: - For CAP Node.js projects, it adds the `@cap-js/sqlite` package. - For CAP Java projects, it adds a Maven dependency for the SQLite JDBC driver. - If MTA deployment is used, it adds an SQLite service to the `mta.yaml` file. > [!tip] > Using `cds add sqlite` is the recommended way to set up SQLite in your CAP projects, as it covers both CAP Node.js and CAP Java projects in one step, as well as requisite additions for MTA deployment, if applicable. ### Manual Setup for Node.js > Source: /docs/guides/databases/sqlite#manual-setup-for-nodejs Run this if you want to manually set up SQLite for CAP Node.js projects, instead of using `cds add sqlite`: ```sh npm add @cap-js/sqlite -D ``` > [!tip] Plug & Play Configuration > The `@cap-js/sqlite` package uses the `cds-plugin` technique to auto-configure your application for using an in-memory SQLite database by default for the development profile. No further configuration is necessary. ### Manual Setup for Java > Source: /docs/guides/databases/sqlite#manual-setup-for-java To use SQLite, add a Maven dependency to the SQLite JDBC driver: ::: code-group ```xml [srv/pom.xml] org.xerial sqlite-jdbc runtime ``` ::: ### Using Maven Archetype > Source: /docs/guides/databases/sqlite#using-maven-archetype Alternatively, when a new CAP Java project is created with the [Maven Archetype](../../java/developing-applications/building#the-maven-archetype), you can specify the in-memory database to be used. Use the option `-DinMemoryDatabase=sqlite` to create a project that uses SQLite as in-memory database. > [!note] Learn More... > - about [supported databases in CAP Java and their configuration.](../../java/cqn-services/persistence-services#database-support) > - about [features and limitations of using CAP Java with SQlite.](../../java/cqn-services/persistence-services#sqlite) ## Using In-Memory Databases > Source: /docs/guides/databases/sqlite#using-in-memory-databases ::: tip Using in-memory databases is the most recommended option for local inner-loop development as well as for test pipelines. They are fast, require no setup, are automatically reset on each application start, and minimize resource usage and costs. ::: ### In CAP Node.js Projects > Source: /docs/guides/databases/sqlite#in-cap-nodejs-projects Node.js projects don't require any build steps to prepare SQLite usage. Instead all necessary artifacts are created on-the-fly when running `cds watch`, indicated by the log output like this: ```log [cds] - connect to db > sqlite { url: ':memory:' } > init from db/data/sap.capire.bookshop-Authors.csv > init from db/data/sap.capire.bookshop-Books.csv > init from db/data/sap.capire.bookshop-Books.texts.csv > init from db/data/sap.capire.bookshop-Genres.csv /> successfully deployed to in-memory database. ``` ### In CAP Java Projects > Source: /docs/guides/databases/sqlite#in-cap-java-projects To use SQLite in Java projects, you need to ensure that the database schema is created before the application starts. You can do this by generating the initial _schema.sql_ file during the build process as follows. Configure the build to create an initial _schema.sql_ file for SQLite using `cds deploy --to sqlite --dry --out srv/src/main/resources/schema.sql`. ::: code-group ```xml [srv/pom.xml] schema.sql cds deploy --to sqlite --dry --out srv/src/main/resources/schema.sql ``` ::: [Learn more about creating an initial database schema](../../java/cqn-services/persistence-services#initial-database-schema-1){.learn-more} With that in place, you need to configure the application to use an in-memory SQLite database. Configure the DB connection in the non-productive `default` profile: ::: code-group ```yaml [srv/src/main/resources/application.yaml] --- spring: config.activate.on-profile: default sql: init: mode: always datasource: url: "jdbc:sqlite:file::memory:?cache=shared" driver-class-name: org.sqlite.JDBC hikari: maximum-pool-size: 1 max-lifetime: 0 ``` ::: [Learn more about configuring an in-memory SQLite database for CAP Java.](../../java/cqn-services/persistence-services#in-memory-storage){.learn-more} ## Using Persistent Databases > Source: /docs/guides/databases/sqlite#using-persistent-databases You can also use persistent SQLite databases. In this case, the database is deployed and initialized by `cds deploy` and not by the CAP runtimes. Follow these steps to use a file-based SQLite database: 1. Specify a database filename in your `db` configuration as follows: ::: code-group ```json [package.json] { "cds": { "requires": { "db": { "kind": "sqlite", "credentials": { "url": "db.sqlite" } // [!code focus] } }}} ``` ::: 2. Run `cds deploy`: ```sh cds deploy ``` 3. Finally – for CAP Java projects only – configure the DB connection - ideally in a dedicated `sqlite` profile: ::: code-group ```yaml [srv/src/main/resources/application.yaml] --- spring: config.activate.on-profile: sqlite datasource: url: "jdbc:sqlite:db.sqlite" driver-class-name: org.sqlite.JDBC hikari: maximum-pool-size: 1 ``` ::: [Learn more about configuring a file-based SQLite database for CAP Java](../../java/cqn-services/persistence-services#file-based-storage){.learn-more} ::: tip Redeploy on changes Remember to always redeploy your database whenever you change your models or your data. Just run `cds deploy` again to do so. ::: ## Using SQLite in Production? > Source: /docs/guides/databases/sqlite#using-sqlite-in-production As stated in the beginning, SQLite is mostly intended to speed up development, but is not fit for production. This is not because of limited warranties or lack of support, but rather because of suitability. A major criterion is this: cloud applications are usually served by server clusters, in which each server is connected to a shared database. SQLite could only be used in such setups with the persistent database file accessed through a network file system. This is rarely available and results in slow performance. Hence, an enterprise client-server database is a more fitting choice for these scenarios. ::: warning SQLite only has limited support for concurrent database access due to its very coarse lock granularity. This makes it badly suited for applications with high concurrency. ::: Having said this, there can indeed be scenarios where SQLite might also be used in production, not as the primary database of a business application, but for edge cases, such as using SQLite as in-memory caches. → [Learn more on the _sqlite.org_ website](https://www.sqlite.org/whentouse.html). For such cases, configure the `sqlite:memory` preset, which runs an in-memory SQLite database in all profiles, including production: ::: code-group ```json [package.json] { "cds": { "requires": { "db": "sqlite:memory" // [!code focus] }}} ``` ::: # Using H2 for Development in CAP Java > Source: /docs/guides/databases/h2 > [!NOTE] > H2 is supported for CAP Java only. For local development and testing, CAP Java supports the [H2](https://www.h2database.com/) database, which can be configured to run in-memory. Learn more about the [features and limitations of using CAP with H2.](../../java/cqn-services/persistence-services#h2) There are various options of how to configure the [H2 database for local development and testing in CAP Java.](../../java/developing-applications/testing#setup--configuration) # Using PostgreSQL > Source: /docs/guides/databases/postgres This guide focuses on the new PostgreSQL Service provided through *[@cap-js/postgres](https://www.npmjs.com/package/@cap-js/postgres)*, which is based on the same new database services architecture as the new [SQLite Service](./sqlite). CAP Java 3 is tested on [PostgreSQL](https://www.postgresql.org/) 16 and most CAP features are supported on PostgreSQL. *Learn about migrating from the former `cds-pg` in the [Migration](#migration-from-cds-pg-in-nodejs) chapter.*{.learn-more} [Learn more about features and limitations of using CAP Java with PostgreSQL.](../../java/cqn-services/persistence-services#postgresql){.learn-more} ## Setup & Configuration > Source: /docs/guides/databases/postgres#setup--configuration To run CAP Java on PostgreSQL, add a Maven dependency to the PostgreSQL feature in `srv/pom.xml`: ```xml com.sap.cds cds-feature-postgresql runtime ``` For CAP Node.js projects, and for CAP Java in order to use the CDS tooling with PostgreSQL, you also need to install the module `@cap-js/postgres`: ```sh npm add @cap-js/postgres ``` After that, you can use the `cds deploy` command to [deploy](#using-cds-deploy) to a PostgreSQL database or to [create a DDL script](#using-liquibase-in-cap-java) for PostgreSQL. ### Auto-Wired Configuration in Node.js > Source: /docs/guides/databases/postgres#auto-wired-configuration-in-nodejs The `@cap-js/postgres` package uses `cds-plugin` technique to auto-configure your application and use a PostgreSQL database for production. You can inspect the effective configuration using `cds env`: ```sh cds env requires.db --for production ``` Output: ```js { impl: '@cap-js/postgres', dialect: 'postgres', kind: 'postgres' } ``` ## Provisioning a DB Instance > Source: /docs/guides/databases/postgres#provisioning-a-db-instance To connect to a PostgreSQL offering from the cloud provider in Production, leverage the [PostgreSQL on SAP BTP, hyperscaler option](https://discovery-center.cloud.sap/serviceCatalog/postgresql-hyperscaler-option). For local development and testing convenience, you can run PostgreSQL in a [docker container](#using-docker). ### CAP Java on SAP BTP > Source: /docs/guides/databases/postgres#cap-java-on-sap-btp To consume a PostgreSQL instance from a CAP Java application running on SAP BTP, consider the following: - Only the Java buildpack `java_buildpack` provided by the Cloud Foundry community allows to consume a PostgreSQL service from a CAP Java application. - By default, the `java_buildpack` initializes a PostgreSQL datasource with the Java CFEnv library. However, to work properly with CAP, the PostgreSQL datasource must be created by the CAP Java runtime and not by the buildpack. You need to disable the [datasource initialization by the buildback](https://docs.cloudfoundry.org/buildpacks/java/configuring-service-connections.html) using `CFENV_SERVICE__ENABLED: false` at your CAP Java service module. The following example shows these configuration settings applied to a CAP Java service: ::: code-group ```yaml [mta.yaml] modules: - name: bookshop-pg-srv type: java path: srv parameters: buildpack: java_buildpack properties: SPRING_PROFILES_ACTIVE: cloud JBP_CONFIG_COMPONENTS: '{jres: ["JavaBuildpack::Jre::SapMachineJRE"]}' JBP_CONFIG_SAP_MACHINE_JRE: '{ jre: { version: "17.+" } }' CFENV_SERVICE_BOOKSHOP-PG-DB_ENABLED: false ``` ::: > `BOOKSHOP-PG-DB` is the real PostgreSQL service instance name in this example. ### Using Docker > Source: /docs/guides/databases/postgres#using-docker You can use Docker to run a PostgreSQL database locally as follows: 1. Install and run [Docker Desktop](https://www.docker.com) 2. Create the following file in your project root directory: ::: code-group ```yaml [pg.yml] services: db: image: postgres:alpine environment: { POSTGRES_PASSWORD: postgres } ports: [ '5432:5432' ] restart: always ``` ::: 3. Create and run the docker container: ```sh docker-compose -f pg.yml up -d ``` ::: tip Testcontainer in CAP Java using Spring Boot With the introduction of [Testcontainers support](https://spring.io/blog/2023/06/23/improved-testcontainers-support-in-spring-boot-3-1) in Spring Boot 3.1, you can create PostgreSQL containers on the fly for local development or testing purposes. ::: ## Service Bindings > Source: /docs/guides/databases/postgres#service-bindings You need a service binding to connect to the PostgreSQL database. In the cloud, use given techniques to bind a cloud-based instance of PostgreSQL to your application. ### Configure Connection Data in CAP Java > Source: /docs/guides/databases/postgres#configure-connection-data-in-cap-java If a PostgreSQL service binding exists, the corresponding `DataSource` is auto-configured. You can also explicitly [configure the connection data](../../java/cqn-services/persistence-services#postgres-connection) of your PostgreSQL database in the _application.yaml_ file. If you run the PostgreSQL database in a [docker container](#using-docker) your connection data might look like this: ::: code-group ```yaml [srv/src/main/resources/application.yaml] spring: config.activate.on-profile: postgres-docker datasource: url: jdbc:postgresql://localhost:5432/postgres username: postgres password: postgres driver-class-name: org.postgresql.Driver ``` ::: To start the application with the new profile `postgres-docker`, the `spring-boot-maven-plugin` can be used: `mvn spring-boot:run -Dspring-boot.run.profiles=postgres-docker`. [Learn more about the configuration of a PostgreSQL database.](../../java/cqn-services/persistence-services#postgresql-1){ .learn-more} ### Service Bindings for CDS Tooling in CAP Java > Source: /docs/guides/databases/postgres#service-bindings-for-cds-tooling-in-cap-java #### Using Defaults with `[pg]` Profile > Source: /docs/guides/databases/postgres#using-defaults-with-pg-profile `@cds-js/postgres` comes with a set of default credentials under the profile `[pg]` that matches the defaults used in the [docker setup](#using-docker). So, if you stick to these defaults you can skip to deploying your database with: ```sh cds deploy --profile pg ``` #### In Your Private `.cdsrc-private.json` > Source: /docs/guides/databases/postgres#in-your-private-cdsrc-privatejson If you don't use the default credentials and want to use just `cds deploy`, you need to configure the service bindings (connection data) for the CDS tooling. Add the connection data to your private `.cdsrc-private.json`: ```json { "requires": { "db": { "kind": "postgres", "credentials": { "host": "localhost", "port": 5432, "user": "postgres", "password": "postgres", "database": "postgres" } } } } ``` ### Configure Service Bindings in Node.js > Source: /docs/guides/databases/postgres#configure-service-bindings-in-nodejs For local development provide the credentials using a suitable [`cds env`](../../node.js/cds-env) technique, like one of the following. #### Using Defaults with `[pg]` Profile > Source: /docs/guides/databases/postgres#using-defaults-with-pg-profile-1 The `@cds-js/postgres` comes with default credentials under profile `[pg]` that match the defaults used in the [docker setup](#using-docker). So, in case you stick to these defaults you can skip the next sections and just go ahead, deploy your database: ```sh cds deploy --profile pg ``` Run your application: ```sh cds watch --profile pg ``` Learn more about that in the [Deployment](#deployment) chapter below.{.learn-more} #### In Your private `~/.cdsrc.json` > Source: /docs/guides/databases/postgres#in-your-private-cdsrcjson Add it to your private `~/.cdsrc.json` if you want to use these credentials on your local machine only: ::: code-group ```json [~/.cdsrc.json] { "requires": { "db": { "[pg]": { "kind": "postgres", "credentials": { "host": "localhost", "port": 5432, "user": "postgres", "password": "postgres", "database": "postgres" } } } } } ``` ::: #### In Project `.env` Files > Source: /docs/guides/databases/postgres#in-project-env-files Alternatively, use a `.env` file in your project's root folder if you want to share the same credentials with your team: ::: code-group ```properties [.env] cds.requires.db.[pg].kind = postgres cds.requires.db.[pg].credentials.host = localhost cds.requires.db.[pg].credentials.port = 5432 cds.requires.db.[pg].credentials.user = postgres cds.requires.db.[pg].credentials.password = postgres cds.requires.db.[pg].credentials.database = postgres ``` ::: ::: tip Using Profiles The previous configuration examples use the [`cds.env` profile](../../node.js/cds-env#profiles) `[pg]` to allow selectively testing with PostgreSQL databases from the command line as follows: ```sh cds watch --profile pg ``` The profile name can be freely chosen, of course. ::: ## Deployment > Source: /docs/guides/databases/postgres#deployment ### Using `cds deploy` > Source: /docs/guides/databases/postgres#using-cds-deploy Deploy your database as usual with that: ```sh cds deploy ``` Or with that if you used profile `[pg]` as introduced in the setup chapter above: ```sh cds deploy --profile pg ``` ### With a Deployer App > Source: /docs/guides/databases/postgres#with-a-deployer-app When deploying to Cloud Foundry, this can be accomplished by providing a simple deployer app. Similar to SAP HANA deployer apps, it is auto-generated for PostgreSQL-enabled projects by running ```sh cds build --production ``` ::: details What `cds build` does… 1. Compiles the model into _gen/pg/db/csn.json_. 2. Copies required `.csv` files into _gen/pg/db/data_. 3. Adds a _gen/pg/package.json_ with this content: ```json { "dependencies": { "@sap/cds": "^10", "@cap-js/postgres": "^3" }, "scripts": { "start": "cds-deploy" } } ``` > **Note the dash in `cds-deploy`**, which is required as we don't use `@cds-dk` for deployment and runtime, so the `cds` CLI executable isn't available. ::: ### Add PostgreSQL Deployment Configuration > Source: /docs/guides/databases/postgres#add-postgresql-deployment-configuration ```sh cds add postgres ``` ::: details See what this does… 1. Adds `@cap-js/postgres` dependency to your _package.json_ `dependencies`. 2. Sets up deployment descriptors such as _mta.yaml_ to use a PostgreSQL instance deployer application. 3. Wires up the PostgreSQL service to your deployer app and CAP backend. ::: ### Deploy > Source: /docs/guides/databases/postgres#deploy You can package and deploy that application, for example using [MTA-based deployment](../deploy/to-cf#add-mta-yaml). ## Automatic Schema Evolution > Source: /docs/guides/databases/postgres#automatic-schema-evolution When redeploying after you changed your CDS models, like adding fields, automatic schema evolution is applied. Whenever you run `cds deploy` (or `cds-deploy`) it executes these steps: 1. Read a CSN of a former deployment from table `cds_model`. 2. Calculate the **delta** to current model. 3. Generate and run DDL statements with: - `CREATE TABLE` statements for new entities - `CREATE VIEW` statements for new views - `ALTER TABLE` statements for entities with new or changed elements - `DROP & CREATE VIEW` statements for views affected by changed entities 4. Fill in initial data from provided _.csv_ files using `UPSERT` commands. 5. Store a CSN representation of the current model in `cds_model`. > You can disable automatic schema evolution, if necessary, by setting cds.requires.db.schema_evolution = false. ::: danger No manual altering Manually altering the database will most likely break automatic schema evolution! ::: ### Limitations > Source: /docs/guides/databases/postgres#limitations Automatic schema evolution only allows changes without potential data loss. #### Allowed > Source: /docs/guides/databases/postgres#allowed - Adding entities and elements - Increasing the length of Strings - Increasing the size of Integers #### Disallowed > Source: /docs/guides/databases/postgres#disallowed - Removing entities or elements - Changes to primary keys - All other type changes For example the following type changes are allowed: ```cds entity Foo { anInteger : Int64; // from former: Int32 aString : String(22); // from former: String(11) } ``` ::: tip If you need to apply such disallowed changes during development, just drop and re-create your database, for example by killing it in docker and re-create it using the `docker-compose` command, [see Using Docker](#using-docker). ::: ### Dry-Run Offline > Source: /docs/guides/databases/postgres#dry-run-offline You can use `cds deploy` with option `--dry` to simulate and inspect how things work. 1. Capture your current model in a CSN file: ```sh cds deploy --dry --model-only --out cds-model.csn ``` 2. Change your models, for example in *[capire/bookshop/db/schema.cds](https://github.com/capire/bookshop/blob/main/db/schema.cds)*: ```cds entity Books { ... title : localized String(222); //> increase length from 111 to 222 foo : Association to Foo; //> add a new relationship bar : String; //> add a new element } entity Foo { key ID: UUID } //> add a new entity ``` 3. Generate delta DDL statements: ```sh cds deploy --dry --delta-from cds-model.csn --out delta.sql ``` 4. Inspect the generated SQL statements, which should look like this: ::: code-group ```sql [delta.sql] -- Drop Affected Views DROP VIEW localized_CatalogService_ListOfBooks; DROP VIEW localized_CatalogService_Books; DROP VIEW localized_AdminService_Books; DROP VIEW CatalogService_ListOfBooks; DROP VIEW localized_sap_capire_bookshop_Books; DROP VIEW CatalogService_Books_texts; DROP VIEW AdminService_Books_texts; DROP VIEW CatalogService_Books; DROP VIEW AdminService_Books; -- Alter Tables for New or Altered Columns ALTER TABLE sap_capire_bookshop_Books ALTER title TYPE VARCHAR(222); ALTER TABLE sap_capire_bookshop_Books_texts ALTER title TYPE VARCHAR(222); ALTER TABLE sap_capire_bookshop_Books ADD foo_ID VARCHAR(36); ALTER TABLE sap_capire_bookshop_Books ADD bar VARCHAR(255); -- Create New Tables CREATE TABLE sap_capire_bookshop_Foo ( ID VARCHAR(36) NOT NULL, PRIMARY KEY(ID) ); -- Re-Create Affected Views CREATE VIEW AdminService_Books AS SELECT ... FROM sap_capire_bookshop_Books AS Books_0; CREATE VIEW CatalogService_Books AS SELECT ... FROM sap_capire_bookshop_Books AS Books_0 LEFT JOIN sap_capire_bookshop_Authors AS author_1 O ... ; CREATE VIEW AdminService_Books_texts AS SELECT ... FROM sap_capire_bookshop_Books_texts AS texts_0; CREATE VIEW CatalogService_Books_texts AS SELECT ... FROM sap_capire_bookshop_Books_texts AS texts_0; CREATE VIEW localized_sap_capire_bookshop_Books AS SELECT ... FROM sap_capire_bookshop_Books AS L_0 LEFT JOIN sap_capire_bookshop_Books_texts AS localized_1 ON localized_1.ID = L_0.ID AND localized_1.locale = session_context( '$user.locale' ); CREATE VIEW CatalogService_ListOfBooks AS SELECT ... FROM CatalogService_Books AS Books_0; CREATE VIEW localized_AdminService_Books AS SELECT ... FROM localized_sap_capire_bookshop_Books AS Books_0; CREATE VIEW localized_CatalogService_Books AS SELECT ... FROM localized_sap_capire_bookshop_Books AS Books_0 LEFT JOIN localized_sap_capire_bookshop_Authors AS author_1 O ... ; CREATE VIEW localized_CatalogService_ListOfBooks AS SELECT ... FROM localized_CatalogService_Books AS Books_0; ``` ::: > **Note:** If you use SQLite, ALTER TYPE commands are not necessary and so, are not supported, as SQLite is essentially typeless. ### Generate Scripts > Source: /docs/guides/databases/postgres#generate-scripts You can use `cds deploy` with option `--script` to generate a script as a starting point for a manual migration. The effect of `--script` essentially is the same as for `--dry`, but it also allows changes that could lead to data loss and therefore are not supported in the automatic schema migration (see [Limitations](#limitations)). For generating such a script, perform the same steps as in section [Dry-Run Offline](#dry-run-offline) above, but replace the command in step 3 by ```sh cds deploy --script --delta-from cds-model.csn --out delta_script.sql ``` If your model change includes changes that could lead to data loss, there will be a warning and a respective comment is added to the dangerous statements in the resulting script. For example, deleting an element or reducing the length of an element would look like this: ::: code-group ```sql [delta_script.sql] ... -- [WARNING] this statement is lossy ALTER TABLE sap_capire_bookshop_Books DROP price; -- [WARNING] this statement could be lossy: length reduction of element "title" ALTER TABLE sap_capire_bookshop_Books ALTER title TYPE VARCHAR(11); ... ``` ::: :::warning Always check and, if necessary, adapt the generated script before you apply it to your database! ::: ## Using Liquibase in CAP Java > Source: /docs/guides/databases/postgres#using-liquibase-in-cap-java In CAP Java projects you can also use [Liquibase](https://www.liquibase.org/) to control when, where, and how database changes are deployed. Liquibase lets you define database changes [in an SQL file](https://docs.liquibase.com/change-types/sql-file.html), use `cds deploy` to quickly generate DDL scripts which can be used by Liquibase. Add a Maven dependency to Liquibase in `srv/pom.xml`: ```xml org.liquibase liquibase-core runtime ``` ::: tip Liquibase license change Please be aware that Liquibase [changed it's license to Functional Source License (FSL)](https://www.liquibase.com/blog/liquibase-community-for-the-future-fsl) with release 5.0. You need to check if this license is compatible with your application. ::: Once `liquibase-core` is on the classpath, [Spring runs database migrations](https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-initialization.migration-tool.liquibase) automatically on application startup and before your tests run. ### ① Initial Schema Version > Source: /docs/guides/databases/postgres#①-initial-schema-version Once you're ready to release an initial version of your database schema, you can create a DDL file that defines the initial database schema. Create a `db/changelog` subfolder under `srv/src/main/resources`, place the Liquibase _change log_ file as well as the DDL scripts for the schema versions here. The change log is defined by the [db/changelog/db.changelog-master.yml](https://docs.liquibase.com/concepts/changelogs/home.html) file: ```yml databaseChangeLog: - changeSet: id: 1 author: me changes: - sqlFile: dbms: postgresql path: db/changelog/v1/model.sql ``` Use `cds deploy` to create the _v1/model.sql_ file: ```sh cds deploy --profile pg --dry --out srv/src/main/resources/db/changelog/v1/model.sql ``` Finally, store the CSN file, which corresponds to this schema version: ```sh cds deploy --model-only --dry --out srv/src/main/resources/db/changelog/v1/model.csn ``` The CSN file is needed as an input to compute the delta DDL script for the next change set. If you start your application with `mvn spring-boot:run` Liquibase initializes the database schema to version `v1`, unless it has already been initialized. ::: warning Don't change the _model.sql_ after it has been deployed by Liquibase as the [checksum](https://docs.liquibase.com/concepts/changelogs/changeset-checksums.html) of the file is validated. These files should be checked into your version control system. Follow step ② to make changes. ::: ### ② Schema Evolution > Source: /docs/guides/databases/postgres#②-schema-evolution If changes of the CDS model require changes on the database, you can create a new change set that captures the necessary changes. Use `cds deploy` to compute the delta DDL script based on the previous model versions (_v1/model.csn_) and the current model. Write the diff into a _v2/delta.sql_ file: ```sh cds deploy --profile pg --dry --delta-from srv/src/main/resources/db/changelog/v1/model.csn --out \ srv/src/main/resources/db/changelog/v2/model.sql ``` Next, add a corresponding change set in the _changelog/db.changelog-master.yml_ file: ```yml databaseChangeLog: - changeSet: id: 1 author: me changes: - sqlFile: dbms: postgresql path: db/changelog/v1/model.sql - changeSet: id: 2 author: me changes: - sqlFile: dbms: postgresql path: db/changelog/v2/model.sql ``` Finally, store the CSN file, which corresponds to this schema version: ```sh cds deploy --model-only --dry --out srv/src/main/resources/db/changelog/v2/model.csn ``` If you now start the application, Liquibase executes all change sets, which haven't yet been deployed to the database. For further schema versions, repeat step ②. ::: info Only compatible changes A delta DDL script is only produced for changes without potential data loss. If the changes in the model could lead to data loss, an error is raised. ::: ## Migration from cds-pg in Node.js > Source: /docs/guides/databases/postgres#migration-from-cds-pg-in-nodejs Thanks to CAP's database-agnostic cds.ql API, we're confident that the new PostgreSQL service comes without breaking changes. ### `cds deploy --model-only` > Source: /docs/guides/databases/postgres#cds-deploy---model-only Not a breaking change, but definitely required to migrate former `cds-pg` databases, is to prepare it for schema evolution. To do so run `cds deploy` once with the `--model-only` flag: ```sh cds deploy --model-only ``` This will...: - Create the `cds_model` table in your database. - Fill it with the current model obtained through `cds compile '*'`. ::: warning IMPORTANT: Your `.cds` models are expected to reflect the deployed state of your database. ::: ### With Deployer App > Source: /docs/guides/databases/postgres#with-deployer-app When you have a SaaS application, upgrade all your tenants using the [deployer app](#with-deployer-app) with CLI option `--model-only` added to the start script command of your *package.json*. After having done that, don't forget to remove the `--model-only` option from the start script, to activate actual schema evolution. ## MTX Support > Source: /docs/guides/databases/postgres#mtx-support ::: warning [Multitenancy](../multitenancy/index.md) and [extensibility](../extensibility/index.md) aren't yet supported on PostgreSQL. ::: # Schema Evolution > Source: /docs/guides/databases/schema-evolution Schema evolution is the capability of a database to adapt its schema (tables, columns, indexes, constraints, etc.) to changes in the data model over time, without losing existing data. CAP provides built-in support for schema evolution across the supported databases, allowing developers to modify their CDS models and have the underlying database schema updated accordingly. {.abstract} ## Drop-Create in Development > Source: /docs/guides/databases/schema-evolution#drop-create-in-development During development, schema evolution is typically handled using a "drop-create" strategy, where you drop and recreate the existing databases or schemas based on the current CDS model. This approach is simple and effective, and most suitable for development phases, as it: - It allows you to quickly iterate on your data models. - It makes incompatible changes the standard, such as adding, removing, or renaming entities and fields. You can see this in action when you run `cds deploy`, which generates the necessary SQL statements to drop existing tables and recreate them or new ones according to the current CDS definitions: ```shell cds deploy --dry ``` ```sql [=> output] DROP TABLE IF EXISTS sap_capire_bookshop_Authors; DROP TABLE IF EXISTS sap_capire_bookshop_Books; DROP TABLE IF EXISTS sap_capire_bookshop_Genres; ... CREATE TABLE sap_capire_bookshop_Authors ...; CREATE TABLE sap_capire_bookshop_Books ...; CREATE TABLE sap_capire_bookshop_Genres ...; ... ``` This is also what happens automatically when running `cds watch` during development. In addition to dropping and recreating tables in-place, you can and should also drop and recreate the entire database or schema, depending on the database system in use. This ensures a clean state that fully reflects the current CDS model. ## Schema Evolution by CAP > Source: /docs/guides/databases/schema-evolution#schema-evolution-by-cap In production environments, you can't use a drop-create strategy, as it would result in data loss. CAP provides mechanisms to handle schema evolution in a more controlled manner, by generating migration scripts that you can review and apply to the database. Let's simulate the workflow with the [@capire/bookshop](https://github.com/capire/bookshop) example. 1. Capture the current state of the database schema: ```shell cds deploy --dry --model-only -o former.csn ``` 2. Make changes to your models. For example, edit `db/schema.cds` like this: ::: code-group ```cds [db/schema.cds] entity Books { ... title : localized String(300); //> increase length to 300 foo : Association to Foo; //> add a new relationship // [!code ++] bar : String; //> add a new element // [!code ++] } entity Foo { key ID: UUID } //> add a new entity // [!code ++] ``` ::: 3. Generate a migration script based on the differences between the former and the current model: ```sh cds deploy --script --delta-from former.csn -o migration.sql ``` 4. Inspect the generated SQL statements, which should look like this: ::: code-group ```sql:line-numbers {13,14} [delta.sql] -- Drop Affected Views DROP VIEW localized_CatalogService_ListOfBooks; DROP VIEW localized_CatalogService_Books; DROP VIEW localized_AdminService_Books; DROP VIEW CatalogService_ListOfBooks; DROP VIEW localized_sap_capire_bookshop_Books; DROP VIEW CatalogService_Books_texts; DROP VIEW AdminService_Books_texts; DROP VIEW CatalogService_Books; DROP VIEW AdminService_Books; -- Alter Tables for New or Altered Columns ALTER TABLE sap_capire_bookshop_Books ALTER title TYPE VARCHAR(300); ALTER TABLE sap_capire_bookshop_Books_texts ALTER title TYPE VARCHAR(300); ALTER TABLE sap_capire_bookshop_Books ADD foo_ID VARCHAR(36); ALTER TABLE sap_capire_bookshop_Books ADD bar VARCHAR(255); -- Create New Tables CREATE TABLE sap_capire_bookshop_Foo ( ID VARCHAR(36) NOT NULL, PRIMARY KEY(ID) ); -- Re-Create Affected Views CREATE VIEW AdminService_Books AS SELECT ... FROM sap_capire_bookshop_Books AS Books_0; CREATE VIEW CatalogService_Books AS SELECT ... FROM sap_capire_bookshop_Books AS Books_0 LEFT JOIN sap_capire_bookshop_Authors AS author_1 O ... ; CREATE VIEW AdminService_Books_texts AS SELECT ... FROM sap_capire_bookshop_Books_texts AS texts_0; CREATE VIEW CatalogService_Books_texts AS SELECT ... FROM sap_capire_bookshop_Books_texts AS texts_0; CREATE VIEW localized_sap_capire_bookshop_Books AS SELECT ... FROM sap_capire_bookshop_Books AS L_0 LEFT JOIN sap_capire_bookshop_Books_texts AS localized_1 ON localized_1.ID = L_0.ID AND localized_1.locale = session_context( '$user.locale' ); CREATE VIEW CatalogService_ListOfBooks AS SELECT ... FROM CatalogService_Books AS Books_0; CREATE VIEW localized_AdminService_Books AS SELECT ... FROM localized_sap_capire_bookshop_Books AS Books_0; CREATE VIEW localized_CatalogService_Books AS SELECT ... FROM localized_sap_capire_bookshop_Books AS Books_0 LEFT JOIN localized_sap_capire_bookshop_Authors AS author_1 O ... ; CREATE VIEW localized_CatalogService_ListOfBooks AS SELECT ... FROM localized_CatalogService_Books AS Books_0; ``` ::: > [!note] > If you use SQLite, `ALTER ... TYPE` commands are not necessary and so, are not supported, as SQLite is essentially typeless. That means, statements for changing the type or length of a column will not show up in migration scripts for SQLite (lines 13,14 above). ### Disallowed Changes > Source: /docs/guides/databases/schema-evolution#disallowed-changes Some changes to the CDS model are considered disallowed in the context of schema evolution, as they could lead to data loss or inconsistencies. The following list shows examples of such changes: - Renaming entities or fields (instead, add new ones and migrate data) - Changing data types in incompatible ways (for example, from String to Integer) - Removing entities or fields (instead, consider deprecating them first) - Reducing the length of strings or binary fields - Reducing the precision of numeric fields When `cds deploy --script` detects such disallowed changes during the generation of migration scripts, it prints a warning and adds corresponding comments to the generated SQL script, which you can then review and address manually. For example, if you rename the `descr` field to `details` like this: ::: code-group ```cds [db/schema.cds] entity Books { ... descr : localized String(2000); //> rename former `descr` ... // [!code --] details : localized String(2000); //> ... to `details` // [!code ++] } entity Foo { key ID: UUID } //> add a new entity // [!code ++] ``` ::: ... `cds deploy --script` would print warnings like this: ```js [WARNING] db/schema.cds:4:8: Dropping elements leads to data loss (in entity:“sap.capire.bookshop.Books”/element:“descr”) [WARNING] db/schema.cds:4:24: Dropping elements leads to data loss (in entity:“sap.capire.bookshop.Books.texts”/element:“descr”) [WARNING] Found potentially lossy changes - check generated SQL statements ``` And the generated SQL script would contain comments like these: ::: code-group ```sql [delta.sql] -- [WARNING] this statement is lossy ALTER TABLE sap_capire_bookshop_Books DROP descr; -- [WARNING] this statement is lossy ALTER TABLE sap_capire_bookshop_Books_texts DROP descr; ``` ::: ### Automatic Migration > Source: /docs/guides/databases/schema-evolution#automatic-migration You can enable automatic schema evolution in your `db` configuration: ::: code-group ```json [package.json] { "cds": { "requires": { "db": { "kind": "sqlite", "credentials": { "url": "db.sqlite" }, "schema_evolution": "auto" // [!code focus] } }}} ``` ::: This enables automatic schema migration when you run `cds deploy` in production-like environments. The migration process works as follows: - Whenever you execute `cds deploy` successfully, CAP stores the resulting state of the database schema in an internal table. - Before applying any changes, CAP compares the new state of the CDS models with the stored state and translates any differences into appropriate SQL statements to migrate the schema. > [!important] > CAP applies only non-lossy changes automatically. If it detects lossy changes, `cds deploy` aborts with respective errors and includes comments in the generated SQL script, similar to the general approach described above. ## Schema Evolution by HDI > Source: /docs/guides/databases/schema-evolution#schema-evolution-by-hdi When deploying to SAP HANA, the so-called HANA Deployment Infrastructure (HDI) handles schema evolution automatically. HDI manages the lifecycle of database artifacts and applies necessary schema changes based on the deployed CDS models. This includes creating, altering, or dropping database objects as needed to align with the current CDS model. Learn more about that in the [SAP HANA](hana.md) guide, section [HDI Schema Evolution](hana#hdi-schema-evolution). ## Liquibase for Java Projects > Source: /docs/guides/databases/schema-evolution#liquibase-for-java-projects For Java-based CAP projects, you can also use [Liquibase](https://www.liquibase.org/) to control when, where, and how you deploy database changes. ::: tip Liquibase license change Please be aware that Liquibase [changed its license to Functional Source License (FSL)](https://www.liquibase.com/blog/liquibase-community-for-the-future-fsl) with release 5.0. You need to check if this license is compatible with your application. ::: Learn more about that in the [PostgreSQL](postgres.md) guide, section [Using Liquibase (Java)](postgres#using-liquibase-in-cap-java). # Performance Considerations for CDS Modeling > Source: /docs/guides/databases/performance Find here some considerations and advice for CDS modeling with performance focus. {.abstract} ## Avoid UNION > Source: /docs/guides/databases/performance#avoid-union Using the UNION statement to merge data from different sources should be avoided. Especially, if other activities like SORTING or FILTERING are performed after the UNION statement. In general, there are two use cases for UNIONs: - You want to implement [Polymorphism](#polymorphism) - You port legacy applications to CAP, which already use UNION statements. ::: warning UNIONs in views come with a performance penalty and complex modelling. ::: ::: tip Rules of Thumb: - If you can't change your data model, you might have to use UNION to collect semantically close data. - The effort of transforming data structures to avoid UNION has the benefit of better performance as well as easier modelling and less complex application code. - Starting a new model, you should never need to use UNION. See [Polymorphism](#polymorphism). ::: ### Polymorphism > Source: /docs/guides/databases/performance#polymorphism Polymorphism might be the root cause for severe performance issues due to the usage of UNIONs, CASEs and complex JOINs. Here are some good and bad examples. #### **Bad** > Source: /docs/guides/databases/performance#bad Modeling many semantically related entities: ```cds entity Apples : cuid, managed { description : String; vendor : Association to one Vendor; appleDetails : appleDetailsType; } entity Bananas : cuid, managed { description : String; vendor : Association to one Vendor; bananaDetails : bananaDetailsType; } entity Cherries : cuid, managed { description : String; vendor : Association to one Vendor; cherryDetails : cherryDetailsType; } entity Mangos : cuid, managed { description : String; vendor : Association to one Vendor; mangoDetails : mangoDetailsType; } ``` #### **Good - normalized** > Source: /docs/guides/databases/performance#good---normalized Try to summarize semantically: ```cds entity Fruit : cuid, managed { type : String enum { apple; banana; cherry; mango }; description : String; vendor : Association to one Vendor; appleDetails : Composition of AppleDetails; bananaDetails : Composition of BananaDetails; cherryDetails : Composition of CherryDetails; mangoDetails : Composition of MangoDetails; } ``` You can reach common parts of any `Fruit` from anywhere using the association to `Fruit`. So `Fruit` is like an *interface* to all children. If you need the details of a certain child, you could either follow the corresponding composition or even build a specific view: ```cds view Banana as select from Fruit { type, description, vendor, bananaDetails, } where type = 'banana'; ``` #### **Good - de-normalized** > Source: /docs/guides/databases/performance#good---de-normalized As an alternative you can also use a completely de-normalized version: ```cds aspect apple { appleDetails : appleDetailsType; }; aspect banana { bananaDetails : bananaDetailsType;}; aspect cherry { cherryDetails : cherryDetailsType;}; aspect mango { mangoDetails : mangoDetailsType; }; entity Fruit : apple, banana, cherry, mango, cuid, managed { type : String enum { apple; banana; cherry; mango }; description : String; vendor : Association to one Vendor; } ``` This results in a single, sparsely populated DB table, which is not an issue using modern databases with variable page sizes. The optimizer will take care of it. ::: tip Rules of Thumb: - Come up with a good **general** approach. You get less specific associations and a less complicated model. - The normalized or de-normalized `Fruit` entities have the advantage that there is only one associations to `Vendor` to be provided. ::: ### View Building > Source: /docs/guides/databases/performance#view-building Polymorphism done right, also results in simplified view building. Assume you want to provide a list of all products of a certain vendor. #### **Good** > Source: /docs/guides/databases/performance#good Using the (de-) normalized version: ```cds view FruitsByVendor as select from Fruit { ID, description, vendor } where vendor.description = 'TopFruitCompany'; ``` You have less associations to be built and no UNIONs in your queries. #### **Bad** > Source: /docs/guides/databases/performance#bad-1 Using many semantically related entities: ```cds view FruitsByVendor as select from Apples UNION select from Bananas UNION select from Cherries UNION select from Mangos {ID, description, vendor} where vendor.description = 'TopFruitCompany'; ``` ## Avoid JOIN > Source: /docs/guides/databases/performance#avoid-join We use `OrdersHeaders` and their `OrdersItems` as an example. ```cds entity OrdersHeaders : managed { key ID : UUID; OrderNo : String; buyer : User; currency : Currency; Items : Composition of many OrdersItems on Items.Header = $self; } entity OrdersItems { key ID : UUID; product : Association to Products; quantity : Integer; title : String; price : Double; Header : Association to OrdersHeaders; }; ``` ### View Building > Source: /docs/guides/databases/performance#view-building-1 #### **Bad** > Source: /docs/guides/databases/performance#bad-2 Add a static view, using a JOIN. ```cds view OrdersItemsViewJoin as select OrdersHeaders.ID as Header_ID, OrdersHeaders.OrderNo as OrderNo, OrdersHeaders.buyer as buyer, OrdersHeaders.currency as currency, OrdersItems.ID as Item_ID, OrdersItems.product as product, OrdersItems.quantity as quantity, OrdersItems.title as title, OrdersItems.price as price from OrdersHeaders JOIN OrdersItems on OrdersHeaders.ID = OrdersItems.Header.ID; ``` #### **Good** > Source: /docs/guides/databases/performance#good-1 Use a dynamic entity, where you can query each fields individually, including following the association to OrderItems on demand. ```cds entity OrderItemsViewAssoc as projection on OrdersHeaders; ``` When retrieving the `OrderItemsViewAssoc` via OData, you get only the `OrdersHeaders` without the corresponding `Items` by default. A JOIN will not be executed until you explicitly use the OData feature `$expand` to get the `Items` as well. Additionally, the inclusion of property lists and filters gives better control of the projection of data you like to fetch. ```http GET http://localhost/odata/OrderItemsViewAssoc?$expand=Items&$select=OrderNo,Items/title ``` ### Sorting > Source: /docs/guides/databases/performance#sorting #### **Good** > Source: /docs/guides/databases/performance#good-2 First sort on the `OrdersItems` and then join back to the `OrdersHeaders` with the help of an association: ```cds view SortedOrdersAssoc as select {*, Header.OrderNo, Header.buyer, Header.currency } as Flatten from ( select from OrdersItems {*} order by OrdersItems.title ); ``` #### **Bad** > Source: /docs/guides/databases/performance#bad-3 Sort on the right table after a JOIN. For example: ```cds view SortedOrdersJoin as select OrdersHeaders.ID as Header_ID, OrdersHeaders.OrderNo as OrderNo, OrdersHeaders.buyer as buyer, OrdersHeaders.currency as currency, OrdersItems.ID as Item_ID, OrdersItems.product as product, OrdersItems.quantity as quantity, OrdersItems.title as title, OrdersItems.price as price from OrdersHeaders JOIN OrdersItems on OrdersHeaders.ID = OrdersItems.Header.ID order by title; ``` This can lead to performance issues. ### Filtering > Source: /docs/guides/databases/performance#filtering Basically, what is true for [Sorting](#sorting) is also valid for filtering. #### **Good** > Source: /docs/guides/databases/performance#good-3 ```cds view FilteredOrdersAssoc as select {*, Header.OrderNo, Header.buyer, Header.currency } as Flatten from ( select from OrdersItems {*} where OrdersItems.price > 100 ); ``` #### **Bad** > Source: /docs/guides/databases/performance#bad-4 ```cds view FilteredOrdersJoin as select OrdersHeaders.ID as Header_ID, OrdersHeaders.OrderNo as OrderNo, OrdersHeaders.buyer as buyer, OrdersHeaders.currency as currency, OrdersItems.ID as Item_ID, OrdersItems.product as product, OrdersItems.quantity as quantity, OrdersItems.title as title, OrdersItems.price as price from OrdersHeaders JOIN OrdersItems on OrdersHeaders.ID = OrdersItems.Header.ID where price > 100; ``` This query needs to identify that prices can be filtered before the join. Filtering beforehand prevents the full join from being materialized and then reduced to a smaller subset. ## Calculated Fields > Source: /docs/guides/databases/performance#calculated-fields Database operations on calculated fields cannot leverage any DB indexes. This impacts performance significantly, as calculated fields cause full table scans. Typical examples of calculated fields are: - **String concatenation** - `PrintedName = concat (FirstName, LastName)` - **Formatting** - IBAN with spaces in between `DE99 6611 7788 5544 1122` - **Algebra** - `FinalPrice = Rebate * ListPrice` - **Dynamic calculations** - `Age = dynamicFunction(DateOfBirth)` - [**Case statements**](#case-statement) The following steps show you which option takes precedence over another. Use options one/two as the preferred way and three/four as fallback. 1. Do the calculation on the UI with help of field controls or dedicated custom controls. This applies to all kinds of **String concatenation** and **Formatting**. 2. Pre-calculate using CDS [on write](../../cds/cdl#on-write) calculated fields. 3. Some calculations are dynamic in nature. If possible, use CDS [on read](../../cds/cdl#on-read) calculated fields. 4. As a **very last resort**, use event handlers on *read*. Hints: - Disable sorting, filtering on calculated fields on **live** calculated fields: `@Capabilities.SortRestrictions.NonSortableProperties : [propertyA, propertyB]` - Beware of hidden sorting (UI) & hidden filtering (authorization checks) - Don't use **live** calculated fields in `where` clauses, as in [JOIN](#avoid-join) conditions or association filtering. #### Example: Case Statement → Calculation on Write > Source: /docs/guides/databases/performance#example-case-statement--rarr-calculation-on-write Case statements are often results of porting legacy data models. They are expensive, since they can't leverage indices and require explicit materialization. In addition, sorting or filtering forces a full table scan and expression materialization. If re-modelling to avoid case statements isn't possible, the best optimization is to pre-calculate on write (once) instead on read (many times). **Bad**{.bad} → Explicit case statement: ::: code-group ```cds [service.cds] entity OrdersItemsView as projection on OrdersItems { *, case when quantity > 500 then 'Large' when quantity > 100 then 'Medium' else 'Small' end as category : String }; ``` ::: **Good**{.good} → Redundant attribute filled at write: ::: code-group ```cds [schema.cds] extend my.OrdersItems with { category: String = case when quantity > 500 then 'Large' when quantity > 100 then 'Medium' else 'Small' end stored; } ``` ::: ## Compositions vs Associations > Source: /docs/guides/databases/performance#compositions-vs-associations From the performance perspective there are some cases, where you have to check out carefully if the general semantic rules of compositions vs associations should be applied. In general, go for **compositions** in the following cases: - Parent and child share a life-cycle. - You can semantically establish a clear parent-child-hierarchy. - You never expose a *child* entity on its own. - The parent of an item never changes. - You want to keep entities together transactionally. In general, go for **associations** in the following cases: - You can't semantically establish a clear parent-child-hierarchy: - You expose a document entities fully on their own. - Relationships are likely to change over time. - Individual entities should have individual life-cycles. ::: tip Rule of Thumb: Your arm is composed to your body, your smart phone is associated to you, because it could belong to somebody else tomorrow ... ::: ::: warning Large documents, containing compositions with thousands of children, are copied entirely into draft state, even when only one little part is changed. In such cases, deviate from the general rules above, and decouple the document using associations instead of compositions. ::: ## Legacy Systems > Source: /docs/guides/databases/performance#legacy-systems In legacy systems you find emulations for data types like string-encoded booleans (`“X” = true`, `“ ” = false`) or emulated decimal numbers. A lot of application complexity stems from the emulation of such data types and is unnecessary when using modern infrastructure. In modern systems you have dedicated *native data types*. In addition, legacy systems often used UNIONs to save hard drive space, avoid database JOINs, or to accommodate new features without basic refactoring of the existing models. Don't take over such patterns into newly implemented applications. ::: tip Rules of Thumb: - Each conversion, case statement, or unnecessary data parsing is causing a performance impact and should therefore be avoided. - With each conversion to a native data type, you have the opportunity to simplify the model, simplify the application logic, and improve performance from the start. ::: Common patterns: - String encoded Booleans (like `“X” = true`, `“ ” = false`) might infer future case statements. Better convert them to real booleans ("true" and "false") directly at the beginning in the database. - Convert emulated decimal numbers (like integers with additional positional formatting info) to real decimal numbers on the database. - Multiple attribute columns (like Address01, Address02, Address03, ...) to avoid extensive JOINs in old DB systems are causing a lot of complex queries and business logic. Better use compositions of type instead and only de-normalize your data model where it really makes sense. - Positional strings, like `A_G___U_I`, with complex internal logic might infer future case statements or complex internal calculations. Better use compositions of type here as well. - If you encounter UNION statements in your legacy model, we strongly suggest to re-model as described in the section on [Polymorphism](#polymorphism). - If you encounter CASE statements in your legacy model, we strongly suggest to re-model as described in the section on [Calculated Fields](#calculated-fields). - Omit unnecessary abstraction views. When you are porting an "ABAP" CDS Model starting with the corresponding Virtual Data Model (VDM), C_Views and I_Views don't serve a purpose anymore. Please design your entities for optimized persistence, and your service layer for optimized processing. CAP already has a separation of concerns between the "DB" layer (persistency model) and the "SRV" layer (service / consumption model), there is no need to insert additional and unnecessary further abstraction layers. For example, the public interface layer with views like `I_COSTCENTER` will be replaced by the CDS services from which the OData consumption services are generated. - Legacy systems often have convoluted or overly complex data structures just to satisfy multiple processing requirements or use-cases with the same data structure. In CAP there is no need to create overly complex service entities, since you can use bound and unbound ACTIONs and FUNCTIONs for more complex data manipulation. Keep service entities as simple as possible and make them serve one purpose only, and rather create multiple simple entities instead of a complex one. ## Summary > Source: /docs/guides/databases/performance#summary | Legacy | Re-Model → Modern → Better Performance | |---|---| | String encoded Booleans | Convert them to real booleans on the database. | | Emulated decimal numbers | Convert to real decimal numbers on the database. | | Multiple attribute columns | Better use compositions of type instead. | | Positional strings, like `A_G___U_I` | Better use compositions of type. | | UNION statements | Re-model as described in [Polymorphism](#polymorphism). | | CASE statements | Re-model as described in [Calculated Fields](#calculated-fields). | | Complex data structures to satisfy multiple processing requirements | Use bound and unbound ACTIONs and FUNCTIONs and keep service entities as simple as possible. | | Unnecessary abstraction views (C_Views, I_Views) | Design your entities for optimized persistence, and your service layer for optimized processing. | # Vector Embeddings > Source: /docs/guides/databases/vector-embeddings Vector embeddings convert unstructured content (text, images, and so on) into numeric vectors that encode semantics (meaning). Comparing these vectors enables semantic search, recommendations, and enhanced generative AI features in your CAP application. For example retrieving related records, ranking results by relevance, or augmenting prompts for LLMs. ## Choose an Embedding Model > Source: /docs/guides/databases/vector-embeddings#choose-an-embedding-model Choose an embedding model that fits your use case and data (for example English or multilingual text). The model determines the number of dimensions of the resulting output vector. Check the documentation of the respective embedding model for details. Use the [SAP Generative AI Hub](https://www.sap.com/products/artificial-intelligence/generative-ai-hub.html) for unified consumption of embedding models and LLMs across different vendors and open-source models. Check for available models on the [SAP AI Launchpad](https://help.sap.com/docs/ai-launchpad/sap-ai-launchpad-user-guide/models-and-scenarios-in-generative-ai-hub-fef463b24bff4f44a33e98bb1e4f3148#models). ## Add Embeddings to Your CDS Model > Source: /docs/guides/databases/vector-embeddings#add-embeddings-to-your-cds-model Use the built-in CDL [Vector type](../../cds/types) to store embeddings. Use `Vector` without specifying a dimension to simplify changing the embedding model. If you specify a vector dimension, make sure it matches the embedding model (for example, 768 for *SAP_GXY.20250407*). ```cds extend Incidents with { embedding : Vector; } ``` ## Generate Embeddings > Source: /docs/guides/databases/vector-embeddings#generate-embeddings Use an embedding model to convert your data (for example, incident titles and summaries) into vectors. :::warning Evolve embeddings with your model Store embeddings when you create or update your data. Regenerate embeddings if you change your embedding model. ::: ### Generate Embeddings on the Database > Source: /docs/guides/databases/vector-embeddings#generate-embeddings-on-the-database To generate vector embeddings on write in SAP HANA, you can use the [vector_embedding](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/vector-embedding-function-vector) function as calculated element [on-write](../../cds/cdl#on-write) with embedding models from [SAP HANA NLP](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-vector-engine-guide/creating-text-embeddings-with-nlp-51eb170d038d4099a9bbb85c08fda888) or a configured remote source from SAP AI Core: ```cds extend Incidents with { @cds.api.ignore embedding : Vector = vector_embedding( 'Title: ' || title || ', Summary: ' || summary, 'DOCUMENT', 'SAP_GXY.20250407' ) stored; } ``` :::tip Prefer calculated elements for vector embeddings If the database calculates vector embeddings on write it automatically regenerates the embedding if the input data changes. ::: ::: info Local Testing with H2 and SQLite On H2 and SQLite the `CQL.vectorEmbedding` function is emulated to support local testing. ::: > [!warning] Java only and > The `vector_embedding` function is currently in beta and only supported by the CAP Java runtime. [Learn more about Vector Embeddings in CAP Java](../../java/cds-data#vector-embeddings) {.learn-more} ### Generate Embeddings Programmatically > Source: /docs/guides/databases/vector-embeddings#generate-embeddings-programmatically Alternatively, you can compute vector embeddings in your application layer using the [SAP Cloud SDK for AI](https://sap.github.io/ai-sdk/) to call SAP AI Core services for generating embeddings. :::details Example using SAP Cloud SDK for AI ```Java String question = "Are there patterns with overheating solar inverters?"; var request = OrchestrationEmbeddingRequest .forModel(TEXT_EMBEDDING_3_SMALL) .forInputs(question).asQuery(); OrchestrationEmbeddingResponse response = client.embed(request); float[] embedding = response.getEmbeddingVectors().get(0); CdsVector vector = CdsVector.of(embedding); ``` ::: :::tip Use SAP Cloud SDK for AI Use the [SAP Cloud SDK for AI](https://sap.github.io/ai-sdk/) for unified access to embedding models and large language models (LLMs) from [SAP AI Core](https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/what-is-sap-ai-core). ::: Learn more about the [SAP Cloud SDK for AI (Java)](https://sap.github.io/ai-sdk/docs/java/getting-started) or the [SAP Cloud SDK for AI (JavaScript)](https://sap.github.io/ai-sdk/docs/js/getting-started) {.learn-more} ## Query for Similarity > Source: /docs/guides/databases/vector-embeddings#query-for-similarity At runtime, use vector functions to search for similar items. In an example Retrieval-Augmented Generation (RAG) scenario, use `CQL.cosineSimilarity` to enhance the context of a user query for the LLM. First, compute the vector embedding of the user query and use it to find related incidents. ::: code-group ```Java [Java] // Compute embedding for user question var query = CQL.val( "Any incidents with solar inverters this month? How were they resolved?"); var embedding = CQL.vectorEmbedding(query, TextType.QUERY, "SAP_GXY.20250407"); // Compute similarity between user question and incident embeddings var similarity = CQL.cosineSimilarity(CQL.get(Incidents.EMBEDDING), embedding); // Find Incidents related to user question ordered by relevance Select.from(INCIDENTS) .columns(i -> similarity.times(100).as("relevance"), i -> i.ID(), i -> i.title(), i -> i.summary(), i -> i.date()) .where(i -> similarity.gt(0.75)) .orderBy(i -> i.get("relevance").desc()); ``` ```js [Node.js] const response = await new AzureOpenAiEmbeddingClient( 'text-embedding-3-small' ).run({ input: 'Any incidents with solar inverters this month? How were they resolved?' }); const questionEmbedding = response.getEmbedding(); let similarIncidents = await SELECT.from('Incidents') .where`cosine_similarity(embedding, to_real_vector(${questionEmbedding})) > 0.75`; ``` ::: # Service Protocols in CAP > Source: /docs/guides/protocols/ CAP supports multiple service protocols to expose your application data and functionality. This guide provides an overview of the available protocols and how to use them in your CAP applications. ## Available Protocols > Source: /docs/guides/protocols/#available-protocols CAP supports the following service protocols: - **Core Data APIs**: CAP provides core data APIs that allow you to interact with your data models programmatically, regardless of the underlying protocol. - **OData**: A widely used protocol for building and consuming RESTful APIs. CAP provides built-in support for OData v2 and v4, allowing you to easily expose your data models as OData services. - **OpenAPI**: A specification for building APIs that allows you to define your API endpoints, request/response formats, and authentication methods. CAP can generate OpenAPI specifications for your services, enabling easy integration with other systems. - **AsyncAPI**: A specification for defining asynchronous APIs, such as those using messaging protocols. CAP supports AsyncAPI for building event-driven applications and integrating with messaging systems. - **MCP**: Model Context Protocol (MCP) is an open protocol that enables seamless integration between LLM applications and external data sources and tools. ## Using Protocols in CAP > Source: /docs/guides/protocols/#using-protocols-in-cap To use a specific protocol in your CAP application, you typically need to configure your service definitions and handlers accordingly. Here are some general steps to get started: 1. **Define Your Data Model**: Use CDS (Core Data Services) to define your data models and entities. 2. **Create Service Definitions**: Define your services in CDS, specifying the entities and operations you want to expose. 3. **Configure Protocols**: Depending on the protocol you want to use, you may need to add specific annotations or configurations in your service definitions. 4. **Implement Service Handlers**: Write the necessary logic to handle requests and responses for your services. 5. **Deploy and Test**: Deploy your CAP application and test the exposed services using tools like Postman or Swagger UI. ## Further Reading > Source: /docs/guides/protocols/#further-reading For more detailed information on each protocol and how to implement them in CAP, refer to the following guides: - [OData APIs](odata.md) - [OpenAPI](openapi.md) - [AsyncAPI](asyncapi.md) - [MCP](mcp.md) These guides provide step-by-step instructions, examples, and best practices for working with each protocol in your CAP applications. ## Conclusion > Source: /docs/guides/protocols/#conclusion Leveraging the various service protocols supported by CAP allows you to build flexible and interoperable applications. Whether you are exposing RESTful APIs with OData, defining APIs with OpenAPI, or building event-driven applications with AsyncAPI, CAP provides the tools and capabilities to meet your integration needs. # Serving OData APIs > Source: /docs/guides/protocols/odata ## Feature Overview > Source: /docs/guides/protocols/odata#feature-overview OData is an OASIS standard that enhances plain REST with standardized system query options like `$select`, `$expand`, `$filter`, and others. The following table provides an overview of the feature coverage: | Query Options | Remarks | Node.js | Java | |----------------|---------------------------------------------|:------------:|:---------:| | `$search` | Search in multiple/all text elements(1)| | | | `$value` | Retrieves single rows/values | | | | `$top`,`$skip` | Requests paginated results | | | | `$filter` | Like SQL where clause | | | | `$select` | Like SQL select clause | | | | `$orderby` | Like SQL order by clause | | | | `$count` | Gets number of rows for paged results | | | | `$apply` | For [data aggregation](#data-aggregation) | | | | `$expand` | Deep-read associated entities | | | | `$compute` | Dynamic expressions for other query options | (2) | | | [Lambda Operators](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361024) | Boolean expressions on a collection | | (3) | | [Parameters Aliases](https://docs.oasis-open.org/odata/odata/v4.01/os/part1-protocol/odata-v4.01-os-part1-protocol.html#sec_ParameterAliases) | Replace literal value in URL with parameter alias | | (4) | - (1) The elements to be searched are specified with the [`@cds.search` annotation](../services/served-ootb#searching-data). - (2) Node.js only supports a limited subset. - (3) The navigation path identifying the collection can only contain one segment. - (4) Supported for key values and for parameters of functions only. System query options can also be applied to an [expanded navigation property](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#_Toc31361039) (nested within `$expand`): | Query Options | Remarks | Node.js | Java | |----------------|------------------------------------------|:-------:|:-----:| | `$select` | Select properties of associated entities | | | | `$filter` | Filter associated entities | | | | `$expand` | Nested expand | | | | `$orderby` | Sort associated entities | | | | `$top`,`$skip` | Paginate associated entities | | | | `$count` | Count associated entities | | | | `$search` | Search associated entities | | | [Learn more in the **Getting Started guide on odata.org**.](https://www.odata.org/getting-started/){.learn-more} [Learn more in the tutorials **Take a Deep Dive into OData**.](https://developers.sap.com/mission.scp-3-odata.html){.learn-more} | Data Modification | Remarks | Node.js | Java | |-------------------|-------------------------------------------|:------------:|:---------:| | [Create an Entity](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_CreateanEntity) | `POST` request on Entity collection | | | | [Update an Entity](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateanEntity) | `PATCH` or `PUT` request on Entity | | | [ETags](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UseofETagsforAvoidingUpdateConflicts) | For avoiding update conflicts | | | | [Delete an Entity](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_DeleteanEntity) | `DELETE` request on Entity | | | | [Delta Payloads](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_DeltaPayloads) | For nested entity collections in [deep updates](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateRelatedEntitiesWhenUpdatinganE) | | | | [Patch Collection](#odata-patch-collection) | Update Entity collection with [delta](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_DeltaPayloads) | | | ## PATCH Entity Collection with Mass Data (Java) > Source: /docs/guides/protocols/odata#patch-entity-collection-with-mass-data-java With OData v4, you can [update a collection of entities](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_UpdateaCollectionofEntities) with a _single_ PATCH request. The request targets the entity collection in the resource path and provides the request body as a [delta payload](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_DeltaPayloads): ```js PATCH /CatalogService/Books Content-Type: application/json { "@context": "#$delta", "value": [ { "ID": 17, "title": "CAP - what's new in 2023", "price": 29.99, "author_ID": 999 }, { "ID": 85, "price": 9.99 }, { "ID": 42, "@removed": { "reason": "deleted" } } ] } ``` The system executes PATCH requests with a delta payload using batch delete and [upsert](../../java/working-with-cql/query-api#bulk-upsert) statements. These requests are more efficient than OData [batch requests](https://docs.oasis-open.org/odata/odata/v4.01/csprd02/part1-protocol/odata-v4.01-csprd02-part1-protocol.html#sec_BatchRequests). Use PATCH on entity collections to upload mass data using a dedicated service secured with [role-based authorization](../security/authorization#requires). Enable delta updates explicitly by annotating the entity with ```cds @Capabilities.UpdateRestrictions.DeltaUpdateSupported ``` Limitations: * Conflict detection via [ETags](../services/served-ootb#etag) is not supported. * The system bypasses [draft flow](../../java/fiori-drafts#bypassing-draft-flow). `IsActiveEntity` must be `true`. * The system ignores [draft locks](../../java/fiori-drafts#draft-lock). Active entities are updated or deleted without canceling drafts. * [Added and deleted links](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_IteminaDeltaPayloadResponse) are not supported. * The header `Prefer=representation` is not yet supported. * The `continue-on-error` preference is not yet supported. * The generic CAP handler support for [upsert](../../java/working-with-cql/query-api#upsert) is limited, for example, audit logging is not supported. ## Mapping of CDS Types > Source: /docs/guides/protocols/odata#mapping-of-cds-types The following table lists [CDS's built-in types](../../cds/types) and their mapping to the OData EDM type system. | CDS Type | OData V4 | | -------------- | --------------------------------------- | | `UUID` | _Edm.Guid_ (1) | | `Boolean` | _Edm.Boolean_ | | `UInt8 ` | _Edm.Byte_ | | `Int16` | _Edm.Int16_ | | `Int32` | _Edm.Int32_ | | `Integer` | _Edm.Int32_ | | `Int64` | _Edm.Int64_ | | `Integer64` | _Edm.Int64_ | | `Decimal` | _Edm.Decimal_ | | `Double` | _Edm.Double_ | | `Date` | _Edm.Date_ | | `Time` | _Edm.TimeOfDay_ | | `DateTime` | _Edm.DateTimeOffset_ | | `Timestamp` | _Edm.DateTimeOffset_ with Precision="7" | | `String` | _Edm.String_ | | `Binary` | _Edm.Binary_ | | `LargeBinary` | _Edm.Binary_ | | `LargeString` | _Edm.String_ | | `Map` | represented as an empty, open complex type | | `Vector` | not supported (2) | > (1) Mapping can be changed with, for example, `@odata.Type='Edm.String'` > (2) Type `cds.Vector` must not appear in an OData service OData V2 has the following differences: | CDS Type | OData V2 | | ------------ | ----------------------------------------------- | | `Date` | _Edm.DateTime_ with `sap:display-format="Date"` | | `Time` | _Edm.Time_ | | `Map` | not supported | ### Overriding Type Mapping > Source: /docs/guides/protocols/odata#overriding-type-mapping Use the annotation `@odata.Type` first to override standard type mappings, then additionally define `@odata {MaxLength, Precision, Scale, SRID}`. `@odata.Type` is effective on scalar CDS types only and the value must be a valid OData (EDM) primitive type for the specified protocol version. Unknown types and non-matching facets are silently ignored. No further value constraint checks are applied. These annotations allow you to produce additional OData EDM types that are not available in the standard type mapping. Use this approach during the import of external service APIs. ```cds entity Foo { // ... @odata: { Type: 'Edm.GeometryPolygon', SRID: 0 } geoCollection : LargeBinary; }; ``` Another prominent use case is the CDS type `UUID`, which maps to `Edm.Guid` by default. However, the OData standard imposes restrictive rules for _Edm.Guid_ values. For example, only hyphenated strings are allowed, which can conflict with existing data. You can override the default mapping as follows: ```cds entity Books { key ID : UUID @odata.Type:'Edm.String'; // ... } ``` ::: warning This annotation affects the client-side facing API only. No automatic data modification occurs behind the scenes, such as rounding, truncation, or conversion. You must perform all the required modifications on the data stream so that the values match their type in the API. If you don't do the required conversions, you can "cast" any scalar CDS type into any incompatible EDM type: ```cds entity Foo { // ... @odata: {Type: 'Edm.Decimal', Scale: 'floating' } str: String(17) default '17.4'; } ``` This translates into the following OData API contract: ```xml ``` The client can now rightfully expect float numbers to be transmitted, but in reality the values are still strings. ::: ## OData Annotations > Source: /docs/guides/protocols/odata#odata-annotations The following sections explain how to add OData annotations to CDS models and how to map them to EDMX outputs. The translation considers only annotations defined in the vocabularies mentioned in [Annotation Vocabularies](#vocabularies). ### Terms and Properties > Source: /docs/guides/protocols/odata#terms-and-properties OData defines a strict two-fold key structure composed of `@.`. All annotations are always specified as a _Term_ with either a primitive value, a record value, or collection values. The properties themselves may, in turn, be primitives, records, or collections. #### Example > Source: /docs/guides/protocols/odata#example ```cds @Common.Label: 'Customer' @UI.HeaderInfo: { TypeName : 'Customer', TypeNamePlural : 'Customers', Title : { Value : name } } entity Customers { /* ... */ } ``` This is represented in CSN as follows: ```jsonc {"definitions":{ "Customers":{ "kind": "entity", "@Common.Label": "Customer", "@UI.HeaderInfo.TypeName": "Customer", "@UI.HeaderInfo.TypeNamePlural": "Customers", "@UI.HeaderInfo.Title.Value": {"=": "name"}, /* ... */ } }} ``` And would render to EDMX as follows: ```xml ``` ::: tip The value for `@UI.HeaderInfo` is flattened to individual key-value pairs in CSN and 'restructured' to a record for OData exposure in EDMX. ::: For each annotated target definition in CSN, the rules for restructuring from CSN sources are: 1. Annotations with a single-identifier key are skipped (as OData annotations always have a `@Vocabulary.Term...` key signature). 2. All individual annotations with the same `@` prefix are collected. 3. If there's only one annotation without a suffix, → that one is a scalar or array value of an OData term. 4. If there are more annotations with suffix key parts →, it's a record value for the OData term. ### Qualified Annotations > Source: /docs/guides/protocols/odata#qualified-annotations OData provides [qualified annotations](https://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_Toc453752511), which allow you to specify different values for a given property. CDS syntax for annotations was extended to allow appending OData-style qualifiers after a `#` sign to an annotation key, but always only as the last component of a key in the syntax. For example, this is supported: ```cds @Common.Label: 'Customer' @Common.Label#Legal: 'Client' @Common.Label#Healthcare: 'Patient' @Common.ValueList: { Label: 'Customers', CollectionPath:'Customers' } @Common.ValueList#Legal: { Label: 'Clients', CollectionPath:'Clients' } ``` and would render as follows in CSN: ```json { "@Common.Label": "Customer", "@Common.Label#Legal": "Clients", "@Common.Label#Healthcare": "Patients", "@Common.ValueList.Label": "Customers", "@Common.ValueList.CollectionPath": "Customers", "@Common.ValueList#Legal.Label": "Clients", "@Common.ValueList#Legal.CollectionPath": "Clients", } ``` CDS provides no interpretation and no special handling for these qualifiers. You must write and apply them exactly as your chosen OData vocabularies specify them. ### Primitives > Source: /docs/guides/protocols/odata#primitives > Note: The `@Some` annotation isn't a valid term definition. The following example illustrates the rendering of primitive values. The system maps primitive annotation values (Strings, Numbers, `true`, and `false`) to corresponding OData annotations as follows: ```cds @Some.Boolean: true @Some.Integer: 1 @Some.Number: 3.14 @Some.String: 'foo' ``` ```xml ``` #### Null Value > Source: /docs/guides/protocols/odata#null-value A `null` value can be set either as an [annotation expression](#expression-annotations) or as a [dynamic expression](#dynamic-expressions): ```cds @Some.NullXpr: (null) // annotation expression, short form @Some.NullFunc: ($Null()) // annotation expression, functional form @Some.NullDyn: { $edmJson: { $Null } } // dynamic expression ``` All three expressions result in the following rendering: ```xml ``` [Have a look at our *CAP SFLIGHT* sample, showcasing the usage of OData annotations.](https://github.com/SAP-samples/cap-sflight/blob/main/app/travel_processor/capabilities.cds){.learn-more} ### Records > Source: /docs/guides/protocols/odata#records > Note: The `@Some` annotation isn't a valid term definition. The following example illustrates the rendering of record values. The system maps record-like source structures to `` nodes in EDMX, with primitive types translated analogously to what was mentioned earlier: ```cds @Some.Record: { Null: (null), Boolean: true, Integer: 1, Number: 3.14, String: 'foo' } ``` ```xml ``` If possible, the type of the record in OData is deduced from the information in the [OData Annotation Vocabularies](#vocabularies): ```cds @Common.ValueList: { CollectionPath: 'Customers' } ``` ```xml ``` Frequently, the OData record type cannot be determined unambiguously, for example if the type found in the vocabulary is abstract. Then you need to explicitly specify the type by adding a property named `$Type` in the record. For example: ```cds @UI.Facets : [{ $Type : 'UI.CollectionFacet', ID : 'Customers' }] ``` ```xml ``` There is one exception for a very prominent case: if the deduced [record type is `UI.DataFieldAbstract`](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/UI.md), the compiler by default automatically chooses `UI.DataField`: ```cds @UI.Identification: [{ Value: deliveryId }] ``` ```xml ``` To overwrite the default, use an explicit `$Type` like shown previously. [Have a look at our *CAP SFLIGHT* sample, showcasing the usage of OData annotations.](https://github.com/SAP-samples/cap-sflight/blob/a7b166b7b9b3d2adb1640b4b68c3f8a26c6961c1/app/travel_processor/value-helps.cds){.learn-more} ### Collections > Source: /docs/guides/protocols/odata#collections > Note: The `@Some` annotation isn't a valid term definition. The following example illustrates the rendering of collection values. The system maps arrays to `` nodes in EDMX. If primitives appear as direct elements of the array, these elements are wrapped into individual primitive child nodes of the resulting collection as is. The system applies the rules for records and collections recursively: ```cds @Some.Collection: [ null, true, 1, 3.14, 'foo', { $Type:'UI.DataField', Label:'Whatever', Hidden } ] ``` ```xml true 1 3.14 foo ``` ### References > Source: /docs/guides/protocols/odata#references > Note: The `@Some` annotation isn't a valid term definition. The following example illustrates the rendering of reference values. The system maps references in CDS annotations to `Path` properties or nested `` elements, respectively: ```cds @Some.Term: My.Reference @Some.Record: { Value: My.Reference } @Some.Collection: [ My.Reference ] ``` ```xml My/Reference ``` As the compiler isn't aware of the semantics of such references, the mapping is very simplistic: each `.` in a path is replaced by a `/`. Use [expression-valued annotations](#expression-annotations) for more convenience. Use a [dynamic expression](#dynamic-expressions) if the generic mapping can't produce the desired ``: ```cds @Some.Term: {$edmJson: {$Path: '/com.sap.foo.EntityContainer/EntityName/FieldName'}} ``` ```xml /com.sap.foo.EntityContainer/EntityName/FieldName ``` ### Enumeration Values > Source: /docs/guides/protocols/odata#enumeration-values The system maps enumeration symbols to corresponding `EnumMember` properties in OData. Here are a couple of examples of enumeration values and the annotations that are generated. The first example is for a term in the [Common vocabulary](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/Common.md): ```cds @Common.TextFormat: #html ``` ```xml ``` The second example is for a (record type) term in the [Communication vocabulary](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/Communication.md): ```cds @Communication.Contact: { gender: #F } ``` ```xml ``` ### Expressions > Source: /docs/guides/protocols/odata#expressions If the value of an OData annotation is an [expression](../../cds/cdl#expressions-as-annotation-values), the OData backend provides improved handling of references and automatic mapping from CDS expression syntax to OData expression syntax. One of the main use cases for such dynamic expressions is SAP Fiori. Examples: ```cds @UI.Hidden: (status <> 'visible') @UI.CreateHidden : (to_Travel.TravelStatus.code != #Open) ``` Note that SAP Fiori supports dynamic expressions only for [specific annotations](https://ui5.sap.com/#/topic/0e7b890677c240b8ba65f8e8d417c048). #### Flattening > Source: /docs/guides/protocols/odata#flattening In contrast to [simple references](#references), the references in expression-like annotation values are correctly handled during model transformations, like other references in the model. When the CDS model is flattened for OData, the flattening is consequentially also applied to these references, and they are translated to the flat model. ::: tip Although CAP supports structured types and elements, we recommend using them only if they bring a real benefit. In general, you should keep your models as flat as possible. ::: Example: ```cds type Price { @Measures.ISOCurrency: (currency) // [!code highlight] amount : Decimal; currency : String(3); // [!code highlight] } service S { entity Product { key id : Integer; name : String; price : Price; } } ``` Structured element `price` of `S.Product` is unfolded to flat elements `price_amount` and `price_currency`. Accordingly, the reference in the annotation is rewritten from `currency` to `price_currency`: ```xml ``` Example: ```cds service S { entity E { key id : Integer; f : Association to F; @Some.Term: (f.struc.y) // [!code highlight] val : Integer; } entity F { key id : Integer; struc { y : Integer; } } } ``` The OData backend is aware of the semantics of a path and distinguishes association path steps from structure access. The CDS path `f.struc.y` is translated to the OData path `f/struc_y`: ```xml ``` #### Managed Associations > Source: /docs/guides/protocols/odata#managed-associations The OData backend translates managed associations into unmanaged associations plus explicit foreign key elements. During this translation, the system copies annotations assigned to the managed association to the respective foreign key elements. Example: ```cds service S { entity Authors { key ID : Integer; name : String; } entity Books { key ID : Integer; author : Association to Authors; } annotate Books:author with @Common.Text: (author.name); // [!code highlight] } ``` Resulting OData API: ```xml ``` Instead of relying on this copy mechanism, you can also explicitly annotate a foreign key element: ```cds annotate Books:author.ID with @Common.Text: ($self.author.name); // here $self is necessary ``` The system always rewrites a path that addresses a key element in the target of a managed association to address the local foreign key element. Example: ```cds service S { entity Travels { key id : Integer; status : Association to TravelStatus; }; entity TravelStatus { key code : String(1) enum {Open = 'O'; Accepted = 'A'; Canceled = 'X'; }; } @UI.CreateHidden : (travel.status.code != #Open) // [!code highlight] entity Bookings { key id : Integer; travel : Association to Travels; } } ``` Resulting OData API: ```xml travel/status_code O ``` #### Expression Translation > Source: /docs/guides/protocols/odata#expression-translation If the expression you provide as an annotation value is more complex than just a reference, the OData backend translates CDS expressions to the corresponding OData expression syntax. The backend rejects expressions that are not applicable in an OData API. ::: info While the flattening of references described in the preceding section is applied to all annotations, the syntactic translation of expressions is only done for annotations defined in one of the [OData vocabularies](#vocabularies). ::: The following operators and clauses of CDL are supported: * `case when ... then ... else ...` and the logical ternary operator ` ? : ` * Logical: `and`, `or`, `not` * Relational: `=`, `<>`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `between ... and ...` * Unary `+` and `-` * Arithmetic: `+`, `-`, `*`, `/` * Concat: `||` * `cast(...)` Example: ```cds @Some.Xpr: ( -(a + b) ) ``` ```xml a b ``` You can use such expressions, for example, for [some Fiori UI annotations](https://ui5.sap.com/#/topic/0e7b890677c240b8ba65f8e8d417c048): ```cds service S { @UI.LineItem: [ // ... { Value: (status), // [!code highlight] Criticality: ( status = 'O' ? 2 : ( status = 'A' ? 3 : 0 ) ) // [!code highlight] }] entity Order { key id : Integer; // ... status : String; } } ``` If you need to access an element of an entity in an annotation for a bound action or function, use a path that navigates via an explicitly defined [binding parameter](../../cds/cdl#bound-actions). Example: ```cds service S { entity Order { key id : Integer; // ... status : String; } actions { @Core.OperationAvailable: ( :in.status <> 'A' ) // [!code highlight] action accept (in: $self) } } ``` In addition, the following functions are supported: * `$Null()` representing the `null` value [`Null`]([annotation expression](#null-value)). * `Div(...)` (or `$Div(...)`) and `Mod(...)` (or `$Mod(...)`) for integer division and modulo * [`Has(...)`](https://docs.oasis-open.org/odata/odata/v4.02/csd01/part2-url-conventions/odata-v4.02-csd01-part2-url-conventions.html#Has) (or `$Has(...)`) * the functions listed in sections [5.1.1.5](https://docs.oasis-open.org/odata/odata/v4.02/csd01/part2-url-conventions/odata-v4.02-csd01-part2-url-conventions.html#StringandCollectionFunctions) through [5.1.1.11](https://docs.oasis-open.org/odata/odata/v4.02/csd01/part2-url-conventions/odata-v4.02-csd01-part2-url-conventions.html#GeoFunctions) of [OData URL conventions](https://docs.oasis-open.org/odata/odata/v4.02/odata-v4.02-part2-url-conventions.html) + See examples below for the syntax for `cast` and `isof` (section [5.1.1.10](https://docs.oasis-open.org/odata/odata/v4.02/csd01/part2-url-conventions/odata-v4.02-csd01-part2-url-conventions.html#TypeFunctions)) + The names of the geo functions (section [5.1.1.11](https://docs.oasis-open.org/odata/odata/v4.02/csd01/part2-url-conventions/odata-v4.02-csd01-part2-url-conventions.html#GeoFunctions)) need to be escaped like
`![geo.distance]` * [`fillUriTemplate(...)`](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_FunctionodatafillUriTemplate) and [`uriEncode(...)`](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_FunctionodatauriEncode) * `Type(...)` (or `$Type(...)`) is to be used to specify a type name with their corresponding type facets such as `MaxLength(...)`, `Precision(...)`, `Scale(...)` and `SRID(...)` (or `$MaxLength(...)`, `$Precision(...)`, `$Scale(...)`, `$SRID(...)`) Example: ```cds @Some.Func1: ( concat(a, b, c) ) @Some.Func2: ( round(aNumber) ) @Some.Func3: ( $Cast(aValue, $Type('Edm.Decimal', $Precision(38), $Scale(19)) ) ) @Some.Func4: ( $IsOf(aValue, $Type('Edm.Decimal', $Precision(38), $Scale(19)) ) ) @Some.Func5: ( ![geo.distance](a, b) ) @Some.Func6: ( fillUriTemplate(a, b) ) ``` If a functional expression starts with a `$`, all inner function must also be `$` functions and vice versa. Instead of `[$]Type(...)` an EDM primitive type name can be directly used as function name like in CDL. It is worth to mention that there are two alternatives for the cast function, one in the EDM and one in the CDS domain: ```cds @Some.ODataStyleCast: ( Cast(aValue, Decimal(38, 'variable') ) ) // => Edm.Decimal @Some.ODataStyleCast2: ( Cast(aValue, PrimitiveType()) ) // => Edm.PrimitiveType @Some.SQLStyleCast: ( cast(aValue as Decimal(38, variable)) ) // => cds.Decimal @Some.SQLStyleCast2: ( cast(aValue as String) ) // => cds.String without type facets ``` Both `cast` functions look similar, but there are some differences: The OData style `Cast` _function_ starts with a capital letter and the SQL `cast` _operator_ uses the keyword `as` to delimit the element reference from the type specifier. The OData `Cast` requires an EDM primitive type to be used either as `[$]Type()` or as direct type function whereas the SQL `cast` requires a scalar CDS type as argument which is then converted into the corresponding EDM primitive type. ::: info CAP only provides a syntactic translation. It is up to each client whether an expression value is supported for a particular annotation. See, for example, [SAP Fiori Elements' list of supported annotations](https://ui5.sap.com/#/topic/0e7b890677c240b8ba65f8e8d417c048). ::: Use a [dynamic expression](#dynamic-expressions) if the desired EDMX expression cannot be obtained via the automatic translation of a CDS expression. ### Annotating Annotations > Source: /docs/guides/protocols/odata#annotating-annotations OData can annotate annotations. This often occurs in combination with enums like `UI.Importance` and `UI.TextArrangement`. CDS has no corresponding language feature. For OData annotations, you can achieve nesting in the following way: * To annotate a Record, add an additional element to the CDS source structure. The name of this element is the full name of the annotation, including the `@`. See `@UI.Importance` in the following example. * To annotate a single value or a Collection, add a parallel annotation that has the nested annotation name appended to the outer annotation name. See `@UI.Criticality` and `@UI.TextArrangement` in the following example. ```cds @UI.LineItem: [ {Value: ApplicationName, @UI.Importance: #High}, // [!code highlight] {Value: Description}, {Value: SourceName}, {Value: ChangedBy}, {Value: ChangedAt} ] @UI.LineItem.@UI.Criticality: #Positive // [!code highlight] @Common.Text: Text @Common.Text.@UI.TextArrangement: #TextOnly // [!code highlight] ``` Alternatively, annotating a single value or a Collection by turning them into a structure with an artificial property `$value` is still possible, but deprecated: ```cds @UI.LineItem: { $value:[ /* ... */ ], @UI.Criticality: #Positive } @Common.Text: { $value: Text, @UI.TextArrangement: #TextOnly } ``` As `TextArrangement` is common, there's a shortcut for this specific situation: ```cds ... @Common: { Text: Text, TextArrangement: #TextOnly } ``` In any case, the resulting EDMX is: ```xml ... ``` ### EDM JSON Expression Syntax > Source: /docs/guides/protocols/odata#edm-json-expression-syntax ::: tip Use CDS expression syntax Use the EDM JSON expression syntax only as a fallback mechanism. Whenever possible, use [expression-like annotation values](#expression-annotations) instead. For the following example, simply write `@UI.Hidden: (status <> 'visible')`. ::: In case you want to have an expression as value for an OData annotation that cannot be written as a [CDS expression ](#expression-annotations), you can use the "edm-json inline mechanism" by providing an [EDM JSON expression](https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/odata-csdl-json-v4.01.html#_Toc38466479) as defined in the [JSON representation of the OData Common Schema Language](https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/odata-csdl-json-v4.01.html) enclosed in `{ $edmJson: { ... }}`. Note that here the CDS syntax for string literals with single quotes (`'foo'`) applies, and that paths are not automatically recognized but need to be written as `{$Path: 'fieldName'}`. The CDS compiler translates the expression into the corresponding [XML representation](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#_Toc38530421). For example, the CDS annotation: ```cds @UI.Hidden: {$edmJson: {$Ne: [{$Path: 'status'}, 'visible']}} ``` is translated to: ```xml status visible ``` ### `sap:` Annotations > Source: /docs/guides/protocols/odata#sap-annotations In general, back ends and SAP Fiori UIs understand or expect OData V4 annotations. You should use those rather than the OData V2 SAP extensions.
If necessary, CDS automatically translates OData V4 annotations to OData V2 SAP extensions when you invoke it with `v2` as the OData version. This means you shouldn't need to deal with this at all. Nevertheless, in case you need to do so, you can add `sap:...` attribute-style annotations as follows: ```cds @sap.applicable.path: 'to_eventStatus/EditEnabled' action EditEvent(...) returns SomeType; ``` Which would render to OData EDMX as follows: ```xml ... ``` The rules are: * Only strings are supported as values. * The first dot in `@sap.` is replaced by a colon `:`. * Subsequent dots are replaced by dashes. ### Differences to ABAP > Source: /docs/guides/protocols/odata#differences-to-abap In contrast to ABAP CDS, we apply a **generic, isomorphic approach** where names and positions of annotations are exactly as specified in the [OData Vocabularies](#vocabularies). This has the following advantages: * Single source of truth — users only need to consult the official OData specs * Speed — we don't need complex case-by-case mapping logic * No bottlenecks — we always support the full set of OData annotations * Bidirectional mapping — we can translate CDS to EDMX and vice versa Last but not least, it also saves us lots of effort as we don't have to write derivatives of all the OData vocabulary specs. ## Annotation Vocabularies > Source: /docs/guides/protocols/odata#annotation-vocabularies When translating a CDS model to an OData API, by default only those annotations are considered that are part of the standard OASIS or SAP vocabularies listed below. You can add further vocabularies to the translation process [using configuration.](#additional-vocabularies) ### OASIS Vocabularies { target="_blank"} > Source: /docs/guides/protocols/odata#oasis-vocabularies--targetblank | Vocabulary | Description | |-----------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------| | [@Aggregation](https://github.com/oasis-tcs/odata-vocabularies/tree/main/vocabularies/Org.OData.Aggregation.V1.md){target="_blank"} | for describing aggregatable data | | [@Authorization](https://github.com/oasis-tcs/odata-vocabularies/tree/main/vocabularies/Org.OData.Authorization.V1.md){target="_blank"} | for authorization requirements | | [@Capabilities](https://github.com/oasis-tcs/odata-vocabularies/tree/main/vocabularies/Org.OData.Capabilities.V1.md){target="_blank"} | for restricting capabilities of a service | | [@Core](https://github.com/oasis-tcs/odata-vocabularies/tree/main/vocabularies/Org.OData.Core.V1.md){target="_blank"} | for general purpose annotations | | [@JSON](https://github.com/oasis-tcs/odata-vocabularies/tree/main/vocabularies/Org.OData.JSON.V1.md){target="_blank"} | for JSON properties | | [@Measures](https://github.com/oasis-tcs/odata-vocabularies/tree/main/vocabularies/Org.OData.Measures.V1.md){target="_blank"} | for monetary amounts and measured quantities | | [@Repeatability](https://github.com/oasis-tcs/odata-vocabularies/tree/main/vocabularies/Org.OData.Repeatability.V1.md){target="_blank"} | for repeatable requests | | [@Temporal](https://github.com/oasis-tcs/odata-vocabularies/tree/main/vocabularies/Org.OData.Temporal.V1.md){target="_blank"} | for temporal annotations | | [@Validation](https://github.com/oasis-tcs/odata-vocabularies/tree/main/vocabularies/Org.OData.Validation.V1.md){target="_blank"} | for adding validation rules | ### SAP Vocabularies{target="_blank"} > Source: /docs/guides/protocols/odata#sap-vocabulariestargetblank | Vocabulary | Description | |--------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------| | [@Analytics](https://github.com/SAP/odata-vocabularies/tree/main/vocabularies/Analytics.md){target="_blank"} | for annotating analytical resources | | [@CodeList](https://github.com/SAP/odata-vocabularies/tree/main/vocabularies/CodeList.md){target="_blank"} | for code lists | | [@Common](https://github.com/SAP/odata-vocabularies/tree/main/vocabularies/Common.md){target="_blank"} | for all SAP vocabularies | | [@Communication](https://github.com/SAP/odata-vocabularies/tree/main/vocabularies/Communication.md){target="_blank"} | for annotating communication-relevant information | | [@DataIntegration](https://github.com/SAP/odata-vocabularies/tree/main/vocabularies/DataIntegration.md){target="_blank"} | for data integration | | [@Hierarchy](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/Hierarchy.md){target="_blank"} | for hierarchies | | [@PDF](https://github.com/SAP/odata-vocabularies/tree/main/vocabularies/PDF.md){target="_blank"} | for PDF | | [@PersonalData](https://github.com/SAP/odata-vocabularies/tree/main/vocabularies/PersonalData.md){target="_blank"} | for annotating personal data | | [@Session](https://github.com/SAP/odata-vocabularies/tree/main/vocabularies/Session.md){target="_blank"} | for sticky sessions for data modification | | [@UI](https://github.com/SAP/odata-vocabularies/tree/main/vocabularies/UI.md){target="_blank"} | for presenting data in user interfaces | [Learn more about annotations in CDS and OData and how they work together](https://github.com/SAP-samples/odata-basics-handsonsapdev/blob/annotations/bookshop/README.md){.learn-more} ### Additional Vocabularies > Source: /docs/guides/protocols/odata#additional-vocabularies Assuming you have a vocabulary `com.MyCompany.vocabularies.MyVocabulary.v1`, you can set the configuration option cds.cdsc.odataVocabularies.MyVocabulary: {"Alias": "MyVocabulary", "Namespace": "com.sap.vocabularies.MyVocabulary.v1", "Uri": "\"}. With this configuration, all annotations prefixed with `MyVocabulary` are considered in the translation. ```cds service S { @MyVocabulary.MyAnno: 'My new Annotation' entity E { /*...*/ }; }; ``` The annotation is added to the OData API, as well as the mandatory reference to the vocabulary definition: ```xml ... ``` The compiler evaluates neither annotation values nor the URI. It is your responsibility to make the URI accessible if necessary. Unlike for the standard vocabularies listed above, the compiler has no access to the content of the vocabulary, so the values are translated generically. ## Data Aggregation > Source: /docs/guides/protocols/odata#data-aggregation Data aggregation in OData V4 is leveraged by the `$apply` system query option, which defines a pipeline of transformations that is applied to the _input set_ specified by the URI. On the _result set_ of the pipeline, the standard system query options come into effect.
### Example > Source: /docs/guides/protocols/odata#example-1 ```http GET /Orders(10)/books? $apply=filter(year eq 2000)/ groupby((author/name),aggregate(price with average as avg))/ orderby(title)/ top(3) ``` This request operates on the books of the order with ID 10. First, it filters out the books from the year 2000 to create an intermediate result set. The system then groups the intermediate result set by author name and averages the price. Finally, the system sorts the result set by title and retains only the top three entries. ::: warning If the `groupby` transformation only includes a subset of the entity keys, the result order might be unstable. ::: ### Transformations > Source: /docs/guides/protocols/odata#transformations | Transformation | Description | Node.js | Java | |------------------------------|----------------------------------------------|:------------------:|:-----:| | `filter` | filter by filter expression | | | | `search` | filter by search term or expression | | | | `groupby` | group by dimensions and aggregates values | | | | `aggregate` | aggregate values | | | | `compute` | add computed properties to the result set | | | | `expand` | expand navigation properties | | | | `concat` | append additional aggregation to the result | | | | `skip` / `top` | paginate | | | | `orderby` | sort the input set | | | | `topcount`/`bottomcount` | retain highest/lowest _n_ values | | | | `toppercent`/`bottompercent` | retain highest/lowest _p_% values | | | | `topsum`/`bottomsum` | retain _n_ values limited by sum | | | | `TopLevels` | retain only _n_ levels of a hierarchy | 2 | 1,2 | | `ancestors/descendants` | retain ancestors/descendants of specific nodes | 2 | 1,2 | 1 - supported on SAP HANA, H2 ad PostgreSQL only 2 - only to support requests from the UI5 Tree Table #### `concat` > Source: /docs/guides/protocols/odata#concat The [`concat` transformation](https://docs.oasis-open.org/odata/odata-data-aggregation-ext/v4.0/cs02/odata-data-aggregation-ext-v4.0-cs02.html#_Toc435016581) applies additional transformation sequences to the input set and concatenates the result: ```http GET /Books?$apply= filter(author/name eq 'Bram Stroker')/ concat( aggregate($count as totalCount), groupby((year), aggregate($count as countPerYear))) ``` This request filters all books, keeping only books by Bram Stroker. From these books, `concat` calculates (1) the total count of books _and_ (2) the count of books per year. The result is heterogeneous. The `concat` transformation must be the last of the apply pipeline. If `concat` is used, then `$apply` can't be used in combination with other system query options. #### `skip`, `top`, and `orderby` > Source: /docs/guides/protocols/odata#skip-top-and-orderby Beyond the standard transformations specified by OData, CDS Java supports the transformations `skip`, `top`, and `orderby` that allow you to sort and paginate an input set: ```http GET /Order(10)/books? $apply=orderby(price desc)/ top(500)/ groupby((author/name),aggregate(price with max as maxPrice)) ``` This query groups the 500 most expensive books by author name and determines the price of the most expensive book per author. ### Aggregation Methods > Source: /docs/guides/protocols/odata#aggregation-methods | Aggregation Method | Description | Node.js | Java | |--------------------|----------------------------------|:-------:|:-----:| | `min` | smallest value | | | | `max` | largest | | | | `sum` | sum of values | | | | `average` | average of values | | | | `countdistinct` | count of distinct values | | | | custom method | custom aggregation method | | | | custom aggregate | predefined custom aggregate | | | | `$count` | number of instances in input set | | | ### Custom Aggregates > Source: /docs/guides/protocols/odata#custom-aggregates Instead of explicitly using an expression with an aggregation method in the `aggregate` transformation, the client can use a _custom aggregate_. A custom aggregate can be considered as a virtual property that aggregates the input set. It's calculated on the server side. The client doesn't know _How_ the custom aggregate is calculated. They can only be used for the special case when a default aggregation method can be specified declaratively on the server side for a measure. A custom aggregate is declared in the CDS model as follows: * The measure must be annotated with an `@Aggregation.default` annotation that specifies the aggregation method. * The CDS entity should be annotated with an `@Aggregation.CustomAggregate` annotation to expose the custom aggregate to the client. ```cds @Aggregation.CustomAggregate#stock : 'Edm.Decimal' entity Books as projection on bookshop.Books { ID, title, @Aggregation.default: #SUM stock }; ``` With this definition, it's now possible to use the custom aggregate `stock` in an `aggregate` transformation: ```http GET /Books?$apply=aggregate(stock) HTTP/1.1 ``` which is equivalent to: ```http GET /Books?$apply=aggregate(stock with sum as stock) HTTP/1.1 ``` #### Currencies and Units of Measure > Source: /docs/guides/protocols/odata#currencies-and-units-of-measure If a property represents a monetary amount, it may have a related property that indicates the amount's *currency code*. Analogously, a property representing a measured quantity can be related to a *unit of measure*. To indicate that a property is a currency code or a unit of measure, it can be annotated with the [Semantics Annotations](https://help.sap.com/docs/SAP_NETWEAVER_750/cc0c305d2fab47bd808adcad3ca7ee9d/fbcd3a59a94148f6adad80b9c97304ff.html) `@Semantics.currencyCode` or `@Semantics.unitOfMeasure`. The aggregation method (typically, sum) is specified with the `@Aggregation.default` annotation. ```cds @Aggregation.CustomAggregate#amount : 'Edm.Decimal' @Aggregation.CustomAggregate#currency : 'Edm.String' entity Sales { key id : GUID; productId : GUID; @Semantics.amount.currencyCode: 'currency' @Aggregation.default: #SUM amount : Decimal(10,2); @Semantics.currencyCode currency : String(3); } ``` All properties annotated with `@Semantics.currencyCode` or `@Semantics.unitOfMeasure` are exposed as a [custom aggregate](./odata#custom-aggregates) with the property's name that returns: * The property's value if it's unique within a group of dimensions * `null` otherwise A custom aggregate for a currency code or unit of measure should also be exposed by the `@Aggregation.CustomAggregate` annotation. Moreover, a property for a monetary amount or a measured quantity should be annotated with `@Semantics.amount.currencyCode` or `@Semantics.quantity.unitOfMeasure` to reference the corresponding property that holds the amount's currency code or the quantity's unit of measure, respectively. ### Other Features > Source: /docs/guides/protocols/odata#other-features | Feature | Node.js | Java | |-----------------------------------------|:-------:|:-----:| | use path expressions in transformations | | | | chain transformations | | | | chain transformations within group by | | | | `groupby` with `rollup`/`$all` | | | | `$expand` result set of `$apply` | | | | `$filter`/`$search` result set | | | | sort result set with `$orderby` | | | | paginate result set with `$top`/`$skip` | | | ## Open Types > Source: /docs/guides/protocols/odata#open-types An entity type or a complex type may be declared as _open_, which allows clients to add properties dynamically to instances of the type. Clients do this by specifying uniquely named property values in the payload used to insert or update an instance of the type. To indicate that the entity or complex type is open, annotate the corresponding type with `@open`: ```cds service CatalogService { @open // [!code focus] entity Book { // [!code focus] key id : Integer; // [!code focus] } // [!code focus] } ``` The _cds build_ for OData v4 renders the entity type `Book` in `edmx` with the [`OpenType` attribute](https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_OpenEntityType) set to `true`: ```xml // [!code focus] ``` The entity `Book` is open, which allows the client to enrich the entity with additional properties. Example 1: ```json {"id": 1, "title": "Tow Sawyer"} ``` Example 2: ```json {"title": "Tow Sawyer", "author": { "name": "Mark Twain", "age": 74 } } ``` Open types can also be referenced in non-open types and entities. This, however, doesn't make the referencing entity or type open. ```cds service CatalogService { type Order { guid: Integer; book: Book; } @open // [!code focus] type Book {} // [!code focus] } ``` The following payload for `Order` is allowed: `{"guid": 1, "book": {"id": 2, "title": "Tow Sawyer"}}` Note that type `Order` itself is not open, so it doesn't allow dynamic properties, in contrast to type `Book`. ::: warning Dynamic properties are not persisted in the underlying data source automatically and must be handled completely by custom code. ::: ### Java Type Mapping > Source: /docs/guides/protocols/odata#java-type-mapping #### Simple Types > Source: /docs/guides/protocols/odata#simple-types The simple values of a deserialized JSON payload can be of type: `String`, `Boolean`, `Number` or simply an `Object` for `null` values. |JSON | Java Type of the `value` | |-------------------------|--------------------------------| |`{"value": "Tom Sawyer"}`| `java.lang.String` | |`{"value": true}` | `java.lang.Boolean` | |`{"value": 42}` | `java.lang.Number` (Integer) | |`{"value": 36.6}` | `java.lang.Number` (BigDecimal)| |`{"value": null}` | `java.lang.Object` | #### Structured Types > Source: /docs/guides/protocols/odata#structured-types The complex and structured types are deserialized to `java.util.Map`, whereas collections are deserialized to `java.util.List`. |JSON | Java Type of the `value` | |-------------------------------------------------------------------|--------------------------------------| |`{"value": {"name": "Mark Twain"}}` | `java.util.Map` | |`{"value":[{"name": "Mark Twain"}, {"name": "Charlotte Bronte"}}]}`| `java.util.List>`| ## Singletons > Source: /docs/guides/protocols/odata#singletons A singleton is a special one-element entity introduced in OData V4. You can address it directly by its name from the service root without specifying the entity's keys. Annotate an entity with `@odata.singleton` or `@odata.singleton.nullable` to use it as a singleton within a service, for example: ```cds service Sue { @odata.singleton entity MySingleton { key id : String; // can be omitted in OData v4.01 prop : String; assoc : Association to myEntity; } } ``` You can also define it as an ordered `SELECT` from another entity: ```cds service Sue { @odata.singleton entity OldestEmployee as select from Employees order by birthyear; } ``` ### Requesting Singletons > Source: /docs/guides/protocols/odata#requesting-singletons As mentioned earlier, you can access singletons without specifying keys in the request URL. They can contain navigation properties, and other entities can include singletons as their navigation properties as well. The `$expand` query option is also supported. ```http GET …/MySingleton GET …/MySingleton/prop GET …/MySingleton/assoc GET …/MySingleton?$expand=assoc ``` ### Updating Singletons > Source: /docs/guides/protocols/odata#updating-singletons The following request updates a _prop_ property of a singleton _MySingleton_: ```http PATCH/PUT …/MySingleton {prop: “New value”} ``` ### Deleting Singletons > Source: /docs/guides/protocols/odata#deleting-singletons A `DELETE` request to a singleton is possible only if you annotate a singleton with `@odata.singleton.nullable`. An attempt to delete a singleton annotated with `@odata.singleton` results in an error. ### Creating Singletons > Source: /docs/guides/protocols/odata#creating-singletons Since singletons represent a one-element entity, the system doesn't support a `POST` request.
## V2 Support > Source: /docs/guides/protocols/odata#v2-support While CAP defaults to OData V4, the latest protocol version, older projects may need to fall back to OData V2, for example, to keep using existing V2-based UIs. ::: warning OData V2 is deprecated. Use OData V2 only if you need to support existing UIs or if you need to use specific controls that don't work with V4 _yet_, such as tree tables (sap.ui.table.TreeTable). ::: ### Enabling OData V2 via CDS OData V2 Adapter in Node.js Apps > Source: /docs/guides/protocols/odata#enabling-odata-v2-via-cds-odata-v2-adapter-in-nodejs-apps CAP Node.js supports serving the OData V2 protocol through the [_OData V2 adapter for CDS_](https://www.npmjs.com/package/@cap-js-community/odata-v2-adapter), which translates between the OData V2 and V4 protocols. For Node.js projects, add the CDS OData V2 adapter as express.js middleware as follows: 1. Add the adapter package to your project: ```sh npm add @cap-js-community/odata-v2-adapter ``` 2. Access OData V2 services at [http://localhost:4004/odata/v2/${path}](http://localhost:4004/odata/v2). 3. Access OData V4 services at [http://localhost:4004/odata/v4/${path}](http://localhost:4004/odata/v4) (as before). Example: Read service metadata for `CatalogService`: - CDS: ```cds @path:'/browse' service CatalogService { ... } ``` - OData V2: `GET http://localhost:4004/odata/v2/browse/$metadata` - OData V4: `GET http://localhost:4004/odata/v4/browse/$metadata` [Find detailed instructions at **@cap-js-community/odata-v2-adapter**.](https://www.npmjs.com/package/@cap-js-community/odata-v2-adapter){.learn-more} ### Using OData V2 in Java Apps > Source: /docs/guides/protocols/odata#using-odata-v2-in-java-apps In CAP Java, serving the OData V2 protocol is supported natively by the [CDS OData V2 Adapter](../../java/migration#v2adapter). ## Miscellaneous > Source: /docs/guides/protocols/odata#miscellaneous ### Omitting Elements From APIs > Source: /docs/guides/protocols/odata#omitting-elements-from-apis Add annotation `@cds.api.ignore` to suppress unwanted entity fields (for example, foreign key fields) in APIs exposed from the CDS model, that is, OData or OpenAPI. For example: ```cds entity Books { ... @cds.api.ignore author : Association to Authors; } ``` Note that `@cds.api.ignore` is effective on regular elements that are rendered as `Edm.Property` only. The annotation doesn't suppress an `Edm.NavigationProperty`, which is rendered for associations or compositions. If you annotate a managed association, the system propagates the annotations to the (generated) foreign keys. In the previous example, the system mutes the foreign keys of the managed association `author` in the API. ### Absolute Context URL > Source: /docs/guides/protocols/odata#absolute-context-url In some scenarios, you need an absolute [context URL](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_ContextURL). In the Node.js runtime, you can achieve this through configuration `cds.odata.contextAbsoluteUrl`. You can use your own URL (including a protocol and a service path), for example: ```js cds.odata.contextAbsoluteUrl = "https://your.domain.com/yourService" ``` to customize the annotation as follows: ```json { "@odata.context":"https://your.domain.com/yourService/$metadata#Books(title,author,ID)", "value":[ {"ID": 201,"title": "Wuthering Heights","author": "Emily Brontë"}, {"ID": 207,"title": "Jane Eyre","author": "Charlotte Brontë"}, {"ID": 251,"title": "The Raven","author": "Edgar Allan Poe"} ] } ``` If you set `contextAbsoluteUrl` to something truthy that doesn't match `http(s)://*`, the system constructs an absolute path based on the environment of the application on a best effort basis. We encourage you to stay with the default relative format, if possible, as it's proxy safe. ### Parallel Processing of Atomicity Groups in Node.js Apps > Source: /docs/guides/protocols/odata#parallel-processing-of-atomicity-groups-in-nodejs-apps ###### Atomicity Groups > Source: /docs/guides/protocols/odata#atomicity-groups By default, atomicity groups in an OData `$batch` request are processed sequentially. In some specific scenarios, such as custom overview pages with multiple data sources, this may result in high roundtrip times. Hence, for `$batch` requests that exclusively contain `GET` requests, you can enable parallel processing of atomicity groups to improve throughput. cds.odata.max_batch_parallelization = 1 specifies the maximum number of atomicity groups processed concurrently. The default is `1`, which means sequential processing. ::: warning OData Specification Violation Parallel processing of atomicity groups is in conflict with the OData specification for `multipart/mixed`, which requires sequential processing. For example, the `continue-on-error` preference default can then no longer be adhered to. ::: # Publishing to OpenAPI > Source: /docs/guides/protocols/openapi You can convert CDS models to the [OpenAPI Specification](https://www.openapis.org), a widely adopted API description standard. ## Usage from CLI > Source: /docs/guides/protocols/openapi#usage-from-cli For example, this is how you convert all services in `srv/` and store the API files in the `docs/` folder: ```sh cds compile srv --service all -o docs --to openapi ``` With the `--openapi:diagram` parameter, you can also include a [yuml](https://yuml.me/) entity-relationship diagram of the service entities in the Open API file. ![A screenshot of the entity-relationship diagram.](assets/openapi-diagram.png){ .adapt } The default value of the server URL is the service base path as declared in the CDS source. If you have a **single server** and you want to set the server URL, use `--openapi:url ` option. Include the service path in the URL. For that, you can use the `${service-path}` variable. If you want to configure **multiple servers**, you can use `--openapi:servers ` which accepts stringified JSON of the server object. Here, you can pass multiple server objects by passing the stringified JSON objects as an array. ```sh cds compile srv service.cds --to openapi --openapi:servers "\"'[{\\\"url\\\":\\\"api.sandbox.com\\\",\\\"description\\\":\\\"Test URL\\\"},{\\\"url\\\":\\\"api.prod.com\\\",\\\"description\\\":\\\"Production URL\\\"}]'\"" ``` _Note:_ `--openapi:url` is ignored when this option is specified. Use the `--openapi:config-file ` option to provide configurations for all supported options in a configuration file. This file accepts a JSON format that incorporates all the OpenAPI compile options. Inline options take precedence over those defined in the configuration file. ```sh cds compile srv service.cds --to openapi --openapi:config-file configFile.json ``` Here is an example where `--openapi:config-file` option is used with other inline options: ```sh cds compile srv service.cds --to openapi --openapi:config-file configFile.json --odata-version 4.0 --openapi:diagram false ``` In the above command, the `--openapi:diagram` and `--odata-version` inline options override the `--openapi:diagram` and `--odata-version` options in the _configFile.json_ if they are also present there. ## Swagger UI > Source: /docs/guides/protocols/openapi#swagger-ui #### Embedded in Node.js > Source: /docs/guides/protocols/openapi#embedded-in-nodejs In Node.js apps, the standard Swagger UI can be served with the help of the [`cds-swagger-ui-express`](https://www.npmjs.com/package/cds-swagger-ui-express) package: ```sh npm add --save-dev cds-swagger-ui-express ``` Swagger UI is then served at `$api-docs/...`. Just follow the _Open API preview_ links on the index page: ![A screenshot showing the link to the Swagger UI.](assets/swagger-link.png){ .adapt} #### Embedded in Java > Source: /docs/guides/protocols/openapi#embedded-in-java Swagger UI is not available out of the box for CAP Java. However, check out this [commit in our CAP Java sample application](https://github.com/SAP-samples/cloud-cap-samples-java/commit/67f0ba618fc7da131d1a104f7a23e8b836e14d93) that demonstrates how to integrate a Swagger UI into your Spring Boot application. #### Online Swagger Editor > Source: /docs/guides/protocols/openapi#online-swagger-editor Alternatively, you can use the [online Swagger editor](https://editor.swagger.io/) with the OpenAPI files produced with the [CLI](#cli). In this case, you likely need to enable [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) because the `swagger.io` site needs to call `localhost`. You can use the [`cors` middleware](https://www.npmjs.com/package/cors), for example. ## Annotations > Source: /docs/guides/protocols/openapi#annotations The OData to OpenAPI Mapping can be fine-tuned via annotations in the CSDL (`$metadata`) documents. See [Frequently Asked Questions](#faq) for examples on how to use these annotations. ## Core Annotations > Source: /docs/guides/protocols/openapi#core-annotations | Term | Annotation Target | OpenAPI field | |--------------------|-------------------------------------------------------------------------------|-------------------------------------------------------------------| | `Computed` | Property | omit from Create and Update structures | | `DefaultNamespace` | Schema | path templates for actions and functions without namespace prefix | | `Description` | Action, ActionImport, Function, FunctionImport | `summary` of Operation Object | | `Description` | EntitySet, Singleton | `description` of Tag Object | | `Description` | EntityType | `title` of Request Body Object | | `Description` | ComplexType, EntityType, EnumerationType, TypeDefinition | `title` of Schema Object | | `Description` | Parameter | `description` of Parameter Object (fallback if `LongDescription` not present) | | `Description` | Property | `description` of Schema Object (fallback if `LongDescription` not present) | | `Description` | Schema, EntityContainer | `info.title` | | `Example` | Property | `example` of Schema Object | | `Immutable` | Property | omit from Update structure | | `LongDescription` | Action, ActionImport, Function, FunctionImport | `description` of Operation Object | | `LongDescription` | Parameter | `description` of Parameter Object | | `LongDescription` | Property | `description` of Schema Object | | `LongDescription` | Schema, EntityContainer | `info.description` | | `Permissions:Read` | Property | omit read-only properties from Create and Update structures | | `SchemaVersion` | Schema | `info.version` | ## Capabilities > Source: /docs/guides/protocols/openapi#capabilities | Term | Annotation Target | OpenAPI field | |-------------------------------------------------------------|----------------------|-------------------------------------------------------------------------------------------------------| | `CountRestrictions`
 `/Countable` | EntitySet | `$count` system query option for `GET` operation | | `DeleteRestrictions`
 `/Deletable` | EntitySet, Singleton | `DELETE` operation for deleting an existing entity | |  `/Description` | EntitySet, Singleton | `summary` of Operation Object | |  `/LongDescription` | EntitySet, Singleton | `description` of Operation Object | | `ExpandRestrictions`
 `/Expandable` | EntitySet, Singleton | `$expand` system query option for `GET` operations | | `FilterRestrictions`
 `/Filterable` | EntitySet | `$filter` system query option for `GET` operation | |  `/RequiredProperties` | EntitySet | required properties in `$filter` system query option for `GET` operation (parameter description only) | |  `/RequiresFilter` | EntitySet | `$filter` system query option for `GET` operation is `required` | | `IndexableByKey` | EntitySet | `GET`, `PATCH`, and `DELETE` operations for a single entity within an entity set | | `InsertRestrictions`
 `/Insertable` | EntitySet | `POST` operation for inserting a new entity | |  `/Description` | EntitySet | `summary` of Operation Object | |  `/LongDescription` | EntitySet | `description` of Operation Object | | `KeyAsSegmentSupported` | EntityContainer | `paths` URL templates use key-as-segment style instead of parenthesis style | | `NavigationRestrictions`
 `/RestrictedProperties` | EntitySet, Singleton | operations via a navigation path | |   `/DeleteRestrictions/...` | EntitySet, Singleton | `DELETE` operation for deleting a contained entity via a navigation path | |   `/FilterRestrictions/...` | EntitySet, Singleton | `$filter` system query option for reading related entities via a navigation path | |   `/InsertRestrictions/...` | EntitySet, Singleton | `POST` operation for inserting a new related entity via a navigation path | |   `/ReadByKeyRestrictions/...` | EntitySet, Singleton | `GET` operation for reading a contained entity by key via a navigation path | |   `/ReadRestrictions/...` | EntitySet, Singleton | `GET` operation for reading related entities via a navigation path | |   `/SearchRestrictions/...` | EntitySet, Singleton | `$search` system query option for reading related entities via a navigation path | |   `/SelectSupport/...` | EntitySet, Singleton | `$select` system query option for reading related entities via a navigation path | |   `/SkipSupported` | EntitySet, Singleton | `$skip` system query option for reading contained entities via a navigation path | |   `/SortRestrictions/...` | EntitySet, Singleton | `$orderby` system query option for reading related entities via a navigation path | |   `/TopSupported` | EntitySet, Singleton | `$top` system query option for reading contained entities via a navigation path | |   `/UpdateRestrictions/...` | EntitySet, Singleton | `PATCH` operation for modifying a contained entity via a navigation path | |  `/Description` | EntitySet | `summary` of Operation Object | |  `/LongDescription` | EntitySet | `description` of Operation Object | | `ReadRestrictions`
 `/Readable` | EntitySet, Singleton | `GET` operation for reading an entity set or singleton | |  `/Description` | EntitySet, Singleton | `summary` of Operation Object | |  `/LongDescription` | EntitySet, Singleton | `description` of Operation Object | |  `ReadByKeyRestrictions`
  `/Readable` | EntitySet | `GET` operation for reading a single entity by key | | `SearchRestrictions`
 `/Searchable` | EntitySet | `$search` system query option for `GET` operation | | `SelectSupport`
 `/Supported` | EntitySet, Singleton | `$select` system query option for `GET` operation | | `SkipSupported` | EntitySet | `$skip` system query option for `GET` operation | | `SortRestrictions`
 `/NonSortableProperties` | EntitySet | properties not listed in `$orderby` system query option for `GET` operation | |  `/Sortable` | EntitySet | `$orderby` system query option for `GET` operation | | `TopSupported` | EntitySet | `$top` system query option for `GET` operation | | `UpdateRestrictions`
 `/Updatable` | EntitySet, Singleton | `PATCH` operation for modifying an existing entity | |  `/Description` | EntitySet, Singleton | `summary` of Operation Object | |  `/LongDescription` | EntitySet, Singleton | `description` of Operation Object | | `BatchSupport`
 `/Supported` | EntityContainer | `Batch` Support for the service | ## Validation > Source: /docs/guides/protocols/openapi#validation | Term | Annotation Target | OpenAPI field | |-----------------|-------------------|-----------------------------------------------------------| | `AllowedValues` | Property | `enum` of Schema Object - list of allowed (string) values | | `Exclusive` | Property | `exclusiveMinimum`/`exclusiveMaximum` of Schema Object | | `Maximum` | Property | `maximum` of Schema Object | | `Minimum` | Property | `minimum` of Schema Object | | `Pattern` | Property | `pattern` of Schema Object | ## Authorization > Source: /docs/guides/protocols/openapi#authorization | Term | Annotation Target | OpenAPI field | |-------------------|-------------------|--------------------------------------------------------------------------------| | `Authorizations` | EntityContainer | `securitySchemes` of Components Object/`securityDefinitions` of Swagger Object | | `SecuritySchemes` | EntityContainer | `security` of OpenAPI/Swagger Object | This is an example of a CDS service annotated with the annotations above: ```cds annotate MyService with @( Authorization: { Authorizations: [ { $Type : 'Authorization.Http', Name : 'Basic', Scheme : 'basic' }, { $Type : 'Authorization.Http', Name : 'JWT', Scheme : 'bearer', BearerFormat : 'JWT' }, { $Type : 'Authorization.OAuth2ClientCredentials', Name : 'OAuth2', Scopes : [{ Scope : 'some_scope', Description: 'Scope description' }], RefreshUrl : 'https://some.host/oauth/token/refresh', TokenUrl : 'https://some.host/oauth/token' }, ], SecuritySchemes: [ { Authorization : 'Basic' }, { Authorization : 'JWT', RequiredScopes : [] }, { Authorization : 'OAuth2' }, ] } ); ``` [See it in context.](https://github.com/chgeo/cds-swagger-ui-express/blob/651013b529168b30c024f8653c249f170ba9d114/tests/app/services.cds#L35-L55){.learn-more} ## Common > Source: /docs/guides/protocols/openapi#common | Term | Annotation Target | OpenAPI field | |--------------------|------------------------------|--------------------------------------------------------------------| | `Label` | EntitySet, Singleton | `name` of Tag Object and entry in `tags` array of Operation Object | ## OpenAPI > Source: /docs/guides/protocols/openapi#openapi | Term | Annotation Target | OpenAPI field | |-------------------|-------------------|--------------------------------------------------------------------------------| | `externalDocs` | EntityContainer | Links to external documentation that explain more about APIs are helpful to developers. | | `Extensions` | EntityContainer | To add the sap defined (`x-sap`) specification extensions. This annotation can an be used in root, entity and in function/action level. | This is an example of a CDS service annotated with the annotations above: ```cds annotate SampleService with @( OpenAPI:{ externalDocs: { description: 'API Guide', url : 'https://help.sap.com/docs/product/sample.html' }, Extensions: { ![compliance-level]: 'sap:base:v1' } } ); ``` ## Frequently Asked Questions > Source: /docs/guides/protocols/openapi#frequently-asked-questions Examples for typical questions on how to fine-tune the generated OpenAPI descriptions. ### Suppress GET (list and by-key) on an entity set? > Source: /docs/guides/protocols/openapi#suppress-get-list-and-by-key-on-an-entity-set To suppress both types of GET requests to an entity set, annotate it with ```json "@Capabilities.ReadRestrictions": { "Readable": false } ``` ### Suppress GET (list) on an entity set? > Source: /docs/guides/protocols/openapi#suppress-get-list-on-an-entity-set To suppress only GET list requests to an entity set and still allow GET by-key, annotate it with ```json "@Capabilities.ReadRestrictions": { "Readable": false, "ReadByKeyRestrictions": { "Readable": true } } ``` ### Suppress GET (by-key) on an entity set? > Source: /docs/guides/protocols/openapi#suppress-get-by-key-on-an-entity-set To suppress only GET by-key requests to an entity set and still allow GET list, annotate it with ```json "@Capabilities.ReadRestrictions": { "ReadByKeyRestrictions": { "Readable": false } } ``` # Publishing to AsyncAPI > Source: /docs/guides/protocols/asyncapi You can convert events in CDS models to the [AsyncAPI specification](https://www.asyncapi.com), a widely adopted standard used to describe and document message-driven asynchronous APIs. ## Usage from CLI > Source: /docs/guides/protocols/asyncapi#usage-from-cli Use the following command to convert all services in `srv/` and store the generated AsyncAPI documents in the `docs/` folder: ```sh cds compile srv --service all -o docs --to asyncapi ``` For each service that is available in the `srv/` files, an AsyncAPI document with the service name is generated in the output folder. If you want to generate one AsyncAPI document for all the services, you can use `--asyncapi:merged` flag: ```sh cds compile srv --service all -o docs --to asyncapi --asyncapi:merged ``` [Learn how to programmatically convert the CSN file into an AsyncAPI Document](../../node.js/cds-compile#asyncapi){.learn-more} ## Presets > Source: /docs/guides/protocols/asyncapi#presets Use presets to add configuration for the AsyncAPI export tooling. ::: code-group ```json [.cdsrc.json] { "export": { "asyncapi": { "application_namespace": "sap.example" [...] } } } ``` ::: | Term | Preset Target | AsyncAPI field | Remarks | |-------------------------|---------------|-------------------------------|--------------------------------------------------------------------------------------------------------------------------| | `merged.title` | Service | info.title | Mandatory when `--asyncapi:merged` flag is given.
`title` from here is used in the generated AsyncAPI document. | | `merged.version` | Service | info.version | Mandatory when `--asyncapi:merged` flag is given.
`version` from here is used in the generated AsyncAPI document | | `merged.description` | Service | info.description | Optional when `--asyncapi:merged` flag is given.
`description` from here is used in the generated AsyncAPI document. | | `merged.short_text` | Service | x-sap-shortText | Optional when `--asyncapi:merged` flag is given.
The value from here is used in the generated AsyncAPI document. | | `application_namespace` | Document | x-sap-application-namespace | Mandatory | | `event_spec_version` | Event | x-sap-event-spec-version | | | `event_source` | Event | x-sap-event-source | | | `event_source_params` | Event | x-sap-event-source-parameters | | | `event_characteristics` | Event | x-sap-event-characteristics | | ## Annotations > Source: /docs/guides/protocols/asyncapi#annotations Use annotations to add configuration for the AsyncAPI export tooling. ::: tip Annotations will take precedence over [presets](#presets). ::: | Term (`@AsyncAPI.`) | Annotation Target | AsyncAPI field | Remarks | |------------------------|-------------------|-------------------------------|-------------------------------------------------------------------------------------------------------------------------| | `Title` | Service | info.title | Mandatory | | `SchemaVersion` | Service | info.version | Mandatory | | `Description` | Service | info.description | | | `StateInfo` | Service | x-sap-stateInfo | | | `ShortText` | Service | x-sap-shortText | | | `EventSpecVersion` | Event | x-sap-event-spec-version | | | `EventSource` | Event | x-sap-event-source | | | `EventSourceParams` | Event | x-sap-event-source-parameters | | | `EventCharacteristics` | Event | x-sap-event-characteristics | | | `EventStateInfo` | Event | x-sap-stateInfo | | | `EventSchemaVersion` | Event | x-sap-event-version | | | `EventType` | Event | | Optional; The value from this annotation will be used to
overwrite the default event type in the AsyncAPI document. | For example: ```cds @AsyncAPI.Title : 'CatalogService Events' @AsyncAPI.SchemaVersion: '1.0.0' @AsyncAPI.Description : 'Events emitted by the CatalogService.' service CatalogService { @AsyncAPI.EventSpecVersion : '2.0' @AsyncAPI.EventCharacteristics: { ![state-transfer]: 'full-after-image' } @AsyncAPI.EventSchemaVersion : '1.0.0' event SampleEntity.Changed.v1 : projection on CatalogService.SampleEntity; } ``` ## Extensions > Source: /docs/guides/protocols/asyncapi#extensions `@AsyncAPI.Extensions` can be used to provide arbitrary extensions. If a specific annotation exists for a given extension, it takes precedence over the definition using @AsyncAPI.Extensions. For example, if both `@AsyncAPI.ShortText` and `@AsyncAPI.Extensions: { ![sap-shortText]: 'baz' }` are provided, the value from `@AsyncAPI.ShortText` will override the one defined in @AsyncAPI.Extensions. For example: ```cds @AsyncAPI.Extensions : { ![foo-bar] : 'baz', ![sap-shortText] : 'Service Base 1' } service CatalogService { @AsyncAPI.Extensions : { ![sap-event-source] : '/{region}/sap.app.test' } event SampleEntity.Changed.v1 : projection on CatalogService.SampleEntity; } ``` The `@AsyncAPI.Extensions` annotation can be applied at both the service level and the event level. Since the AsyncAPI specification requires all extensions to be prefixed with `x-`, the compiler will automatically add this prefix. Therefore, do not include the `x-` prefix when specifying extensions in `@AsyncAPI.Extensions`. ### Behavior with `--merged` flag > Source: /docs/guides/protocols/asyncapi#behavior-with---merged-flag When the `--merged` CLI flag is used: - Extensions defined via `@AsyncAPI.Extensions` on `services` are **ignored**. - Extensions defined via `@AsyncAPI.Extensions` on `events` remain effective and are applied as expected. ## Type Mapping > Source: /docs/guides/protocols/asyncapi#type-mapping CDS Type to AsyncAPI Mapping | CDS Type | AsyncAPI Supported Types | |----------------------------------------|-----------------------------------------------------------------------------------------------------| | `UUID` | `{ "type": "string", "format": "uuid" }` | | `Boolean` | `{ "type": "boolean" }` | | `Integer` | `{ "type": "integer" }` | | `Integer64` | `{ "type": "string", "format": "int64" }` | | `Decimal`, `{precision, scale}` | `{ "type": "string", "format": "decimal", "x-sap-precision": , "x-sap-scale": }` | | `Decimal`, without scale | `{ "type": "string", "format": "decimal", "x-sap-precision": }` | | `Decimal`, without precision and scale | `{ "type": "string", "format": "decimal" }` | | `Double` | `{ "type": "number" }` | | `Date` | `{ "type": "string", "format": "date" }` | | `Time` | `{ "type": "string", "format": "partial-time" }` | | `DateTime` | `{ "type": "string", "format": "date-time" }` | | `Timestamp` | `{ "type": "string", "format": "date-time" }` | | `String`, `{maxLength}` | `{ "type": "string", "maxLength": length }` | | `Binary`, `{maxLength}` | `{ "type": "string", "maxLength": length }` | | `LargeBinary` | `{ "type": "string" }` | | `LargeString` | `{ "type": "string" }` | # Model Context Protocol Adapter > Source: /docs/guides/protocols/mcp The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is an open-source standard that enables direct integration between large language model (LLM) applications and external data sources. Any CAP service can be turned into an MCP server, allowing AI agents and LLM-powered tools to interact with the service without additional implementation work. All that is required is annotating it with [ `@mcp`](#serving-mcp). From CAP perspective MCP is just another protocol which we serve similar to _OData_, _GraphQL_, _REST_, or _HCQL_. > [!caution] SAP API Policy Applies! > The CAP MCP adapter, as documented herein, is designed exclusively to expose _custom_ CAP application services via MCP. > It is **not an SAP-endorsed architecture**, data service, or service-specific pathway for purposes of section 2.2.2 of the [_SAP API Policy_](https://help.sap.com/doc/sap-api-policy), and should not be relied upon as a basis for compliance with any exception described in that section. > In particular, it is not an endorsed pathway for exposing, proxying, or providing agentic access to _SAP Application APIs_ via MCP. > For SAP-endorsed architectures covering agentic access to SAP APIs, refer to the reference architectures published on the > **[SAP Architecture Center](https://architecture.learning.sap.com/docs/ref-arch/98efa0)**. > [!note] > > This guide is about the *MCP Adapter* – for example, as provided through the [*@cap-js/mcp*](https://github.com/cap-js/mcp) plugin – which powers domain-specific application use cases, for example, to respond to questions for CAP-based applications like *"List all overstocked books"*. > > In parallel, there's also the *MCP Server* plugin ([*@cap-js/mcp-server*](https://github.com/cap-js/mcp-server)), which serves a different purpose, though, that is: AI-assisted *development* of CAP projects. ## Preliminaries > Source: /docs/guides/protocols/mcp#preliminaries Following are one-time preparatory setup tasks. Basically, you need to ensure that you have access to LLM(s) to use with your MCP clients in local test-drives.
### Get Sample > Source: /docs/guides/protocols/mcp#get-sample We use the [`@capire/bookshop`](https://github.com/capire/bookshop) as a running sample hereinafter. Clone it and open it in VSCode as follows: ::: code-group ```shell [Node.js] git clone https://github.com/capire/bookshop ``` ```shell [Java] git clone https://github.com/SAP-samples/cloud-cap-samples-java bookshop ``` ::: ```shell code bookshop ``` ## Adding MCP Plugins > Source: /docs/guides/protocols/mcp#adding-mcp-plugins ### In CAP Node.js Projects > Source: /docs/guides/protocols/mcp#in-cap-nodejs-projects Within your project root run this to add the [`@cap-js/mcp`](https://github.com/cap-js/mcp) plugin: ```shell [Node.js] npm add @cap-js/mcp ``` ### In CAP Java Projects > Source: /docs/guides/protocols/mcp#in-cap-java-projects Add this to the *srv/pom.xml* file: ::: code-group ```xml [srv/pom.xml] com.sap.cds cds-adapter-mcp ${cds.services.version} ``` ::: > [!note] Not yet public > The feature is not yet released publicly. Stay tuned. > > Make sure internal artifactory is configured for Maven build as described in [*Java > Getting Started > Setting Up Local Development*](../../java/getting-started.md#local). ## Serving MCP > Source: /docs/guides/protocols/mcp#serving-mcp ### Annotate services with `@mcp` > Source: /docs/guides/protocols/mcp#annotate-services-with-mcp Simply add the `@mcp` annotation to an existing service to expose it via MCP. For example, add the following to `srv/cat-service.cds`: ::: code-group ```cds [srv/cat-service.cds] // Serve via OData, HCQL and REST annotate CatalogService with @odata @hcql @rest; annotate CatalogService with @mcp; // [!code focus] ``` ::: Start your server with `cds watch` or `mvn cds:watch` and note that the MCP server starts: ::: code-group ```shell [Node.js] [cds] - serving CatalogService { at: [ ..., '/mcp/browse' ], # [!code ++] ... } ``` ```shell [Java] INFO com.sap.cds.adapter.mcp.McpServlet : MCP Server initialized at endpoint '/mcp/browse' for service 'CatalogService' ``` ::: You can also specify an alternative path under which the MCP server should be served as follows: ```cds annotate CatalogService with @mcp:'books' ``` > [!tip] Just Another Protocol > From the perspective of a developer in a CAP-based project, `@mcp` is just another protocol for your services, similar to `@odata`, `@graphql`, `@rest`, or `@hcql`. The adapter takes care of the rest, with all the standard CAP features you know working out of the box also with MCP, including annotations like `@cds.query.limit`, etc. ### Tailored services for MCP use > Source: /docs/guides/protocols/mcp#tailored-services-for-mcp-use In case you want to tailor the entities or elements served via MCP you can also create specific services for MCP and annotate only those with `@mcp`. For example, you could create a `BooksService` that only exposes a subset of the entities of the `AdminService` like that: ::: code-group ```cds [srv/books-service.cds] using { AdminService } from './admin-service'; @mcp service BooksService { entity Authors as projection on AdminService.Authors { ID, name, books, } entity Books as projection on AdminService.Books { ID, title, stock, price, author, genre.name as genre, currency.name as currency, } } ``` ::: > => See also: [_Use Case-Oriented Services_](../../get-started/bookshop#use-case-specific-services) in the getting started guide. ### Adding Context Information > Source: /docs/guides/protocols/mcp#adding-context-information As LLMs rely heavily on context information to create high-quality output, the adapter evaluates existing doc comments and annotations to provide additional information about the service, entities, elements, actions, and parameters to the LLM. This information is included in the output of the [`describe`](#tool-describe) tool and can be used by agents to better understand the data model and available actions/functions. In particular, the following information is evaluated: - [Doc comments](../../cds/cdl#doc-comments) -> most recommended (Node.js only) - `@title` - `@description` > [!note] > Doc comments are only supported in Node.js. In Java, use `@title` and `@description` annotations instead. For example, you can add doc comments to your entities and their elements like that: ```cds /** * This is the author entity. * It contains information about book authors. */ entity Authors { /** The ID of the author. */ ID : Integer; /** The name of the author. */ name : String; /** The books written by the author. */ books : Association to many Books; } ``` ## Test-drive Locally > Source: /docs/guides/protocols/mcp#test-drive-locally With the above setup, your CAP services are exposed via MCP. To consume them, you need an MCP client. For local testing, you can use tools like [Claude Code](https://code.claude.com/docs/en/overview) or [Opencode](https://opencode.ai/), which have built-in support for MCP and can be easily configured to connect to your local CAP server. ### Using Claude Code > Source: /docs/guides/protocols/mcp#using-claude-code 1. Install [Claude Code](https://code.claude.com/docs/en/overview), for example via Homebrew: ```shell brew install claude-code ``` 2. Optionally add [Claude Code for VSCode](https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code): ```shell code --install-extension anthropic.claude-code ```
### Using OpenCode > Source: /docs/guides/protocols/mcp#using-opencode 1. Install [OpenCode](https://opencode.ai/), for example via npm: ```shell npm i -g opencode-ai ```
### Run your CAP server > Source: /docs/guides/protocols/mcp#run-your-cap-server With an MCP Client installed locally, (re-)run your CAP server in a terminal and keep it running to serve MCP requests. ::: code-group ```shell [Node.js] cds watch ``` ```shell [Java] mvn cds:watch ``` ::: > [!tip] Using Autowired CAP Services > Whenever you start your application, the MCP adapter automatically registers all MCP endpoints with local MCP clients, so you can just go ahead and run queries from your MCP client without any additional configuration. This makes it super easy to test and interact with your services via MCP during development. Learn more about that in section [*Autowired MCP Clients*](#autowired-mcp-clients) below. ### Running Queries > Source: /docs/guides/protocols/mcp#running-queries With one of the above clients, you can now run queries against your local MCP server. #### With Claude Code CLI: > Source: /docs/guides/protocols/mcp#with-claude-code-cli ```shell claude "list books with authors and genres" ``` ::: code-group ```zsh [=> Output] ⏺ cds:AdminService - query (MCP)(entity: "Books", select: ["ID","title","author.name","genre.name","stock","price"], limit: 20) ⎿  { "entity": "Books", "count": 5, … +43 lines (ctrl+o to expand) ┌─────┬───────────────────┬───────────────────┬─────────┬───────┬────────┐ │ ID │ Title │ Author │ Genre │ Stock │ Price │ ├─────┼───────────────────┼───────────────────┼─────────┼───────┼────────┤ │ 201 │ Wuthering Heights │ Emily Brontë │ Drama │ 12 │ 11.11 │ ├─────┼───────────────────┼───────────────────┼─────────┼───────┼────────┤ │ 207 │ Jane Eyre │ Charlotte Brontë │ Drama │ 11 │ 12.34 │ ├─────┼───────────────────┼───────────────────┼─────────┼───────┼────────┤ │ 251 │ The Raven │ Edgar Allan Poe │ Mystery │ 333 │ 13.13 │ ├─────┼───────────────────┼───────────────────┼─────────┼───────┼────────┤ │ 252 │ Eleonora │ Edgar Allan Poe │ Romance │ 555 │ 14.00 │ ├─────┼───────────────────┼───────────────────┼─────────┼───────┼────────┤ │ 271 │ Catweazle │ Richard Carpenter │ Fantasy │ 22 │ 150.00 │ └─────┴───────────────────┴───────────────────┴─────────┴───────┴────────┘ 5 books total across 4 genres (Drama, Mystery, Romance, Fantasy) and 4 authors. ``` ::: Here's the same query ran in Claude Code for VSCode: ![Claude Code interface displaying query results in VSCode editor with a table showing books data, a sidebar with available tools and prompts, and syntax highlighting indicating the integration of MCP tools with the code editor environment](./assets/mcp/claude-vscode.png){} #### With Opencode CLI: > Source: /docs/guides/protocols/mcp#with-opencode-cli ```shell opencode run list books with authors and genres ``` ::: code-group ```zsh [=> Output] ⚙ cds_AdminService_query {"entity":"Books","select":["ID","title","stock","price","author.name","genre.name"],"limit":20} Here are the books with their authors and genres: | ID | Title | Author | Genre | Stock | Price | |-----|-------------------|-------------------|---------|-------|--------| | 201 | Wuthering Heights | Emily Brontë | Drama | 12 | 11.11 | | 207 | Jane Eyre | Charlotte Brontë | Drama | 11 | 12.34 | | 251 | The Raven | Edgar Allan Poe | Mystery | 333 | 13.13 | | 252 | Eleonora | Edgar Allan Poe | Romance | 555 | 14.00 | | 271 | Catweazle | Richard Carpenter | Fantasy | 22 | 150.00 | 5 books total. Edgar Allan Poe has two entries, and Drama is the most common genre. ``` ::: You can also run `opencode web` to open the OpenCode web interface, which provides a more user-friendly way to interact with your MCP servers, including features like tool inspection and query building. Here's a screenshot of a simple session: ![OpenCode web interface dashboard showing a sidebar with available MCP tools and a main panel displaying query results in a table format with database records and their properties](./assets/mcp/opencode-web.png){} ### Inspect Log Output > Source: /docs/guides/protocols/mcp#inspect-log-output When you run queries, you can inspect the log output of your CAP server to see the incoming MCP requests and how they are processed. This can be helpful for debugging and understanding the interaction between the MCP client and your CAP services. For example, for the above query, you should see log output similar to this: ::: code-group ```js [Node.js] [mcp] - query { service: 'AdminService', entity: 'Books', select: [ { ref: [ 'ID' ] }, { ref: [ 'title' ] }, { ref: [ 'stock' ] }, { ref: [ 'price' ] }, { ref: [ 'author', 'name' ] }, { ref: [ 'genre', 'name' ] } ] } ``` ```js [Java] INFO com.sap.cds.adapter.mcp.McpServlet : Received MCP query request for entity 'Books' with select fields [ID, title, author.name, genre.name, stock, price] and limit 20 ``` ::: ## Under the Hood > Source: /docs/guides/protocols/mcp#under-the-hood ### Autowired MCP Clients > Source: /docs/guides/protocols/mcp#autowired-mcp-clients Whenever you start your application, the MCP adapter automatically registers all MCP servers with local MCP clients – currently supported for [Claude Code](https://code.claude.com/docs) and [Opencode](https://opencode.ai/) - so you can just go ahead and run queries from your MCP client without any additional configuration. This makes it super easy to test and interact with your services via MCP during development. > [!tip] MCP servers > Note the distinction between the CAP server that listens on a certain port, and MCP servers which are just endpoints provided and served by the CAP server. We use the term "MCP server" despite this, to align with MCP terminology. During startup, information about the MCP server endpoints is added to the client-specific configuration files like that: ::: code-group ```json [~/.claude.json] { "mcpServers": { "cds:AdminService": { "type": "http", "url": "http://localhost:4004/mcp/admin", "headers": { "Authorization": "Basic YWxpY2U6" } }, "cds:CatalogService": { "type": "http", "url": "http://localhost:4004/mcp/browse", "headers": { "Authorization": "Basic YWxpY2U6" } } }, } ``` ```json [~/.config/opencode/opencode.json] { "$schema": "https://opencode.ai/config.json", "mcp": { "cds:AdminService": { "type": "remote", "url": "http://localhost:4004/mcp/admin", "headers": { "Authorization": "Basic YWxpY2U6" }, "enabled": true }, "cds:CatalogService": { "type": "remote", "url": "http://localhost:4004/mcp/browse", "headers": { "Authorization": "Basic YWxpY2U6" }, "enabled": true } } } ``` ::: When the application stops, the added configuration is removed. This is only intended for local development. > [!warning] For Development Only > The autowiring is only enabled during development (that is, when running `cds watch` or `mvn cds:watch`) and not meant for production use cases. #### Mock Authentication > Source: /docs/guides/protocols/mcp#mock-authentication Note that the automatic client configuration adds `Authorization` headers for the mock user `alice` (Node.js) or `privileged` (Java). If your service requires something different, you can customize the credentials via the `cds.mcp.autowire` configuration: ```json [package.json] { "cds": { "mcp": { "autowire": { "user": "admin", "password": "admin" } } } } ``` #### Opting out of Autowiring > Source: /docs/guides/protocols/mcp#opting-out-of-autowiring You can opt out of this by setting the `cds.mcp.autowire` option to `false`, like so in your `package.json`: ```json [package.json] { "cds": { "mcp": { "autowire": false } } } ``` Manually add the MCP server config to your client, for example with Claude Code CLI: ```shell claude mcp add --transport http CatalogService http://localhost:4004/mcp/browse ``` ### MCP served out of the box > Source: /docs/guides/protocols/mcp#mcp-served-out-of-the-box The adapter creates an MCP server per CAP service, hence each CAP application can expose multiple MCP servers. By default, the adapter creates the following tools for each MCP server, which can be used by LLMs and AI agents to interact with the service. > [!warning] > Tools are meant to be used by LLMs and AI agents and do not constitute a stable API. > They may change in the future based on the needs of LLMs and AI agents. For stable APIs, please use the existing CAP protocols like OData, REST, GraphQL, etc. #### Tool: `describe` > Source: /docs/guides/protocols/mcp#tool-describe This tool returns information about the entities and their elements exposed by the service. It also returns information about unbound actions and functions. If you do not provide a parameter, the tool describes all exposed entities, actions and functions. The optional parameter `entity` restricts the output to a single entity, the optional parameter `action` restricts the output to a single action/function. The tool provides an enum that lists all available entities, actions and functions. #### Tool: `query` > Source: /docs/guides/protocols/mcp#tool-query This tool is used to read data from the service. The only required parameter is `entity`, an enum that lists all entities exposed by the service. This tool takes all provided parameters and translates them to a [CQN](../../cds/cqn) query, which the service runs via `service.run(query)`. The parameter descriptions explain how to use them. Parameters of `query` requests: | Parameter | Description | |-----------|--------------------------------------------------------------------------------------------------------------| | select | Array of [`expr`](../../cds/cqn#expr) objects or `strings` ; allows path expressions along associations. | | entity | The entity to query (enum values from `describe`) | | where | Array of [`xpr`](../../cds/cqn#where) objects used as predicates used for filtering | | limit | An integer limiting the results to return | | one | Return a single record instead of an array. Implies `limit:1`; default: `false` | | distinct | Return only unique rows; default: `false` (Node.js only) | | groupBy | An array of [`ref`](../../cds/cqn#ref) objects or `strings` to group results. | | orderBy | List of objects to order the results (ref, sort, nulls) | #### Tool: `call_action` > Source: /docs/guides/protocols/mcp#tool-callaction This tool is used to call unbound actions or functions. The required parameter `action` is an enum that lists all unbound actions and functions exposed by the service. The parameters of the action or function to call can be provided via the optional parameter `parameters`, that must contain all required parameters of the action or function. The tool takes these parameters and calls the action or function on the service. ### Inspect the Tools > Source: /docs/guides/protocols/mcp#inspect-the-tools You can start an [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) to inspect tools: ```bash npx @modelcontextprotocol/inspector ``` The inspector should automatically open in your browser. 1. Select _Streamable HTTP_ as Transport Type. 2. Enter the URL of your service - for example, `http://localhost:4004/mcp/browse`. 3. Select `Via Proxy` as connection type and select _Connect_. 4. Go to the _Tools_ tab and select _List Tools_. 5. To get data, select the _query_ tool. 6. Choose an entity. 7. Scroll down and select _Run Tool_. ## Current Limitations > Source: /docs/guides/protocols/mcp#current-limitations ### Query and Actions Only > Source: /docs/guides/protocols/mcp#query-and-actions-only The MCP tools created by the adapter are currently focused on reading data and calling [**_unbound_** actions and functions](../../cds/cdl#actions) only. This means that you can use MCP to [`query`](#tool-query) data from your CAP services, while any data changes need to be implemented via unbound actions for now. For example, action `submitOrder` in the `CatalogService` ultimately creates an Order: ::: code-group ```cds [srv/catalog-service.cds] service CatalogService { ... @requires: 'authenticated-user' action submitOrder ( book: Books:ID, quantity: Integer ); // [!code focus] } ``` ::: Future versions of the adapter may add support for data changes using CREATE, UPDATE, and DELETE operations. ### Prompt Injection Attacks > Source: /docs/guides/protocols/mcp#prompt-injection-attacks > [!caution] > The MCP adapter does not perform any input validation or output validation to prevent prompt injection attacks. > Agents can potentially be manipulated by data returned from the service to execute unintended actions. For any deployment ensure you use infrastructure and practices that mitigate prompt injection risks and connect only to trusted MCP agents (e.g., Joule). ### Missing Governance Controls > Source: /docs/guides/protocols/mcp#missing-governance-controls > [!caution] > The adapter itself does not provide any built-in governance features: there is no automatic rate limiting, no specific audit logging of agent actions, no approval workflows for sensitive operations, and no policy enforcement layer. Before using MCP in a productive environment, put appropriate controls for example by using MCP Gateway of SAP Integration Suite or integrate with SAP Agent Gateway (not GA yet). > [!caution] > The CAP MCP adapter must not be used as a gateway or proxy for SAP Application APIs. The adapter is not an SAP-endorsed architecture, data service, or service-specific pathway under section 2.2.2 of the [_SAP API Policy_](https://help.sap.com/docs/business-accelerator-hub/sap-business-accelerator-hub/sap-api-policy) and is not an endorsed mechanism for exposing, proxying, or providing agentic access to SAP Application APIs. > Any use of SAP Application APIs must be in accordance with the [_SAP API Policy_](https://help.sap.com/docs/business-accelerator-hub/sap-business-accelerator-hub/sap-api-policy). For SAP-endorsed patterns on agentic access to SAP Application APIs, consult the [_SAP Architecture Center_](https://architecture.learning.sap.com/docs/ref-arch/98efa0) reference architectures. # Services & Platform Integration > Source: /docs/guides/integration/ CAP applications integrate at multiple levels — from reusing services and data within your application, to connecting with external microservices, to leveraging platform services for identity, storage, and observability. The guides in this section covers the various CAP-level service integration and data federation patterns, as well as platform capabilities available to your CAP projects. {.abstract}
[CAP-level Service Integration](calesi.md) : Learn how to consume and expose services using CAP's built-in service integration features (Calesi), enabling seamless communication between services at the framework level. [CAP-level Data Federation](data-federation.md) : On top of CAP-level service integration, CAP provides advanced data federation capabilities out-of-the-box, including service-level replication, HANA virtual tables, synonyms, and SAP data products. [Inner Loop Development](inner-loops.md) : Decouple parallel development across distributed teams by swapping production-grade services with local mocks during development. In the context of application service integration, mock imported APIs of remote services in consuming applications. [Reuse and Compose](reuse-and-compose) : Explore in this guide how to reuse and compose enhanced solutions by reusing modular services from other projects, and adapt them to your needs with solution-specific projections and extensions. # CAP-Level Service Integration > Source: /docs/guides/integration/calesi The *'Calesi'* Pattern {.subtitle} Integrating remote services - from other applications, third-party services, or platform services - is a fundamental aspect of cloud application development. CAP provides an easy and platform-agnostic way to do so: Remote services represented as CAP services, which you can consume _as if they were local_, while the CAP runtimes manage the communication and resilience details under the hood. Not the least, CAP mocks remote services automatically for local inner-loop development and testing. {.abstract} > [!tip] The 'Calesi' Pattern – Guiding Principles > > 1. Remote services are proxied by CAP services, ... → *everything's a CAP service* > 2. Consumed in protocol-agnostic ways → *... as if they were local* > 3. Mocked out of the box → *fast-track inner-loop development* > 4. With varying implementations → *evolution w/o disruption* > 5. Extensible through event handlers → *intrinsic extensibility* > > => Application developers stay at CAP level -> *Focused on Domain* [toc]:./ ## Preliminaries > Source: /docs/guides/integration/calesi#preliminaries ### Teaser > Source: /docs/guides/integration/calesi#teaser With CAP, Service integration is greatly simplified. Consumption of remote services from other applications, third-party services, or platform services is as easy as calling them _as if they were local_: 1. Clone the bookshop sample, and start the server in a terminal: ```shell git clone https://github.com/capire/bookshop cds watch bookshop ``` 2. Start *cds repl* in a second terminal and run this code: ```shell cds repl ``` ```js :line-numbers cats = await cds.connect.to ('http://localhost:4004/hcql/admin') await cats.read `Authors { ID, name, books { ID, title, genre.name as genre } }` ``` ::: The graphic below illustrates what happened here: ![Diagram illustrating CAP-level service integration showing two scenarios: Local services where Consumer connects to Service via CQL, and Remote services where Consumer connects to Proxy via CQL, Proxy connects to Protocol Adapter via OData, and Protocol Adapter connects to Service via CQL. ](assets/remoting.drawio.svg) Remote CAP services can be consumed using the same high-level, uniform APIs as for local services – that is, **_as if they were local_**. `cds.connect` automatically constructs remote proxies, which translate all local requests into protocol-specific ones, sent to remote services. Thereby also taking care of all connectivity, remote communication, principal propagation, as well as generic resilience. > [!note] Model Free > > Note that in the exercise above, the consumer side didn't even have any information about the service provider, except for the URL endpoint and protocols served, which it got from the service binding. In particular no API/service definitions at all – neither in *CDS*, *OData*, nor in *OpenAPI*. ### Overview > Source: /docs/guides/integration/calesi#overview While the above teaser nicely demonstrates the simplicity of CAP-level service integration, CAP can facilitate real-life integration scenarios even more, if we've captured APIs in CDS models. The remainder of this guide walks us through the steps to provide and share such APIs, import them to consuming apps as CDS models and use these in there as if they were local. The graphic below shows the flow of essential steps involved: ![Workflow diagram showing five numbered steps of CAP-level service integration. Service Provider box on left contains step 1 Service Definition in blue and Domain Models in gray. Packaged API box in center shows step 2 Service Interface in light gray. Service Consumer box on right displays step 4 Consumption Views in light blue and step 5 Own Models in blue. Arrows connect the components left to right. Below, numbered list describes: 1 Expose Service Interfaces as usual, 2 Export APIs using cds export and npm publish, 3 Import APIs using cds import or npm add, 4 Add Consumption Views defining what to consume, 5 Use with own models as if they were local. ](assets/calesi-overview.drawio.svg) #### ### The XTravels Sample > Source: /docs/guides/integration/calesi#the-xtravels-sample In this guide we use the _XTravels_ sample application as our running example. It's a modernized adaptation of the [ABAP Flight reference sample](https://help.sap.com/docs/abap-cloud/abap-rap/abap-flight-reference-scenario), reimplemented using CAP and split into two microservices: - The [*@capire/xflights*](https://github.com/capire/xflights) service provides flight-related master data, such as *Flights*, *Airports*, *Airlines*, and *Supplements* (like extra luggage, meals, etc.). It exposes this data via a CAP service API. - The [*@capire/xtravels*](https://github.com/capire/xtravels) application allows travel agents to plan travels on behalf of travellers, including bookings of flights. The application obtains *Customer* data from a SAP S/4HANA system, while it consumes *Flights*, *Airports*, and *Airlines* from *@capire/xflights*, as indicated by the green and blue areas in the screenshot below. ![XTravels application interface showing a travel request form. The interface displays customer information including name, email, and address fields highlighted in green, sourced from S/4HANA. Below that, a flight booking section shows departure and arrival airports, dates, and times highlighted in blue, sourced from the XFlights service. The layout demonstrates data federation from multiple backend systems presented in a unified user interface. ](assets/xtravels-screenshot.png) The resulting entity-relationship model: ![Architecture diagram showing three systems: XFlights on the left containing Flights, Airlines, and Airports entities in light blue, XTravels in the center containing Travels, Bookings, and Supplements entities in darker blue, and S/4HANA on the right containing Customers entity in gray. Arrows connect Bookings to Flights, Travels to Customers, and Bookings to Supplements, illustrating data relationships between the systems in a federated service integration pattern. ](assets/xtravels-sample.drawio.svg) From a service integration perspective, this sample mainly shows a data federation scenario, where the application consumes data from different upstream systems (XFlights and S/4HANA) – most frequently in a readonly fashion – to display it together with the application's local data. #### Getting Started > Source: /docs/guides/integration/calesi#getting-started [`cap/samples`]: #getting-started So, let's dive into the details of CAP-level service integration, using the XTravels sample as our running example. Clone both repositories as follows to follow along: ```sh mkdir -p cap/samples cd cap/samples git clone https://github.com/capire/xflights git clone https://github.com/capire/xtravels ``` ## Providing CAP-level APIs > Source: /docs/guides/integration/calesi#providing-cap-level-apis In case of CAP service providers, as for [*@capire/xflights*](https://github.com/capire/xflights), you define [CAP services](../services/index) for all inbound interfaces, which includes (private) interfaces to your application's UIs, as well as public APIs to any other remote consumers. ### Defining Service APIs > Source: /docs/guides/integration/calesi#defining-service-apis Open the _cap/samples/xflights_ folder in Visual Studio Code, and have a look at the service definition in `srv/data-service.cds` in there: ::: code-group ```cds :line-numbers [cap/samples/xflights/srv/data-service.cds] using sap.capire.flights as x from '../db/schema'; namespace sap.capire.flights; @odata @hcql service FlightsService { @readonly entity Flights as projection on x.Flights {flights.*,*}; @readonly entity Airlines as projection on x.Airlines; @readonly entity Airports as projection on x.Airports; } ``` ::: This declares a CAP service named `FlightsService`, served over _OData_ and _HCQL_ protocols, which exposes _Flights_, _Airlines_, and _Airports_ as readonly projections on underlying domain model entities, with _Flights_ as a denormalized view. #### Using Denormalized Views > Source: /docs/guides/integration/calesi#using-denormalized-views Let's have a closer look at the denormalized view for _Flights_, which basically flattens the association to `FlightConnection`. The projection `{flights.*,*}` shown in line 3 above, is a simplified version of the following actual definition found in `srv/data-service.cds`: ```cds :line-numbers=5 [cap/samples/xflights/srv/data-service.cds] @readonly entity Flights as projection on x.Flights {flights.*,*}; // [!code --] @readonly entity Flights as projection on x.Flights { *, // all fields from Flights flight.{*} excluding {ID}, // all fields from FlightConnection key flight.ID, // with flight ID preserved as key key date, // with date preserved as key } excluding { flight }; // which we flattened above ``` This definition is more complicated because we need to preserve the primary keys elements `flight.ID` and `date`, as OData disallows entities without keys. > [!tip] Use Case-Oriented Services > Denormalized views are a common way to tailor provided APIs to fit your use case. While normalization is required _within_ _XFlights_ to avoid redundancies, we flatten it here, to make life easier for external consumers. \ > => See also: [_Use Case-Oriented Services_](../../get-started/bookshop#use-case-specific-services) in the getting started guide. > ### Exporting APIs > Source: /docs/guides/integration/calesi#exporting-apis Use `cds export` to generate APIs for given [service definitions](#defining-service-apis). For example, run that within the _cap/samples/xflights_ folder for the service definition we saw earlier, which would print some output as shown below: ```shell cds export srv/data-service.cds ``` ```log Exporting APIs to apis/data-service ... > apis/data-service/services.csn > apis/data-service/index.cds > apis/data-service/package.json /done. ``` By default, it outputs to an `./apis/` subfolder, where `` is the `.cds` file's basename. Use the `--to` option to specify a different output folder. #### Exported Service Definitions > Source: /docs/guides/integration/calesi#exported-service-definitions The essential component of the generated output is the `services.csn` file, which contains a cleansed, ***interface-only*** version of your service definition. It includes the _inferred element signatures_ of served entities but removes all projections to underlying entities and their dependencies. To get an idea of the effect, run `cds export` in dry-run mode like this: ```shell cds export srv/data-service.cds --dry ``` ```zsh Kept: 6 • sap.capire.flights.FlightsService • sap.capire.flights.FlightsService.Flights • sap.capire.flights.FlightsService.Airlines • sap.capire.flights.FlightsService.Airports • sap.capire.flights.FlightsService.Supplements • sap.capire.flights.FlightsService.SupplementTypes Skipped: 31 - sap.capire.flights.Flights - sap.capire.flights.FlightConnections - sap.capire.flights.Airlines - sap.capire.flights.Airports - sap.capire.flights.Supplements - sap.capire.flights.SupplementTypes - Language - Currency - Country - Timezone - sap.common - sap.common.Locale - sap.common.Languages - sap.common.Countries - sap.common.Currencies - sap.common.Timezones - sap.common.CodeList - sap.common.TextsAspect - sap.common.FlowHistory - cuid - managed - temporal - User - sap.capire.flights.Supplements.texts - sap.capire.flights.SupplementTypes.texts - sap.common.Languages.texts - sap.common.Countries.texts - sap.common.Currencies.texts - sap.common.Timezones.texts - sap.capire.flights.FlightsService.Supplements.texts - sap.capire.flights.FlightsService.SupplementTypes.texts Total: 37 ``` ::: details Compare to original service definition... We can also compare the above to the respective output for the complete provided service like that: ```shell cds export srv/data-service.cds --dry > x.log cds minify srv/data-service.cds --dry > m.log code --diff *.log ``` This opens a diff view in VSCode, which would display these differences: ```zsh Kept: 26 # [!code --] Kept: 6 # [!code ++] • sap.capire.flights.FlightsService • sap.capire.flights.FlightsService.Flights •• sap.capire.flights.FlightsService.Airlines •• sap.capire.flights.FlightsService.Airports • sap.capire.flights.FlightsService.Supplements •• sap.capire.flights.FlightsService.SupplementTypes •• sap.capire.flights.Flights # [!code --] ••• sap.capire.flights.FlightConnections # [!code --] •••• sap.capire.flights.Airlines # [!code --] ••••• sap.common.Currencies # [!code --] •••••• sap.common.Currencies.texts # [!code --] ••••••• sap.common.Locale # [!code --] ••••••• sap.common.TextsAspect # [!code --] •••••• sap.common.CodeList # [!code --] ••••• Currency # [!code --] ••••• cuid # [!code --] •••• sap.capire.flights.Airports # [!code --] ••••• sap.common.Countries # [!code --] •••••• sap.common.Countries.texts # [!code --] ••••• Country # [!code --] •• sap.capire.flights.Supplements # [!code --] ••• sap.capire.flights.SupplementTypes # [!code --] •••• sap.capire.flights.SupplementTypes.texts # [!code --] ••• sap.capire.flights.Supplements.texts # [!code --] ••• sap.capire.flights.FlightsService.SupplementTypes.texts # [!code --] •• sap.capire.flights.FlightsService.Supplements.texts # [!code --] Skipped: 11 # [!code --] Skipped: 31 # [!code ++] ... ``` ::: In addition to the generated `services.csn` file, an `index.cds` file was added, which you can modify as needed. It won't be overridden on subsequent runs of `cds export`. ### Packaged APIs > Source: /docs/guides/integration/calesi#packaged-apis The third generated file is `package.json`: ::: code-group ```json [apis/data-service/package.json] { "name": "@capire/xflights-data-service", "version": "0.1.3" } ``` ```json [=> modified] { "name": "@capire/xflights-data-service", // [!code --] "name": "@capire/xflights-data", // [!code ++] "version": "0.1.3" } ``` ::: You can modify this file. `cds export` won't overwrite your changes. In our xflights/xtravels sample, we changed the package name to `@capire/xflights-data`. > [!tip] Yet Another CAP Package (YACAP) > The generated output is a complete CAP package. You can add additional files to the *./apis* subfolder: models in *.cds* files, data in *.csv* files, I18n bundles, or even *.js* or *.java* files with custom logic for consumers. #### Adding Initial Data and I18n Bundles > Source: /docs/guides/integration/calesi#adding-initial-data-and-i18n-bundles You can use these `cds export` options to add I18n bundles and initial data, which generates files next to the `.csn` file: ```shell cds export srv/data-service.cds --texts ``` ```log > apis/data-service/_i18n/i18n.properties > apis/data-service/_i18n/i18n_de.properties > apis/data-service/_i18n/i18n_fr.properties ``` ```shell cds export srv/data-service.cds --data ``` ```log > apis/data-service/data/sap.capire.flights.FlightsService.Flights.csv > apis/data-service/data/sap.capire.flights.FlightsService.Airlines.csv > apis/data-service/data/sap.capire.flights.FlightsService.Airports.csv > apis/data-service/data/sap.capire.flights.FlightsService.Supplements.csv ``` The `.csv` data comes from the source application's initial data, filtered and transformed for the exposed entities, including denormalizations and calculated fields. The application actually reads it via an instance of that service. #### Plug & Play Config > Source: /docs/guides/integration/calesi#plug--play-config Use the `--plugin` option to turn the package into a CAP plugin and benefit from CAP's plug & play configuration features in consuming apps: ```shell cds export srv/data-service.cds --plugin ``` This would add this to the generated output: ::: code-group ```js [apis/data-service/cds-plugin.js] // just a tag file for plug & play ``` ::: ::: code-group ```json [apis/data-service/package.json] { "name": "@capire/xflights-data", "version": "0.1.13", "cds": { // [!code focus] "requires": { // [!code focus] "sap.capire.flights.FlightsService": true // [!code focus] } // [!code focus] } // [!code focus] } ``` ::: ### Publishing APIs > Source: /docs/guides/integration/calesi#publishing-apis The output of `cds export` is a valid _npm_ or _Maven_ package, which can be published to any npm-compatible registry, such as the public [*npmjs.com*](https://www.npmjs.com/) registry, or private registries like [*GitHub Packages*](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry), [*Azure Artifacts*](https://learn.microsoft.com/en-us/azure/devops/artifacts/npm/npmrc), or [*JFrog Artifactory*](https://jfrog.com/confluence/display/JFROG/NPM+Registry). For example: ```shell npm publish ./apis/data-service ``` ::: details Using GitHub Packages ... Within the [_capire_](https://github.com/capire) org, we're publishing to [GitHub Packages](https://docs.github.com/packages), which requires you to `npm login` once like that, prior to publishing: ```sh npm login --scope=@capire --registry=https://npm.pkg.github.com ``` As for the password, use a 'Personal Access Token (classic)' with the `read:packages` scope (for retrieving and installing a package). Read more about that in the [_GitHub Packages_](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-npm-registry#authenticating-to-github-packages) docs. ::: ::: details Not using npm registries ... Instead of publishing to npm registries we can also share packages any other way. For example we could create an archive that we upload to some marketplace like [*SAP Business Accelerator Hub*](https://api.sap.com), or team-internal ones. For Node.js we'd use `npm pack` to create installable archives, which would print some output with the last line telling us the filename of the created archive: ```shell npm pack ./apis/data-service ``` ```zsh [=> output] npm notice ... npm notice 4.9kB services.csn npm notice 410B index.cds npm notice 61B package.json npm notice ... capire-xflights-data-0.1.13.tgz ``` > [!warning] > > Not using package registries like *npm* or *Maven* also means that you'll loose all their support for semver-based dependency management. ::: > [!tip] Best Practice: Using Proven Standards > CAP leverages standard and widely adopted package management tools and practices, such as _npm_ and _Maven_ for sharing and distributing reuse packages. This allows you to use established and battle-tested workflows and tools for versioning, publishing, consuming, and upgrading packages. At the same time it allows us to not reinvent those wheels, and focus on what matters most: allowing you to focus on domain, and be as productive as possible. ## Importing APIs > Source: /docs/guides/integration/calesi#importing-apis On the consumer side, like [*@capire/xtravels*](https://github.com/capire/xtravels) in our [sample scenario](#the-xtravels-sample), we import packaged APIs from CAP and non-CAP sources using `npm add` and `cds import` respectively. ### Packaged APIs > Source: /docs/guides/integration/calesi#packaged-apis-1 Import packaged APIs provided by CAP service providers like that: ```shell npm add @capire/xflights-data ``` This makes the exported models with all accompanying artifacts available in the target project's `node_modules` folder. In addition, it adds a respective package dependency to the consuming application's *package.json* like this: ::: code-group ```json [xtravels/package.json] {... "dependencies": { ... "@capire/xflights-data": "0.1.12" } } ``` ::: This allows us to update imported APIs later on using standard commands like `npm update`. ### OData APIs > Source: /docs/guides/integration/calesi#odata-apis You can also `cds import` APIs from other sources, such as OData APIs for customer data from SAP S/4 HANA systems: 1. Get an [_OData EDMX_](https://api.sap.com/api/API_BUSINESS_PARTNER/overview) source, for example, from [*SAP Business Accelerator Hub*](https://api.sap.com): ::: details Detailed steps through SAP Business Accelerator Hub ... - Open https://api.sap.com in your browser - Navigate to \> [_SAP S/4HANA Cloud Public Edition_](https://api.sap.com/products/SAPS4HANACloud) \> [_APIs_](https://api.sap.com/products/SAPS4HANACloud/apis) \> [_OData V2_](https://api.sap.com/products/SAPS4HANACloud/apis/ODATA) - Find and open [_Business Partner (A2X)_](https://api.sap.com/api/API_BUSINESS_PARTNER/overview) - Switch to the *API Specification* subtab. - Click the download icon next to *OData EDMX* to download the `.edmx` file. ::: 2. Import that to the current project: ```shell cds import ~/Downloads/API_BUSINESS_PARTNER.edmx ``` This copies the specified *.edmx* file into the `srv/external/` subfolder of your project, and generates a `.csn` file with the same basename next to it: ```zsh srv/external ├── API_BUSINESS_PARTNER.csn └── API_BUSINESS_PARTNER.edmx ``` > Add option `--as cds` to generate a human-readable `.cds` file instead of `.csn`. > [!tip] Import from other APIs > You can use `cds import` in the same way as for OData to import SAP data products, [_OpenAPI_](../protocols/openapi) definitions, [_AsyncAPI_](../protocols/asyncapi) definitions, or from [_ABAP RFC_](../../plugins/#abap-rfc). For example: > ```shell > cds import --data-product ... > cds import --odata ... > cds import --openapi ... > cds import --asyncapi ... > cds import --rfc ... > ``` > [Learn more about `cds import` in the tools guides.](../../tools/apis/cds-import){.learn-more} ### Reuse Packages > Source: /docs/guides/integration/calesi#reuse-packages If you find yourself importing the same APIs every time in new projects, you can create a package that imports the APIs once and reuse it instead. Reusable packages use the same techniques as `cds export` and provide the same plug & play convenience. For the _XTravels_ sample, we created the [`@capire/s4`](https://github.com/capire/s4) reuse package as follows: 1. We started a new CAP project – clone the repository into the [`cap/samples`] folder we created in the beginning and open it in VS Code to follow along: ```shell git clone https://github.com/capire/s4.git code s4 ``` 2. We imported the [OData API](https://api.sap.com/api/API_BUSINESS_PARTNER/overview) as [outlined above](#odata-apis). ```shell cds import ~/Downloads/API_BUSINESS_PARTNER.edmx ``` 2. Edited the `cds import`-generated `package.json` to look like that: ::: code-group ```json :line-numbers [package.json] { "name": "@capire/s4", "version": "1.0.0", "cds": { "requires": { "sap.capire.s4.business-partner": { "service": "API_BUSINESS_PARTNER", "kind": "odata-v2" } } } } ``` ::: 3. Added the following files to expose the imported API in a CAP-idiomatic way: ::: code-group ```js [cds-plugin.js] // just a tag file for plug & play configuration in consuming apps ``` ::: ::: code-group ```cds :line-numbers [srv/business-partners.cds] using from './srv/external/API_BUSINESS_PARTNER'; annotate API_BUSINESS_PARTNER with @cds.external:2; ``` ::: ::: code-group ```cds :line-numbers [index.cds] // Entry point to allow imports like: using from '@capire/s4'; using from './srv/business-partners'; ``` ::: 4. Added some initial data using `cds add data`. ```shell cds add data -o ./srv/external/data -f A_BusinessPartner ``` 5. Finally published the package to [_Github Packages_](https://github.com/features/packages). ```shell npm publish ``` In the consuming project [*@capire/xtravels*](https://github.com/capire/xtravels) we then simply added this package in the same way as we added the `@capire/xflights-data` package before: ```shell npm add @capire/s4 ``` > [!tip] Pre-built Integration Packages > In effect, pre-built integration packages apply the same best practice techniques as the `cds export` command does when generating [Packaged APIs](#packaged-apis). Such packages can be reused in any CAP project by a simple `npm add` command, thereby avoiding the need to re-import raw API definitions in each consuming project from scratch. Last but not least, they allow central version management based on _npm_ and _Maven_. ## Integrating Models > Source: /docs/guides/integration/calesi#integrating-models With imported APIs, you can now use them in your own models. For example, the XTravels application combines customer data from SAP S/4HANA with travels and flight bookings from xflights. With the integrated models, you can already run the application, as CAP [mocks integrations automatically](#mocked-out-of-the-box). For real integration, you'll need [custom code](#integration-logic), which we'll cover later. > [!tip] AI Agents 'capire' CAP > We can use AI agents to help us analyse and understand our models. Actually, the following sections are based on a response by *Claude Sonnet* to the question: *"Find and explain all references"*, with the entity definition for the `Flights` consumption view selected as context. ### Consumption Views > Source: /docs/guides/integration/calesi#consumption-views Imported APIs often contain more entities and elements than you need. So, before we continue, we first create *Consumption Views* to capture what you actually want to use, focusing on entities and elements you need close access to. Create two new files `apis/capire/xflights.cds` and `apis/capire/s4.cds`: ::: code-group ```cds :line-numbers [apis/capire/xflights.cds] using { sap.capire.flights.FlightsService as x } from '@capire/xflights-data'; namespace sap.capire.xflights; @federated entity Flights as projection on x.Flights { ID, date, departure, arrival, modifiedAt, airline.icon as icon, airline.name as airline, origin.name as origin, destination.name as destination, } @federated entity Supplements as projection on x.Supplements { ID, type, descr, price, currency, modifiedAt, } ``` ::: ::: code-group ```cds :line-numbers [apis/capire/s4.cds] using { API_BUSINESS_PARTNER as S4 } from '@capire/s4'; namespace sap.capire.s4; @federated entity Customers as projection on S4.A_BusinessPartner { BusinessPartner as ID, PersonFullName as Name, LastChangeDate as modifiedAt, } where BusinessPartnerCategory == 1; // 1 = Person ``` ::: The noteworthy aspects here are: - We map names to match our domain, for example, `A_Business_Partner` -> `Customers`, and choose simpler names for the elements we want to use. - For entity `Flights` we flatten data from associations directly into the consumption view. This is another [denormalization](#using-denormalized-views) to make life easier for us in the xtravels app. - The namespaces `sap.capire.s4` and `sap.capire.xflights` reflect the source systems but differ from the original namespaces to avoid name clashes. - We add `@federated` annotations, which we'll use later on to automate [data federation](#data-federation). > [!tip] Always use Consumption Views > > Even though they are optional, it's a good practice to always define consumption views on top of imported APIs. They declare what you need, enabling automated data federation. They also map imported definitions to your domain by renaming, flattening, or restructuring. > [!warning] Protocol-specific Limitations > > Depending on the service provider and protocols, limitations apply to consumption views. In particular, OData doesn't support denormalization like we used for the `Flights` view. This works here because xflights also serves the HCQL protocol (see the `@hcql` annotation in its [definition](#defining-service-apis)), which is CAP's native protocol. ### Associations > Source: /docs/guides/integration/calesi#associations With consumption views in place, you can now reference them from your models _as if they were local_, creating mashups of imported and local definitions. ::: code-group ```cds :line-numbers=1 [db/schema.cds] using { sap.capire.xflights as x } from '../apis/capire/xflights'; ``` ::: ```cds :line-numbers=25 entity Bookings { // ... Flight : Association to x.Flights; } ``` - Each _Booking_ references a _Flight_ from the external xflights service, which allows us to display flight details alongside bookings. #### Associations from Remote > Source: /docs/guides/integration/calesi#associations-from-remote ::: code-group ```cds :line-numbers=1 [db/schema.cds] using { sap.capire.xflights as x } from '../apis/capire/xflights'; ``` ::: ```cds :line-numbers=73 extend x.Flights with columns { Bookings : Association to many Bookings on Bookings.Flight = $self } ``` - Adds a backlink from _Flights_ to _Bookings_ for bidirectional traversal. ::: details Limitations of Remote Extensions Extensions to remote entities, as shown above, are only possible for elements which would not require changes to the remote service's actual data. This is the case for _virtual_ elements and _calculated_ fields, as well as **_unmanaged_** associations, as all foreign keys are local. It's not possible for regular elements or _managed_ associations, though. ::: ### Constraints > Source: /docs/guides/integration/calesi#constraints ::: code-group ```cds :line-numbers=44 [srv/travel-constraints.cds] annotate TravelService.Bookings with { ... Flight @mandatory { date @assert: (case when date not between $self.Travel.BeginDate and $self.Travel.EndDate then 'ASSERT_BOOKING_IN_TRAVEL_PERIOD' end); }; } ``` ::: - Adds a constraint to the _Flight.date_ element to ensure that the flight date of a booked _Flight_ falls within the travel period of the associated _Travel_. ### Serving UIs > Source: /docs/guides/integration/calesi#serving-uis ::: code-group ```cds :line-numbers=1 [srv/travel-service.cds] using { sap.capire.xflights as x } from '../apis/capire/xflights'; ``` ::: ```cds :line-numbers=16 @fiori service TravelService { ... @readonly entity Flights as projection on x.Flights; } ``` - Exposes the _Flights_ entity in the _TravelService_ for UI consumption. This is required as associations to non-exposed entities would be cut off, which would apply to the _Bookings_ -> _x.Flights_ association if we did not expose _x.Flights_. #### Fiori Annotations > Source: /docs/guides/integration/calesi#fiori-annotations On top of the mashed up models we can add Fiori annotations as usual to serve Fiori UIs – again: _as if they were local_. For example, following are excerpts of Fiori annotations referring to the `A_BusinessPartner` entity imported from S/4 (via the `Customers` consumption view, and the association to that from the local `Travels` entity). ::: code-group ```cds [app/common/labels.cds] annotate s4.Customers with @title: '{i18n>Customer}' { ... ID @title: '{i18n>Customer}' @Common.Text: Name; } ``` ::: ```cds annotate our.Travels with { ... Customer @title: '{i18n>Customer}' @Common: { Text: (Customer.Name), TextArrangement: #TextOnly }; } ``` ::: code-group ```cds [app/common/code-lists.cds] annotate our.Travels { ... Customer @Common.ValueList: { CollectionPath: Customers, ... } } ``` ::: ::: code-group ```cds [app/travels/layouts.cds] annotate TravelService.Travels with @UI: { ... SelectionFields: [ (Customer.ID), ... ], LineItem: [ { Value: (Customer.ID), .... }, ... ], FieldGroup #Tx: { Data: [ { Value: (Customer.ID) }, ... ]} } ``` ::: ```cds annotate TravelService.Bookings with @UI: { ... HeaderInfo: { Title: { Value: (Travel.Customer.Name) }, ... }, FieldGroup #GI: { Data: [ { Value: (Travel.Customer.ID) }, ... ]}, } ``` There are similar references to `Flights` entity from xflights in other parts of the Fiori annotations, which we omit here for brevity. ### Mocked Out of the Box > Source: /docs/guides/integration/calesi#mocked-out-of-the-box With mashed up models in place, we can run applications in _'airplane mode'_ without upstream services running. CAP mocks imported services automatically _in-process_ with mock data in the same _in-memory_ database as our own data. 1. Start the xtravels application locally using `cds watch` as usual, and note the output about the integrated services being mocked automatically: ```shell :line-numbers=1 cds watch ``` ```zsh [cds] - mocking sap.capire.s4.business-partner { at: [ '/odata/v4/s4-business-partner' ], decl: 's4/external/API_BUSINESS_PARTNER.csn:7' } ``` ```zsh [cds] - mocking sap.capire.flights.FlightsService { at: [ '/odata/v4/data', '/rest/data', '/hcql/data' ], decl: 'xflights/apis/data-service/services.csn:3' } ``` 2. Open the Fiori UI in the browser -> it displays data from both, local and imported entities, seamlessly integrated as shown in the screenshot below (the data highlighted in green is mocked data from `@capire/s4`). ![XTravels Fiori list view showing a table of travel requests, with the Customer highlighted in green.](assets/xtravels-list.png) > [!tip] Fast-track Inner-Loop Development → Spawning Parallel Tracks > > The mocked-out-of-the-box capabilities of CAP, with remoted services mocked in-process and a shared in-memory database, allows us to greatly speed up development and time to market. For real remote operations there is additional investment required, of course. But the agnostic nature of CAP-level Service Integration also allows you to spawn two working tracks running in parallel: One team to focus on domain and functionality, and another one to work on the integration logic under the hood. Learn more about mocking and inner loop development in the [*Inner Loop Development*](./inner-loops) guide. #### Integration Logic Required > Source: /docs/guides/integration/calesi#integration-logic-required While everything just works nicely when mocked in-process and with a shared in-memory database, let's move closer to the target setup and use `cds mock` to run the services to be integrated in separate processes. 1. First run these commands **in two separate terminals**: ```shell :line-numbers=1 cds mock apis/capire/xflights.cds ``` ```shell :line-numbers=2 cds mock apis/capire/s4.cds ``` 2. Start the xtravels server as usual **in a third terminal**, and note that it now _connects_ to the other services instead of mocking them: ```shell :line-numbers=3 cds watch ``` ```zsh [cds] - connect to sap.capire.s4.business-partner > odata { url: 'http://localhost:54476/odata/v4/s4-business-partner' } ``` ```zsh [cds] - connect to sap.capire.flights.FlightsService > hcql { url: 'http://localhost:54475/hcql/data' } ``` 2. Open the Fiori UI in the browser again -> data from the S/4 service is missing now, as we have not yet implemented the required custom code for the actual data integration, the same applies to the flight data from _xflights_: ![XTravels Fiori list view showing a table of travel requests, with the Customer column empty.](assets/xtravels-list-.png) ![XTravels Fiori details view showing a travel requests, with the flights data missing](assets/xtravels-bookings-.png) ## Integration Logic > Source: /docs/guides/integration/calesi#integration-logic This chapter walks you through the typical use cases and solution patterns that you should be aware of when implementing required integration logic. The following sections do that on the example of [CAP Node.js SDK](../../node.js/); the same principles and patterns apply to CAP Java, as documented in the [CAP Java SDK](../../java/) reference documentation. ### Connecting to Remote Services > Source: /docs/guides/integration/calesi#connecting-to-remote-services It all starts with connecting to remote services, which we do like that in the xtravels project: ::: code-group ```js :line-numbers=21 [srv/travel-service.js] const s4 = await cds.connect.to ('sap.capire.s4.business-partner') const xflights = await cds.connect.to ('sap.capire.flights.FlightsService') ``` ::: The `cds.connect.to()` function used here is the single common way to address service instances. It's used for and works the same way for both, local as well as remote services: - for **local** services, it returns the local service providers – that is, instances of [`cds.ApplicationService`](../../node.js/app-services), or your application-specific subclasses thereof. - for **remote** services, it returns a remote service proxy – that is, instances of [`cds.RemoteService`](../../node.js/remote-services), generically constructed by the client libs. ![Diagram illustrating CAP-level service integration showing two scenarios: Local services where Consumer connects to Service via CQL, and Remote services where Consumer connects to Proxy via CQL, Proxy connects to Protocol Adapter via OData, and Protocol Adapter connects to Service via CQL. ](assets/remoting.drawio.svg) > [!tip] Agnostic to Location and Protocol > Always use `cds.connect.to()` to connect to both local and remote services. Both inherit from the [`cds.Service`](../../node.js/core-services) base class, which constitutes the uniform interface for consuming CAP services – in turn agnostic to underlying protocols, and agnostic to whether its local or remote at all. ### Uniform, Agnostic APIs > Source: /docs/guides/integration/calesi#uniform-agnostic-apis The uniform and protocol-agnostic programming interface offered through [`cds.Service`](../../node.js/core-services) is centered around these methods: - [`cds.connect.to ()`](../../node.js/cds-connect) → connects to remote services, as shown above. - [`srv.run ()`](../../node.js/core-services#srv-run-query) → executes advanced, deep queries with remote services. - [`srv.send ()`](../../node.js/core-services#srv-send-request) → synchronous communication, for all kinds of services. - [`srv.emit ()`](../../node.js/core-services#srv-emit-event) → asynchronous communication, via messaging middlewares. - [`srv.on ()`](../../node.js/core-services#srv-on-event) → subscribe event handlers to events from other services. Here are some typical usages found in the xflights/xtravels sample: ```js :line-numbers=1 await xflights.run (SELECT.from`Flights`.where`modifiedAt > ${latest}`) await xflights.send ('POST','BookingCreated', { flight, date, seats }) await this.emit ('Flights.Updated', { flight, date, free_seats }) // this = xflights service xflights.on ('Flights.Updated', async msg => { ... }) ``` - Line 1 – queries the xflights service for updated flights since the last sync - Line 2 – calls a custom action of the xflights service (synchronously). - Line 3 – emits asynchronous events from the xflights service. - Line 4 – subscribes an event handler to events from the xflights service. The [`srv.send()`](../../node.js/core-services#srv-send-request) method – and its [_REST-style_ derivatives](../../node.js/core-services#rest-style-api) – is the most flexible option, as it allows to send all kinds of requests to all kinds of services – including non-CAP services, and non-OData services, down to very technical services, for which no API schema might exist at all. The, [`srv.emit()`](../../node.js/core-services#srv-emit-event) method – with [`srv.on()`](../../node.js/core-services#srv-on-event) on subscribers' side – promotes asynchronous communication via events, which is most recommended for reasons of decoupling and scalability. It requires the target service to be connected via a messaging middleware, though. The [`srv.run()`](../../node.js/core-services#srv-run-query) method – and its [_CRUD-style_ derivatives](../../node.js/core-services#crud-style-api) – is the most powerful option, and closest to the use cases of data-centric business applications. It requires the target service to support querying, though, like CAP application services, OData services, or GraphQL services. > [!tip] Choosing the Right Method > Choose the method that best fits your use case and the capabilities of the target service. Prefer `srv.run()` for its power and conceptual expressiveness with data-centric operations. Consider `srv.emit()` for decoupled, asynchronous communication whenever possible. Retreat to `srv.send()` for maximum flexibility only when needed. > [!tip] Staying at CAP Level > Always stay at CAP level when integrating services, using the uniform and protocol-agnostic [_Core Service APIs_](../../node.js/core-services) outlined above, combined with [_CQL_](../../cds/cql) as CAP's universal query language. This allows CAP to automate things like protocol translations, data federation, resilience for you, as well as mocking services out of the box, thereby promoting fast inner loops. Only retreat to lower levels when absolutely necessary. ### Testing with `cds repl` > Source: /docs/guides/integration/calesi#testing-with-cds-repl We can use `cds repl` to experiment the options to send requests and queries to remote services interactively. Do so as follows... From within the xtravels project's root folder `cap/samples/xtravels`, start by mocking the remote services in separate terminals, then start xtravels server within `cds repl` in a third terminal: ```shell :line-numbers=1 cds mock apis/capire/xflights.cds ``` ```shell :line-numbers=2 cds mock apis/capire/s4.cds ``` ```shell :line-numbers=3 cds repl ./ ``` Within the REPL, connect to local and remote services: ```js const TravelService = await cds.connect.to ('TravelService') const xflights = await cds.connect.to ('sap.capire.flights.FlightsService') const s4 = await cds.connect.to ('sap.capire.s4.business-partner') ``` Read data directly from the remote `A_BusinessPartner` entity. ```js await s4.run (SELECT.from`A_BusinessPartner`.limit (3)) await s4.read`A_BusinessPartner`.limit (3) // shorthand // [!code focus] ``` > The variant on line 2 is a convenient shorthand for the one on line 1. ::: details See results output ... ```zsh => [ { BusinessPartner: '000001', PersonFullName: 'Mrs. Theresia Buchholm', LastChangeDate: '2024-01-19', LastChangeTime: '21:48:32', BusinessPartnerCategory: '1' }, { BusinessPartner: '000002', PersonFullName: 'Mr. Johannes Buchholm', LastChangeDate: '2024-01-08', LastChangeTime: '11:22:01', BusinessPartnerCategory: '1' }, { BusinessPartner: '000003', PersonFullName: 'Mr. James Buchholm', LastChangeDate: '2022-11-04', LastChangeTime: '15:27:46', BusinessPartnerCategory: '1' } ] ``` ::: Read the same data via the `sap.capire.s4.Customers` consumption view: ```js const { Customers } = cds.entities ('sap.capire.s4') await s4.read (Customers) .limit (3) // [!code focus] ``` ::: details See results output ... ```zsh => [ { ID: '000001', Name: 'Mrs. Theresia Buchholm', modifiedAt: '2024-01-19' }, { ID: '000002', Name: 'Mr. Johannes Buchholm', modifiedAt: '2024-01-08' }, { ID: '000003', Name: 'Mr. James Buchholm', modifiedAt: '2022-11-04' } ] ``` Note how field names and structure are adapted to our domain. ::: ::: details See OData requests ... Watch the log output in the second terminal to see the translated OData requests being received by the remote service, for example: ```zsh [odata] - GET /odata/v4/s4-business-partner/A_BusinessPartner { '$top': '3' } ``` ```zsh [odata] - GET /odata/v4/s4-business-partner/A_BusinessPartner { '$select': 'BusinessPartner,PersonFullName,LastChangeDate', '$top': '3' } ``` ::: CRUD some data into remote `A_BusinessPartner` entity, still via the `sap.capire.s4.Customers` consumption view: ```js await s4.insert ({ ID: '123', Name: 'Sherlock' }) .into (Customers) await s4.create (Customers, { ID: '456', Name: 'Holmes' }) await s4.read`ID, Name` .from (Customers) .where`length(ID) <= 3` await s4.update (Customers,'123') .with ({ modifiedAt: '2026-01-01' }) await s4.delete (Customers,'123') await s4.delete (Customers) .where`ID = ${'456'}` ``` > [!tip] Always use Consumption Views > Even when accessing remote services directly, always prefer doing so via consumption views as shown above. They map the remote definitions to your domain, and allow CAP to automatically translate queries accordingly. This includes renaming, flattening, restructuring, as well as filtering out unnecessary data. ### Modifying CQNs > Source: /docs/guides/integration/calesi#modifying-cqns Queries in CAP are represented as first-class [CQN](../../cds/cqn) objects under the hood. When querying remote services, we can inspect and modify those query objects prior to forwarding them to target services for execution. Let's try that out in `cds repl`, which we [started before](#testing-with-cds-repl). 1. Construct and inspect an example of an inbound query: ```js q1 = SELECT`ID, Name`.from (Customers) .where`length(ID) <= 3` ``` ```zsh => cds.ql { SELECT: { from: { ref: [ 'sap.capire.s4.Customers' ] }, columns: [ { ref: [ 'ID' ] }, { ref: [ 'Name' ] } ], where: [ { func:'length', args: [ {ref:['ID']} ] }, '<=', { val:3 } ], } } ``` 2. Create a clone of that query to modify it without changing the original one: ```js q2 = cds.ql.clone (q1) // get a clone to keep q1 intact ``` 3. Modify our cloned query as needed. For example, let's replace the existing where clause, and add an order by clause like this: ```js q2.SELECT.where = cds.ql.predicate`contains (Name,'Astrid')` q2.orderBy `Name asc` ``` ```zsh => cds.ql { SELECT: { // ... as before ..., where: [ { func: 'contains', args: [ {ref:['Name']}, {val:'Astrid'} ] } # [!code focus] ], orderBy: [ {ref:['Name'], sort: 'asc' } ] # [!code focus] }, } ``` 4. Finally forward / run the modified query: ```js await s4.run (q2) ``` ::: details See results output ... ```zsh => [ { ID: '000096', Name: 'Mrs. Astrid Detemple' }, { ID: '000037', Name: 'Mrs. Astrid Gutenberg' }, { ID: '000164', Name: 'Mrs. Astrid Hoffen' }, { ID: '000399', Name: 'Mrs. Astrid Kramer' }, { ID: '000087', Name: 'Mrs. Astrid Martin' }, { ID: '000527', Name: 'Mrs. Astrid Sommer' }, { ID: '000203', Name: 'Mrs. Astrid Waldmann' } ] ``` ::: > [!tip] Powerful Query Adaptation > Modifying queries prior to forwarding them to remote services is a powerful technique to implement advanced integration scenarios. For example, you can adapt queries to the capabilities of target services, implement custom filtering, paging, or sorting logic, or even split and merge queries across multiple services. ::: details First-Class Query Objects On a side note: We leverage key principles of [_first-class objects_](https://google.com/search?q=first+class+objects+programming) here, as known from functional programming and dynamic languages: As queries are represented as first-class CQN objects, we can construct and manipulate them programmatically at runtime, pass them as arguments, and return them from functions. And, not the least, this opens the doors for things like higher-order queries, query delegation – for example push down to databases –, and late materialization. ::: > [!warning] Always Clone Before Modifying > As always, great power comes with great responsibility: Ensure to [`cds.ql.clone`](../../node.js/cds-ql#cds-ql-clone) CQNs before modifying them, as they are shared across the entire request processing pipeline. Failing to do so may lead to unexpected side effects and hard-to-debug issues. And CAP runtimes can only optimize for _immutable_ CQNs. ### Data Federation > Source: /docs/guides/integration/calesi#data-federation There are many scenarios where data from remote services needs to be in close access locally. For example, in the xtravels app we want to display lists of flight details alongside bookings in Fiori UIs. This requires joining data from the local `Bookings` entity with data from the remote `Flights` entity. Relying on live calls to remote services per row is clearly not an option. Instead, we'd rather ensure that data required in close access is really available locally, so it can be joined with own data using SQL JOINs. This is what _data federation_ is all about. #### Basic Implementation > Source: /docs/guides/integration/calesi#basic-implementation Following would be a basic implementation for replicating flights data from the remote xflights service into local database tables of the xtravels app: 1. Annotate your consumption _views_ with `@cds.persistence.table` to turn them into _tables_ to persist replicated data locally: ::: code-group ```cds [db/schema.cds] // turn into table to persist replicated data annotate x.Flights with @cds.persistence.table; ``` ::: 2. Implement logic to replicate updated data, for example like that: ```js [srv/data-replication.js] const xflight = await cds.connect.to ('sap.capire.flights.FlightsService') const {Flights} = cds.entities ('sap.capire.xflights') let {latest} = await SELECT.one`max(modifiedAt) as latest`.from (Flights) let touched = await xflight.read (Flights).where`modifiedAt > ${latest||0}` if (touched.length) await UPSERT (touched).into (Flights) ``` #### Generic Implementation > Source: /docs/guides/integration/calesi#generic-implementation While the above is a valid implementation for data replication, it is specific to the `Flights` entity, which means we would need to write similar code for each entity we want to replicate. Therefore, we actually implemented a more generic solution for data federation in xtravels, which automatically kicks in on any entity tagged with the `@federated` annotated, which we already used in our [consumption views](#consumption-views): ::: code-group ```cds [apis/capire/xflights.cds] @federated entity Flights as projection on x.Flights { ... } @federated entity Supplements as projection on x.Supplements { ... } ``` ```cds [apis/capire/s4.cds] @federated entity Customers as projection on S4.A_BusinessPartner { ... } ``` ::: Besides the advantages of reusability and maintainability, this also allows us to easily add new entities for data federation just by annotating them with `@federated`, without the need to write any custom code at all. The projections defined in such _`@federated` consumption views_ also declare exactly what data needs to be in close access, and what not, thereby avoiding overfetching. Learn more about that generic solution in the [_CAP-level Data Federation_](data-federation) guide. > [!tip] When to Use Data Federation > Data federation is essential when remote data is needed in close access for joins with local data, filtering, or sorting operations. It drastically improves read performance and reduces latency, as well as overall load. It also increases resilience and high availability by reducing dependencies on other services. ### Delegation > Source: /docs/guides/integration/calesi#delegation Even with [data federation](#data-federation) in place, there are still several scenarios where we need to reach out to remote services on demand. Value helps are a prime example for that; for example, to select `Customers` from a drop-down list when creating new travels. Although we could serve that from replicated data as well, this would require replicating **_all_** relevant customer data locally, which is often overkill. The code below shows how we simply delegate value help requests for `Customers` in xtravels to the connected S/4 service: ::: code-group ```js [srv/travel-service.js] this.on ('READ', Customers, req => s4.run (req.query)) ``` ::: The event handler intercepts all direct `READ` requests to the `Customers` entity, and just forwards the query as-is to the connected S/4 service. ::: details Try this in `cds repl` ... ```shell :line-numbers=1 cds mock apis/capire/s4.cds ``` ```shell :line-numbers=2 cds repl ./ ``` Within the `cds repl` session in the second terminal, run this: ```js await TravelService.read`ID, Name`.from`Customers`.limit(3) ``` This issues a `READ` request to the local `TravelService.Customers` entity, which is intercepted by the above event handler, and delegated to the remote S/4 service. The result comes back translated to the structure of the `Customers` consumption view: ```zsh => [ { ID: '000001', Name: 'Mrs. Theresia Buchholm' }, { ID: '000002', Name: 'Mr. Johannes Buchholm' }, { ID: '000003', Name: 'Mr. James Buchholm' } ] ``` See the log output of in the first terminal where we `cds mock`ed the S/4 service to observe the translated OData request being received by the remote service: ```zsh [odata] - GET /odata/v4/s4-business-partner/A_BusinessPartner { '$select': 'BusinessPartner,PersonFullName', '$top': '3' } ``` ::: #### Automatic Query Translation > Source: /docs/guides/integration/calesi#automatic-query-translation Note that for the handler above, incoming requests always refer to: - the [`TravelService.Customers`](#serving-uis) entity – which is a view on: - the [`sap.capire.s4.Customers`](#consumption-views) entity – which in turn is a view on: - the [`A_BusinessPartner`](#odata-apis) remote entity. In effect, we are delegating a query to the S/4 service, which refers to an entity actually not known to that remote service. How could that work at all? It works because we fuelled the CAP runtime with CDS models, so the generic handlers detect such situations, and automatically translate delegated queries into valid queries targeted to underlying remote entities – that is, `A_BusinessPartner` in our example. When doing so, all column references in select clauses, where clauses, etc., are translated and delegated as well, and the results' structure transformed back to that of the original target – that is, `TravelService.Customers` above. ### Navigation > Source: /docs/guides/integration/calesi#navigation Automatic translation of delegated queries, [as shown above](#automatic-query-translation), has limitations when navigations and expands are involved. Let's explore those limitations and how to deal with them on the example of the _Bookings -> Flights_ association. Try running the following query in `cds repl`, with the xflights service mocked in a separate process, as before: ```js const { Bookings } = cds.entities ('sap.capire.travels') ``` ```js await SELECT.from (Bookings) .where`Flight.origin like '%Ken%'` ``` - With data federation in place, this would work (if all flight data had been replicated). - Without data federation, though, this would fail with a runtime error. For that to really work cross-service – that is, without data federation, or bypassing it – we'd have to split the query, manually dispatch the parts to involved services, and correlate results back, for example, like this: ```js await SELECT.from (Bookings) .where`Flight.ID in ${( await xflights.read`ID`.from`Flights`.where`origin.name like '%Ken%'` ).map (f => f.ID)}` ``` ::: details The above can also be written like that, of course: ```js const flights = await xflights.read`ID`.from`Flights`.where`origin.name like '%Ken%'` const flightIDs = flights.map (f => f.ID) await SELECT.from (Bookings) .where`Flight.ID in ${flightIDs}` ``` ::: > [!tip] What is 'Navigation'? > The term 'navigation' commonly refers to traversing associations between entities in queries. In CAP, this is typically expressed using [path expressions](../../cds/cql#path-expressions) along (chains of) associations – for example, `flight.origin.name` –, which can show up in all query clauses (_select_, _from_, _where_, _order by_, and _group by_). ### Expands > Source: /docs/guides/integration/calesi#expands Similar to navigations, expands across associations also require special handling when we cannot serve them from federated data. Try running the following query in `cds repl`, with the xflights service mocked in a separate process, as before: ```js await SELECT.from (Bookings) .columns`{ Flight { ID, date, destination } }` .where`exists Flight` .limit(3) ``` ::: details See results output ... ```zsh => [ { Flight: { ID: 'SW1537', date: '2023-08-04', destination: 'Miami International Airport' } }, { Flight: { ID: 'SW1537', date: '2023-08-04', destination: 'Miami International Airport' } }, { Flight: { ID: 'SW1537', date: '2023-08-04', destination: 'Miami International Airport' } } ] ``` ::: To achieve the same without data federation, we'd have to manually fetch nested data from the remote service for each row, and fill it into the outer results, for example like this: ```js await SELECT.from(Bookings).columns`Flight_ID, Flight_date`.limit(3) .then (all => Promise.all (all.map (async b => ({ Flight: await xflights.read`ID, date, destination.name as destination` .from`Flights`.where`ID = ${b.Flight_ID} and date = ${b.Flight_date}` })))) ``` We can do similar things for expands across associations from remote data to local ones, for example like that: ```js const { Customers } = cds.entities ('sap.capire.s4') const { Travels } = cds.entities ('sap.capire.travels') await s4.read(Customers).columns`{ ID, Name }` .then (all => Promise.all (all.map (async c => Object.assign (c, { Travels: await SELECT`ID`.from(Travels).where`Customer.ID = ${c.ID}` })))) ``` ### Outboxed Emits > Source: /docs/guides/integration/calesi#outboxed-emits Use [_transactional outbox_](../../guides/events/event-queues) for write operations, which you want to take place reliably, but don't need the results in your current execution context. As in this event hander example: ::: code-group ```js :line-numbers=30 [srv/travel-service.js] const xflights_ = cds.outboxed (xflights) // [!code focus] this.after ('SAVE', Travels, ({ Bookings=[] }) => { return Promise.all (Bookings.map (booking => { let { Flight_ID: flight, Flight_date: date } = booking return xflights_.send ('POST', 'BookingCreated', { flight, date }) // [!code focus] })) }) ``` ::: - Line 29 – We create an _outboxed_ version of the connected xflights service. - Line 34 – We use that outboxed service to send events to the xflights service. This creates ultimate resilience, as the events are stored in a local outbox table within the same transaction as the `SAVE` operation on `Travels`. A separate process then takes care of reliably forwarding those events to the xflights service, retrying in case of failures, etc. ## Learn More > Source: /docs/guides/integration/calesi#learn-more - [CAP-level Data Federation](data-federation) – Explore different patterns and strategies for data federation in CAP applications. - [Inner Loop Development](inner-loops) – Understand how to develop and test integrated applications efficiently using CAP's inner loop development features. # CAP-level Data Federation > Source: /docs/guides/integration/data-federation CAP applications can integrate and federate data from multiple external data sources, enabling close access to distributed data. This guide provides an overview of the core concepts and techniques for implementing data federation in CAP applications, and how CAP helps solving this generically, and thus serving data federation out of the box. {.abstract} ## Preliminaries > Source: /docs/guides/integration/data-federation#preliminaries ### Prerequisites > Source: /docs/guides/integration/data-federation#prerequisites You should be familiar with the content in the [_CAP-level Service Integration_](calesi.md) guide, as we build upon that foundation here. In particular, you should have read and understood these sections: - [*Overview* of *CAP-level Service Integration*](calesi.md#overview) - [_Providing & Exporting APIs_](calesi.md#providing-cap-level-apis) - [_Importing APIs_](calesi.md#importing-apis) - [_Consumption Views_](calesi.md#consumption-views) In addition, you should have read the introduction to [the _XTravels_ sample](calesi.md#the-xtravels-sample) application, which we continue to use as our running example. ### Motivation > Source: /docs/guides/integration/data-federation#motivation There are many scenarios where data from remote services needs to be in close access locally. For example when we display lists of local data joined with remote data, as we introduce in the [*CAP-level Service Integration*](calesi.md#integration-logic-required) guide: ![XTravels Fiori list view showing a table of travel requests, with the Customer column empty.](assets/xtravels-list-.png) ![XTravels Fiori details view showing a travel requests, with the flights data missing](assets/xtravels-bookings-.png) When we run that and look into the log output of the xtravels app server, we see some bulk requests as shown below, which indicates that the Fiori client is desparately trying to fetch the missing customer data. If we'd scroll the list in the UI this would repeat like crazy. ```js [odata] - POST /odata/v4/travel/$batch [odata] - > GET /Travels(ID=4133,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4132,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4131,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4130,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4129,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4128,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4127,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4126,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4125,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4124,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4123,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4122,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4121,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4120,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4119,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4118,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4117,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4116,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4115,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4114,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4113,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4112,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4111,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4110,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4109,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4108,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4107,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4106,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4105,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } [odata] - > GET /Travels(ID=4104,IsActiveEntity=true) { '$select': 'Customer', '$expand': 'Customer($select=ID,Name)' } ``` Relying on live calls to remote services per row is clearly not an option. Instead, we'd rather ensure that data required in close access is really available locally, so it can be joined with own data using SQL JOINs. This is what _data federation_ is all about. ### The XTravels Sample > Source: /docs/guides/integration/data-federation#the-xtravels-sample We'll use the same [XTravels sample](calesi.md#the-xtravels-sample) and setup as in the [_CAP-level Service Integration_](calesi.md) guide. If you haven't done so already, clone the required repositories to follow along: ```sh :line-numbers mkdir -p cap/samples cd cap/samples git clone https://github.com/capire/xtravels git clone https://github.com/capire/xflights git clone https://github.com/capire/s4 ``` [@capire/xtravels]: https://github.com/capire/xtravels [@capire/xflights]: https://github.com/capire/xflights [@capire/s4]: https://github.com/capire/s4 ```sh :line-numbers=6 echo '{"workspaces":["xflights","xtravels","s4"]}' > package.json npm install ``` > [!note] > Line 6 above turns the `cap/samples` folder into a root for `npm workspaces` to optimize the `npm install` locally. > We'll learn more about that in the [_Inner Loop Development guide_](inner-loops.md). ## Federated Consumption Views > Source: /docs/guides/integration/data-federation#federated-consumption-views Tag [consumption views](calesi#consumption-views) with the `@federated` annotation, to express your intent to have that data federated, that is, in close access locally. For example, we did so in our consumption view for entities imported from XFlights as well as for the S/4 Business Partners entity: ::: code-group ```cds :line-numbers [apis/capire/xflights.cds] @federated entity Flights as projection on x.Flights { ... } @federated entity Supplements as projection on x.Supplements { ... } ``` ::: ::: code-group ```cds :line-numbers [apis/capire/s4.cds] @federated entity Customers as projection on S4.A_BusinessPartner { BusinessPartner as ID, PersonFullName as Name, LastChangeDate as modifiedAt, } where BusinessPartnerCategory == 1; // 1 = Person ``` ::: > [!tip] Stay Intentional -> What, not how! -> Minimal Assumptions > > By tagging entities with `@federated` we stay _intentional_ about **_what_** we want to achieve, and avoid any premature assumptions about **_how_** things are actually implemented. => This allows CAP runtimes – or your own _generic_ solutions, as in this case – to choose the best possible implementation strategies for the given environment and use case, which may differ between development, testing, and production environments, or might need to evolve over time. ## Service-level Replication > Source: /docs/guides/integration/data-federation#service-level-replication Next we implement a generic solution for data federation, which automates the basic hard-coded approach for data federation presented [before](calesi#data-federation). Here's the complete code, as found in [`srv/data-federation.js`](https://github.com/capire/xtravels/blob/main/srv/data-federation.js): ::: code-group ```js:line-numbers [srv/data-federation.js] const PROD = process.env.NODE_ENV === 'production' /* eslint-disable no-console */ const cds = require ('@sap/cds') const feed = [] // Collect all entities to be federated, and prepare replica tables PROD || cds.on ('loaded', csn => { for (let e of cds.linked(csn).entities) { if (e['@federated']) { let srv = remote_srv4(e) if (is_remote(srv)) { e['@cds.persistence.table'] = true //> turn into table for replicas feed.push ({ entity: e.name, remote: srv }) } } } }) // Setup and schedule replications for all collected entities PROD || cds.once ('served', () => Promise.all (feed.map (async each => { const srv = await cds.connect.to (each.remote) srv._once ??=! srv.on ('replicate', replicate) await srv.schedule ('replicate', each) .every ('10 minutes') }))) // Event handler for replicating single entities async function replicate (req) { let { entity } = req.data, remote = this let { latest } = await SELECT.one `max(modifiedAt) as latest` .from (entity) let rows = await remote.run ( SELECT.from (entity) .where `modifiedAt > ${latest}` ) if (rows.length) await UPSERT (rows) .into (entity); else return console.log ('Replicated', rows.length, 'entries', { for: entity, via: this.kind }) } // Helpers to identify remote services, and check whether they are connected const remote_srv4 = entity => entity.__proto__._service?.name const is_remote = srv => cds.requires[srv]?.credentials?.url ``` ::: Let's have a closer look at this code, which handles these main tasks: 1. **Prepare Persistence** – When the model is `loaded`, before it's deployed to the database, we collect all to be `@federated` entities, check whether their respective services are remote, and if so, turn them into tables for local replicas (line 11). 2. **Setup Replication** – Later when all services are `served`, we connect to each remote one (line 20), register a handler for replication (line 21), and schedule it to be invoked repeatedly (line 22). 3. **Replicate Data** – Finally, the `replicate` handler implements a simple polling-based data federation strategy, based on `modifiedAt` timestamps (lines 28-32), with the actual call to remote happening on line 29. > [!tip] CAP-level Querying -> agnostic to databases & protocols > We work with **database-agnostic** and **protocol-agnostic** [CQL queries](../../cds/cql) both for interacting with the local database as well as for querying remote services. In effect, we got a fully generic solution for replication, that is, it works for **_any_** remote service that supports OData, or HCQL. ## Test Drive Locally > Source: /docs/guides/integration/data-federation#test-drive-locally Let's see the outcome in action: to activate the above data federation code, edit `xtravels/srv/server.js` file and uncomment the single line of code in there like this: ::: code-group ```js [srv/server.js] process.env.NODE_ENV || require ('./data-federation') ``` ::: With that in place, we can start the xtravels app again, and see the data federation in action. Do so by running the following commands from within the `cap/samples` root folder in separate terminals, and in that order: ```shell :line-numbers=1 cds watch s4 ``` ```shell :line-numbers=2 cds watch xflights ``` ```shell :line-numbers=3 cds watch xtravels ``` In the logs of the xtravels app server, you should now see the output of the replication handler, showing that entries from the remote services are replicated locally: ```zsh Replicated 49 entries { for: 'sap.capire.xflights.Supplements', via: 'hcql' } Replicated 44 entries { for: 'sap.capire.xflights.Flights', via: 'hcql' } Replicated 727 entries { for: 'sap.capire.s4.Customers', via: 'odata' } ``` The S/4 Business Partner service in terminal 1 shows the incoming OData request(s): ```zsh [odata] - GET /odata/v4/s4-business-partner/A_BusinessPartner { '$select': 'BusinessPartner,PersonFullName,LastChangeDate', '$filter': 'LastChangeDate gt 2024-12-31' } ``` While the xflights service in terminal 2 shows its incoming HCQL requests like that: ```zsh [hcql] - GET /hcql/data/ { SELECT: { from: { ref: [ 'sap.capire.flights.FlightsService.Flights' ] }, columns: [ { ref: [ 'ID' ], as: 'ID' }, { ref: [ 'date' ], as: 'date' }, { ref: [ 'departure' ], as: 'departure' }, { ref: [ 'arrival' ], as: 'arrival' }, { ref: [ 'free_seats' ], as: 'free_seats' }, { ref: [ 'modifiedAt' ], as: 'modifiedAt' }, { ref: [ 'airline', 'icon' ], as: 'icon' }, { ref: [ 'airline', 'name' ], as: 'airline' }, { ref: [ 'origin', 'name' ], as: 'origin' }, { ref: [ 'destination', 'name' ], as: 'destination' } ], where: [ { ref: [ 'modifiedAt' ] }, '>', { val: '2026-01-28T17:38:28.929Z' } ] } } ``` Finally, open the Fiori UI in the browser again, and see that customer data from S/4 as well as flight data from xflights is now displayed properly, thanks to the data federation implemented above. ![XTravels Fiori list view showing tarvel requests, now with customer names again.](assets/xtravels-list.png) ![XTravels Fiori details view showing a travel requests, now with flight data again.](assets/xtravels-bookings.png) # Inner-Loop Development > Source: /docs/guides/integration/inner-loops CAP promotes fast inner-loop development by allowing us to easily swap production-grade services with local mocks during development, without any changes to CDS models nor implementations. Similar in the context of application service integration, imported APIs of remote services and applications can be mocked out of the box in consuming applications. This in turn greatly promotes decoupled parallel development across distributed teams working on different microservices. {.abstract} ## Preliminaries > Source: /docs/guides/integration/inner-loops#preliminaries ### What is Inner Loop? > Source: /docs/guides/integration/inner-loops#what-is-inner-loop ![inner-loop-turntable](assets/inner-loop-turntable.png){.ignore-dark} Many of us likely remember that turntable thing in the playgrounds: stay close to the center – the inner loop –, and it rotates at ultimate speed, lean out and it slows down. We see similar effects when running through full *code - build - deploy - start* cycles to see the effects of incremental changes in overly cloud-based development models. And it's not only the turnaround times for individual developers, it's also the runtime for tests, the operating costs induced by both, the impact on support (local setups allow to reproduce things, complex setups don't), up to severe resilience issues (whenever a cloud service isn't available development stops for whole teams). Here's a very rough comparison from a real world example: | Aspect | Overly Cloud-Based | Local Inner Loop | Gain | |-----------------------------------|:------------------:|:----------------:|:------:| | Turnaround times | 6+ min | 2 sec | > 100x | | Test pipelines | 40+ min | 4 min | > 10x | | Support time to reproduce/resolve | hours, days | minutes | > 10x | | Resilience re service outages | poor | ultimate | high | | Operating costs / TCD | high | low | high | ### The XTravels Sample > Source: /docs/guides/integration/inner-loops#the-xtravels-sample We'll use the same [XTravels sample](calesi.md#the-xtravels-sample) and setup as described in the [_CAP-level Service Integration_](calesi.md) guide. If you haven't done so already, clone the required repositories to follow along: ```sh :line-numbers mkdir -p cap/samples cd cap/samples git clone https://github.com/capire/xtravels git clone https://github.com/capire/xflights git clone https://github.com/capire/s4 ``` [@capire/xtravels]: https://github.com/capire/xtravels [@capire/xflights]: https://github.com/capire/xflights [@capire/s4]: https://github.com/capire/s4 ```sh :line-numbers=6 echo '{"workspaces":["xflights","xtravels","s4"]}' > package.json npm install ``` > [!note] > > Line 6 above turns the `cap/samples` folder into a root for `npm workspaces`. For the time being this simply optimizes the `npm install`. We'll revisit that in chapter [*Using `npm` Workspaces*](#using-npm-workspaces) below. #### Activate Generic Data Federation > Source: /docs/guides/integration/inner-loops#activate-generic-data-federation In addition, activate generic data federation as described in the [_CAP-level Data Federation_](data-federation.md) guide, by editing `xtravels/srv/server.js` file and uncommenting the single line of code in there like this: ::: code-group ```js [xtravels/srv/server.js] process.env.NODE_ENV || require ('./data-federation') ``` ::: ## Mocked Out of the Box > Source: /docs/guides/integration/inner-loops#mocked-out-of-the-box Within the context of application service integration and microservice architecture, we'd need to mock remote services in a consuming app to reach an inner loop. CAP handles this automatically for us, based on: - A CDS service definition is all we need to serve a fully functional OData service - APIs imported via `cds export` and `cds import` are CDS service definition - ⇒ CAP can serve/mock remote APIs out of the box Let's demonstrate that within the xtravels project... ### In-Process, Shared DB – `cds watch` > Source: /docs/guides/integration/inner-loops#in-process-shared-db--cds-watch With mashed up models in place, we can run applications in _'airplane mode'_ without upstream services running. CAP mocks imported services automatically _in-process_ with mock data in the same _in-memory_ database as our own data. 1. Start the xtravels application locally using `cds watch` as usual, and note the output about the integrated services being mocked automatically: ```shell :line-numbers=1 cds watch ``` ```zsh [cds] - mocking sap.capire.s4.business-partner { at: [ '/odata/v4/s4-business-partner' ], decl: 's4/external/API_BUSINESS_PARTNER.csn:7' } ``` ```zsh [cds] - mocking sap.capire.flights.FlightsService { at: [ '/odata/v4/data', '/rest/data', '/hcql/data' ], decl: 'xflights/apis/data-service/services.csn:3' } ``` 2. Open the Fiori UI in the browser -> it displays data from both, local and imported entities, seamlessly integrated as shown in the screenshot below (the data highlighted in green is mocked data from `@capire/s4`). ![XTravels Fiori list view showing a table of travel requests, with the Customer highlighted in green.](assets/xtravels-list.png) ### Separate Processes – `cds mock` > Source: /docs/guides/integration/inner-loops#separate-processes--cds-mock ###### cds-mock > Source: /docs/guides/integration/inner-loops#cds-mock We can also use `cds mock` to mock remote services in separate processes, which brings us closer to the target setup: 1. From within the xtravels project's root folder `cap/samples/xtravels`, start by mocking the remote services in separate terminals, then start xtravels server in a third terminal: ```shell :line-numbers=1 cds mock apis/capire/xflights.cds ``` ```shell :line-numbers=2 cds mock apis/capire/s4.cds ``` ```shell :line-numbers=3 cds watch ``` Note in the log output of the xtravels server that it now _connects_ to the other services instead of mocking them: ```zsh [cds] - connect to sap.capire.s4.business-partner > odata { url: 'http://localhost:54476/odata/v4/s4-business-partner' } ``` ```zsh [cds] - connect to sap.capire.flights.FlightsService > hcql { url: 'http://localhost:54475/hcql/data' } ``` 2. Open the Fiori UI in the browser again -> data from the S/4 service is missing now, as we have not yet implemented the required custom code for the actual data integration, the same applies to the flight data from _xflights_: ![XTravels Fiori list view showing a table of travel requests, with the Customer column empty.](assets/xtravels-list-.png) ![XTravels Fiori details view showing a travel requests, with the flights data missing](assets/xtravels-bookings-.png) > [!tip] Mocking for Inner-Loop Development > A service definition is all we need to serve fully functional CAP services via OData or HCQL. Hence, service APIs imported via `cds import` are automatically mocked by CAP runtimes during development. This allows us to develop and test integrated applications in fast inner loops, without the need to connect to real remote services. > [!tip] Decoupled Development → Contracts First > > Local inner loops allow promoting decoupled development of separate parts / applications / microservices in larger solution projects. Each team can focus on their local domain and functionality with the required remote services mocked for them based on imported APIs. These APIs are the contracts between the individual teams. > [!tip] Fast-track Inner-Loop Development → Spawning Parallel Tracks > > The mocked-out-of-the-box capabilities of CAP, with remote services mocked in-process and a shared in-memory database, allows us to greatly speed up development and time to market. For real remote operations there is additional investment required, of course. But the agnostic nature of CAP-level Service Integration also allows you to spawn two working tracks running in parallel: One team to focus on domain and functionality, and another one to work on the integration logic under the hood. ### Providing Mock Data > Source: /docs/guides/integration/inner-loops#providing-mock-data There are different options to provide initial data, test data, and mock data: - In case of `@capire/xflights-data`, we generated the package content using `cds export --data` option, which added `.csv` files next to the `.cds` files. - In case of `@capire/s4`, we explicitly added `.csv` files next to the `.cds` files. - In addition, we could add `.csv` files for imported entities in the consuming apps `db/data` or `test/data` folders. In all cases, the `.csv` files are placed next to the `.cds` files, and hence they are automatically detected and loaded into the in-memory database. For Java, make sure to add the `--with-mocks` option to the `cds deploy` command used to generate the `schema.sql` in `srv/pom.xml`. This ensures that tables for the mocked remote entities are created in the database. [Learn more about *Adding Initial Data*](../databases/initial-data) {.learn-more} ## Run with Real Services > Source: /docs/guides/integration/inner-loops#run-with-real-services Instead of mocking required services by the imported APIs [using `cds mock` as shown above](#cds-mock), we can also run the real *xflights* and *s4* services from their respective home folders which we [cloned already in the beginning](#the-xtravels-sample). Do so by running the following commands from within the `cap/samples` root folder in separate terminals, and in that order: ```shell :line-numbers=1 cds watch s4 ``` ```shell :line-numbers=2 cds watch xflights ``` ```shell :line-numbers=3 cds watch xtravels ``` In the log output of the xtravels server we should see that it _connects_ to the other services, in the same way as above: ```zsh [cds] - connect to sap.capire.s4.business-partner > odata { url: 'http://localhost:54476/odata/v4/s4-business-partner' } ``` ```zsh [cds] - connect to sap.capire.flights.FlightsService > hcql { url: 'http://localhost:54475/hcql/data' } ``` [Go on as above...](#cds-mock) ## Test-drive w/ `cds repl` > Source: /docs/guides/integration/inner-loops#test-drive-w-cds-repl We can use `cds repl` to experiment the options to send requests and queries to remote services interactively. Do so as follows... From within the xtravels project's root folder `cap/samples/xtravels`, start by again running the remote services in separate terminals, then start xtravels server again in a third terminal, this time within `cds repl` instead of `cds watch`: ```shell :line-numbers=1 cds watch s4 ``` ```shell :line-numbers=2 cds watch xflights ``` ```shell :line-numbers=3 cds repl xtravels ``` Within the REPL, connect to local and remote services: ```js const TravelService = await cds.connect.to ('TravelService') const xflights = await cds.connect.to ('sap.capire.flights.FlightsService') const s4 = await cds.connect.to ('sap.capire.s4.business-partner') ``` Read data directly from the remote `A_BusinessPartner` entity. ```js await s4.run (SELECT.from`A_BusinessPartner`.limit (3)) await s4.read`A_BusinessPartner`.limit (3) // shorthand // [!code focus] ``` > The variant on line 2 is a convenient shorthand for the one on line 1. ::: details See results output ... ```zsh => [ { BusinessPartner: '000001', PersonFullName: 'Mrs. Theresia Buchholm', LastChangeDate: '2024-01-19', LastChangeTime: '21:48:32', BusinessPartnerCategory: '1' }, { BusinessPartner: '000002', PersonFullName: 'Mr. Johannes Buchholm', LastChangeDate: '2024-01-08', LastChangeTime: '11:22:01', BusinessPartnerCategory: '1' }, { BusinessPartner: '000003', PersonFullName: 'Mr. James Buchholm', LastChangeDate: '2022-11-04', LastChangeTime: '15:27:46', BusinessPartnerCategory: '1' } ] ``` ::: Read the same data via the `sap.capire.s4.Customers` consumption view: ```js const { Customers } = cds.entities ('sap.capire.s4') await s4.read (Customers) .limit (3) // [!code focus] ``` ::: details See results output ... ```zsh => [ { ID: '000001', Name: 'Mrs. Theresia Buchholm', modifiedAt: '2024-01-19' }, { ID: '000002', Name: 'Mr. Johannes Buchholm', modifiedAt: '2024-01-08' }, { ID: '000003', Name: 'Mr. James Buchholm', modifiedAt: '2022-11-04' } ] ``` Note how field names and structure are adapted to our domain. ::: ::: details See OData requests ... Watch the log output in the second terminal to see the translated OData requests being received by the remote service, for example: ```zsh [odata] - GET /odata/v4/s4-business-partner/A_BusinessPartner { '$top': '3' } ``` ```zsh [odata] - GET /odata/v4/s4-business-partner/A_BusinessPartner { '$select': 'BusinessPartner,PersonFullName,LastChangeDate', '$top': '3' } ``` ::: CRUD some data into remote `A_BusinessPartner` entity, still via the `sap.capire.s4.Customers` consumption view: ```js await s4.insert ({ ID: '123', Name: 'Sherlock' }) .into (Customers) await s4.create (Customers, { ID: '456', Name: 'Holmes' }) await s4.read`ID, Name` .from (Customers) .where`length(ID) <= 3` await s4.update (Customers,'123') .with ({ modifiedAt: '2026-01-01' }) await s4.delete (Customers,'123') await s4.delete (Customers) .where`ID = ${'456'}` ``` Go on like that and try out similar requests with the other services, that is, `TravelService` and `xflights`. For the latter you might run into `401` errors, in that case run the following once in the REPL to run in privileged mode: ```js cds.User.default = cds.User.privileged ``` ## Using `npm` Workspaces > Source: /docs/guides/integration/inner-loops#using-npm-workspaces So far we assumed we mainly worked within the *xtravels* project, and we consumed the API from xflights via `npm publish` / `npm install`. There might be situations where we would want to shortcut this process. For example, we might want to consume a very latest version of the xflights API, which is not yet published to the *npm* registry. Or we might even want to work on both projects simultaneously, and test our latest changes to *xflights* in *xtravels* in close loops. So, in essence, instead of exercising a workflow like that again and again: - ( *develop* → *export* → *publish* ) → *npmjs.com* → ( *update* → *consume* ) ... we can use *npm workspaces* technique to work locally and speed up things as follows (we did that already above, shown here again for local completeness): ```sh :line-numbers mkdir -p cap/samples cd cap/samples git clone https://github.com/capire/xtravels git clone https://github.com/capire/xflights git clone https://github.com/capire/s4 ``` ```sh :line-numbers=6 echo '{"workspaces":["xflights","xtravels","s4"]}' > package.json npm install ``` Add a link to the local `@capire/xflights-data` API package, included with the cloned xflights sources: ```shell npm add ./xflights/apis/data-service ``` Check the installation using `npm ls`, which would yield output as below, showing that `@capire/xtravel`'s dependency to `@capire/xflights-data` is nicely fulfilled by a local link to `./xflights/apis/data-service`: ```shell npm ls @capire/xflights-data ``` ```zsh samples@ ~/cap/samples ├── @capire/xflights-data@0.1.11 -> ./xflights/apis/data-service └─┬ @capire/xtravels@1.0.0 -> ./xtravels └── @capire/xflights-data@0.1.11 deduped -> ./xflights/apis/data-service ``` Start the xtravels application → and note the sources loaded from *./xflights/apis/data-service*, and the information further below about the `FlightsService` service mocked automatically: ```shell cds watch xtravels ``` ```zsh [cds] - loaded model from 20 file(s): xtravels/srv/travel-service.cds xtravels/db/schema.cds xtravels/db/xflights.cds xflights/apis/data-service/index.cds xflights/apis/data-service/services.csn ... ``` ```zsh [cds] - mocking sap.capire.flights.FlightsService { at: [ '/odata/v4/data', '/rest/data', '/hcql/data' ], decl: 'xflights/apis/data-service/services.csn:3', } ``` > [!tip] > > So, using `npm` workspaces we've streamlined our workflows as follows: > > - Before: ( *change* → *export* → *publish* ) → *npmjs.com* → ( *update* → *consume* ) > - After: ( *change* → *export* ) → ( *consume* ) ## Using Proxy Packages > Source: /docs/guides/integration/inner-loops#using-proxy-packages The usage of *npm workspaces* technique as described above streamlined our workflows as follows: - Before: ( *develop* → *export* → *publish* ) → *npmjs.com* → ( *update* → *consume* ) - After: ( *develop* → *export* ) → ( *consume* ) We can streamline that even more by eliminating the export step as follows... Create a new subfolder `xflights-api-shortcut` in which we add a _package.json_ and an _index.cds_ file as follows: ```shell mkdir xflights-api-shortcut ``` ::: code-group ```json [package.json] { "name": "@capire/xflights-data", "dependencies": { "@capire/xflights": "*" } } ``` ::: ::: code-group ```cds [index.cds] using from '@capire/xflights/srv/data-service'; ``` :::
Using the shell's "here document" technique You can also create these two files from the command line as follows: ```shell cat > xflights-api-shortcut/package.json << EOF { "name": "@capire/xflights-data", "dependencies": { "@capire/xflights": "*" } } EOF ``` Take the same approach for the `index.cds` file: ```shell cat > xflights-api-shortcut/index.cds << EOF using from '@capire/xflights/srv/data-service'; EOF ```
With that in place, change our API package dependency in the workspace root as follows: ```shell npm add ./xflights-api-shortcut ``` Check the effect of that → note how `@capire/xflights-data` dependencies now link to `./xflights-api-shortcut`: ```shell npm ls @capire/xflights-data ``` ```zsh samples@ ~/cap/samples ├── @capire/xflights-data@ -> ./xflights-api-shortcut └─┬ @capire/xtravels@1.0.0 -> ./xtravels └── @capire/xflights-data@ deduped -> ./xflights-api-shortcut≤ ``` Start the *xtravels* application → and note the sources loaded from *./xflights-api-shortcut*, and the information further below about the `FlightsService` service now being _served_, not _mocked_ anymore: ```shell cds watch xtravels ``` ```zsh [cds] - loaded model from 20 file(s): xtravels/srv/travel-service.cds xtravels/db/schema.cds xtravels/db/xflights.cds xflights-api-shortcut/index.cds xflights/srv/data-service.cds xflights/db/schema.cds ... ``` ```zsh [cds] - serving sap.capire.flights.FlightsService { at: [ '/odata/v4/data', '/rest/data', '/hcql/data' ], decl: 'xflights/apis/data-service/services.csn:3', } ``` > [!tip] > > So, in total, we've streamlined our workflows as follows: > > - Before: ( *change* → *export* → *publish* ) → *npmjs.com* → ( *update* → *consume* ) > - Step 1: ( *change* → *export* ) → ( *consume* ) > - Step 2: ( *change* ) → ( *consume* ) # CAP Service Composition > Source: /docs/guides/integration/reuse-and-compose Explore in this guide how to compose enhanced solutions by reusing services and other content from modular projects, and adapt them to your needs with solution-specific projections and extensions. {.abstract} ## Introduction and Overview > Source: /docs/guides/integration/reuse-and-compose#introduction-and-overview CAP promotes reuse and composition by importing content from reuse packages. Reused content, shared and imported that way, can comprise models, code, initial data, and i18n bundles. ### Usage Scenarios > Source: /docs/guides/integration/reuse-and-compose#usage-scenarios By applying CAP's techniques for reuse, composition, and integration, you can address several different usage scenarios, as depicted in the following illustration. ![This graphic is explained in the accompanying text.](../extensibility/assets/scenarios.drawio.svg) 1. **Verticalized/Composite Solutions** — Pick one or more reuse packages/services. Enhance them, mash them up into a composite solution, and offer this as a new packaged solution to clients. 2. **Prebuilt Extension Packages** — Instead of offering a new packaged solution, you could also just provide your enhancements as a prebuilt extension package, for example, for **verticalization**, which you in turn offer to others as a reuse package. 3. **Prebuilt Integration Packages** — Prebuilt extension packages could also involve prefabricated integrations to services in back-end systems, such as S/4HANA and SAP SuccessFactors. 4. **Prebuilt Business Data Packages** — A variant of prebuilt integration packages, in which you would provide a reuse package that provides initial data for certain entities, like a list of *Languages*, *Countries*, *Regions*, *Currencies*, etc. 5. **Customizing SaaS Solutions** — Customers, who are subscribers of SaaS solutions, can apply the same techniques to adapt SaaS solutions to their needs. They can use prebuilt extension or business data packages, or create their own custom-defined ones. ### Examples from [sample repositories](https://github.com/capire) > Source: /docs/guides/integration/reuse-and-compose#examples-from-sample-repositorieshttpsgithubcomcapire In the following sections, we frequently refer to examples from the [capire org](https://github.com/capire): ![The screenshot is explained in the following text.](../extensibility/assets/cap-samples.drawio.svg) - **[@capire/bookshop](https://github.com/capire/bookshop)** provides a basic bookshop app and **reuse services** . - **[@capire/common](https://github.com/capire/common)** is a **prebuilt extension** and **business data** package for *Countries*, *Currencies*, and *Languages*. - **[@capire/reviews](https://github.com/capire/reviews)** provides an independent **reuse service**. - **[@capire/orders](https://github.com/capire/orders)** provides another independent **reuse service**. - **[@capire/bookstore](https://github.com/capire/bookstore)** combines all of the above into a **composite application**. ### Preparation for Exercises > Source: /docs/guides/integration/reuse-and-compose#preparation-for-exercises If you want to exercise the code snippets in following sections, do the following: **1)**   Get capire/bookstore: ```sh git clone https://github.com/capire/bookstore cd bookstore npm install ``` **2)**   Start a sample project: ```sh cds init sample --nodejs cd sample npm i # ... run the upcoming commands in here > Source: /docs/guides/integration/reuse-and-compose#-run-the-upcoming-commands-in-here ``` ## Importing Reuse Packages > Source: /docs/guides/integration/reuse-and-compose#importing-reuse-packages CAP and CDS promote reuse of prebuilt content based on `npm` or `Maven` techniques. The following figure shows the basic procedure for `npm`. ![This graphic shows how packages are used, provided, and deployed. ALl the details around that are explained in the following sections.](../extensibility/assets/reuse-overview.drawio.svg) > We use `npm` and `Maven` as package managers simply because we didn't want to reinvent the wheel here. ### Using `npm add/install` from _npm_ Registries > Source: /docs/guides/integration/reuse-and-compose#using-npm-addinstall-from-npm-registries Use _`npm add/install`_ to import reuse packages to your project, like so: ```sh npm add @capire/bookshop @capire/common ``` This installs the content of these packages into your project's `node_modules` folder and adds corresponding dependencies: ::: code-group ```json [package.json] { "name": "sample", "version": "1.0.0", "dependencies": { "@capire/bookshop": "^1.0.0", "@capire/common": "^1.0.0", ... } } ``` ::: > These dependencies allow you to use `npm outdated`, `npm update`, and `npm install` later to get the latest versions of imported packages. ### Importing from Other Sources > Source: /docs/guides/integration/reuse-and-compose#importing-from-other-sources In addition to importing from _npm_ registries, you can also import from local sources. This can be other CAP projects that you have access to, or tarballs of reuse packages, for example, downloaded from some marketplace. ```sh npm add ~/Downloads/@capire-bookshop-1.0.0.tgz npm add ../bookshop ``` > You can use `npm pack` to create tarballs from your projects if you want to share them with others. ### Importing from Maven Dependencies > Source: /docs/guides/integration/reuse-and-compose#importing-from-maven-dependencies Add the dependency to the reuse package to your `pom.xml`: ::: code-group ```xml [pom.xml] com.sap.capire bookshop 1.0.0 ``` ::: As Maven dependencies are - in contrast to `npm` packages - downloaded into a global cache, you need to make the artifacts from the reuse package available in your project locally. The CDS Maven Plugin provides a simple goal named `resolve`, that performs this task for you and extracts reuse packages into the `target/cds/` folder of the CAP project. Include this goal into the `pom.xml`, if not already present: ::: code-group ```xml [pom.xml] com.sap.cds cds-maven-plugin ${cds.services.version} ... cds.resolve resolve ... ``` ::: ### Embedding vs. Integrating Reuse Services > Source: /docs/guides/integration/reuse-and-compose#embedding-vs-integrating-reuse-services By default, when importing reuse packages, all imported content becomes an integral part of your project, it literally becomes **embedded** in your project. This applies to all the things an imported package can contain, such as: - Domain models - Service definitions - Service implementations - i18n bundles - Initial data [See an example for a data package for `@sap/cds/common`](../../cds/common#prebuilt-data){ .learn-more} However, you decide which parts to actually use and activate in your project by means of model references as shown in the following sections. Instead of embedding reuse content, you can also **integrate** with remote services, deployed as separate microservices as outlined in [*Service Integration*](#service-integration). ## Reuse & Extend Models > Source: /docs/guides/integration/reuse-and-compose#reuse--extend-models Even though all imported content is embedded in your project, you decide which parts to actually use and activate by means of model references. For example, if an imported package comes with three service definitions, it's still you who decides which of them to serve as part of your app, if any. The rule is: ::: tip Active by Reachability Everything that you are referring to from your own models is served. Everything outside of your models is ignored. ::: ### Via `using from` Directives > Source: /docs/guides/integration/reuse-and-compose#via-using-from-directives Use the definitions from imported models through [`using` directives](../../cds/cdl#model-imports) as usual. For example, like in [@capire/bookstore](https://github.com/capire/bookstore/blob/main/srv/mashup.cds#L28-L30), simply add all: ::: code-group ```cds [bookstore/srv/mashup.cds] // Ensure models from all imported packages are loaded using from '@capire/orders/app/fiori'; using from '@capire/data-viewer'; using from '@capire/common'; ``` ::: The `cds` compiler finds the imported content in `node_modules` when processing imports with absolute targets as shown previously. ### Using _index.cds_ Entry Points > Source: /docs/guides/integration/reuse-and-compose#using-indexcds-entry-points The above `using from` statements assume that the imported packages provide _index.cds_ in their roots as [public entry points](#entry-points), which they do. For example see [@capire/bookshop/index.cds](https://github.com/capire/bookshop/blob/main/index.cds): ::: code-group ```cds [bookshop/index.cds] // exposing everything... using from './db/schema'; using from './srv/cat-service'; using from './srv/admin-service'; ``` ::: This _index.cds_ imports and therefore activates everything. Running `cds watch` in your project would show you this log output, indicating that all initial data and services from your imported packages are now embedded and served from your app: ```log [cds] - connect to db > sqlite { database: ':memory:' } > filling sap.common.Currencies from common/data/sap.common-Currencies.csv > filling sap.common.Currencies_texts from common/data/sap.common-Currencies_texts.csv > filling sap.capire.bookshop.Authors from bookshop/db/data/sap.capire.bookshop-Authors.csv > filling sap.capire.bookshop.Books from bookshop/db/data/sap.capire.bookshop-Books.csv > filling sap.capire.bookshop.Books_texts from bookshop/db/data/sap.capire.bookshop-Books_texts.csv > filling sap.capire.bookshop.Genres from bookshop/db/data/sap.capire.bookshop-Genres.csv /> successfully deployed to sqlite in-memory db [cds] - serving AdminService { at: '/admin', impl: 'bookshop/srv/admin-service.js' } [cds] - serving CatalogService { at: '/browse', impl: 'bookshop/srv/cat-service.js' } ``` ### Using Different Entry Points > Source: /docs/guides/integration/reuse-and-compose#using-different-entry-points If you don't want everything, but only a part, you can change your `using from` directives like this: ```cds using { CatalogService } from '@capire/bookshop/srv/cat-service'; ``` The output of `cds watch` would reduce to: ```log [cds] - connect to db > sqlite { database: ':memory:' } > filling sap.capire.bookshop.Authors from bookshop/db/data/sap.capire.bookshop-Authors.csv > filling sap.capire.bookshop.Books from bookshop/db/data/sap.capire.bookshop-Books.csv > filling sap.capire.bookshop.Books_texts from bookshop/db/data/sap.capire.bookshop-Books_texts.csv > filling sap.capire.bookshop.Genres from bookshop/db/data/sap.capire.bookshop-Genres.csv /> successfully deployed to sqlite in-memory db [cds] - serving CatalogService { at: '/browse', impl: 'bookshop/srv/cat-service.js' } ``` > Only the CatalogService is served now. ::: tip Check the _readme_ files that come with reuse packages for information about which entry points are safe to use. ::: ### Extending Imported Definitions > Source: /docs/guides/integration/reuse-and-compose#extending-imported-definitions You can freely use all definitions from the imported models in the same way as you use definitions from your own models. This includes using declared types, adding associations to imported entities, building views on top of imported entities, and so on. You can even extend imported definitions, for example, add elements to imported entities, or add/override annotations, without limitations. Here's an example from the [@capire/bookstore](https://github.com/capire/bookstore/blob/main/srv/mashup.cds#L11-L16): ::: code-group ```cds [bookstore/srv/mashup.cds] // Extend Books with access to Reviews and average ratings using { sap.capire.reviews.api.ReviewsService as reviews } from '@capire/reviews'; using { sap.capire.bookshop.Books } from '@capire/bookshop'; extend Books with { rating : type of reviews.AverageRatings:rating; // average rating numberOfReviews : Integer @title : '{i18n>NumberOfReviews}'; } ``` ::: ## Reuse & Extend Code > Source: /docs/guides/integration/reuse-and-compose#reuse--extend-code Service implementations, in particular custom-coding, are also imported and served in embedding projects. Follow the instructions if you need to add additional custom handlers. ### In Node.js > Source: /docs/guides/integration/reuse-and-compose#in-nodejs One way to add your own implementations is to replace the service implementation as follows: 1. Add/override the `@impl` annotation ```cds using { CatalogService } from '@capire/bookshop'; annotate CatalogService with @impl:'srv/my-cat-service-impl'; ``` 2. Place your implementation in `srv/my-cat-service-impl.js`: ::: code-group ```js [srv/my-cat-service-impl.js] module.exports = cds.service.impl (function(){ this.on (...) // add your event handlers }) ``` ::: 3. If the imported package already had a custom implementation, you can include that as follows: ::: code-group ```js [srv/my-cat-service-impl.js] const base_impl = require ('@capire/bookshop/srv/cat-service') module.exports = cds.service.impl (async function(){ this.on (...) // add your event handlers await base_impl.call (this,this) }) ``` ::: > Make sure to invoke the base implementation exactly like that, with `await`. And make sure to check the imported package's readme to check whether access to that implementation module is safe. ### In Java > Source: /docs/guides/integration/reuse-and-compose#in-java You can provide your own implementation in the same way, as you do for your own services: 1. Import the service in your CDS files ```cds using { CatalogService } from 'com.sap.capire/bookshop'; ``` 2. Add your own implementation next to your other event handler classes: ```java @Component @ServiceName("CatalogService") public class CatalogServiceHandler implements EventHandler { @On(/* ... */) void myHandler(EventContext context) { // ... } } ``` ## Reuse & Extend UIs > Source: /docs/guides/integration/reuse-and-compose#reuse--extend-uis If imported packages provide UIs, you can also serve them as part of your app — for example, using standard [express.js](https://expressjs.com) middleware means in Node.js. The *@capire/bookstore* app has this [in its `srv/mashup.js`](https://github.com/capire/bookstore/blob/c44d97bc00d4c56f5dff02f820011b25db8312e4/srv/mashup.js#L4-L9) to serve [the Vue.js app imported with *@capire/bookshop*](https://github.com/capire/bookshop/tree/main/app/vue) using the `app.serve().from()` method: ::: code-group ```js [srv/mashup.js] const cds = require('@sap/cds') // Add routes to UIs from imported packages cds.once('bootstrap',(app)=>{ app.serve ('/bookshop') .from ('@capire/bookshop','app/vue') app.serve ('/reviews') .from ('@capire/reviews','app/vue') app.serve ('/orders') .from('@capire/orders','app/orders') }) ``` ::: [More about Vue.js in our _Getting Started in a Nutshell_](../../get-started/bookshop#serving-uis){.learn-more} [Learn more about serving Fiori UIs.](../uis/fiori){.learn-more} This ensures all static content for the app is served from the imported package. In both cases, all dynamic requests to the service endpoint anyways reach the embedded service, which is automatically served at the same endpoint it was served in the bookshop. In case of Fiori elements-based UIs, the reused UIs can be extended by [extending their models as decribed above](#reuse-models), in this case overriding or adding Fiori annotations. ## Service Integration > Source: /docs/guides/integration/reuse-and-compose#service-integration Instead of embedding and serving imported services as part of your application, you can decide to integrate with them, having them deployed and run as separate microservices. ### Import the Remote Service's APIs > Source: /docs/guides/integration/reuse-and-compose#import-the-remote-services-apis This is described in the [Import Reuse Packages section](#import) → for example using `npm add`. Here's the effect of this step in [@capire/bookstore](https://github.com/capire/bookstore/blob/main/package.json): ::: code-group ```json [bookstore/package.json] "dependencies": { "@capire/bookshop": "*", "@capire/reviews": "*", "@capire/orders": "*", "@capire/common": "*", "@capire/data-viewer": "*", ... }, ``` ::: ### Configuring Required Services > Source: /docs/guides/integration/reuse-and-compose#configuring-required-services To configure required remote services in Node.js, simply add the respective entries to the [`cds.requires` config option](../../node.js/cds-env). You can see an example in [@capire/bookstore/package.json](https://github.com/capire/bookstore/blob/main/package.json), which integrates [@capire/reviews](https://github.com/capire/reviews) and [@capire/orders](https://github.com/capire/orders) as remote service: ::: code-group ```json [bookstore/package.json] "cds": { "requires": { "ReviewsService": { "kind": "odata", "model": "@capire/reviews" }, "OrdersService": { "kind": "odata", "model": "@capire/orders" }, } } ``` ::: > Essentially, this tells the service loader to not serve that service as part of your application, but expects a service binding at runtime in order to connect to the external service provider. #### Restricted Reuse Options > Source: /docs/guides/integration/reuse-and-compose#restricted-reuse-options Because models of integrated services only serve as imported APIs, you're restricted with respect to how you can use the models of services to integrate with. For example, only adding fields is possible, or cross-service navigation and expands. Yet, there are options to make some of these work programmatically. This is explained in the [next section](#delegating-calls) based on the integration of [@capire/reviews](https://github.com/capire/reviews) in [@capire/bookstore](https://github.com/capire/bookstore). ### Delegating Calls to Remote Services > Source: /docs/guides/integration/reuse-and-compose#delegating-calls-to-remote-services Let's start from the following use case: The bookshop app exposed through [@capire/bookstore](https://github.com/capire/bookstore) will allow end users to see the top 10 book reviews in the details page. To avoid [CORS issues](https://developer.mozilla.org/de/docs/Web/HTTP/CORS), the request from the UI goes to the main `CatalogService` serving the end user's UI and is delegated from that to the remote `ReviewsService`, as shown in this sequence diagram: ![This TAM graphic shows how the requests are routed between services.](../extensibility/assets/delegate-requests.drawio.svg) And this is how we do that in [@cap/bookstore](https://github.com/capire/bookstore/blob/main/srv/mashup.js): ::: code-group ```js [bookstore/srv/mashup.js] const CatalogService = await cds.connect.to ('CatalogService') const ReviewsService = await cds.connect.to ('ReviewsService') CatalogService.prepend (srv => srv.on ('READ', 'Books/reviews', (req) => { console.debug ('> delegating request to ReviewsService') const [id] = req.params, { columns, limit } = req.query.SELECT return ReviewsService.read ('Reviews',columns).limit(limit).where({subject:String(id)}) })) ``` ::: Let's look at that step by step: 1. We connect to both the `CatalogService` (local) and the `ReviewsService` (remote) to mash them up. 2. We register an `.on` handler with the `CatalogService`, which delegates the incoming request to the `ReviewsService`. 3. We wrap that into a call to `.prepend` because the `.on` handler needs to supersede the default generic handlers provided by the CAP runtime → see [ref docs for `srv.prepend`.](../../node.js/core-services#srv-prepend) ### Running with Mocked Remote Services > Source: /docs/guides/integration/reuse-and-compose#running-with-mocked-remote-services If you start [@capire/bookstore](https://github.com/capire/bookstore) locally with `cds watch`, all [required services](https://github.com/capire/bookstore/blob/main/package.json#L26-L36) are automatically mocked, as you can see in the log output when the server starts: ```log [cds] - serving AdminService { at: '/admin', impl: 'bookshop/srv/admin-service.js' } [cds] - serving CatalogService { at: '/browse', impl: 'bookshop/srv/cat-service.js' } [cds] - mocking OrdersService { at: '/orders', impl: 'orders/srv/orders-service.js' } [cds] - mocking ReviewsService { at: '/reviews', impl: 'reviews/srv/reviews-service.js' } ``` > → `OrdersService` and `ReviewsService` are mocked, that is, served in the same process, in the same way as the local services. This allows development and testing functionality with minimum complexity and overhead in fast, closed-loop dev cycles. As all services are co-located in the same process, sharing the same database, you can send requests like this, which join/expand across *Books* and *Reviews*: ```http GET http://localhost:4004/browse/Books/201? &$expand=reviews &$select=ID,title,rating ``` ### Testing Remote Integration Locally > Source: /docs/guides/integration/reuse-and-compose#testing-remote-integration-locally As a next step, following CAP's [Grow-as-you-go](../../get-started/features#grow-as-you-go) philosophy, we can run the services as separate processes to test the remote integration, but still locally in a low-complexity setup. We use the [_automatic binding by `cds watch`_](#bindings-via-cds-watch) as follows: 1. Start the three servers separately, each in a separate shell (from within the root folder in your cloned projects): ```sh cds watch orders --port 4006 ``` ```sh cds watch reviews --port 4005 ``` ```sh cds watch bookstore --port 4004 ``` 2. Send a few requests to the reviews service (port 4005) to add `Reviews`: ```http POST http://localhost:4005/Reviews Content-Type: application/json;IEEE754Compatible=true Authorization: Basic itsme:secret {"subject":"201", "title":"boo", "rating":3 } ``` 3. Send a request to bookshop (port 4004) to fetch reviews via `CatalogService`: ```http GET http://localhost:4004/browse/Books/201/reviews? &$select=rating,date,title &$top=3 ``` > You can find a script for this in [@capire/bookstore/test/requests.http](https://github.com/capire/bookstore/blob/main/test/requests.http). ### Binding Required Services > Source: /docs/guides/integration/reuse-and-compose#binding-required-services Service bindings provide the details about how to reach a required service at runtime, that is, providing the necessary credentials, most prominently the target service's `url`. #### Basic Mechanism Using `cds.env` and Process env Variables > Source: /docs/guides/integration/reuse-and-compose#basic-mechanism-using-cdsenv-and-process-env-variables At the end of the day, the CAP Node.js runtime expects to find the service bindings in the respective entries in `cds.env.requires`: 1. Configured required services constitute endpoints for service bindings: ::: code-group ```json [package.json] "cds": { "requires": { "ReviewsService": {...}, } } ``` ::: 2. These are made available to the runtime via `cds.env.requires`. ```js const { ReviewsService } = cds.env.requires ``` 3. Service bindings essentially fill in `credentials` to these entries. ```js const { ReviewsService } = cds.env.requires //> ReviewsService.credentials = { //> url: "http://localhost:4005/reviews" //> } ``` While you could do the latter in test suites, you would never provide credentials in a hard-coded way like that in productive code. Instead, you'd use one of the options presented in the following sections. #### Automatic Bindings by `cds watch` > Source: /docs/guides/integration/reuse-and-compose#automatic-bindings-by-cds-watch When running separate services locally as described [in the previous section](#testing-locally), this is done automatically by `cds watch`, as indicated by this line in the bootstrapping log output: ```log [cds] - using bindings from: { registry: '~/.cds-services.json' } ``` You can cmd/ctrl-click or double click on that to see the file's content, and find something like this: ::: code-group ```json [~/.cds-services.json] { "cds": { "provides": { "OrdersService": { "kind": "odata", "credentials": { "url": "http://localhost:4006/orders" } }, "ReviewsService": { "kind": "odata", "credentials": { "url": "http://localhost:4005/reviews" } }, "AdminService": { "kind": "odata", "credentials": { "url": "http://localhost:4004/admin" } }, "CatalogService": { "kind": "odata", "credentials": { "url": "http://localhost:4004/browse" } } } } } ``` ::: Whenever you start a CAP server with `cds watch`, this is what happens automatically: 1. For all *provided* services, corresponding entries are written to _~/.cds-services.json_ with respective `credentials`, namely the `url`. 2. For all *required* services, corresponding entries are fetched from _~/.cds-services.json_. If found, the `credentials` are filled into the respective entry in `cds.env.requires.` [as introduced previously](#bindings-via-cds-env). In effect, all the services that you start locally in separate processes automatically receive their required bindings so they can talk to each other out of the box. #### Through Process Environment Variables > Source: /docs/guides/integration/reuse-and-compose#through-process-environment-variables You can pass credentials as process environment variables, for example in ad-hoc tests from the command line: ```sh export cds_requires_ReviewsService_credentials_url=http://localhost:4005/reviews cds watch bookstore ``` ... or add them to a local `.env` file for repeated local tests: ::: code-group ```properties [.env] cds.requires.ReviewsService.credentials = { "url": "http://localhost:4005/reviews" } ``` ::: > Note: never check in or deploy these `.env` files! #### Through `VCAP_SERVICES` > Source: /docs/guides/integration/reuse-and-compose#through-vcapservices When deploying to Cloud Foundry, service bindings are provided in `VCAP_SERVICES` process environment variables [as documented here](../../node.js/cds-connect#vcap-services). #### In Target Cloud Environments > Source: /docs/guides/integration/reuse-and-compose#in-target-cloud-environments Find information about how to do so in different environment under these links: - [Deploying Services using MTA Deployer](https://help.sap.com/docs/HANA_CLOUD_DATABASE/c2b99f19e9264c4d9ae9221b22f6f589/33548a721e6548688605049792d55295.html) - [Service Bindings in SAP BTP Cockpit](https://help.sap.com/docs/SERVICEMANAGEMENT/09cc82baadc542a688176dce601398de/0e6850de6e7146c3a17b86736e80ee2e.html) - [Service Bindings using the Cloud Foundry CLI](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/296cd5945fd84d7d91061b2b2bcacb93.html) - [Service Binding in Kyma](hhttps://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/d1aa23c492694d669c89a8d214f29147.html) ## Providing Reuse Packages > Source: /docs/guides/integration/reuse-and-compose#providing-reuse-packages In general, every CAP-based product can serve as a reuse package consumed by others. There's actually not much to do. Just create models and implementations as usual. The following sections are about additional things to consider as a provider of a reuse package. ### Considerations for Maven-based reuse packages > Source: /docs/guides/integration/reuse-and-compose#considerations-for-maven-based-reuse-packages When providing your reuse package as a Maven dependency you need to ensure that the CDS, CSV and i18n files are included into the JAR. Place them in a `cds` folder in your `resources` folder under a unique module directory (for example, leveraging group ID and artifact ID): ```txt src/main/resources/cds/ com.sap.capire/bookshop/ index.cds CatalogService.cds data/ com.sap.capire.bookshop-Books.csv i18n/ i18n.properties ``` This structure ensures that the CDS Maven Plugin `resolve` goal extracts these files correctly to the `target/cds/` folder. >Note that `com.sap.capire/bookshop` is used when importing the models with a `using` directive. ### Provide Public Entry Points > Source: /docs/guides/integration/reuse-and-compose#provide-public-entry-points Following the Node.js 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 reuse packages, similar to the use of _index.js_ files in Node. For example: ::: code-group ```cds [provider/index.cds] namespace my.reuse.package; using from './db/schema'; using from './srv/cat-service'; using from './srv/admin-service'; ``` ::: This allows your users to refer to your models in `using` directives using just the package name, like so: ::: code-group ```cds [consumer/some.cds] using { my.thing } from 'my-reuse-package'; ``` ::: In addition, you might want to provide other entry points to ease partial usage options. For example, you could provide a _schema.cds_ file in your root, to allow using the domain model without services: ::: code-group ```cds [consumer/more.cds] using { my.domain.entity } from 'my-reuse-package/schema'; using { my.service } from 'my-reuse-package/services'; ``` ::: ### Provide Custom Handlers > Source: /docs/guides/integration/reuse-and-compose#provide-custom-handlers #### In Node.js > Source: /docs/guides/integration/reuse-and-compose#in-nodejs-1 In general, custom handlers can be placed in files matching the naming of the _.cds_ files they belong to. In a reuse package, you have to use the `@impl` annotation to make it explicit which custom handler to use. In addition you need to use the fully qualified module path inside the `@impl` annotation. Imagine that our bookshop is an _@sap_-scoped reuse module and the _CatalogService_ has a custom handler. This is how the service definition would look: ::: code-group ```cds [bookshop/srv/cat-service.cds] service CatalogService @(impl: '@sap/bookshop/srv/cat-service.js') {...} ``` ::: #### In Java > Source: /docs/guides/integration/reuse-and-compose#in-java-1 If your reuse project is Spring Boot independent, register your custom event handler classes in a `CdsRuntimeConfiguration`: ::: code-group ```java [src/main/java/com/sap/capire/bookshop/BookshopConfiguration.java] package com.sap.capire.bookshop; public class BookshopConfiguration implements CdsRuntimeConfiguration { @Override public void eventHandlers(CdsRuntimeConfigurer configurer) { configurer.eventHandler(new CatalogServiceHandler()); } } ``` ::: Additionally, register the `CdsRuntimeConfiguration` class in a `src/main/resources/META-INF/services/com.sap.cds.services.runtime.CdsRuntimeConfiguration` file to be detected by CAP Java: ::: code-group ``` txt [src/main/resources/META-INF/services/com.sap.cds.services.runtime.CdsRuntimeConfiguration] com.sap.capire.bookshop.BookshopConfiguration ``` ::: Alternatively, if your reuse project is Spring Boot-based, define your event handler classes as Spring beans. Then use Spring Boot's [auto-configuration mechanism](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.developing-auto-configuration) to ensure that your classes are registered automatically when importing the reuse package as a dependency. ### Add a Readme > Source: /docs/guides/integration/reuse-and-compose#add-a-readme You should inform potential consumers about the recommended ways to reuse content provided by your package. At least provide information about: - What is provided – schemas, services, data, and so on - What are the recommended, stable entry points ### Publish/Share with Consumers > Source: /docs/guides/integration/reuse-and-compose#publishshare-with-consumers The preferred way to share reuse packages is by publishing to registries, like _npmjs.org_, _pkg.github.com_ or _Maven Central_. This allows consumers to apply proper version management. However, at the end of the day, any other way to share packages, which you create with `npm pack` or `mvn package` would work as well. ## Customizing SaaS Usage > Source: /docs/guides/integration/reuse-and-compose#customizing-saas-usage Subscribers of SaaS solutions can use the same *reuse and extend* techniques to tailor the application to their requirements, for example by: - Adding/overriding annotations - Adding custom fields and entities - Adding custom data - Adding custom i18n bundles - Importing prebuilt extension packages The main difference is how and from where the import happens: 1. The reuse package, in this case, is the subscribed SaaS application. 2. The import happens via `cds pull`. 3. The imported package is named according to the `cds.extends` entry in package.json 4. The extensions are applied via `cds push`. [Learn more in the **SaaS Extensibility** guide.](../extensibility/customization){.learn-more} # Events and Messaging > Source: /docs/guides/events/ # Core Eventing in CAP > Source: /docs/guides/events/core-concepts This guides introduces CAP's intrinsic support for emitting and receiving events in the very core of the runtimes' processing models. ## Intrinsic Eventing in CAP > Source: /docs/guides/events/core-concepts#intrinsic-eventing-in-cap As introduced in [About CAP](../../get-started/concepts#events), everything happening at runtime is in response to events, and all service implementations take place in [event handlers](../services/custom-code#custom-event-handlers). All CAP services intrinsically support emitting and reacting to events, as shown in this simple code snippet (you can copy & run it in `cds repl`): ```js let srv = new cds.Service // Receiving Events srv.on ('some event', msg => console.log('1st listener received:', msg)) srv.on ('some event', msg => console.log('2nd listener received:', msg)) // Emitting Events await srv.emit ('some event', { foo:11, bar:'12' }) ``` ::: tip Intrinsic support for events The core of CAP's processing model: all services are event emitters. Events can be sent to them, emitted by them, and event handlers register with them to react to such events. ::: ### Emitters and Receivers > Source: /docs/guides/events/core-concepts#emitters-and-receivers In contrast to the previous code sample, emitters and receivers of events are decoupled, in different services and processes. And as all active things in CAP are services, so are usually emitters and receivers of events. Typical patterns look like that: ```js class Emitter extends cds.Service { async someMethod() { // inform unknown receivers about something happened await this.emit ('some event', { some:'payload' }) }} ``` ```js class Receiver extends cds.Service { async init() { // connect to and register for events from Emitter const Emitter = await cds.connect.to('Emitter') Emitter.on ('some event', msg => {...}) }} ``` ::: tip Emitters vs Receivers **Emitters** usually emit messages to *themselves* to inform *potential* listeners about certain events. **Receivers** connect to *Emitters* to register handlers to such emitted events. ::: ### Ubiquitous Events > Source: /docs/guides/events/core-concepts#ubiquitous-events A *Request* in CAP is actually a specialization of an *Event Message*. The same intrinsic mechanisms of sending and reacting to events are used for asynchronous communication in inverse order. A typical flow: ![Clients send requests to services which are handled in event handlers.](assets/sync.drawio.svg) Asynchronous communication looks similar, just with reversed roles: ![Services emit event. Receivers subscribe to events which are handled in event hanlders. ](assets/async.drawio.svg) ::: tip Event Listeners vs Interceptors Requests are handled the same ways as events, with one major difference: While `on` handlers for events are *listeners* (all are called), handlers for synchronous requests are *interceptors* (only the topmost is called by the framework). An interceptor then decides whether to pass down control to `next` handlers or not. ::: ### Asynchronous APIs > Source: /docs/guides/events/core-concepts#asynchronous-apis To sum up, handling events in CAP is done in the same way as you would handle requests in a service provider. Also, emitting event messages is similar to sending requests. The major difference is that the initiative is inverted: While *Consumers* connect to *Services* in synchronous communications, the *Receivers* connect to _Emitters_ in asynchronous ones; _Emitters_ in turn don't know _Receivers_. ![This graphic is explained in the accompanying text.](assets/sync-async.drawio.svg) ::: tip Blurring the line between synchronous and asynchronous API In essence, services receive events. The emitting service itself or other services can register handlers for those events in order to implement the logic of how to react to these events. ::: ## Books Reviews Sample > Source: /docs/guides/events/core-concepts#books-reviews-sample The following explanations walk us through a books review example from cap/samples: * **[@capire/bookshop](https://github.com/capire/bookshop)** provides the well-known basic bookshop app. * **[@capire/reviews](https://github.com/capire/reviews)** provides an independent service to manage reviews. * **[@capire/bookstore](https://github.com/capire/bookstore)** combines both into a composite application. ![This graphic is explained in the accompanying text.](assets/cap-samples.drawio.svg) ::: tip Follow the instructions in [*cap/samples/readme*](https://github.com/capire/samples) for getting the samples and exercising the following steps. ::: ### Declaring Events in CDS > Source: /docs/guides/events/core-concepts#declaring-events-in-cds Package `@capire/reviews` provides a `ReviewsService` API, [declared like that](https://github.com/capire/reviews/tree/main/srv/reviews-service.cds): ```cds service ReviewsService @(path:'reviews/api') { /** * Summary of average ratings per subject. */ @readonly entity AverageRatings as projection on my.Reviews { key subject, round(avg(rating),2) as rating : my.Rating, count(*) as reviews : Integer, } group by subject; /** * Informs about changes in a subject's average rating. */ event AverageRatings.Changed : AverageRatings; // [!code focus] } ``` [Learn more about declaring events in CDS.](../../cds/cdl#events){.learn-more} As we can read from the definition, the service's synchronous API allows to read average ratings per subject; the service's asynchronous API declares the `AverageRatings.Changed` event that shall be emitted whenever a subject's average rating changes. ::: tip **Services in CAP** combine **synchronous** *and* **asynchronous** APIs. Events are declared on conceptual level focusing on domain, instead of low-level wire protocols. ::: ### Emitting Events > Source: /docs/guides/events/core-concepts#emitting-events Find the code to emit events in *[@capire/reviews/srv/reviews-service.js](https://github.com/capire/reviews/tree/main/srv/reviews-service.js#L32-L37)*: ```js // Inform API event subscribers about new avg ratings for reviewed subjects const api = await cds.connect.to ('sap.capire.reviews.api.ReviewsService') this.after (['CREATE','UPDATE','DELETE'], 'Reviews', async function(_,req) { const { subject, rating, reviews } = await api.get ('AverageRatings', { subject: req.data.subject }) return api.emit ('AverageRatings.Changed', { subject, rating, reviews }) // [!code focus] }) ``` [Learn more about `srv.emit()` in Node.js.](../../node.js/core-services#srv-emit-event){.learn-more} [Learn more about `srv.emit()` in Java.](../../java/services#an-event-based-api){.learn-more} Method `srv.emit()` is used to emit event messages. As you can see, emitters usually emit messages to themselves, that is, `this`, to inform potential listeners about certain events. Emitters don't know the receivers of the events they emit. There might be none, there might be local ones in the same process, or remote ones in separate processes. ::: tip Messaging on Conceptual Level Simply use `srv.emit()` to emit events, and let the CAP framework care for wire protocols like CloudEvents, transports via message brokers, multitenancy handling, and so forth. ::: ### Receiving Events > Source: /docs/guides/events/core-concepts#receiving-events Find the code to receive events in *[@capire/bookstore/srv/mashup.js](https://github.com/capire/bookstore/blob/main/srv/mashup.js#L49-L52)* (which is the basic bookshop app enhanced by reviews, hence integration with `ReviewsService`): ```js // Update Books' average ratings when ReviewsService signals updated reviews ReviewsService.on ('AverageRatings.Changed', (msg) => { console.debug ('> received:', msg.event, msg.data) const { subject, count, rating } = msg.data // ... }) ``` [Learn more about registering event handlers in Node.js.](../../node.js/core-services#srv-on-before-after){.learn-more} [Learn more about registering event handlers in Java.](../../java/event-handlers/index.md#introduction-to-event-handlers){.learn-more} The message payload is in the `data` property of the inbound `msg` object. ::: tip To have more control over imported service definitions, you can set the `model` configuration of your external service to a cds file where you define the external service and only use the imported definitions your app needs. This way, plugins like [Open Resource Discovery (ORD)](../../plugins/index#ord-open-resource-discovery) know which parts of the external service you actually use in your application. ::: ## In-Process Eventing > Source: /docs/guides/events/core-concepts#in-process-eventing As emitting and handling events is an intrinsic feature of the CAP core runtimes, there's nothing else required when emitters and receivers live in the same process. ![This graphic is explained in the accompanying text.](assets/local.drawio.svg) Let's see that in action... ### 1. Run CAP Server > Source: /docs/guides/events/core-concepts#1-run-cap-server Run the following command to start a reviews-enhanced bookshop as an all-in-one server process: ```sh cds watch bookstore ``` It produces a trace output like that: ```log [cds] - mocking ReviewsService { path: '/reviews', impl: '../reviews/srv/reviews-service.js' } [cds] - mocking OrdersService { path: '/orders', impl: '../orders/srv/orders-service.js' } [cds] - serving CatalogService { path: '/browse', impl: '../bookshop/srv/cat-service.js' } [cds] - serving AdminService { path: '/admin', impl: '../bookshop/srv/admin-service.js' } [cds] - server listening on { url: 'http://localhost:4004' } [cds] - launched at 5/25/2023, 4:53:46 PM, version: 7.0.0, in: 991.573ms ``` As apparent from the output, both, the two bookshop services `CatalogService` and `AdminService` as well as our new `ReviewsService`, are served in the same process (mocked, as the `ReviewsService` is configured as required service in _[bookstore/package.json](https://github.com/capire/bookstore/blob/main/package.json#L30-L31)_). ### 2. Add Reviews > Source: /docs/guides/events/core-concepts#2-add-reviews Now, open [http://localhost:4004/reviews](http://localhost:4004/reviews) to display the Vue.js UI that is provided with the reviews service sample: ![A vue.js UI, showing the bookshop sample with the adding a review functionality](assets/capire-reviews.png) - Choose one of the reviews. - Change the 5-star rating with the dropdown. - Choose *Submit*. - Enter *bob* to authenticate. → In the terminal window you should see a server reaction like this: ```log [cds] - PATCH /reviews/Reviews/148ddf2b-c16a-4d52-b8aa-7d581460b431 < emitting: reviewed { subject: '201', count: 2, rating: 4.5 } > received: reviewed { subject: '201', count: 2, rating: 4.5 } ``` Which means the `ReviewsService` emitted a `reviewed` message that was received by the enhanced `CatalogService`. ### 3. Check Ratings > Source: /docs/guides/events/core-concepts#3-check-ratings Open [http://localhost:4004/bookshop](http://localhost:4004/bookshop) to see the list of books served by `CatalogService` and refresh to see the updated average rating and reviews count: ![A vue.js UI showing the pure bookhsop sample without additional features.](assets/capire-books.png) # Transactional Event Queues > Source: /docs/guides/events/event-queues The *'Transactional Outbox'* Pattern, generalized {.subtitle} Persist events and scheduled tasks in the same database transaction as your business data, then process them asynchronously with retries and a dead letter queue. {.abstract} > [!tip] Transactional Event Queues – Guiding Principles > > 1. Queued work is written in the same transaction as your business data → *no phantom events, no lost events* > 2. A background runner dispatches it after commit, not during the request → *fast request handling, durable side effects* > 3. Failed work is retried with exponential backoff; unrecoverable entries become dead letters → *ultimate resilience* > > => Application developers stay focused on the domain, not on failure modes. ## Motivation > Source: /docs/guides/events/event-queues#motivation Distributed side effects are hard to get right. An application may commit local data, but a follow-up remote call can still fail because of network errors, service outages, or a process crash. Two-phase commits across a database and a remote service or message broker are impractical in modern cloud architectures, so applications instead aim for **eventual consistency**: the local state and the remote state diverge briefly, but converge after the dispatch completes (or after compensation, if it fails permanently). *Transactional Event Queues* are CAP's mechanism for that. They store the follow-up work in the database as part of the **same transaction** as your business data. Once the transaction commits, a background runner reads pending messages and dispatches them — retrying with exponentially increasing delays on failure, and moving the message to a dead letter queue after a configurable number of attempts. ![Two side-by-side diagrams contrasting two integration patterns. On the left, on a red background labelled 'High Risk of Inconsistent Data', Service A calls Service B directly and each service writes to its own database — if either step fails after the other has committed, the two databases diverge. On the right, on a green background labelled 'Eventual Consistency', Service A writes to its own database and into an event queue inside that same database in one transaction; the queued message is then dispatched to Service B asynchronously. Service B still writes to its own database. Either both writes on the Service-A side commit together or neither does, and the call to Service B is retried until it succeeds.](assets/event-queues-motivation.drawio.svg) Because the queued message and your business data share the same database transaction, you get two core guarantees: - **No phantom events**: if the transaction rolls back, no message is sent. - **No lost events**: if the transaction commits, the queued work is persisted and processed eventually. CAP avoids duplicate execution under normal operation, but handlers must still be idempotent to tolerate rare crash windows or external side effects. This pattern is widely known as the [*'Transactional Outbox'*](https://microservices.io/patterns/data/transactional-outbox.html), but CAP's event queues go beyond outbound messages. They cover three use cases: - **Outbox**: defer outbound calls to remote services and emit messages to message brokers until the transaction succeeds. - **Inbox**: acknowledge inbound messages immediately and process them asynchronously. - **Scheduled Tasks**: run periodic or delayed work such as data replication. ### Pub/Sub vs. Event Queues > Source: /docs/guides/events/event-queues#pubsub-vs-event-queues These are sometimes confused but solve different problems. **Pub/sub**, typically realized through a message broker, addresses *loosely coupled microservices*. A producer publishes events without knowing who consumes them; consumers subscribe by topic. The unit of trust is the broker. **Event queues** address *asynchronous workload processing within one service*. They turn a piece of work into a database row that survives commit, restart, and retry, then dispatch it later: to the same service in process, to a remote service, or to a message broker. The unit of trust is the database transaction. The two patterns complement each other: when the dispatch target *is* a message broker, the event queue is the transactional bridge that makes pub/sub safe across the local commit. The [Inbox](#inbox) does the mirror image on the receiving side. > [!note] Related patterns > [*Event Sourcing*](https://microservices.io/patterns/data/event-sourcing.html) solves the same atomic-state-change-and-publish problem by establishing a source of truth through an append-only event log. Event queues persist messages only until processed and then delete them — they're a transactional bridge to remote systems, not the system of record. > [!tip] When not to use event queues > If you need an immediate, synchronous response from a remote system, use a normal service call. Queued calls execute asynchronously and discard the direct return value. For purely local logic that finishes inside the current request, an event queue adds nothing. ## Outbox > Source: /docs/guides/events/event-queues#outbox The outbox defers outbound calls to remote services and emits messages to message brokers until the main transaction succeeds. This prevents sending requests or messages to external systems when your transaction has not yet committed. ### Programmatic Use > Source: /docs/guides/events/event-queues#programmatic-use **Example:** In the *xtravels* application, when an agent creates a `Bookings` record (a flight booking tied to a travel), the application also notifies *xflights* of the booking. The straightforward implementation is to call *xflights* directly from an `after CREATE` handler: ```js const xflights = await cds.connect.to('xflights') this.after('CREATE', 'Bookings', async (_, req) => { const { flight_ID: flight, flight_date: date } = req.data // Anti-pattern: the remote call happens before the local commit is safe // [!code --] await xflights.send('POST', 'BookingCreated', { flight, date }) // [!code --] }) ``` This works when everything succeeds, but it's not safe: if the surrounding transaction later fails, the external booking may already exist while the local `Bookings` row is rolled back. The outbox fixes this. Wrap the remote service in `cds.queued()` (Node.js) or `OutboxService.outboxed()` (Java) and dispatch as before. The call is now persisted within the current transaction and sent after commit: ::: code-group ```js [Node.js] const xflights = await cds.connect.to('xflights') const qd_xflights = cds.queued(xflights) this.after('CREATE', 'Bookings', async (_, req) => { const { flight_ID: flight, flight_date: date } = req.data // Persisted within the current transaction, sent after commit // [!code ++] await qd_xflights.send('POST', 'BookingCreated', { flight, date }) // [!code ++] }) ``` ```java [Java] @Autowired OutboxService outbox; @Autowired TravelService xflights; @After(event = CqnService.EVENT_CREATE, entity = Bookings_.CDS_NAME) void notifyXFlights(List bookings) { xflights = outbox.outboxed(xflights); // Persisted within the current transaction, sent after commit // [!code ++] bookings.forEach(b -> xflights.bookingCreated(...)); // [!code ++] } ``` ```java [Java w/o @Autowired] @After(event = CqnService.EVENT_CREATE, entity = Bookings_.CDS_NAME) void notifyXFlights(List bookings) { OutboxService outbox = runtime.getServiceCatalog() .getService(OutboxService.class, "XFlightsOutbox"); TravelService xflights = runtime.getServiceCatalog() .getService(TravelService.class, "xflights"); xflights = outbox.outboxed(xflights); // Persisted within the current transaction, sent after commit // [!code ++] bookings.forEach(b -> xflights.bookingCreated(...)); // [!code ++] } ``` ::: If the transaction rolls back, no booking request is sent. > [!tip] Enabled by default > Event queues are enabled by default — there's nothing to install or activate. The persistent queue starts with your application; the configuration shown later is only for tuning. > [!tip] Node.js: await is still needed > Even though processing is asynchronous, you still need to `await` because the message is written to the database within the current transaction. The `xflights` connection here stands in for any remote service you've configured under `cds.requires`. The complete setup of the *xtravels* application and the *xflights* service it consumes lives in the [*@capire/xtravels*](https://github.com/capire/xtravels) sample. A queued call changes *when* work happens and *what the caller can expect back*: - A **direct** call returns the remote service's result (or error) before the local transaction commits. - A **queued** call writes the message to the queue inside the local transaction and returns. The actual remote dispatch happens after commit, in the background. > [!warning] Queued calls discard the direct return value > A queued service persists the request and returns after the message is stored, not after the remote operation finishes. Any return value from `send()` or `run()` is therefore not available to the caller. To act on the outcome, register a [callback handler](#callbacks) on `#succeeded` or `#failed`. To get the original synchronous service from a queued proxy: ::: code-group ```js [Node.js] const xflights = cds.unqueued(qd_xflights) ``` ```java [Java] CqnService xflights = OutboxService.unboxed(outboxedXFlights); ``` ::: ### By Configuration > Source: /docs/guides/events/event-queues#by-configuration > [!note] Node.js only > Outboxing required services by configuration is available in Node.js only. To outbox a required service centrally, without touching handler code, set a flag on its configuration. Every call from your handlers is then queued automatically. ::: code-group ```json [Node.js - package.json] { "cds": { "requires": { "messaging": { "outboxed": true } } } } ``` ::: This is the typical setup for **technical services**, such as messaging and audit logging, where every emit must be durable. CAP enables it by default for those services (see [*Auto-Outboxed Services*](#auto-outboxed-services) below). For **business services**, however, a class-level flag is usually too coarse. Remote integrations called from domain handlers typically need *some* calls outboxed, for example, the post-commit notification to *xflights*, while others stay synchronous (a read-through query, a probe before commit). For finer control, prefer the programmatic path with `cds.queued()` or `srv.schedule()`. ### Auto-Outboxed Services > Source: /docs/guides/events/event-queues#auto-outboxed-services Some services are outboxed automatically, so you don't need to wrap or configure them: | Service | Description | |---------|-------------| | `cds.MessagingService` | All messaging services | | `cds.AuditLogService` | Audit log events | This ensures that messaging and audit log events are sent reliably and never lost because of transaction rollbacks. They use the persistent queue by default. [Learn more about auto-outboxed services in Node.js.](../../node.js/event-queues#queueing-a-service){.learn-more} [Learn more about auto-outboxed services in Java.](../../java/event-queues#default-outbox-services){.learn-more} ### Callbacks > Source: /docs/guides/events/event-queues#callbacks-alpha- > [!note] Node.js only > Callback events `#succeeded` and `#failed` are currently available in Node.js only. Java doesn't have an equivalent yet, but it's on the roadmap. Because queued calls return after the message is *stored*, not after the remote operation completes, you can't use the return value of `send()` or `run()` to react to success or failure. Instead, register a callback handler on the queued service: - `/#succeeded`: fires when processing completes successfully. - `/#failed`: fires when the message becomes a dead letter (after all retries are exhausted). **Example:** After *xflights* successfully processes a `BookingCreated` event, the *xtravels* application replicates the booking confirmation back into its own database. If the booking fails, the application updates the local `Bookings` row to surface the error in its UI. ::: code-group ```js [Node.js] const xflights = await cds.connect.to('xflights') // Called when the queued booking succeeds xflights.after('BookingCreated/#succeeded', async (result, req) => { console.log('Flight booked successfully:', result) // Replicate booking details from remote }) // Called when the queued booking fails after max retries xflights.after('BookingCreated/#failed', async (error, req) => { console.log('Flight booking failed:', error) // Trigger compensation logic }) ``` ::: This is also the foundation for [SAGA-style](https://microservices.io/patterns/data/saga.html) compensation across distributed systems: once an outboxed call has gone out, you maintain consistency by reacting to outcomes and applying compensation logic where needed. > [!tip] Register on specific events > Callback handlers must be registered for the specific `#succeeded` or `#failed` events. > The `*` wildcard handler is not called for these events. ## Inbox > Source: /docs/guides/events/event-queues#inbox The inbox mirrors the [*'Outbox'* pattern](#outbox) for inbound messages. When a message arrives from a broker, the messaging service immediately persists it to the database, acknowledges it to the broker, and schedules its processing. This brings two advantages: - **Quick acknowledgment**: the broker no longer waits for your processing to complete, which keeps consumer throughput high under load. - **Controlled processing rate**: if a burst of messages arrives, they are queued in your database and processed at a controlled pace. > [!note] Especially useful when broker redelivery doesn't fit > Some message brokers don't allow redelivery or payload correction. Others have fixed redelivery timeouts that expire when your processing legitimately takes longer than the broker's window. With the inbox, the broker's job ends at acknowledgement and failures are handled inside your app via the [dead letter queue](#dead-letter-queue), where you have full control over retry timing, payload correction, and discard. Enable the inbox in your configuration: ::: code-group ```json [Node.js — package.json] { "cds": { "requires": { "messaging": { "inboxed": true } } } } ``` ```yaml [Java — application.yaml] cds: messaging: services: - name: messaging-name inbox: enabled: true ``` ::: > [!warning] Inboxing shifts failure handling to your application > With inboxing enabled, the broker considers the message delivered as soon as your app stores it. > If later processing fails, recovery no longer happens in the broker; it happens in your application's retry and dead letter queue flow. ## Scheduled Tasks > Source: /docs/guides/events/event-queues#scheduled-tasks Event queues are not limited to outbound calls and messaging. You can schedule arbitrary work such as data replication, cache refresh, or garbage collection. A scheduled task is identified by its event name and exists only once: a subsequent `schedule()` call with the same name overwrites the previous schedule (tasks are upserted, not deduplicated). This makes scheduling idempotent, which is convenient during application startup, where the same registration code runs on every boot. **Example:** Replicate airport master data from the *xflights* service every 10 minutes. ::: code-group ```js [Node.js] const xflights = await cds.connect.to('xflights') await xflights.schedule('replicate', { entity: 'Airports' }).every('10m') ``` ```java [Java] @Autowired OutboxService outbox; @Autowired TravelService xflights; Schedulable.of(xflights, outbox) .scheduled(Schedule.create().every(Duration.ofMinutes(10))) .replicateTravels(...); ``` ::: The `schedule()` method queues like `cds.queued(srv).send(event, data)`, that is within the current transaction and dispatched after commit, but it **upserts** a singleton task keyed by event name (or by `.as(name)`) instead of inserting a new entry on every call. It also accepts optional timing: ::: code-group ```js [Node.js] // Execute once, as soon as possible await xflights.schedule('cleanup', { olderThan: '30d' }) // Execute once, after a delay await xflights.schedule('cleanup', { olderThan: '30d' }) .after('1h') // [!code highlight] // Execute repeatedly — supports time strings and cron expressions await xflights.schedule('replicate', { entity: 'Airports' }) .every('10m') // [!code highlight] await xflights.schedule('replicate', { entity: 'Airports' }) .every('*/10 * * * *') // [!code highlight] // Remove a previously scheduled task await xflights.unschedule('replicate') ``` ```java [Java] @Autowired OutboxService outbox; // Execute once, as soon as possible outbox.submit("cleanup", message, Schedule.NOW); // Execute once, after a delay outbox.submit("cleanup", message, Schedule.create().after(Duration.ofHours(1))); // [!code highlight] // Execute repeatedly outbox.submit("replicate", message, Schedule.create().every(Duration.ofMinutes(10))); // [!code highlight] // Execute repeatedly on a cron expression (6-field Spring syntax) outbox.submit("replicate", message, Schedule.create().cron("0 */10 * * * *")); // [!code highlight] // Remove a previously scheduled task outbox.submit("replicate", OutboxMessage.create(), Schedule.create().cancel()); ``` ::: **Node.js** — `.after()` accepts milliseconds or a time string (`'1s'`, `'10m'`, `'1h'`). `.every()` accepts the same plus a five-field cron expression. Fluent calls can be combined in any order; `.as()` is typically chained last. **Java** — `after(Duration)` and `every(Duration)` accept a `java.time.Duration`. `cron(String)` uses the six-field [Spring cron syntax](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/support/CronExpression.html) (second minute hour day month weekday). `cron` is mutually exclusive with `after`/`every`; `after` and `every` may be combined. > [!note] `every` is a post-execution delay > The interval defined by `.every()` is applied *after* a successful execution completes — it is not a fixed-rate interval. The next run is scheduled from the moment the previous run finishes, not from when it started. To schedule the same event with different payloads as independent tasks, give each its own task name with `.as()`: ::: code-group ```js [Node.js] // Two independent singleton tasks for the same "replicate" event await xflights.schedule('replicate', { entity: 'Airports' }).every('10m') .as('replicate-airports') // [!code highlight] await xflights.schedule('replicate', { entity: 'Airlines' }).every('1h') .as('replicate-airlines') // [!code highlight] // Each can be removed independently by its task name await xflights.unschedule('replicate-airports') await xflights.unschedule('replicate-airlines') ``` ```java [Java] OutboxMessage airports = OutboxMessage.create(); airports.setParams(Map.of("entity", "Airports")); outbox.submit("replicate", airports, Schedule.create().as("replicate-airports").every(Duration.ofMinutes(10))); // [!code highlight] OutboxMessage airlines = OutboxMessage.create(); airlines.setParams(Map.of("entity", "Airlines")); outbox.submit("replicate", airlines, Schedule.create().as("replicate-airlines").every(Duration.ofHours(1))); // [!code highlight] // Each can be removed independently by its task name outbox.submit("replicate", OutboxMessage.create(), Schedule.create().as("replicate-airports").cancel()); outbox.submit("replicate", OutboxMessage.create(), Schedule.create().as("replicate-airlines").cancel()); ``` ::: > [!important] Re-submitting replaces both schedule and payload > When a named task is re-submitted, both the schedule *and* the payload are replaced. If you only want to update the timing, you still need to provide the full payload. Re-submitting while the task is currently being processed is safe — the updated schedule and payload take effect after the current execution completes. > [!note] Cancellation semantics > Cancelling a scheduled task removes it from the schedule so no future executions occur. A currently running execution **completes** — cancellation is not an interrupt. If the task was already picked up for processing at the moment the cancellation is submitted, at most one additional execution may occur. Cancelling a non-existent task is a silent no-op. > [!tip] Real-world example: data federation > The [data federation guide](../integration/data-federation) uses `srv.schedule().every()` to implement polling-based replication, fetching incremental updates from remote services on a regular interval. ## End-to-End Example > Source: /docs/guides/events/event-queues#end-to-end-example The following example from [*@capire/xtravels*](https://github.com/capire/xtravels) ties together queueing, callbacks, and local state updates — a choreography-based SAGA pattern across two microservices. > [!note] Uses an alpha API > This example relies on [Callbacks](#callbacks), which are currently `` and Node.js-only. ```js [srv/travel-service.js] const cds = require('@sap/cds') module.exports = class TravelService extends cds.ApplicationService { async init() { const xflights = await cds.connect.to('xflights') const qd_xflights = cds.queued(xflights) const messaging = await cds.connect.to('messaging') const { Flights, Travels } = this.entities const { Bookings } = cds.entities('sap.capire.travels') // After saving a Travel, emit a BookingCreated event for each booking. // Travel_ID + Pos are carried as headers so the callbacks can correlate back. this.after('SAVE', Travels, (_, req) => { const { Bookings: bookings = [] } = req.data return Promise.all(bookings.map(booking => { const { Flight_ID: flight, Flight_date: date, Travel_ID, Pos } = booking return qd_xflights.emit('BookingCreated', { flight, date }, { Travel_ID, Pos }) })) }) // xflights confirmed the seat — mark the booking as Confirmed xflights.after('BookingCreated/#succeeded', async (_, req) => { const { Travel_ID, Pos } = req.headers await UPDATE(Bookings, { Travel_ID, Pos }).set({ Status_code: 'C' }) }) // xflights rejected the seat (e.g. no availability) — mark as Failed // This is not a rollback: the booking was never confirmed, so there is nothing to undo. // The status is recorded explicitly, leaving it visible for manual resolution or retry. xflights.after('BookingCreated/#failed', async (err, req) => { const { Travel_ID, Pos } = req.headers await UPDATE(Bookings, { Travel_ID, Pos }).set({ Status_code: 'F' }) }) // Keep the local Flights replica current whenever xflights updates seat counts. // The inbox (inboxed: true on messaging) stores the event before acknowledging the broker, // so it is processed reliably even if xflights is temporarily ahead of xtravels. // FlightUpdated intentionally carries no seat count in its payload — messages can overtake each // other, so we re-read the authoritative current value from xflights instead. messaging.on('FlightUpdated', async (event) => { const { flight_ID: ID, date } = event.data const { free_seats } = await xflights.read(Flights, { ID, date }).columns('free_seats') await UPDATE(Flights, { ID, date }).set({ free_seats }) }) await super.init() } } ``` The correlation context (`Travel_ID`, `Pos`) is passed as **headers** on the queued emit and available on `req.headers` in the callbacks — the payload itself carries only the business data needed by xflights. The `FlightUpdated` handler illustrates the inbox pattern: the broker acknowledges delivery as soon as the message is stored, and the re-read from xflights avoids stale data from out-of-order messages. This example highlights three design rules. First, use callbacks or persisted status updates for outcomes, not direct return values. Second, carry correlation context in event headers, not in the payload. Third, re-read authoritative state at processing time rather than trusting the event payload when messages can overtake each other. ## Configuration > Source: /docs/guides/events/event-queues#configuration The persistent queue is enabled by default, which means messages are stored in the `cds.outbox.Messages` table within the current transaction. The `outbox` namespace is historical and the table backs all three patterns. You only configure the queue when you want to deviate from the defaults. ::: code-group ```json [Node.js — package.json] { "cds": { "requires": { "queue": { "maxAttempts": 11 //> default: 10 } } } } ``` ```yaml [Java — application.yaml] cds: outbox: services: DefaultOutboxUnordered: maxAttempts: 11 #> default: 10 ``` ::: ::: details Node.js — `cds.requires.queue` | Option | Default | Description | |--------|---------|-------------| | `maxAttempts` | `10` | Maximum retries before a message becomes a dead letter | | `timeout` | `"1h"` | Time after which a `processing` message is considered abandoned and eligible for reprocessing | ::: ::: details Java — per outbox service | Option | Default | Description | |--------|---------|-------------| | `maxAttempts` | `10` | Maximum retries before the entry becomes a dead letter | | `enabled` | `true` | Set to `false` to disable an outbox service | A separate, runtime-global setting controls how long a `processing` entry can be held before another instance may pick it up: ```yaml cds.outbox.persistent.statusLock.timeout: PT1H # default ``` ::: To disable event queues entirely, set `cds.requires.queue: false`. To disable queueing for a specific service in Node.js, set `outboxed: false` on it (for example, `cds.requires.messaging.outboxed: false`). In Java, set `cds.outbox.services..enabled: false`. ## Operations > Source: /docs/guides/events/event-queues#operations Once event queues are in production, you need to understand runner coordination, how authorization crosses the queue boundary, failure and retry behavior, dead letter queue management, and observability. ### Locking > Source: /docs/guides/events/event-queues#locking CAP uses **application-level locking** to coordinate processors across application instances. When a runner picks up a message, it sets the message's `status` to `processing`. Other runners skip messages in that state. After processing, the row lock is released. The message is deleted (on success) or rescheduled (on failure) in the processing transaction. > [!warning] Migrating across `@sap/cds` major versions > This guide describes the implementation in `@sap/cds` 10+. Older versions select messages differently: > > - **`@sap/cds` 8** does **not** check the `status` column at all. > - **`@sap/cds` 9** checks `status` but holds a row-level lock for the duration of processing (`legacyLocking: true` is the default in cds 9). > - **`@sap/cds` 10** uses application-level locking via `status` and releases the row lock after selection. > > A rolling upgrade from `@sap/cds` 8 directly to 10 can therefore lead to **double-processing of messages**, because `@sap/cds` 8 instances pick up messages that an `@sap/cds` 10 instance has already marked `processing`. Plan downtime, drain the queue before upgrading, or upgrade through `@sap/cds` 9 first. ### Authorization > Source: /docs/guides/events/event-queues#authorization When an event is processed asynchronously, the original HTTP request context is no longer available. CAP handles this as follows: - The **user ID** is stored with the queued message and re-created when the message is processed. - **User roles, attributes, and tokens** are *not* stored. Asynchronous processing always runs in privileged mode. No principal propagation occurs across the queue boundary, by design. That would require CAP to persist authentication tokens in some encrypted form, and those tokens often expire long before the queued work runs. *"Privileged mode"* means `@requires` annotations do not gate execution in queued handlers — the runtime grants full service access regardless of the stored user ID. If your handler must enforce the original caller's identity, carry the relevant claims via **payload or headers** at queue time and read them during processing. For scheduled tasks, headers are a natural fit since they stay in-process: ```js // Schedule a task, carrying the originating user as a header await xflights.schedule('replicate', { entity: 'Airports' }, { requestedBy: req.user.id }) // At processing time — read from headers xflights.on('replicate', async (req) => { const { requestedBy } = req.headers // use requestedBy to derive authorization or audit context }) ``` > [!warning] Headers are forwarded to the target system > When a **queued outbound call** (to a remote service or message broker) is dispatched, CAP forwards the stored headers to the target. Do not carry sensitive data — authentication tokens, personal data, secrets — in headers on outbound calls. For **scheduled tasks**, which are processed in-process and never leave the application, headers are not forwarded and this restriction does not apply. As a consequence, queued calls reach their target system in the context of a *technical user* of the calling application, not the original end user. Queue only those calls that the target system can authorize for a technical user, for example, service-to-service calls that do not depend on the end-user identity. ### Error Handling > Source: /docs/guides/events/event-queues#error-handling When processing fails, the system retries the message with exponentially increasing delays. After a configurable maximum number of attempts, the message is moved to the dead letter queue. Some errors are identified as *unrecoverable*, for example, when a topic is forbidden by the broker. These messages are immediately moved to the dead letter queue without further retries. To mark your own errors as unrecoverable in Node.js, for example, when *xflights* rejects a `replicate` request with a permanent 4xx response: ```js xflights.on('replicate', async (req) => { try { // call xflights to fetch the delta for the entity // and write the result to the database } catch (e) { if (e.code >= 400 && e.code < 500) { // [!code highlight] // semantic error — don't retry // [!code highlight] e.unrecoverable = true // [!code highlight] } // [!code highlight] throw e } }) ``` In Java, suppress retries by catching the error and calling `context.setCompleted()`: ```java @On(service = "XFlightsOutbox", event = "replicate") void replicate(OutboxMessageEventContext context) { try { // call xflights to fetch the delta for the entity // and write the result to the database } catch (HttpClientErrorException e) { if (e.getStatusCode().is4xxClientError()) { // [!code highlight] // semantic error — don't retry // [!code highlight] context.setCompleted(); // [!code highlight] return; // [!code highlight] } // [!code highlight] throw e; // transient — let the runner retry } } ``` ### Dead Letter Queue > Source: /docs/guides/events/event-queues#dead-letter-queue Messages that exceed the maximum retry count remain in the `cds.outbox.Messages` database table with their error information intact. These entries form the *dead letter queue* and require manual intervention, either to fix the underlying issue and retry, or to discard the message. > [!warning] Increasing `maxAttempts` between deployments > You can raise `maxAttempts` between deployments. Older entries that had reached the previous maximum are retried automatically after the new deployment. If the dead letter queue is large, this causes unintended load on the system. For triage, query the table directly: ```sql SELECT ID, target, status, attempts, lastAttemptTimestamp, lastError FROM cds_outbox_Messages ORDER BY timestamp DESC; ``` You can also expose a CDS service to manage dead-letter entries with bound *revive* and *delete* actions: **1. Define the service** ```cds [srv/outbox-dead-letter-queue-service.cds] using from '@sap/cds/srv/outbox'; @requires: 'internal-user' service OutboxDeadLetterQueueService { @readonly entity DeadOutboxMessages as projection on cds.outbox.Messages actions { action revive(); action delete(); }; } ``` > [!warning] Restrict access > The dead letter queue contains sensitive data. > Ensure the service is accessible only to internal users. **2. Filter for dead entries** Because `maxAttempts` is configurable, its value is not added as a static filter to the projection. Apply it programmatically. ::: code-group ```js [Node.js — srv/outbox-dead-letter-queue-service.js] const cds = require('@sap/cds') module.exports = class OutboxDeadLetterQueueService extends cds.ApplicationService { async init() { this.before('READ', 'DeadOutboxMessages', function (req) { const { maxAttempts } = cds.env.requires.queue req.query.where('attempts >= ', maxAttempts) }) await super.init() } } ``` ```java [Java — DeadOutboxMessagesHandler.java] @Component @ServiceName(OutboxDeadLetterQueueService_.CDS_NAME) public class DeadOutboxMessagesHandler implements EventHandler { private final PersistenceService db; public DeadOutboxMessagesHandler( @Qualifier(PersistenceService.DEFAULT_NAME) PersistenceService db) { this.db = db; } @Before(event = CqnService.EVENT_READ, entity = DeadOutboxMessages_.CDS_NAME) public void addDeadEntryFilter(CdsReadEventContext context) { Optional outboxFilters = createOutboxFilters(context.getCdsRuntime()); outboxFilters.ifPresent(filter -> { CqnSelect modified = copy(context.getCqn(), new Modifier() { @Override public CqnPredicate where(Predicate where) { return filter.and(where); } }); context.setCqn(modified); }); } private Optional createOutboxFilters(CdsRuntime runtime) { CdsProperties.Outbox outboxConfigs = runtime.getEnvironment().getCdsProperties().getOutbox(); return runtime.getServiceCatalog().getServices(OutboxService.class) .map(service -> { OutboxServiceConfig config = outboxConfigs.getService(service.getName()); return CQL.get(Messages.TARGET).eq(service.getName()) .and(CQL.get(Messages.ATTEMPTS).ge(config.getMaxAttempts())); }) .reduce(Predicate::or); } } ``` ::: **3. Implement bound actions** Entries in the dead letter queue can be *revived* by resetting the retry counter to zero, or *deleted* permanently. ::: code-group ```js [Node.js — srv/outbox-dead-letter-queue-service.js] this.on('revive', 'DeadOutboxMessages', async function (req) { await UPDATE(req.subject).set({ attempts: 0 }) }) this.on('delete', 'DeadOutboxMessages', async function (req) { await DELETE.from(req.subject) }) ``` ```java [Java — DeadOutboxMessagesHandler.java] @On public void reviveOutboxMessage(DeadOutboxMessagesReviveContext context) { CqnAnalyzer analyzer = CqnAnalyzer.create(context.getModel()); Map key = analyzer.analyze(context.getCqn()).rootKeys(); Messages msg = Messages.create((String) key.get(Messages.ID)); msg.setAttempts(0); db.run(Update.entity(Messages_.class).entry(key).data(msg)); context.setCompleted(); } @On public void deleteOutboxEntry(DeadOutboxMessagesDeleteContext context) { CqnAnalyzer analyzer = CqnAnalyzer.create(context.getModel()); Map key = analyzer.analyze(context.getCqn()).rootKeys(); db.run(Delete.from(Messages_.class).byId(key.get(Messages.ID))); context.setCompleted(); } ``` ::: ### Observability > Source: /docs/guides/events/event-queues#observability Both stacks export queue metrics through OpenTelemetry, sourced from the `cds.outbox.Messages` table: | Metric | Description | Type | |---|---|---| | `cold` (`com.sap.cds.outbox.coldEntries`) | Entries that exhausted retries and won't be retried — the dead letter queue size. | Gauge | | `remaining` (`com.sap.cds.outbox.remainingEntries`) | Entries pending delivery. | Gauge | | `min` / `med` / `max storage time` (`com.sap.cds.outbox.{min,med,max}StorageTimeSeconds`) | How long entries have been sitting in the outbox, in seconds. | Gauge | | `incoming` (`com.sap.cds.outbox.incomingMessages`) | Messages submitted to the outbox. | Counter | | `outgoing` (`com.sap.cds.outbox.outgoingMessages`) | Messages successfully dispatched. | Counter | Metrics are scoped per microservice instance, outbox name, and tenant. The Java integration is built in. For Node.js, add `@cap-js/telemetry` to your dependencies. Queue metrics are then emitted alongside CAP's other telemetry signals. [Learn more about Java OpenTelemetry integration.](../../java/operating-applications/observability#open-telemetry){.learn-more} [Learn more about `@cap-js/telemetry`.](https://github.com/cap-js/telemetry#queue){.learn-more} ## Next Steps > Source: /docs/guides/events/event-queues#next-steps For stack-specific APIs, configuration keys, and troubleshooting, see the following: - [Event Queues in Node.js](../../node.js/event-queues) — `cds.queued`, `cds.unqueued`, `cds.flush`, `srv.schedule` (incl. `#succeeded` / `#failed` callbacks), queue configuration, troubleshooting. - [Event Queues in Java](../../java/event-queues) — `OutboxService`, `AsyncCqnService`, custom outbox services, the technical outbox API, error-handling patterns, and event versioning for blue/green deployments. Most event-queue usage comes through messaging or remote services. From here you'll likely want to look at: - [Messaging](messaging) — emitting and consuming events between CAP applications and via brokers; messaging services are auto-outboxed. - [CAP-Level Service Integration](../integration/calesi) — consuming remote services as if they were local; outboxing them centrally with `outboxed: true`. - [CAP-Level Data Federation](../integration/data-federation) — using `srv.schedule().every()` for polling-based replication from remote services. # CAP-level Messaging > Source: /docs/guides/events/messaging How CAP's Messaging Services connect to message brokers to exchange event messages with remote services, complementing CAP's intrinsic event support. ## Why Using Messaging? > Source: /docs/guides/events/messaging#why-using-messaging Using messaging has two major advantages: ::: tip Resilience If a receiving service goes offline for a while, event messages are safely stored, and guaranteed to be delivered to the receiver as soon as it goes online again. ::: ::: tip Decoupling Emitters of event messages are decoupled from the receivers and don't need to know them at the time of sending. This way a service is able to emit events that other services can register on in the future, for example, to implement **extension** points. ::: ## Using Message Channels > Source: /docs/guides/events/messaging#using-message-channels When emitters and receivers live in separate processes, you need to add a message channel to forward event messages. CAP provides messaging services, which take care for that message channel behind the scenes as illustrated in the following graphic: ![The reviews service and the catalog service, each in a seperate process, are connected to the messaging service which holds the messaging channel behind the scenes.](assets/remote.drawio.svg) ::: tip Uniform, Agnostic Messaging CAP provides messaging services, which transport messages behind the scenes using different messaging channels and brokers. All of this happens without the need to touch your code, which stays on conceptual level. ::: ### 1. Use `file-based-messaging` in Development > Source: /docs/guides/events/messaging#1-use-file-based-messaging-in-development For quick tests during development, CAP provides a simple file-based messaging service implementation. Configure that as follows for the `[development]` profile: ```jsonc "cds": { "requires": { "messaging": { "[development]": { "kind": "file-based-messaging" } }, } } ``` [Learn more about `cds.env` profiles.](../../node.js/cds-env#profiles){.learn-more} In our samples, you find that in [@capire/reviews/package.json](https://github.com/capire/reviews/blob/main/package.json) as well as [@capire/bookstore/package.json](https://github.com/capire/bookstore/blob/main/package.json), which you'll run in the next step as separate processes. ### 2. Start the `reviews` Service and `bookstore` Separately > Source: /docs/guides/events/messaging#2-start-the-reviews-service-and-bookstore-separately First start the `reviews` service separately: ```sh cds watch reviews ``` The trace output should contain these lines, confirming that you're using `file-based-messaging`, and that the `ReviewsService` is served by that process at port 4005: ```log [cds] - connect to messaging > file-based-messaging { file: '~/.cds-msg-box' } [cds] - serving ReviewsService { path: '/reviews', impl: '../reviews/srv/reviews-service.js' } [cds] - server listening on { url: 'http://localhost:4005' } [cds] - launched at 5/25/2023, 4:53:46 PM, version: 7.0.0, in: 593.274ms ``` Then, in a separate terminal start the `bookstore` server as before: ```sh cds watch bookstore ``` This time the trace output is different to [when you started all in a single server](./core-concepts#start-server). The output confirms that you're using `file-based-messaging`, and that you now *connected* to the separately started `ReviewsService` at port 4005: ```log [cds] - connect to messaging > file-based-messaging { file: '~/.cds-msg-box' } [cds] - mocking OrdersService { path: '/orders', impl: '../orders/srv/orders-service.js' } [cds] - serving CatalogService { path: '/browse', impl: '../reviews/srv/cat-service.js' } [cds] - serving AdminService { path: '/admin', impl: '../reviews/srv/admin-service.js' } [cds] - connect to ReviewsService > odata { url: 'http://localhost:4005/reviews' } [cds] - server listening on { url: 'http://localhost:4004' } [cds] - launched at 5/25/2023, 4:55:46 PM, version: 7.0.0, in: 1.053s ``` ### 3. Add or Update Reviews > Source: /docs/guides/events/messaging#3-add-or-update-reviews Similar to before, open [http://localhost:4005/vue/index.html](http://localhost:4005/vue/index.html) to add or update reviews. → In the terminal window for the `reviews` server you should see this: ```log [cds] - PATCH /reviews/Reviews/74191a20-f197-4829-bd47-c4676710e04a < emitting: reviewed { subject: '251', count: 1, rating: 3 } ``` → In the terminal window for the `bookstore` server you should see this: ```log > received: reviewed { subject: '251', count: 1, rating: 3 } ``` ::: tip **Agnostic Messaging APIs** Without touching any code the event emitted from the `ReviewsService` got transported via `file-based-messaging` channel behind the scenes and was received in the `bookstore` as before, when you used in-process eventing → which was to be shown (*QED*). ::: ### 4. Shut Down and Restart Receiver → Resilience by Design > Source: /docs/guides/events/messaging#4-shut-down-and-restart-receiver--resilience-by-design You can simulate a server outage to demonstrate the value of messaging for resilience as follows: 1. Terminate the `bookstore` server with `Ctrl` + `C` in the respective terminal. 2. Add or update more reviews as described before. 3. Restart the receiver with `cds watch bookstore`. → You should see some trace output like that: ```log [cds] - server listening on { url: 'http://localhost:4004' } [cds] - launched at 5/25/2023, 10:45:42 PM, version: 7.0.0, in: 1.023s [cds] - [ terminate with ^C ] > received: reviewed { subject: '207', count: 1, rating: 2 } > received: reviewed { subject: '207', count: 1, rating: 2 } > received: reviewed { subject: '207', count: 1, rating: 2 } ``` ::: tip **Resilience by Design** All messages emitted while the receiver was down stayed in the messaging queue and are delivered when the server is back. ::: ### Have a Look Into _~/.cds-msg-box_ > Source: /docs/guides/events/messaging#have-a-look-into-cds-msg-box You can watch the messages flowing through the message queue by opening _~/.cds-msg-box_ in a text editor. When the receiver is down and therefore the message not already consumed, you can see the event messages emitted by the `ReviewsService` in entries like that: ```json ReviewsService.reviewed {"data":{"subject":"201","count":4,"rating":5}, "headers": {...}} ``` ## Using Multiple Channels > Source: /docs/guides/events/messaging#using-multiple-channels By default CAP uses a single message channel for all messages. For example: If you consume messages from SAP S/4HANA in an enhanced version of `bookstore`, as well as emit messages a customer could subscribe and react to in a customer extension, the overall topology would look like that: ![The reviews service, bookstore, and the SAP S/4HANA system send events to a common message bus. The bookstore also receives events and customer extensions as well.](assets/composite1.drawio.svg) ### Using Separate Channels > Source: /docs/guides/events/messaging#using-separate-channels Now, sometimes you want to use separate channels for different emitters or receivers. Let's assume you want to have a dedicated channel for all events from SAP S/4HANA, and yet another separate one for all outgoing events, to which customer extensions can subscribe too. This situation is illustrated in this graphic: ![The graphic shows seperate message channels for each event emitter and its subscribers.](assets/composite2.drawio.svg) This is possible when using [low-level messaging](#low-level-messaging), but comes at the price of loosing all advantages of conceptual-level messaging as explained in the following. ### Using `composite-messaging` Implementation > Source: /docs/guides/events/messaging#using-composite-messaging-implementation To avoid falling back to low-level messaging, CAP provides the `composite-messaging` implementation, which basically acts like a transparent dispatcher for both, inbound and outbound messages. The resulting topology would look like that: ![Each emitter and subscriber has its own message channel. In additon there's a composite message channel that dispatches to/from each of those seperate channels.](assets/composite3.drawio.svg) ::: tip **Transparent Topologies** The `composite-messaging` implementation allows to flexibly change topologies of message channels at deployment time, without touching source code or models. ::: ### Configuring Individual Channels and Routes > Source: /docs/guides/events/messaging#configuring-individual-channels-and-routes You would configure this in `bookstore`'s _package.json_ as follows: ```jsonc "cds": { "requires": { "messaging": { "kind": "composite-messaging", "routes": { "ChannelA": ["**/ReviewsService/*"], "ChannelB": ["**/sap/s4/**"] "ChannelC": ["**/bookshop/**"] } }, "ChannelA": { "kind": "enterprise-messaging", ... }, "ChannelB": { "kind": "enterprise-messaging", ... }, "ChannelC": { "kind": "enterprise-messaging", ... } } } ``` In essence, you first configure a messaging service for each channel. In addition, you would configure the default `messaging` service to be of kind `composite-messaging`. In the `routes`, you can use the glob pattern to define filters for event names, that means: - `**` will match any number of characters. - `*` will match any number of characters except `/` and `.`. - `?` will match a single character. ::: tip You can also refer to events declared in CDS models, by using their fully qualified event name (unless annotation `@topic` is used on them). ::: ## Low-Level Messaging > Source: /docs/guides/events/messaging#low-level-messaging In the previous sections it's documented how CAP promotes messaging on conceptual levels, staying agnostic to topologies and message brokers. While CAP strongly recommends staying on that level, CAP also offers lower-level messaging, which loses some of the advantages but still stays independent from specific message brokers. ::: tip Messaging as Just Another CAP Service All messaging implementations are provided through class `cds.MessagingService` and broker-specific subclasses of that. This class is in turn a standard CAP service, derived from `cds.Service`, hence it's consumed as any other CAP service, and can also be extended by adding event handlers as usual. ::: #### Configure Messaging Services > Source: /docs/guides/events/messaging#configure-messaging-services As with all other CAP services, add an entry to `cds.requires` in your _package.json_ or _.cdsrc.json_ like that: ```jsonc "cds": { "requires": { "messaging": { "kind": // ... }, } } ``` [Learn more about `cds.env` and `cds.requires`.](../../node.js/cds-env#services){.learn-more} You're free how you name your messaging service. Could be `messaging` as in the previous example, or any other name you choose. You can also configure multiple messages services with different names. #### Connect to the Messaging Service > Source: /docs/guides/events/messaging#connect-to-the-messaging-service Instead of connecting to an emitter service, connect to the messaging service: ```js const messaging = await cds.connect.to('messaging') ``` #### Emit Events to Messaging Service > Source: /docs/guides/events/messaging#emit-events-to-messaging-service Instead of emitter services emitting to themselves, emit to the messaging service: ```js await messaging.emit ('ReviewsService.reviewed', { ... }) ``` #### Receive Events from Messaging Service > Source: /docs/guides/events/messaging#receive-events-from-messaging-service Instead of registering event handlers with a concrete emitter service, register handlers on the messaging service: ```js messaging.on ('ReviewsService.reviewed', msg => console.log(msg)) ```
#### Declared Events and `@topic` Names > Source: /docs/guides/events/messaging#declared-events-and-topic-names When declaring events in CDS models, be aware that the fully qualified name of the event is used as topic names when emitting to message brokers. Based on the following model, the resulting topic name is `my.namespace.SomeEventEmitter.SomeEvent`. ```cds namespace my.namespace; service SomeEventEmitter { event SomeEvent { ... } } ``` If you want to manually define the topic, you can use the `@topic` annotation: ```cds //... @topic: 'some.very.different.topic-name' event SomeEvent { ... } ``` #### Conceptual vs. Low-Level Messaging > Source: /docs/guides/events/messaging#conceptual-vs-low-level-messaging When looking at the previous code samples, you see that in contrast to conceptual messaging you need to provide fully qualified event names now. This is just one of the advantages you lose. Have a look at the following list of advantages you have using conceptual messaging and lose with low-level messaging. - Service-local event names (as already mentioned) - Event declarations (as they go with individual services) - Generated typed API classes for declared events - Run in-process without any messaging service ::: tip Always prefer conceptual-level API over low-level API variants. Besides the things listed above, this allows you to flexibly change topologies, such as starting with co-located services in a single process, and moving single services out to separate micro services later on. ::: ## CloudEvents Standard > Source: /docs/guides/events/messaging#cloudevents-standard CAP messaging has built-in support for formatting event data compliant to the [CloudEvents](https://cloudevents.io/) standard. Enable this using the `format` config option as follows: ```json "cds": { "requires": { "messaging": { "format": "cloudevents" } } } ``` With this setting, all mandatory and some more basic header fields, like `type`, `source`, `id`, `datacontenttype`, `specversion`, `time` are filled in automatically. The event name is used as `type`. The message payload is in the `data` property anyways. ::: tip CloudEvents is a wire protocol specification. Application developers shouldn't have to care for such technical details. CAP ensures that for you, by filling in the respective fields behind the scenes. ::: # Messaging via SAP Integration Suite, Advanced Event Mesh > Source: /docs/guides/events/is-aem [SAP Integration Suite, advanced event mesh](https://www.sap.com/products/technology-platform/integration-suite/advanced-event-mesh.html) allows you to, amongst others, integrate non-SAP systems into your event-driven architecture. CAP provides out-of-the-box support for SAP Integration Suite, advanced event mesh, via CAP plugins, available for: [![Node.js logo](/logos/nodejs.svg){}](https://github.com/cap-js/advanced-event-mesh#readme) [![Java logo](/logos/java.svg){}](https://github.com/cap-java/cds-feature-advanced-event-mesh#readme) [Learn more about _SAP Integration Suite, advanced event mesh_.](https://help.pubsub.em.services.cloud.sap){.learn-more} # Messaging via SAP Event Mesh > Source: /docs/guides/events/event-mesh CAP provides out-of-the-box support for [SAP Event Mesh](https://help.sap.com/docs/event-mesh), and automatically handles many things behind the scenes, so that application coding stays agnostic and focused on conceptual messaging. ::: warning The following guide is based on a productive (paid) account on SAP BTP. It's not supported to use the trial offering of SAP Event Mesh. ::: ## Prerequisite: Create an Instance of SAP Event Mesh > Source: /docs/guides/events/event-mesh#prerequisite-create-an-instance-of-sap-event-mesh - [Follow this tutorial](https://developers.sap.com/group.cp-enterprisemessaging-get-started.html) to create an instance of SAP Event Mesh with plan `default`. - Alternatively follow [one of the guides in SAP Help Portal](https://help.sap.com/docs/SAP_EM/bf82e6b26456494cbdd197057c09979f/3ef34ffcbbe94d3e8fff0f9ea2d5911d.html). ::: tip **Important:** You don't need to manually create queues or queue subscriptions as CAP takes care for that automatically based on declared events and subscriptions. ::: ## Use `enterprise-messaging` > Source: /docs/guides/events/event-mesh#use-enterprise-messaging Add the following to your _package.json_ to use SAP Event Mesh: ```jsonc "cds": { "requires": { "messaging": { "[production]": { "kind": "enterprise-messaging" }, } } } ``` [Learn more about `cds.env` profiles](../../node.js/cds-env#profiles){.learn-more} [Learn how to use the CloudEvents protocol.](../../node.js/messaging#cloudevents-protocol){.learn-more} :::tip **Behind the Scenes** The `enterprise-messaging` implementation handles these things automatically and transparently: - Creation of queues & subscriptions for event receivers - Handling all broker-specific handshaking and acknowledgments - Constructing topic names as expected by the broker ::: ### Optional: Add `namespace` Prefixing Rules > Source: /docs/guides/events/event-mesh#optional-add-namespace-prefixing-rules SAP Event Mesh documentation recommends to prefix all event names with the service instance's configured `namespace`, both, when emitting as well as when subscribing to events. If you followed these rules, add corresponding rules to your configuration in _package.json_ to have CAP's messaging service implementations enforcing these rules automatically: ```json "cds": { "requires": { "messaging": { "publishPrefix": "$namespace/", "subscribePrefix": "$namespace/" } } } ``` The variable `$namespace` is resolved from your SAP Event Mesh service instance's configured `namespace` property. ## Run Tests in `hybrid` Setup > Source: /docs/guides/events/event-mesh#run-tests-in-hybrid-setup Before [deploying to the cloud](#deploy-to-the-cloud-with-mta), you may want to run some ad-hoc tests with a hybrid setup, that is, keep running the CAP services locally, but using the SAP Event Mesh instance from the cloud. Do that as follows: 1. Configure CAP to use the `enterprise-messaging-shared` implementation in the `reviews` and `bookstore` sample: ```jsonc "cds": { "requires": { "messaging": { "[hybrid]": { "kind": "enterprise-messaging-shared" } } } } ``` > The `enterprise-messaging-shared` variant is for single-tenant usage and uses AMQP by default. Thus, it requires much less setup for local tests compared to the production variant, which uses HTTP-based protocols by default. 2. Add `@sap/xb-msg-amqp-v100` as dependency to `reviews` and `bookstore`: ```sh npm add @sap/xb-msg-amqp-v100 ``` [Learn more about SAP Event Mesh (Shared).](../../node.js/messaging#event-mesh-shared){.learn-more} 3. Create a service key for your Event Mesh instance [→ see help.sap.com](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/4514a14ab6424d9f84f1b8650df609ce.html) 4. Bind to your Event Mesh instance's service key from `reviews` and `bookstore`: ```sh cds bind -2 : ``` [Learn more about `cds bind` and hybrid testing.](../../tools/cds-bind){.learn-more} 5. Run your services in separate terminal shells with the `hybrid` profile: ```sh cds watch reviews --profile hybrid ``` ```sh cds watch bookstore --profile hybrid ``` [Learn more about `cds.env` profiles.](../../node.js/cds-env#profiles){.learn-more} 6. Test your app [as described in the messaging guide](messaging#add-or-update-reviews). ### CAP Automatically Creates Queues and Subscriptions > Source: /docs/guides/events/event-mesh#cap-automatically-creates-queues-and-subscriptions When you run the services with a bound instance of SAP Event Mesh as documented in a previous section, CAP messaging service implementations will automatically create a queue for each receiver process. The queue name is chosen automatically and the receiver's subscriptions added. ### Optional: Configure Queue Names > Source: /docs/guides/events/event-mesh#optional-configure-queue-names In case you want to manage queues yourself, use config option `queue.name` as follows: ```jsonc "cds": { "requires": { "messaging": { // ... "queue": { "name": "$namespace/my/own/queue" } } } } ``` In both cases — automatically chosen queue names or explicitly configured ones — if the queue already exists it's reused, otherwise it's created. [Learn more about queue configuration options.](../../node.js/messaging#message-brokers){.learn-more} ## Deploy to the Cloud (with MTA) > Source: /docs/guides/events/event-mesh#deploy-to-the-cloud-with-mta A general description of how to deploy CAP applications to SAP BTP's Cloud Foundry, can be found in the [Deploy to Cloud* guide](../deploy/). As documented there, MTA is frequently used to deploy to SAP BTP. Follow these steps to ensure binding of your deployed application to the SAP Event Mesh instance. ### 1. Specify Binding to SAP Event Mesh Instance > Source: /docs/guides/events/event-mesh#1-specify-binding-to-sap-event-mesh-instance Add SAP Event Mesh's service instance's name to the `requires` section of your CAP application's module, and a matching entry to the `resources` section, for example: ```yaml modules: - name: bookstore-srv requires: - name: resources: # SAP Event Mesh - name: type: org.cloudfoundry.managed-service parameters: service: enterprise-messaging service-plan: ``` [Learn more about using MTA.](../deploy/){.learn-more} ::: warning Make sure to use the exact `name` and `service-plan` used at the time creating the service instance you want to use. ::: ### 2. Optional: Auto-Create SAP Event Mesh Instances > Source: /docs/guides/events/event-mesh#2-optional-auto-create-sap-event-mesh-instances MTA can also create the service instance automatically. To do so, you need to additionally provide a service descriptor file and reference that through the `path` parameter in the `resources` section: ```yaml resources: # SAP Event Mesh as above... parameters: path: ./ ``` [Learn more about Service Descriptors for SAP Event Mesh.](https://help.sap.com/docs/SAP_EM/bf82e6b26456494cbdd197057c09979f/5696828fd5724aa5b26412db09163530.html){.learn-more} # Messaging via SAP Cloud Application Event Hub > Source: /docs/guides/events/event-hub [SAP Cloud Application Event Hub](https://help.sap.com/docs/event-broker) is the new default offering for messaging in SAP Business Technology Platform (SAP BTP). CAP provides out-of-the-box support for SAP Cloud Application Event Hub, and automatically handles many things behind the scenes, so that application coding stays agnostic and focused on conceptual messaging. ::: warning The following guide is based on a productive (paid) account on SAP BTP. ::: ## Prerequisite: Set up SAP Cloud Application Event Hub > Source: /docs/guides/events/event-hub#prerequisite-set-up-sap-cloud-application-event-hub Follow guides [Initial Setup](https://help.sap.com/docs/sap-cloud-application-event-hub/sap-cloud-application-event-hub-service-guide/initial-setup) as well as [Integration Scenarios → CAP Application as a Consumer](https://help.sap.com/docs/sap-cloud-application-event-hub/sap-cloud-application-event-hub-service-guide/cap-application-as-subscriber) to set up SAP Cloud Application Event Hub in your account. ## Configuration > Source: /docs/guides/events/event-hub#configuration ### Use `event-broker` in Node.js > Source: /docs/guides/events/event-hub#use-event-broker-in-nodejs Install plugin [`@cap-js/event-broker`](../../plugins/index.md#event-hub): ```sh npm add @cap-js/event-broker ``` And add the following to your _package.json_ to use SAP Cloud Application Event Hub: ```jsonc "cds": { "requires": { "messaging": { // kind "event-broker" is derived from the service's technical name "[production]": { "kind": "event-broker" } } } } ``` [Learn more about configuring SAP Cloud Application Event Hub in CAP Node.js.](../../node.js/messaging#event-broker){.learn-more} [Learn more about `cds.env` profiles.](../../node.js/cds-env#profiles){.learn-more} ### Use `event-hub` in Java > Source: /docs/guides/events/event-hub#use-event-hub-in-java Install plugin [`com.sap.cds:cds-feature-event-hub`](../../plugins/index.md#event-hub) and add the following to your _application.yaml_ to use SAP Cloud Application Event Hub: ::: code-group ```xml [srv/pom.xml] com.sap.cds cds-feature-event-hub ${latest-version} ``` ```yaml [srv/src/main/resources/application.yaml] cds: messaging.services: - name: "messaging-name" kind: "event-hub" ``` ::: [Find the latest version on Maven central.](https://central.sonatype.com/artifact/com.sap.cds/cds-feature-event-hub/versions){.learn-more} [Learn more about configuring SAP Cloud Application Event Hub in CAP Java.](../../java/messaging#using-real-brokers){.learn-more} ## Hybrid Testing > Source: /docs/guides/events/event-hub#hybrid-testing Since SAP Cloud Application Event Hub sends events via HTTP, you won't be able to receive events on your local machine unless you use a tunneling service. Therefore we recommend to use a messaging service of kind [`local-messaging`](../../node.js/messaging#local-messaging) for local testing. ## Prepare for MTA Deployment > Source: /docs/guides/events/event-hub#prepare-for-mta-deployment A general description of how to deploy CAP applications to SAP BTP Cloud Foundry, can be found in the [Deploy to Cloud guide](../deploy/). As documented there, MTA is frequently used to deploy to SAP BTP. [Learn more about using MTA.](../deploy/){.learn-more} Follow these steps to ensure proper binding of your deployed application to the SAP Cloud Application Event Hub instance. The guide makes use of the [@capire/incidents](https://github.com/cap-js/incidents-app) reference application. We'll start with the definition of the app itself: ::: code-group ```yaml [mta.yaml] modules: - name: incidents-srv provides: - name: incidents-srv-api properties: url: ${default-url} #> needed in references below ``` ::: ### Add SAP Cloud Application Event Hub Instance > Source: /docs/guides/events/event-hub#add-sap-cloud-application-event-hub-instance Your SAP Cloud Application Event Hub configuration must include your system namespace as well as the webhook URL. ::: code-group ```yaml [mta.yaml in Node.js] resources: - name: incidents-event-broker type: org.cloudfoundry.managed-service requires: - name: incidents-srv-api parameters: service: event-broker service-plan: event-connectivity config: # unique identifier for this event broker instance # should start with own namespace (i.e., "foo.bar") and may not be longer than 15 characters systemNamespace: cap.incidents webhookUrl: ~{incidents-srv-api/url}/-/cds/event-broker/webhook ``` ```yaml [mta.yaml in Java] resources: - name: incidents-event-broker type: org.cloudfoundry.managed-service parameters: service: event-broker service-plan: event-connectivity config: # unique identifier for this event broker instance # should start with own namespace (i.e., "foo.bar") and may not be longer than 15 characters systemNamespace: cap.incidents webhookUrl: ~{incidents-srv-api/url}/messaging/v1.0/eb requires: - name: incidents-srv-api ``` ::: ### Add Identity Authentication Service Instance > Source: /docs/guides/events/event-hub#add-identity-authentication-service-instance Your Identity Authentication service instance must be configured to include your SAP Cloud Application Event Hub instance under `consumed-services` in order for your application to accept requests from SAP Cloud Application Event Hub. For this purpose, the Identity Authentication service instance should further be `processed-after` the SAP Cloud Application Event Hub instance. ::: code-group ```yaml {6,14} [mta.yaml] resources: - name: incidents-ias type: org.cloudfoundry.managed-service requires: - name: incidents-srv-api processed-after: # for consumed-services (cf. below), incidents-event-broker must already exist # -> ensure incidents-ias is created after incidents-event-broker - incidents-event-broker parameters: service: identity service-plan: application config: consumed-services: - service-instance-name: incidents-event-broker xsuaa-cross-consumption: true #> if token exchange from IAS token to XSUAA token is needed display-name: cap.incidents #> any value, e.g., reuse MTA ID home-url: ~{incidents-srv-api/url} ``` ::: ### Bind the Service Instances > Source: /docs/guides/events/event-hub#bind-the-service-instances Finally, we can bring it all together by binding the two service instances to the application. The bindings must both be parameterized with `credential-type: X509_GENERATED` and `authentication-type: X509_IAS`, respectively, to enable Identity Authentication service-based authentication. ::: code-group ```yaml {1-3} [mta.yaml] modules: - name: incidents-srv provides: - name: incidents-srv-api properties: url: ${default-url} requires: #[!code focus:10] - name: incidents-ias #[!code ++] parameters: #[!code ++] config: #[!code ++] credential-type: X509_GENERATED #[!code ++] app-identifier: cap.incidents #> any value, e.g., reuse MTA ID [!code ++] - name: incidents-event-broker #[!code ++] parameters: #[!code ++] config: #[!code ++] authentication-type: X509_IAS #[!code ++] ``` ::: # Receiving Events from SAP S/4HANA Cloud Systems > Source: /docs/guides/events/s4 SAP S/4HANA integrates SAP Event Mesh as well as SAP Cloud Application Event Hub for messaging. Hence, it is relatively easy for CAP-based application to receive events from SAP S/4HANA systems. This guide provides detailed information on that. ## Find & Import APIs > Source: /docs/guides/events/s4#find--import-apis Find and `cds import` the API specification of an SAP S/4HANA service you want to receive events from. For example, for "BusinessPartner" using [SAP Business Accelerator Hub](https://api.sap.com/): 1. Find / open [Business Partner (A2X) API](https://api.sap.com/api/API_BUSINESS_PARTNER). 2. Choose button *"API Specification"*. 3. Download the EDMX spec from this list: ![Showing all available specifications on the SAP Business Accelerator Hub.](./assets/api-specification.png){ } 1. Import it as a CDS model: ```sh cds import ``` ## Find Information About Events > Source: /docs/guides/events/s4#find-information-about-events For example, using [SAP Business Accelerator Hub](https://api.sap.com/): 1. [Find the BusinessPartner Events page.](https://api.sap.com/event/SAPS4HANABusinessEvents_BusinessPartnerEvents/overview) 2. Choose _View Event Reference_. 3. Expand the _POST_ request shown. 4. Choose _Schema_ tab. 5. Expand the `data` property. ![Shows the event reference page on the SAP Business Accelerator Hub, highlighting the data property.](assets/business-partner-events.png){.mute-dark} The expanded part, highlighted in red, tells you all you need to know: - the event name: `sap.s4.beh.businesspartner.v1.BusinessPartner.Changed.v1` - the payload's schema → in `{...}` > All the other information on this page can be ignored, as it's about standard CloudEvents wire format attributes, which are always the same, and handled automatically by CAP behind the scenes for you. ## Add Missing Event Declarations > Source: /docs/guides/events/s4#add-missing-event-declarations In contrast to CAP, the asynchronous APIs of SAP S/4HANA are separate from synchronous APIs (that is, OData, REST). On CAP side, you need to fill this gap. For example, for an already imported SAP S/4HANA BusinessPartner API: ```cds // filling in missing events as found on SAP Business Accelerator Hub using { API_BUSINESS_PARTNER as S4 } from './API_BUSINESS_PARTNER'; extend service S4 with { event BusinessPartner.Created @(topic:'sap.s4.beh.businesspartner.v1.BusinessPartner.Created.v1') { BusinessPartner : String } event BusinessPartner.Changed @(topic:'sap.s4.beh.businesspartner.v1.BusinessPartner.Changed.v1') { BusinessPartner : String } } ``` ::: tip If using SAP Event Mesh, please see [CloudEvents Standard](messaging#cloudevents) and [Node - Messaging - CloudEvents Protocol](../../node.js/messaging#cloudevents-protocol) to learn about `format: 'cloudevents'`, `publishPrefix` and `subscribePrefix`. :::
## Consume Events Agnostically > Source: /docs/guides/events/s4#consume-events-agnostically With agnostic consumption, you can easily receive events from SAP S/4HANA the same way as from CAP services as already explained in this guide, for example like that: ```js const S4Bupa = await cds.connect.to ('API_BUSINESS_PARTNER') S4bupa.on ('BusinessPartner.Changed', msg => {...}) ``` ## Configure CAP > Source: /docs/guides/events/s4#configure-cap To ease the pain of the afore-mentioned topic rewriting effects, CAP has built-in support for [SAP Event Mesh](./event-mesh) as well as [SAP Cloud Application Event Hub](./event-hub). Configure the messaging service as follows, to let it automatically create correct technical topics to subscribe to SAP S/4HANA events: For SAP Event Mesh: ```json "cds": { "requires": { "messaging": { "kind": "enterprise-messaging-shared", "format": "cloudevents", // implicitly applied default prefixes "publishPrefix": "$namespace/ce/", "subscribePrefix": "+/+/+/ce/" } } } ``` **Note:** In contrast to the default configuration recommended in the [SAP Event Mesh documentation](https://help.sap.com/docs/SAP_EM/bf82e6b26456494cbdd197057c09979f/5499e2e74e674c69b057072272c80d4f.html), ensure you configure your service instance to allow the pattern `+/+/+/ce/*` for subscriptions. That is, **do not** restrict `subscribeFilter`s to `${namespace}`! For SAP Cloud Application Event Hub: ```json "cds": { "requires": { "messaging": { "kind": "event-broker" } } } ``` With that, your developers can enter event names as they're found on SAP Business Accelerator Hub. And our CDS extensions, as previously described, simplify to that definition: ```cds // filling in missing events as found on SAP Business Accelerator Hub using { API_BUSINESS_PARTNER as S4 } from './API_BUSINESS_PARTNER'; extend service S4 with { event BusinessPartner.Created @(topic:'sap.s4.beh.businesspartner.v1.BusinessPartner.Created.v1') { BusinessPartner : String } event BusinessPartner.Changed @(topic:'sap.s4.beh.businesspartner.v1.BusinessPartner.Changed.v1') { BusinessPartner : String } } ``` ## Configure SAP S/4HANA > Source: /docs/guides/events/s4#configure-sap-s4hana As a prerequisite for consuming SAP S/4HANA events, the SAP S/4HANA system itself needs to be configured to send out specific event messages to a specific SAP Event Mesh or SAP Cloud Application Event Hub service instance. How to create the necessary service instances and use them with a CAP application was already described in the previous sections [SAP Event Mesh](./event-mesh) and [SAP Cloud Application Event Hub](./event-hub), respectively. A description of how to configure an SAP S/4HANA system to send out specific events is out of scope of this documentation here. See [this documentation](https://help.sap.com/docs/SAP_S4HANA_CLOUD/0f69f8fb28ac4bf48d2b57b9637e81fa/82e97d5329044732af1efd996bfdc2ab.html) for more details. ## Using Low-Level Messaging > Source: /docs/guides/events/s4#using-low-level-messaging Instead of adding events found on [SAP Business Accelerator Hub](https://api.sap.com/content-type/Events/events/packages) to a CDS service model, it's also possible to use a messaging service directly to consume events from SAP S/4HANA. You have to bind the `messaging` service directly to the SAP Event Mesh or SAP Cloud Application Event Hub service instance that the SAP S/4HANA system sends the event messages to. Then you can consume the event by registering a handler on the `type` of the event that should be received (`sap.s4.beh.businesspartner.v1.BusinessPartner.Changed.v1` in the example): ```js const messaging = await cds.connect.to ('messaging') messaging.on ('sap.s4.beh.businesspartner.v1.BusinessPartner.Changed.v1', (msg) => { const { BusinessPartner } = msg.data console.log('--> Event received: BusinessPartner changed (ID="'+BusinessPartner+'")') }) ``` All the complex processes, like determining the correct technical topic to subscribe to and adding this subscription to a queue, will be done automatically in the background. # CAP Security and Data Privacy > Source: /docs/guides/security/ Security, data protection and data privacy are critical aspects of modern application development, with significant legal and ethical implications. \ The guides in this section are for developers, operators, administrators, and security professionals who need to understand how to develop, deploy and operate secure and compliant CAP applications. {.abstract} #### Data Protection vs. Data Privacy: > Source: /docs/guides/security/#data-protection-vs-data-privacy | Feature | Data Privacy | Data Protection | |:-----------|:-----------------------------------------------|:-----------------------------------------------------------| | **Focus** | **_Who_** has access and **_how_** it is used. | Protect against unauthorized access. | | **Nature** | A legal concept / human rights. | A suite of security measures. | | **Goals** | Ensures user consent and lawful data handling. | Ensures data availability, integrity, and confidentiality. |
# Overview of Security Concepts and Architecture > Source: /docs/guides/security/overview This section provides an overview of the security concepts and architecture of CAP applications on different platforms. ## Key Concepts > Source: /docs/guides/security/overview#key-concepts CAP's security architecture is built on several fundamental principles that enable flexible, secure, and maintainable applications. These concepts work together to provide comprehensive security while maintaining developer productivity and operational efficiency. ### Pluggable Building Blocks > Source: /docs/guides/security/overview#pluggable-building-blocks CAP divides the different security-related tasks into separate and independent building blocks, each with a standard CAP implementation suitable for most scenarios. ![Overview Security Components with CAP](./assets/security-components.drawio.svg){ } The building blocks are: - [Authentication](./authentication ) - [CAP Users](./cap-users) - [CAP Authorization](./authorization) - [Outbound Authentication](./remote-authentication) **By separating these concerns**, CAP ensures that each security function can be configured and customized independently without affecting other parts of the system, providing maximum flexibility. For example, authentication can be delegated to a [separate ingress component](./authentication#fully-auth), while authorization remains within the application service close to the data. ### Customizable > Source: /docs/guides/security/overview#customizable Due to the plugin-based architecture, **you can modify CAP's standard functions as required or, if necessary, completely replace them**. This flexibility is crucial for scenarios where the default methods do not fully meet your application's requirements. Moreover, this integration helps to easily incorporate non-CAP and even non-BTP services, thereby providing a flexible and interoperable environment. ![Overview Customizable Components with CAP](./assets/security-customizable.drawio.svg){ } For instance, you can define specific endpoints with a [custom authentication strategy](./authentication#custom-auth). Likewise, you can override the CAP representation of the request user to match additional, application-specific requirements. ### Built on Best of Breed > Source: /docs/guides/security/overview#built-on-best-of-breed CAP does not deal with user login flows, password and credential management, user sessions, or any cryptographic logic - **and applications should definitely not do so!** Instead, **CAP seamlessly integrates with battle-tested [platform services](#btp-services)** that handle these critical security topics centrally. This approach not only simplifies the implementation but also enhances security by leveraging robust, well-tested mechanisms provided by the platform. Built on platform services, CAP allows developers to focus on core application functionality without worrying about the intricacies of security implementation. Most notably, authentication is covered by CAP-integration of [platform's identity services](./authentication#ias-auth). Likewise, TLS termination is offered by the [platform infrastructure](#platform-environment). ![Overview Platform Integration with CAP](./assets/security-platform-integration.drawio.svg){ } ### Decoupled from Business Logic > Source: /docs/guides/security/overview#decoupled-from-business-logic As security functions are factorized into independent components, **application code is entirely decoupled** and hence is not subject to change for any security-related adaptations. This ensures that business logic remains independent of platform services, which are often subject to security-hardening initiatives. As a welcome side effect, this also allows testing application security in a **local test or development setup in a self-contained way**. For instance, CAP allows performing outbound service calls via [Remote Services while handling authentication completely under the hood](./remote-authentication#remote-services). This abstraction layer ensures that developers do not need to worry about the details of authentication. ::: warning Low-level application code is prone to configuration changes Application code that doesn't build on the abstractions provided by CAP, but instead uses the interfaces of the underlying security services directly, is highly vulnerable to configuration changes or behavioral changes on this level. For example, the application cannot be switched from TLS to mTLS-based communication with the platform services without rewriting custom code, if it doesn't consistently use Remote Services. ::: ### Secure by Default > Source: /docs/guides/security/overview#secure-by-default CAP security features are configured by default. If different behavior is required, you must explicitly reconfigure or add custom code accordingly. CAP's security autoconfiguration approach significantly reduces the risk of misconfiguration - **override only when absolutely necessary and when all effects are safely controlled**. For instance, endpoints of deployed CAP applications are [automatically authenticated](./authentication#model-auth), providing a secure baseline. Making endpoints public requires manual configuration in either the CAP model or the middleware. ::: warning Application projects are still responsible overall CAP cannot guarantee end-to-end [product security](./data-protection) across all application layers by default. The application is responsible for coordinated overall configuration. ::: ## Security Architecture > Source: /docs/guides/security/overview#security-architecture CAP applications run in a specific context that has a major impact on the security [architecture](#architecture-overview). CAP requires a dedicated [platform environment](#platform-environment) to integrate with to ensure end-to-end security. ### Architecture Overview > Source: /docs/guides/security/overview#architecture-overview The following diagram provides a high-level overview of the security-relevant components and interfaces of a deployed CAP application in a cloud environment: ![This TAM graphic is explained in the accompanying text.](./assets/cap-security-architecture-overview.png){} To serve a business request, different runtime components are involved: a request, issued by a UI or technical client ([public zone](#public-zone)), is forwarded by a gateway or ingress router to the CAP application. For a UI request, an [Application Router](https://help.sap.com/docs/btp/sap-business-technology-platform/application-router) instance acts as a proxy to manage the login flow and the browser session. The CAP application can have additional services such as a CAP sidecar. All application components ([application zone](#application-zone)) might make use of platform services such as database or identity service ([platform zone](#platform-zone)). #### Public Zone > Source: /docs/guides/security/overview#public-zone From CAP's point of view, all components without specific security requirements belong to the public zone. Therefore, you shouldn't rely on the behavior or structure of consumer components like browsers or technical clients for the security of server components. The platform's gateway provides a single point of entry for any incoming call and defines the API visible to the public zone. Since malicious users have free access to the public zone, you must protect these endpoints carefully. Ideally, you should limit the number of exposed endpoints to a minimum, perhaps through proper network configuration. #### Platform Zone > Source: /docs/guides/security/overview#platform-zone The platform zone contains all platform components and services that are *configured and maintained* by the application provider. CAP applications consume these low-level [platform services](#btp-services) to handle more complex business requests. For instance, the persistence service stores business data and the identity service authenticates the business user. Both play a fundamental role. The platform zone also includes the gateway, which is the main entry point for external requests. Additionally, it may contain extra ingress routers. #### Application Zone > Source: /docs/guides/security/overview#application-zone The application zone comprises all microservices that represent a CAP application. They are tightly integrated and form a **unit of trust**. The application provider is responsible for *developing, deploying, and operating* these services: - The [Application Router](https://help.sap.com/docs/btp/sap-business-technology-platform/application-router) acts as an optional reverse proxy wrapping the application service and providing business-independent functionality required for UIs. This includes serving UI content, providing a login flow as well as managing the session with the browser. You can deploy it as an application (reusable module) or alternatively consume it as a [service](https://help.sap.com/docs/btp/sap-business-technology-platform/managed-application-router). - The CAP application service exposes the API to serve business requests. Usually, it makes use of lower-level platform services. As built on CAP, a significant number of security requirements is covered either out of the box or by adding minimal configuration. - The optional CAP sidecar (reusable module) is used to outsource application-independent tasks such as providing multitenancy and extension support. Application providers (platform users) have privileged access to the application zone. In contrast, application subscribers (business users) are restricted to a minimal interface. ::: warning Do not share secrets Application providers **must not share any secrets from the application zone** such as binding information with other components or persons. In a production environment, we recommend deploying and operating the application on behalf of a technical user. ::: ### Platform Requirements > Source: /docs/guides/security/overview#platform-requirements There are several assumptions that a CAP application needs to make about the platform environment it is deployed to: 1. Application and (platform) service endpoints are exposed externally by the API gateway via TLS protocol. Hence, the **CAP application can offer a pure HTTP endpoint** without having to enforce TLS and to deal with certificates. 2. The server certificates presented by the external endpoints are signed by a trusted certificate authority. This **frees CAP applications from the need to manage trust certificates**. The underlying runtimes (Java or Node.js VMs) can validate the server certificates by default. 3. **Secrets** that are required to protect the application or to consume other platform services **are injected by the platform** into the application microservices in a secure way. All supported [environments](#cloud) fulfill the given requirements. Additional requirements may be added in future. ::: tip Sign custom certificates Custom domain certificates must be signed by a trusted certificate authority. ::: ::: warning Application endpoints are visible to the public zone Hence, CAP applications need to protect all exposed endpoints. ::: ## Platform Compliance > Source: /docs/guides/security/overview#platform-compliance CAP applications run in a certain environment, that is, in the context of some platform framework that has specific characteristics as explained [before](#platform-environment). The underlying framework has a major impact on the security of the application, regardless of whether it runs a [cloud environment](#cloud) or [local environment](#local). Moreover, CAP applications are tightly integrated with [platform services](#btp-services), in particular with identity and persistence service. ::: warning End-to-end security necessarily requires compliance with all security policies of all involved components CAP application security requires consistent security configuration of the underlying platform and all consumed services. Consult the relevant security documentation accordingly. ::: ### CAP in Local Environment > Source: /docs/guides/security/overview#cap-in-local-environment Security not only plays a crucial role in [cloud environments](#cloud), but also during local development. Apparently the security requirements are different from cloud scenario as local endpoints are typically not exposed for remote clients. But there are still a few things to consider because exploited vulnerabilities could be the basis for attacks on productive cloud services: #### DO: > Source: /docs/guides/security/overview#do - Make sure that locally started HTTP endpoints are bound to `localhost`. - Use [cds bind](../../tools/cds-bind) to run your service in hybrid mode with bindings to cloud service instances. `cds bind` avoids materialization of secrets to local disc, which is inherently dangerous. The opposite is consequently a **Don't**. #### DON'T: > Source: /docs/guides/security/overview#dont - Don't copy bindings manually to `default-env.json` file or otherwise on your local disc. - Don't write sensitive data to application logs, also not via debug logging. - Don't test with real business data, for example, copied from a productive system. ### CAP in Cloud Environment > Source: /docs/guides/security/overview#cap-in-cloud-environment Currently, CAP supports to run on two cloud runtimes of [SAP Business Technology Platform](https://help.sap.com/docs/btp): - [SAP BTP, Cloud Foundry Runtime](https://help.sap.com/docs/btp/sap-business-technology-platform/cloud-foundry-environment) - [SAP BTP, Kyma Runtime](https://help.sap.com/docs/btp/sap-business-technology-platform/kyma-environment) Application providers are responsible for ensuring a **secure platform environment**. In particular, this includes *configuring* [platform services](#btp-services) the application consumes. For instance, you as the provider (user) administrator need to configure the [identity service](#identity-service) to separate platform users from business users that come from different identity providers. Likewise, login policies (for example, multifactor authentication or single-sign-on) must be aligned with company-specific requirements. Note that achieving production-ready security requires meeting all relevant aspects of the **development process** as well. For instance, source code repositories must be protected and must not contain any secrets or personal data. Likewise, the **deployment process** must be secured. This includes not only setting up CI/CD pipelines running on technical platform users, but also defining integration tests to ensure properly secured application endpoints. As part of **secure operations**, application providers must establish patch and vulnerability management, as well as a secure support process. For example, component versions must be updated and credentials must be rotated regularly. ::: warning Applications must use secure platform environments The application provider is responsible to **develop, deploy, and operate the application in a secure platform environment**. CAP offers seamless integration into platform services and tools to help to meet these requirements. ::: Find more about BTP platform security here: [SAP BTP Security](https://help.sap.com/docs/btp/sap-business-technology-platform/security-e129aa20c78c4a9fb379b9803b02e5f6){.learn-more} [SAP BTP Security Recommendations](https://help.sap.com/docs/btp/sap-btp-security-recommendations-c8a9bb59fe624f0981efa0eff2497d7d/sap-btp-security-recommendations){.learn-more} [SAP BTP Security (Community)](https://pages.community.sap.com/topics/btp-security){.learn-more}
### Security Platform Services > Source: /docs/guides/security/overview#security-platform-services SAP BTP provides a range of platform services that your CAP applications can use to meet production-grade security requirements. To ensure the security of your CAP applications, comply with the service level agreement (SLA) of these platform services. *As the provider of the application, you play a key role in meeting these requirements by correctly configuring and using these services.* ::: tip Compliance documents in SAP Trust Center SAP BTP services and the underlying platform infrastructure hold various certifications and attestations, which can be found under the naming of SAP Cloud Platform in the [SAP Trust Center](https://www.sap.com/about/trust-center/certification-compliance/compliance-finder.html?search=SAP%20Business%20Technology%20Platform%20ISO). ::: [Webcast SAP BTP Cloud Identity and Security Services](https://assets.dm.ux.sap.com/webinars/sap-user-groups-k4u/pdfs/221117_sap_security_webcast_series_sap_btp_cloud_identity_and_security_services.pdf){.learn-more} The CAP framework offers flexible APIs that you can integrate with various services, including your custom services. If you replace platform services with your custom ones, ensure that the service level agreements (SLAs) CAP depends on are still met. The most important services for security offered by the platform: #### SAP Cloud Identity Services - Identity Authentication > Source: /docs/guides/security/overview#sap-cloud-identity-services---identity-authentication The Identity Authentication service defines the user base for (CAP) applications and services, and allows you to control access. You can integrate your third-party or on-premise identity provider (IdP) and harden security by defining multifactor authentication or by narrowing client IP ranges. This service helps introduce a strict separation between platform users (provider) and business users (subscribers), a requirement of CAP. It supports various authentication methods, including SAML 2.0 and [OpenID Connect](https://openid.net/connect/), and allows you to configure single sign-on access. [Learn more in the SAP Cloud Identity - Security Guide.](https://help.sap.com/docs/IDENTITY_AUTHENTICATION?#discover_task-security){.learn-more} #### SAP Authorization and Trust Management Service > Source: /docs/guides/security/overview#sap-authorization-and-trust-management-service The service allows customers to manage user authorizations in technical roles at the application level, which can be aggregated into business-level role collections for large-scale cloud scenarios. Developers must define application roles carefully as they form the basic access rules for business data. [Learn more in the SAP Authorization and Trust Management service guide.](https://help.sap.com/docs/btp/sap-business-technology-platform/btp-security){.learn-more} #### SAP BTP Connectivity > Source: /docs/guides/security/overview#sap-btp-connectivity The connectivity service allows SAP BTP applications to securely access remote services that run on the Internet or on-premise. It provides a way to establish a secure communication channel between remote endpoints that are connected via an untrusted network infrastructure. [Learn more in the SAP BTP Connectivity guide.](https://help.sap.com/docs/CP_CONNECTIVITY/cca91383641e40ffbe03bdc78f00f681/cb50b6191615478aa11d2050dada467d.html){.learn-more} #### SAP Malware Scanning Service > Source: /docs/guides/security/overview#sap-malware-scanning-service This service scans transferred business documents for malware and viruses. Currently, there is no CAP integration. A scan must be triggered explicitly by the business application. [Learn more in the SAP Malware Scanning service guide.](https://help.sap.com/docs/btp?#operate_task-security){.learn-more} #### SAP Credential Store > Source: /docs/guides/security/overview#sap-credential-store Credentials managed by applications must be stored securely. This service provides a REST API for (CAP) applications to store and retrieve credentials at runtime. [Learn more in the SAP Credential Store guide.](https://help.sap.com/docs/CREDENTIAL_STORE?#discover_task-security){.learn-more} # Authentication > Source: /docs/guides/security/authentication This guide explains how to authenticate CAP services to resolve CAP users. ## Pluggable Authentication > Source: /docs/guides/security/authentication#pluggable-authentication In essence, authentication verifies the user's identity and validates the presented claims, such as granted roles and tenant membership. Briefly, **authentication ensures _who_ is going to use the service** which is technically reflected in a resulting [user](./cap-users). In contrast, [authorization](../security/authorization#authorization) determines _how_ the user can interact with the application's resources according to the defined access rules. As access control relies on verified claims, authentication is a mandatory prerequisite for authorization. ![Authentication with CAP](./assets/authentication.drawio.svg){ } According to key concept [Pluggable Building Blocks](./overview#key-concept-pluggable), the authentication method can be configured freely. CAP [leverages platform services](overview#key-concept-platform-services) to provide proper authentication strategies to cover all relevant scenarios: - For _local development_ and _unit testing_, [Mock User Authentication](#mock-user-authentication) is an appropriate built-in authentication feature. - For _cloud deployments_, in particular deployments for production, CAP provides integration of several identity services out of the box: - [Identity Authentication Service (IAS)](#ias-auth) provides a full-fledged [OpenId Connect](https://openid.net/connect/) compliant, cross-landscape identity management as first choice for applications. - [XS User Authentication and Authorization Service (XSUAA)](https://help.sap.com/docs/CP_AUTHORIZ_TRUST_MNG) is an [OAuth 2.0](https://oauth.net/2/)-based authorization server to support existing applications and services in the scope of individual BTP landscapes. - CAP applications can run IAS and XSUAA in [hybrid mode](#hybrid-auth) to support a smooth migration from XSUAA to IAS. ## Mock User Authentication > Source: /docs/guides/security/authentication#mock-user-authentication In non-production profile, by default, CAP creates a security configuration which accepts _mock users_. As this authentication strategy is a built-in feature which does not require any platform service, it is perfect for **unit testing and local development scenarios**. Setup and start a simple sample application:
```sh cds init bookshop --java --add sample && cd ./bookshop mvn spring-boot:run ``` ::: tip CAP Java requires certain [Maven dependencies](../../java/security#maven-dependencies) to enable authentication middleware support. Platform starter bundles `cds-starter-cf` and `cds-starter-k8s` ensure all required dependencies out of the box. :::
```sh cds init bookshop --nodejs --add sample && cd ./bookshop cds watch ```
In the application startup trace you can find a log message indicating mock user configuration is active:
```sh MockUsersSecurityConfig : * Security configuration based on mock users found in active profile. * ```
```sh [cds] - using auth strategy { kind: 'mocked', … } ```
Also notice that the application log contains information about all registered mock users: ```sh MockUsersSecurityConfig : Added mock user {"name":"admin","password":"admin", ...} ```
**You should not manually configure authentication for endpoints.** As the mock user authentication is active, all (CAP) endpoints are [authenticated automatically](#model-auth).
::: tip To simplify the development scenario, you can set cds.security.authentication.mode = "model-relaxed" to deactivate authentication of endpoints derived from unrestricted CDS services. ::: If you stay with the standard authentication mode, sending the OData request results in a `401` error response from the server, indicating that the anonymous user has been rejected due to missing authentication. ```sh curl http://localhost:8080/odata/v4/CatalogService/Books --verbose ``` This is the case for all endpoints including the web application page at `/index.html`. Mock users require **basic authentication**, hence sending the same request on behalf of mock user `admin` (password: `admin`) returns successfully (HTTP response `200`). ```sh curl http://admin:admin@localhost:8080/odata/v4/CatalogService/Books ```
::: tip In non-production profile, endpoints derived from unrestricted CDS services are not authenticated to simplify the development scenario. ::: Send an OData request through the restricted `AdminService` as follows: ```sh curl http://localhost:4004/odata/v4/admin/Books --verbose ``` This results in a `401` error response from the server indicating that the anonymous user has been rejected due to missing authentication. This is true for all endpoints including the web application page at `/index.html`. Mock users require **basic authentication**, hence sending the same request on behalf of mock user `alice` (no password) returns successfully (HTTP response `200`). ```sh curl http://alice:@localhost:4004/odata/v4/admin/Books ```
::: info Mock users are deactivated in production profile by default ❗ :::
[Learn more about advanced authentication options.](../../java/security#spring-boot){.learn-more}
[Learn more about advanced authentication options.](../../node.js/authentication#strategies){.learn-more}
### Preconfigured Mock Users > Source: /docs/guides/security/authentication#preconfigured-mock-users For convenience, the runtime creates default mock users to cover typical test scenarios, such as privileged users passing all security checks or users that pass authentication but don't have additional claims. The runtime adds the predefined users to [custom mock users](#custom-mock-users) you define in the application. You can opt out the preconfigured mock users by setting `cds.security.mock.defaultUsers = false`. { .java }
[Learn more about predefined mock users.](../../java/security#preconfigured-mock-users){.learn-more}
[Learn more about predefined mock users.](../../node.js/authentication#mock-users){.learn-more}
### Customization > Source: /docs/guides/security/authentication#customization You can define custom mock users to simulate any type of end users that will interact with your application at production time. Internally, mock users are represented as [CAP users](cap-users#claims) as well. Hence, you can use the mock users, to test your authorization settings or custom handlers, fully decoupled from the actual execution environment.
```yaml [srv/src/main/resources/application.yaml] spring: config.activate.on-profile: default cds: security: mock: users: viewer-user: tenant: CrazyCars roles: - Viewer attributes: Country: [GER, FR] features: - park ```
```yaml [package.json] "cds": { "requires": { "auth": { "[development]": { "kind": "mocked", "users": { "viewer-user": { "password": "pass", "tenant": "CrazyCars", "roles": ["Viewer"], "attr": { ... } } }, "tenants": { "name" : "CrazyCars", "features": [ "cruise", "park" ] } } } } } ```
In the mock user configuration you can specify: - name (mandatory) and tenant - [CAP roles](cap-users#roles) (including pseudo-roles) and [attributes](authorization#user-attrs) affecting authorization - additional attributes - [feature toggles](../extensibility/feature-toggles#feature-toggles) which influence request processing. ::: tip Define the mock users in development profile only. ::: To verify the user properties, activate [user tracing](./cap-users#user-tracing) and send a request using the mock user (such as `viewer-user`). In the application log you will find information about the resolved user after successful authentication:
```sh MockedUserInfoProvider: Resolved MockedUserInfo [id='mock/viewer-user', name='viewer-user', roles='[Viewer]', attributes='{Country=[GER, FR], tenant=[CrazyCars]}' ``` [Learn more about custom mock users.](../../java/security#custom-mock-users){.learn-more}
``` [basic] - authenticated: { user: 'viewer-user', tenant: 'CrazyCars', features: [ 'cruise', 'park' ] } ``` [Learn more about custom mock users.](../../node.js/authentication#mocked){.learn-more}
### Automated Testing > Source: /docs/guides/security/authentication#automated-testing Mock users provide an excellent foundation for automated **unit tests, which are essential for ensuring application security**. The flexibility in defining various types of mock users and the seamless integration into testing code significantly reduces the burden of covering all relevant test combinations.
::: details How to leverage Spring-MVC to use CAP mock users ```java [srv/src/test/java/customer/bookshop/handlers/CatalogServiceTest.java] @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class BookServiceOrdersTest { String BOOKS_URL = "/odata/v4/CatalogService/Books"; @Autowired private MockMvc mockMvc; @Test @WithMockUser(username = "viewer-user") public void testViewer() throws Exception { mockMvc.perform(get(BOOKS_URL)).andExpect(status().isOk()); } @Test public void testUnauthorized() throws Exception { mockMvc.perform(get(BOOKS_URL)).andExpect(status().isUnauthorized()); } } ``` :::
::: tip Integration tests running in production profile should verify that unauthenticated users cannot access any application endpoints❗ :::
[Learn more about unit testing.](../../java/developing-applications/testing#testing-cap-java-applications){.learn-more .node}
[Learn more about testing with authenticated endpoints.](../../node.js/cds-test#authentication){.learn-more} [Learn more about testing.](../../node.js/cds-test#testing-with-cdstest){.learn-more}
## IAS Authentication > Source: /docs/guides/security/authentication#ias-authentication ::: tip **Start new projects with IAS** to take advantage of the best integration options. IAS offers a cross-consumption mode that allows IAS users to consume legacy XSUAA services. ::: [SAP Identity Authentication Service (IAS)](https://help.sap.com/docs/cloud-identity-services) is the preferred platform service for identity management providing the following features: - best of breed authentication mechanisms (single sign-on, multi-factor enforcement) - federation of corporate identity providers (multiple user stores) - cross-landscape user propagation (including on-premise) - streamlined SAP and non-SAP system [integration](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/integrating-service) (due to [OpenId Connect](https://openid.net/connect/) compliance) You can best configure and test IAS authentication in the Cloud, so let's enhance the [previously started bookshop sample application](#mock-user-authentication) with a deployment descriptor for SAP BTP, Cloud Foundry Runtime (CF). ### Get Ready with IAS > Source: /docs/guides/security/authentication#get-ready-with-ias Before working with IAS on CF, you need to do all of the following: - Prepare an IAS (test) tenant. If not available yet, you need to [create](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/get-your-tenant) it now. - [Establish trust](https://help.sap.com/docs/btp/sap-business-technology-platform/establish-trust-and-federation-between-uaa-and-identity-authentication) towards your IAS tenant to use it as identity provider for applications in your subaccount. - Ensure your development environment is [prepared for deploying](../deploy/to-cf#prerequisites) on CF, in particular you require a `cf` CLI session targeting a CF space in the test subaccount (test with `cf target`). You can continue with the sample [already created](#mock-user-authentication). In the project root folder, execute the following command to make your application ready for deployment to CF. ```sh cds add mta ```
::: info Command `add mta` will enhance the project with `cds-starter-cloudfoundry` and therefore all [dependencies required](../../java/security#maven-dependencies) for security are added transitively. :::
You also need to configure database support: ```sh cds add hana ``` ### Adding IAS > Source: /docs/guides/security/authentication#adding-ias Now the application is ready to be enhanced with IAS-support: ```sh cds add ias ``` This command automatically adds a service instance named `bookshop-ias` of type `identity` (plan: `application`) and binds the CAP application to it in the _mta.yaml_. ::: details Generated deployment descriptor for IAS instance and binding ```yaml [mta.yaml] modules: - name: bookshop-srv # [...] requires: - name: bookshop-ias parameters: config: credential-type: X509_GENERATED app-identifier: srv resources: - name: bookshop-ias type: org.cloudfoundry.managed-service parameters: service: identity service-name: bookshop-ias service-plan: application config: display-name: bookshop ``` :::
::: info The [binding](../../java/security#bindings) to service instance of type `identity` is the trigger to automatically enforce IAS authentication at runtime ❗ :::
The binding provides access to the identity services on behalf of a concrete client. **CAP applications can have at most one binding to an IAS instance.** Conversely, multiple CAP applications can share the same IAS instance. Service instance and binding offer the following crucial configuration properties: | Property | Artifact | Description | |-------------------|:-------------------:|---------------------| | `name` | _instance_ | _Name for the IAS application - unique in the tenant_ | | `display-name` | _instance_ | _Human-readable name for the IAS application as it appears in the Console UI for IAS administrators_ | | `multi-tenant` | _instance_ | _Specifies application mode: `false` for single tenant (default), `true` for multiple subscriber tenants (SAAS)_ | | `credential-type` | _binding_ | _`X509_GENERATED` generates a private-key and a signed certificate which is added to IAS application_ | | `app-identifier` | _binding_ | _Ensures stable subject in generated certificate (required for credential rotation)_ | [Learn more about IAS service instance and binding configuration.](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/reference-information-for-identity-service-of-sap-btp){.learn-more}
Now let's pack and deploy the application: ```sh cds up ``` You can test the status with `cf apps` on CLI level or in BTP Cockpit, alternatively. The startup log should confirm the activated IAS authentication:
```sh ... : Loaded feature 'IdentityUserInfoProvider' (IAS: bookshop-ias, XSUAA: ) ```
```sh [cds] - using auth strategy { kind: 'ias', impl: 'node_modules/@sap/cds/lib/srv/middlewares/auth/ias-auth.js' } ```
::: tip The local setup is still runnable on basis of mock users as there is no IAS binding in the environment. ::: For mTLS support which is mandatory for IAS, the CAP application has a second route configured with the `cert.*` domain: ```yaml modules: - name: bookshop-srv # [...] parameters: routes: - route: "${default-url}" - route: "${default-host}.cert.${default-domain}" ``` ::: info Platform-level TLS termination is provided on CF out of the box via `cert.*`-domains. By default, the validated certificate is forwarded via HTTP header `X-Forwarded-Client-Cert` to the CAP endpoint. ::: ::: warning On SAP BTP Kyma Runtime, you might need to adapt configuration parameter `cds.security.authentication.clientCertificateHeader` to match the header used by the component terminating TLS you configured. ::: #### Administrative Console for IAS > Source: /docs/guides/security/authentication#administrative-console-for-ias In the [Administrative Console for Cloud Identity Services](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/accessing-administration-console?version=Cloud) you can see and manage the deployed IAS application. You need a user with administrative privileges in the IAS tenant to access the services at `.accounts400.ondemand.com/admin`. In the Console you can manage the IAS tenant and IAS applications, for example: - Create (test) users in `Users & Authorizations` -> `User Management`. - Deactivate users. - Configure the authentication strategy (password policies, multifactor authentication, and similar) in `Applications & Resources` -> `Applications` (IAS instances listed with their display-name). - Inspect logs in `Monitoring & Reporting` -> `Troubleshooting`. ::: tip In BTP Cockpit, service instance `bookshop-ias` appears as a link that allows direct navigation to the IAS application in the Administrative Console for IAS. ::: ### CLI Level Testing > Source: /docs/guides/security/authentication#cli-level-testing Due to CAP's autoconfiguration, all CAP endpoints are authenticated and expect valid OAuth tokens created for the IAS application. The following request as anonymous user without a token results in a `401 Unauthorized`:
```sh curl https://--bookshop-srv. \ /odata/v4/CatalogService/Books --verbose ```
```sh curl https://--bookshop-srv. \ /odata/v4/catalog/Books --verbose ```
This is expected. Now let's fetch a token as basis for a fully authenticated test request. For doing so, you need to interact with IAS service which requires an authenticated client itself. The overall setup with CLI client and the Cloud services is sketched in the diagram: ![CLI-level Testing of IAS Endpoints](./assets/ias-cli-setup.drawio.svg){} As IAS requires mTLS-protected channels, **client certificates are mandatory** for all of the following requests: - Token request to IAS to fetch a valid IAS token (1) - Business request to the CAP application presenting the token (2) - Initial proof token request to IAS - not required for all business requests (3) As first step add a new client for the IAS application by creating an appropriate service key: ```sh cf create-service-key bookshop-ias bookshop-ias-key \ -c '{"credential-type": "X509_GENERATED"}' ``` The client certificates are presented in the IAS binding and hence can be examined via a service key accordingly. ::: details How to create and retrieve service key credentials ```sh cf service-key bookshop-ias bookshop-ias-key { "credentials": { [...] "certificate": "-----BEGIN CERTIFICATE----- [...] -----END CERTIFICATE-----", "clientid": "2a92c297-8603-4157-9aa9-ca758582abcd", "credential-type": "X509_GENERATED", "key": "-----BEGIN RSA PRIVATE KEY----- [...] -----END RSA PRIVATE KEY-----", "url": "https://.accounts400.ondemand.com", [...] } } ``` ::: ::: warning ❗ **Never share service keys or tokens** ❗ ::: From the credentials, you can prepare local files containing the certificate used to initiate the HTTP request. ::: details How to prepare client X.509 certificate files Copy the public X.509-certificate in property `certificate` into a file `cert-raw.pem` and `key` into a file `key-raw.pem`, accordingly. Both files need to be post-processed to transform the single-line representation into a standard multi-line representation: ```sh awk '{gsub(/\\n/,"\n")}1' .pem > .pem ``` Finally, ensure correct format of both files with ```sh openssl x509 -in .pem -text -noout ``` All the steps can be executed in a single script as shown in the [example](https://cap.cloud.sap/resources/examples/fetch-ias-certs.sh). ::: To fetch a token - either as technical or as named user - the request needs to provide the **client certificate** being send to `/oauth2/token` endpoint of IAS service with URI given in `url` property of the binding: ::: code-group ```sh [Token for technical user] curl --cert cert.pem --key key.pem \ -d "grant_type=client_credentials"\ -d "client_id=" \ https:///oauth2/token ``` ```sh [Token for named user] curl --cert cert.pem --key key.pem \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=password" \ -d "client_id=" \ -d "username=" \ -d "password=" \ -X POST https:///oauth2/token ``` ::: The request returns with a valid IAS token which is suitable for authentication in the CAP application: ```sh {"access_token":"[...]","token_type":"Bearer","expires_in":3600} ``` The final test request needs to provide the **client certificate and the token** being send to the application's route with `cert.*`-domain:
```sh curl --cert cert.pem --key key.pem -H "Authorization: Bearer " \ https://--bookshop-srv.cert. \ /odata/v4/CatalogService/Books ```
```sh curl --cert cert.pem --key key.pem -H "Authorization: Bearer " \ https://--bookshop-srv.cert. \ /odata/v4/catalog/Books ```
The response should contain the queried books accordingly (HTTP response code `200`). Don't forget to delete the service key after your tests: ```sh cf delete-service-key bookshop-ias bookshop-ias-key ``` ### UI Level Testing > Source: /docs/guides/security/authentication#ui-level-testing In the UI scenario, adding an Application Router as an ingress proxy to the deployment simplifies testing significantly. It fetches the required IAS tokens when forwarding requests to the backend service. Enhancing the project with [SAP Cloud Portal](../deploy/to-cf#option-a-sap-cloud-portal) configuration adds an Application Router component as well as HTML5 Application Repository: ```sh cds add portal ``` The resulting setup is sketched in the diagram: ![UI-level Testing of IAS Endpoints](./assets/ias-ui-setup.svg){} To be able to fetch the token, the AppRouter needs a binding to the IAS instance as well. In addition, property `forwardAuthCertificates` needs to be `true` to support the mTLS connection with the service backend which is called by the route with the cert-domain. ::: details AppRouter component with IAS binding ```yaml - name: bookshop [...] requires: - name: srv-api group: destinations properties: name: srv-api url: ~{srv-cert-url} forwardAuthToken: true forwardAuthCertificates: true - name: bookshop-ias parameters: config: credential-type: X509_GENERATED app-identifier: approuter ``` ::: As the login flow is based on an HTTP redirect between the CAP application and IAS login page, IAS needs to know a valid callback URI that the AppRouter offers out of the box. The same is true for the logout flow. ::: details Redirect URIs for login and logout ```yaml - name: bookshop-ias [...] parameters: [...] config: [...] oauth2-configuration: redirect-uris: - ~{app-api/app-protocol}://~{app-api/app-uri}/login/callback post-logout-redirect-uris: - ~{app-api/app-protocol}://~{app-api/app-uri}/*/logout.html ``` ::: Now re-deploy the solution: ```sh cds up ``` Test the application using the URL provided in the Cockpit. The Application Router should redirect to a login flow where you can enter the credentials of a [test user](#ias-admin) you created before in the Administration Console for IAS. ## XSUAA Authentication > Source: /docs/guides/security/authentication#xsuaa-authentication ::: warning **Start new projects with IAS** to take advantage of the best integration options. IAS offers a cross-consumption mode that allows IAS users to consume legacy XSUAA services. ::: [SAP Authorization and Trust Management Service (XSUAA)](https://help.sap.com/docs/btp/sap-business-technology-platform/sap-authorization-and-trust-management-service-in-cloud-foundry-environment) is a platform service for identity and access management which provides: - authentication mechanisms (single sign-on, multi-factor enforcement) - federation of corporate identity providers (multiple user stores) - create and assign access roles ::: tip In contrast to [IAS](#ias-auth), XSUAA does not allow cross-landscape user propagation out of the box. ::: You can best configure and test XSUAA authentication in the Cloud, so let's enhance the sample with a deployment descriptor for SAP BTP, Cloud Foundry Runtime (CF). ### Get Ready with XSUAA > Source: /docs/guides/security/authentication#get-ready-with-xsuaa Before working with XSUAA on CF, you need to ensure your development environment is [prepared for deploying](../deploy/to-cf#prerequisites) to CF. In particular, you require a `cf` CLI session targeting a CF space in the test subaccount (test with `cf target`). :::details If you haven't prepared a sample yet... You can create a bookshop sample as described in [Mock User Authentication](#mock-user-authentication). Execute the following two commands in the project root folder, only if you haven't prepared your sample for IAS in the previous section already. If there is no deployment descriptor yet, execute the following in the project root folder: ```sh cds add mta ``` You also need to configure database support: ```sh [SAP HANA] cds add hana ``` ::: tip For Java Command `add mta` enhances the project with `cds-starter-cloudfoundry` and therefore adds all [dependencies required for security](../../java/security#maven-dependencies) transitively. ::: ### Adding XSUAA > Source: /docs/guides/security/authentication#adding-xsuaa Enhance your [sample application](#mock-user-authentication) with XSUAA-support:
```sh cds add xsuaa ```
```sh cds add xsuaa --for production ```
The command automatically adds a service instance named `bookshop-auth` of type `xsuaa` (plan: `application`) and binds the CAP application to it: ```yaml [mta.yaml] modules: - name: bookshop-srv # [...] requires: - name: bookshop-auth resources: - name: bookshop-auth type: org.cloudfoundry.managed-service parameters: service: xsuaa service-plan: application path: ./xs-security.json config: xsappname: bookshop-${org}-${space} tenant-mode: dedicated role-collections: - name: 'admin (bookshop ${org}-${space})' description: 'generated' role-template-references: - '$XSAPPNAME.admin' ```
::: info Command `cds add xsuaa` enhances the project with [required binding](../../java/security#bindings) to service instance identity and therefore activates XSUAA authentication automatically. :::
**CAP applications should have at most one binding to an XSUAA instance.** Conversely, multiple CAP applications can share the same XSUAA instance.
::: tip In case your application has multiple XSUAA bindings you need to [pin the binding](../../java/security#bindings). :::
There are some mandatory configuration parameters: | Property | Description | |-------------------|-------------------| |`service-plan` | _The plan type reflecting various application scenarios. UI applications without API access use plan `application`. All others should use plan `broker`._ | |`path` | _File system path to the [application security descriptor](#xsuaa-security-descriptor)._ | |`xsappname` | _A unique application name within the subaccount. All XSUAA artifacts are prefixed with it (wildcard `$XSAPPNAME`)._ | |`tenant-mode` | _`dedicated` is suitable for a single-tenant application. Mode `shared` is mandatory for a multitenant application._ | ::: warning Upgrading the `service-plan` from type `application` to `broker` is not supported. Start with plan `broker` if you want to provide technical APIs in future. ::: [Learn more about XSUAA application security descriptor configuration syntax.](https://help.sap.com/docs/btp/sap-business-technology-platform/application-security-descriptor-configuration-syntax){.learn-more} #### Security Descriptor > Source: /docs/guides/security/authentication#security-descriptor The security descriptor in the `xs-security.json` file contains [XSUAA authorization artifacts](https://help.sap.com/docs/btp/sap-business-technology-platform/authorization-entities). In general, XSUAA artifacts have a hierarchical relationship with role collections as root elements. Role collections can be assigned to end users. For convenience, when adding the XSUAA facet, these artifacts are initially derived from the CDS model: - **XSUAA scopes**: For every [CAP role](./cap-users#roles) in the CDS model, a dedicated scope is generated with the exact name of the CDS role. - **XSUAA attributes**: For every [CAP attribute](./authorization#user-attrs) in the CDS model, one attribute is generated. - **XSUAA role templates**: For every scope, a dedicated role template with the exact name is generated. The role templates are building blocks for concrete role collections that finally can be assigned to users. ```json { "scopes": [ { "name": "$XSAPPNAME.admin", "description": "admin" } ], "attributes": [], "role-templates": [ { "name": "admin", "description": "generated", "scope-references": [ "$XSAPPNAME.admin" ], "attribute-references": [] } ] } ``` [Learn more about XSUAA attributes.](https://help.sap.com/docs/btp/sap-business-technology-platform/setting-up-instance-based-authorizations){.learn-more} [Lean more about XSUAA security descriptor.](https://help.sap.com/docs/btp/sap-business-technology-platform/application-security-descriptor-configuration-syntax){.learn-more} [Learn how to setup mTLS for XSUAA.](https://help.sap.com/docs/btp/sap-business-technology-platform/enable-mtls-authentication-to-sap-authorization-and-trust-management-service-for-your-application){.learn-more} At runtime, after successful authentication, the scope prefix `$XSAPPNAME`is removed by the CAP integration to match the corresponding CAP role. In the [deployment descriptor](#adding-xsuaa), the optional property `role-collections` contains a list of preconfigured role collections. In general, user administrators [create role collections manually](./cap-users#xsuaa-assign) at runtime. However, if the underlying role template has no reference to an attribute, you can prepare a corresponding role collection for convenience. In the example, role collection `admin (bookshop -)` containing the role template `admin` is defined and can be directly assigned to users. ::: tip You can re-generate the file on model changes via ```sh cds compile srv --to xsuaa > xs-security.json ``` ::: Consult [Application Security Descriptor Configuration Syntax](https://help.sap.com/docs/btp/sap-business-technology-platform/application-security-descriptor-configuration-syntax) in the SAP Help documentation for the syntax of the _xs-security.json_ and advanced configuration options. ::: tip If you modify the _xs-security.json_ manually, make sure that the scope names in the file exactly match the role names in the CDS model, as these scope names will be checked at runtime. ::: #### Start and Check the Deployment > Source: /docs/guides/security/authentication#start-and-check-the-deployment Now let's pack and deploy the application:
```sh npm install cds up ```
```sh cds up ```
and wait until the application is up and running. You can test the status with `cf apps` on CLI level or in BTP Cockpit, alternatively. Run `cf logs bookshop-srv --recent` to confirm the activated XSUAA authentication:
```sh ... : Loaded feature 'IdentityUserInfoProvider' (IAS: , XSUAA: bookshop-auth) ```
```sh ... : "using auth strategy { kind: 'xsuaa' … } ```
::: tip The local setup is still runnable on basis of mock users as there is no IAS binding in the environment. ::: ### CLI Level Testing > Source: /docs/guides/security/authentication#cli-level-testing-1 Due to CAP's autoconfiguration, all CAP endpoints are [authenticated automatically](#model-auth) and expect valid XSUAA tokens. The following request as anonymous user without a token results in a `401 Unauthorized`:
```sh curl https://--bookshop-srv. \ /odata/v4/CatalogService/Books --verbose ```
```sh curl https://--bookshop-srv. \ /odata/v4/catalog/Books --verbose ```
This is expected. Now let's fetch an XSUAA token to prepare an authenticated test request. Here, you need to interact with XSUAA service which requires a valid authentication as well. As first step add a new client for XSUAA by creating an appropriate service key: ```sh cf create-service-key bookshop-auth bookshop-auth-key ``` You can inspect the service key credentials as follows: ```sh cf service-key bookshop-auth bookshop-auth-key ``` This command prints the information to the console: ```json { "credentials": { [...] "clientid": "sb-bookshop-...", "clientsecret": "...", "url": "https://.authentication.sap.hana.ondemand.com", [...] } } ``` ::: warning ❗ **Never share service keys or tokens** ❗ ::: As second step, assign the generated role collection with name `admin (bookshop -)` to your **test user**. Follow the instructions from step 4 onwards of [Assign Roles in SAP BTP Cockpit Step](./cap-users#xsuaa-assign). With the credentials, you can send an HTTP request to fetch the token from XSUAA `/oauth/token` endpoint: ::: code-group ```sh [Token for technical user] curl -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -d 'grant_type=client_credentials' \ -d 'client_id=' \ -d 'client_secret=' \ /oauth/token ``` ```sh [Token for named user] curl -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -d 'grant_type=password' \ -d 'client_id=' \ -d 'client_secret=' \ -d 'username=' \ -d 'password=' \ /oauth/token ``` ::: The request returns with a valid XSUAA token which is suitable to pass authentication in the CAP application: ```sh {"access_token":"", "token_type":"bearer","expires_in":43199, [...]} ``` With the token for the technical user, you should be able to access endpoints that have no specific role requirements:
```sh curl -H "Authorization: Bearer " \ https://--bookshop-srv. \ /odata/v4/CatalogService/Books ```
```sh curl -H "Authorization: Bearer " \ https://--bookshop-srv. \ /odata/v4/catalog/Books ```
If you also want to access the `AdminService` that requires the role `admin`, you need to fetch the token for the named user instead. That is the user to whom you assigned the `admin (bookshop -)` role collection. With the token for the named user, the following request should succeed:
```sh curl -H "Authorization: Bearer " \ https://--bookshop-srv. \ /odata/v4/AdminService/Books ```
```sh curl -H "Authorization: Bearer " \ https://--bookshop-srv. \ /odata/v4/admin/Books ```
::: tip Try out sending a request to the `admin` endpoint with the technical user token to see the expected `403 Forbidden` response: ```sh { "error": { "message":"Forbidden","code":"403", … } } ``` ::: Don't forget to delete the service key after your tests: ```sh cf delete-service-key bookshop-auth bookshop-auth-key ``` ### UI Level Testing > Source: /docs/guides/security/authentication#ui-level-testing-1 In the UI scenario, adding an Application Router as an ingress proxy to the deployment simplifies testing significantly. It fetches the required XSUAA tokens when forwarding requests to the backend service. Enhancing the project with [SAP Cloud Portal](../deploy/to-cf#option-a-sap-cloud-portal) configuration adds an Application Router component as well as HTML5 Application Repository: ```sh cds add portal ``` The resulting architecture is very similar to the [IAS scenario](#ui-level-testing), but only with XSUAA service instances instead of IAS service instances. There is one more difference: **By default, XSUAA does not enforce mTLS**. To be able to fetch the token, the Application Router needs a binding to the XSUAA instance. ::: details AppRouter component with XSUAA binding ```yaml modules: - name: bookshop type: approuter.nodejs path: app/router [...] requires: - name: srv-api group: destinations properties: name: srv-api # must be used in xs-app.json as well url: ~{srv-url} forwardAuthToken: true - name: bookshop-auth [...] provides: - name: app-api properties: app-protocol: ${protocol} app-uri: ${default-uri} url: ${default-url} ``` ::: As the login flow is based on an HTTP redirect between the CAP application and XSUAA login page, XSUAA needs to know a valid callback URI that the Application Router offers out of the box. The same is true for the logout flow. ::: details Redirect URIs for login and logout ```yaml - name: bookshop-auth [...] parameters: [...] config: [...] oauth2-configuration: redirect-uris: - https://*~{app-api/app-uri}/** requires: - name: app-api ``` ::: Now update the Cloud deployment: ```sh cds up ``` Verify it by running `cf apps` in the targeted space: ```sh > $ cf apps name requested state processes routes bookshop-portal started web:1/1 --bookshop. bookshop-portal-db-deployer stopped web:0/1 bookshop-portal-srv started web:1/1 --bookshop-srv. ``` Open the route exposed by the `bookshop` UI application in a new browser session. ## Hybrid Authentication > Source: /docs/guides/security/authentication#hybrid-authentication will come soon ## Custom Authentication > Source: /docs/guides/security/authentication#custom-authentication
There are multiple reasons why customization might be required: 1. Endpoints for non-business requests often require specific authentication methods (for example, health check, technical services). 2. The application is deployed in the context of a service mesh with ingress authentication (for example, Istio). 3. The application needs to integrate with a third-party authentication service. ![Endpoints with different authentication strategy](./assets/custom-auth.drawio.svg){} [Advanced configuration options](../../java/security#spring-boot) allow you to control the behaviour of CAP's authentication behaviour according to your needs: - For CAP endpoints you are fine to go with the [automatic authentication](#model-auth) fully derived from the CAP model. - For custom endpoints that should be protected by the same authentication strategy you are also fine with automatic authentication as CAP will cover these endpoints by default. - For custom endpoints that should have a different kind of authentication strategy (for example, X.509, basic or none) you can add a security configuration that [partially overrules](#partially-auth) the CAP integration for exactly these endpoints. - If the authentication is delegated to a different component, just [fully overrule](#fully-auth) CAP authentication and replace it with any suitable strategy. ::: tip Secure by Default **By default, CAP authenticates all endpoints of the microservice, including the endpoints which are not served by CAP itself**. This is the safe baseline on which minor customization steps can be applied on top. :::
Ideally, all authentication use-cases should be covered by the generic implementations CAP provides. However, your application's specific requirements may make it necessary to customize authentication. For these scenarios, the CAP Node.js runtime allows to specify an implementation of a custom authentication middleware in cds.requires.auth.impl, by providing a path relative to the project root. :::warning Be **very** careful when creating your own `auth` implementation. This should be a last resort for when every other possible solution (for example through [modelling](./authorization.md#restrictions) or by [configuration](#pluggable-authentication)) has been investigated and dismissed. ::: Like any other [custom middleware](../../node.js/cds-serve.md#custom-middlewares), the auth middleware you create needs to accept express's `req`, `res` and `next` and end up by sending a response, throwing an error or calling `next()`. Additionally, a custom auth middleware in CAP needs to set `cds.context.user` and, in a multitenant applications, `cds.context.tenant`. ```js module.exports = function custom_auth (req, res, next) { // do your custom authentication cds.context.user = new cds.User({ id: '', roles: ['', ''], attr: { : '', : '' } }) cds.context.tenant = '' } ``` :::tip In case you want to customize the `cds.context.user`, check out [this example](../../node.js/cds-serve#customization-of-cdscontextuser). :::
### Automatic Authentication > Source: /docs/guides/security/authentication#automatic-authentication As the auto-configuration authenticates all service endpoints found in the CDS model by default, you don't need to explicitly activate authentication for these endpoints. Endpoints that should be public can be explicitly annotated with [pseudo-role](cap-users#pseudo-roles) `any`: ```cds service BooksService @(requires: 'any') { @readonly entity Books @(requires: 'any') {...} entity Reviews {...} entity Orders @(requires: 'Customer') {...} } ``` | Path | Authenticated ? | |:--------------------------|:----------------:| | `/BooksService` and `/BooksService/$metadata` | | | `/BooksService/Books` | | | `/BooksService/Reviews` | | | `/BooksService/Orders` | | ::: tip In multitenant applications, anonymous requests to public endpoints are missing the tenant information and hence this gap needs to be filled by custom code. ::: By default, if a CAP service `MyService` is authenticated, also `/MyService/$metadata` is authenticated.
With `cds.security.authentication.authenticateMetadataEndpoints: false` you can switch off this behaviour on a global level. [Learn more about authentication options.](../../java/security#spring-boot){.learn-more}
Automatic authentication enforcement can be disabled via feature flag cds.requires.auth.restrict_all_services: false, or by using [mocked authentication](#mock-user-authentication) explicitly in production.
### Overrule Partially > Source: /docs/guides/security/authentication#overrule-partially If you want to explicitly define the authentication for specific endpoints, **you can add an _additional_ Spring security configuration on top** overriding the default configuration given by CAP: ```java @Configuration @EnableWebSecurity public class CustomSecurityConfig { @Bean @Order(1) // needs to have higher priority than CAP security config public SecurityFilterChain customFilterChain(HttpSecurity http) throws Exception { return http .securityMatcher(AntPathRequestMatcher.antMatcher("/public/**")) .csrf(c -> c.disable()) // don't insist on csrf tokens in put, post etc. .authorizeHttpRequests(r -> r.anyRequest().permitAll()) .build(); } } ``` Due to the custom configuration, all URLs matching `/public/**` are opened for public access in this example. Ensure your custom configuration has higher priority than CAP's default security configuration by decorating the bean with a low order. ::: warning Be cautious with the configuration of the `HttpSecurity` instance in your custom configuration. Make sure that only the intended endpoints are affected. ::: [Learn more about overruling Spring security configuration.](../../java/security#custom-spring-security-config){.learn-more} ### Overrule Fully > Source: /docs/guides/security/authentication#overrule-fully In services meshes such as [Istio](https://istio.io/) the authentication is usually fully delegated to a central ingress gateway and the internal communication with the services is protected by a secure channel: ![Service Mesh with Ingress Gateway](./assets/ingress-auth.drawio.svg){} ::: tip User propagation should be done by forwarding the request token in `Authorization`-header accordingly. This will make standard CAP authorization work properly. ::: ::: warning If you switch off CAP authentication, make sure that the internal communication channels are secured by the given infrastructure. :::
In such architectures, CAP authentication is obsolete and can be deactivated entirely with `cds.security.authentication.mode="never"`. [Learn more about how to switch off authentication.](../../java/security#custom-spring-security-alone){.learn-more}
## Pitfalls > Source: /docs/guides/security/authentication#pitfalls - **Don't miss to configure security middleware.** Endpoints of (CAP) applications deployed on SAP BTP are, by default, accessible from the public network. Without security middleware configured, CDS services are exposed to the public. - **Don't rely on Application Router authentication**. Application Router as a frontend proxy does not shield the backend from incoming traffic. Therefore, you must secure the backend independently. - **Don't deviate from security defaults**. Only when absolutely necessary should experts make the decision to add modifications or replace parts of the standard authentication mechanisms. - **Don't forget to add authentication tests** to ensure properly configured security in your deployed application that rejects unauthenticated requests. ::: warning Without security middleware configured, CDS services are exposed to public. Basic configuration of an authentication strategy is mandatory to protect your CAP application. ::: # CAP-level Users & Roles > Source: /docs/guides/security/cap-users This guide introduces CAP user abstraction and role assignments. ## CAP User Abstraction > Source: /docs/guides/security/cap-users#cap-user-abstraction A successful authentication results in a CAP user representation reflecting the request user in a uniform way. Referring to the [key concepts](./overview#key-concept-decoupled-coding), the abstraction serves to completely decouple authorization and business logic from pluggable authentication strategies. It contains static information about the user such as name, ID, and tenant. Additionally, it contains claims such as roles or assigned attributes that are relevant for [authorization](./authorization). ![CAP Userse](./assets/cap-users.drawio.svg){ } After _successful_ authentication, a **CAP user** is mainly represented by the following properties: - **_Logon name_** identifying the user uniquely - **_Tenant_** describes the tenant of the user (subscriber or provider) that implies the CDS model and business data container. - **_Roles_** the user has been assigned by a user administrator (business [user roles](#roles)) or roles that are derived by the authentication level ([pseudo roles](#pseudo-roles)). - **_Attributes_** the user has been assigned, for example, for instance-based authorization.
The user information is reflected in the `UserInfo` object [attached to the request](#reflection).
The user information is reflected in `req.user` and `req.tenant` [attached to the request](#reflection).
### User Types > Source: /docs/guides/security/cap-users#user-types CAP users can be classified in multiple dimensions: **Business users vs. technical users:** - Business users represent identifiable end users who log in to interact with the system. - Technical users operate on behalf of an entire tenant at a technical API level. **Authenticated users vs. anonymous users** - Authenticated users have successfully completed authentication by presenting valid credentials (for example, a token). - Anonymous users are unidentifiable in general, as they usually don't present any credentials. **Provider vs. subscriber tenant** - The provider tenant includes all users of the application owner. - A subscriber tenant includes all users of a dedicated application customer. Usually, the provider tenant is not subscribed to a [multitenant application](../multitenancy/) and therefore has no business users. There are technical users for the provider and for all subscribers. | Multitenant Application | Business users | Technical user |---------------------------|----------------|---------------- | Provider Tenant | - | | Subscriber Tenants | | In contrast, for a single-tenant application, the provider tenant coincides with the only subscriber tenant and therefore contains all business users. | Single-Tenant Application | Business users | Technical user |---------------------------|----------------|---------------- | Provider (=subscriber) Tenant | | ::: info Apart from anonymous users, all users have a unique tenant. ::: The user types are designed to support various flows, such as: - UI requests executed on behalf of a business user interacting with the CAP backend service. - Backend processing that utilizes platform services on behalf of the technical user of the subscriber tenant. - Asynchronously received messages that process data on behalf of the technical user of a subscriber tenant. - Background tasks that operate on behalf of the technical user of the provider tenant. - etc. Find more details about how to [switch the user context](#switching-users) during request processing. ### Roles > Source: /docs/guides/security/cap-users#roles CAP roles, which are defined on CDS resources such as services and entities, down to the events allowed on them, form the basis of [static access control](authorization#role-based-access-control). Technically, the request user is restricted to the resources for which an appropriate CAP role is assigned. **Such roles should reflect basic operations performed by users interacting with the application**. In the following example, there are two different basic operations defined on domain level: - `ReportIssues` describes users who view existing issues, report new issues and confirm provided solutions. - `ProcessIssues` describes users who process issues. They also write notes for customers. ```cds annotate Issues with @(restrict: [     { grant: ['READ','report', 'confirm'], to: 'ReportIssues' }, { grant: ['READ', 'WRITE'], to: 'ProcessIssues' } ]); annotate Notes with @(restrict: [ { grant: ['READ'] }, // any { grant: ['READ', 'WRITE'], to: 'ProcessIssues' } ]); ``` CAP roles represent basic building blocks of authorization rules that are defined by application developers _at design time_. Independently of that, user administrators combine CAP roles in higher-level policies and assign them to business users in the platform's central authorization management solution _at runtime_. Dynamic assignments of roles to users can be done by - [AMS roles](#roles-assignment-ams) for [IAS authentication](./authentication#ias-auth). - [XSUAA roles](#xsuaa-roles) for [XSUAA authentication](./authentication#xsuaa-auth). ::: info CDS-based authorization deliberately avoids technical concepts, such as _scopes_ in _OAuth_, in favor of user roles, which are closer to the business domain of applications. ::: #### Pseudo Roles > Source: /docs/guides/security/cap-users#pseudo-roles Often it is useful to define access rules that aren't based on an application-specific user role, but rather on the _technical authentication level_ of the request that can be mapped to a pre-defined CAP role. For instance, a service should be accessible only for technical users, with or without user propagation. Such roles are called pseudo roles as they aren't assigned by user administrators, but are added by the runtime automatically on successful authentication, reflecting the technical level:
| Pseudo Role | User Type | Technical Indicator | User Name ($user) | |----------------------|-------------|-------------------------------------------------------------|------------------------------------------------------| | `authenticated-user` | - | _successful authentication_ | _derived from the token_ | | `any` | - | - | _derived from the token if available or `anonymous`_ | | `system-user` | _technical_ | _grant type client credential_ | `system` | | `internal-user` | _technical_ | _grant type client credential and shared identity instance_ | `system-internal` |
| Pseudo Role | User Type | Technical Indicator | User Name | |----------------------|-------------|-------------------------------------------------------------|------------------------------------------------------| | `authenticated-user` | | _successful authentication_ | _derived from the token_ | | `any` | | | _derived from the token if available or `anonymous`_ | | `system-user` | _technical_ | _grant type client credential_ | `system` | | `internal-user` | _technical_ | _grant type client credential and shared identity instance_ | `system` |
The pseudo-role `system-user` allows you to separate access by business users from _technical_ clients. Note that this role does not distinguish between any technical clients sending requests to the API. Pseudo-role `internal-user` allows to define application endpoints that can be accessed exclusively by the own provider tenant on technical level. In contrast to `system-user`, the endpoints protected by this pseudo-role do not allow requests from any external technical clients. Hence it is suitable for **technical intra-application communication**, see [Security > Application Zone](./overview#application-zone). ::: warning All technical clients that have access to the application's XSUAA or IAS service instance can call your service endpoints as `internal-user`. **Refrain from sharing this service instance with untrusted clients**, for instance by passing services keys or [SAP BTP Destination Service](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/create-destinations-from-scratch) instances. ::: ### Model References > Source: /docs/guides/security/cap-users#model-references The object representation of the resolved CAP user is attached to the current request context and has an impact on the request flow, for instance with regard to - [authorizations](./authorization#restrictions) - [enriching business data](../domain/#managed-data) with user data - setting database session variables In the CDS model, some of the user properties can be referenced in annotations or static views: | User Property | CDS Model Reference | CDS Artifact | |-------------------------------|---------------------|--------------------| | Name | `$user` | annotations and static views | | Attribute | `$user.` | [@restrict](./authorization#user-attrs) | | Role | `` | [@requires](./authorization#requires) and [@restrict.to](./authorization#restrict-annotation) | ### Tracing > Source: /docs/guides/security/cap-users#tracing To track down issues during development, it can help to trace the properties of the request user to the application log.
You can activate user tracing by setting logger `com.sap.cds.security.authentication` to log level `DEBUG`: ```yaml logging: level: com.sap.cds.security.authentication: DEBUG ``` This will result in trace output like ```sh Resolved MockedUserInfo [id='mock/admin', name='admin', roles='[admin]', attributes='{tenant=[null]}' ``` for mock users or ```sh c.s.c.f.i.IdentityUserInfoProvider : Resolved XsuaaUserInfo [id='be72646e-279a-4f96-ae40-05989a46b43b', name='max.muster@sap.com', roles='[openid, admin]', attributes=' {tenant=[b2c463bd-da56-488c-8345-2632905acde3]}' ``` for XSUAA users. [Learn more about tracing](../../java/operating-applications/observability#logging-configuration){.learn-more}
You can activate user tracing by setting log level cds.log.levels.auth: "debug": ```json { "cds": { "log": { "levels": { "auth": "debug" } } } } ``` This will result in trace output like ```sh [basic] - 401 > login required [basic] - authenticated: { user: 'alice', tenant: ..., features: [ ... ] } ``` for mock users.
::: warning Refrain from activating user tracing in productive systems. ::: ## Role Assignment with AMS > Source: /docs/guides/security/cap-users#role-assignment-with-ams CAP applications that use the [Identity Authentication Service (IAS)](https://help.sap.com/docs/identity-authentication) for authentication can leverage the [Authorization Management Service (AMS)](https://sap.github.io/cloud-identity-developer-guide/Authorization/GettingStarted.html) to provide comprehensive authorization. Similar to IAS, AMS is part of the [SAP Cloud Identity Services (SCI)](https://help.sap.com/docs/cloud-identity-services). Why is AMS required? Unlike tokens issued by XSUAA, IAS tokens only contain static user information and cannot directly provide CAP roles. AMS acts as a central service to define access policies that include CAP roles and additional filter criteria for instance-based authorizations in CAP applications. _Business users_, technically identified by the IAS ID token, can have AMS policies assigned by user administrators. ::: info Authorizations for technical users can't be addressed by AMS policies yet. ::: The integration with AMS is provided as an easy-to-use plugin for CAP applications. At the time of the request, the AMS policies assigned to the request user are evaluated by the CAP AMS plugin, and the CAP roles and filters are applied to the request context accordingly. This is illustrated in the following diagram: ![The graphic is explained in the following text.](./assets/ams.png){ } The interaction between the CAP application and AMS (via plugin) is as follows: 1. IAS-Authentication is performed independently as a pre-step. 2. The plugin injects **user roles and filters** according to AMS policies assigned to the current request user. 3. CAP performs the authorization on the basis of the CDS authorization model and the injected user claims. ### Adding AMS Support > Source: /docs/guides/security/cap-users#adding-ams-support **AMS is transparent to CAP application code** and can be easily consumed via plugin dependency. To enhance your project with AMS, you can make use of CDS CLI tooling: ```sh cds add ams ``` This automatically adds required configuration for AMS, taking into account the concrete application context (tenant mode and runtime environment etc.). If required, it also runs the new `cds add ias` command to configure the project for IAS authentication. ::: details See dependencies added ::: code-group ```xml-vue [pom.xml] {{versions.cloud_sec_ams}} ``` ```xml [srv/pom.xml - dependencies] com.sap.cloud.security.ams.client jakarta-ams ${sap.cloud.security.ams.version} com.sap.cloud.security.ams.client cap-ams-support ${sap.cloud.security.ams.version} ``` ```xml [srv/pom.xml - plugins] com.sap.cds cds-maven-plugin cds.build cds [...] build --for ams com.sap.cloud.security.ams.client dcl-compiler-plugin ${sap.cloud.security.ams.version} compile compile ${project.basedir}/src/main/resources/ams true pretty true ``` ::: These libraries integrate into the CAP framework to handle incoming requests. Based on the user's assigned [policies](#policies), the user's roles are determined and written to the [UserInfo](#reflection) object. The framework then authorizes the request as usual based on the user's roles. ::: details Node.js plugin `@sap/ams` added to the project ```json [package.json] { "devDependencies": { "@sap/ams": "^3" } } ``` ::: The `@sap/ams` plugin provides multiple build-time features: - Validate `ams.attributes` annotations for type coherence against the AMS schema. - Generate policies from the CDS model during the build using a [custom build task](../deploy/build#custom-build-tasks). - Generate a deployer application during the build to upload the Data Control Language (DCL) base policies. AM provides highly flexible APIs to define and enforce authorization rules at runtime. A relevant subset of these APIs is consumed by CAP apps by way of the AMS CAP integration plugin. ::: warning Make sure not to mix native AMS APIs with those provided by the CAP plugin. ::: ### Adding AMS Support > Source: /docs/guides/security/cap-users#adding-ams-support-1 **AMS is transparent to CAP application code** and can be easily consumed via plugin dependency. To enhance your project with AMS, you can make use of CDS CLI tooling: ```sh cds add ams ``` This automatically adds required configuration for AMS, taking into account the concrete application context (tenant mode and runtime environment etc.). If required, it also runs the new `cds add ias` command to configure the project for IAS authentication. ::: details See dependencies added ```json [package.json] { "dependencies": [ "@sap/ams": "^3", "@sap/xssec": "^4" ], "devDependencies": [ "@sap/ams-dev": "^2" } ``` ::: `@sap/ams` integrates into the CAP framework to handle incoming requests. Based on the user's assigned [policies](#policies), the user's roles are determined to decorate the [user.is](/node.js/authentication#user-is) function with additional roles. The framework then authorizes the request as usual based on the user's roles. For local development, `@sap/ams-dev` needs to compile the DCL files to Data Control Notation (DCN) files in `gen/dcn` which is the machine-readable version of DCL that is required by AMS at runtime. Additionally, `@sap/ams` provides multiple build-time features: - Validate `ams.attributes` annotations for type coherence against the DCL schema. - Generate policies from the CDS model during the build using a [custom build task](../deploy/build#custom-build-tasks). - Generate a deployer application during the build to upload the Data Control Language (DCL) base policies. ::: tip In general, AMS provides highly flexible APIs to define and enforce authorization rules at runtime suitable for native Cloud applications. **In the context of CAP projects, only a limited subset of these APIs is relevant and is offered in a streamlined way via the CAP integration plugins**. ::: ### Prepare CDS Model > Source: /docs/guides/security/cap-users#prepare-cds-model On the level of application domain, you can declaratively introduce access rules in the CDS model, enabling higher-level interaction flows with the entire application domain: - a [CAP role for AMS](#roles-for-ams) can span multiple services and entities, providing a holistic perspective on _how a user interacts with the domain data_. - a [CAP attribute for AMS](#attributes-for-ams) is typically cross-sectional and hence is defined on a domain-global level. The CDS model is fully decoupled from AMS policies which are defined on business level on top by external administrators. Hence, the **rules in the CAP model act as basic building blocks for higher-level business rules** and therefore should have appropriate granularity. #### CAP Roles for AMS > Source: /docs/guides/security/cap-users#cap-roles-for-ams You can define CAP roles in the CDS model as [described before](#roles). ::: tip A CAP role describes a **capability on technical domain level** defined by application developers. In contrast, an AMS policy reflects a coarser-grained **business role on application level** defined by user administrators. ::: Imagine you want to provide two different CAP roles in the bookshop example: `ManageAuthors` allows users to manage the authors of the books being sold. Users with `ManageBooks` work only with the book inventory. As each book has an association to an author, they can only manage books from authors that have already been created before: ```cds service AdminService @(requires: ['ManageAuthors', 'ManageBooks']) { entity Books @(restrict: [ { grant: ['READ'], to: 'ManageAuthors' }, { grant: ['READ', 'WRITE'], to: 'ManageBooks' } ]) as projection on my.Books; entity Authors @(restrict: [ { grant: ['READ', 'WRITE'], to: 'ManageAuthors' }, { grant: ['READ'], to: 'ManageBooks' } ]) as projection on my.Authors; } ``` Both CAP roles are ready to be used in higher-level [AMS policies](#policies). ::: tip You can simply reuse existing CAP roles for AMS. There is no need to modify the CDS model. ::: [Learn more about role-based authorizations in CAP](./authorization#restrictions){.learn-more} #### CAP Attributes for AMS > Source: /docs/guides/security/cap-users#cap-attributes-for-ams Attributes for AMS offer user administrators an additional layer of flexibility to partition domain entities into smaller, more manageable units for access control. The domain attributes, which are exposed to user administrators for defining custom filter conditions, must be predefined by the application developer in the CDS model using the `@ams` annotation. For example, the instances of entity `Books` can be classified by the associated genre. Hence, `genre.name` appears to be a suitable AMS attribute value, exposed under the name `Genre`: ```cds annotate AdminService.Books with @ams.attributes: { Genre: (genre.name) }; ``` In general, the `@ams` annotation operates on the entity level. The value of the AMS attribute needs to point to a single-value property of the target entity (paths are supported). You need to make use of a compiler expression to ensure validity of the value reference. ::: tip Choose attributes exposed to AMS carefully. Attributes you choose should have cross-sectional semantics in the domain. ::: As such attributes are usually shared by multiple entities, it is convenient to add the `@ams`-annotation at the level of a shared aspect as sketched here: ```cds @ams.attributes: { Genre: (genre.name) } aspect withGenre { genre : Association to Genres; } entity Books : withGenre { ... } ``` ### Prepare Base Policies > Source: /docs/guides/security/cap-users#prepare-base-policies CAP roles and attribute filters cannot be directly assigned to business users. Instead, the application defines AMS base policies that include CAP roles and attributes at design time. This allows user administrators to assign them to users or create custom policies based on the base policies at runtime. :::tip AMS policies represent the business-level roles of end users interacting with the application. Often, they reflect real-world jobs or functions. :::
After the application is built, check the `srv/src/main/resources/ams` folder to see the generated AMS *schema* and a *basePolicies* DCL file in a package called `cap` ::: code-group ``` [srv/src/main/resources] └─ ams ├─ cap │ └─ basePolicies.dcl └─ schema.dcl ``` :::
After the application is built, check the *ams/dcl* folder to see the generated AMS *schema* and a *basePolicies* DCL file in a package called *cap*: ::: code-group ``` [./ams] └─ dcl ├─ cap │ └─ basePolicies.dcl └─ schema.dcl ``` :::
[Learn more about policy generation](https://sap.github.io/cloud-identity-developer-guide/CAP/cds-Plugin.html#dcl-generation){.learn-more} The generated policies are a good starting point to add manual modifications. The generated DCL schema includes all AMS attributes exposed for filtering: ```dcl [/ams/schema.dcl] SCHEMA { Genre : String } ``` In the schema you may additionally configure [value help](https://sap.github.io/cloud-identity-developer-guide/Authorization/ValueHelp.html) for the attributes in the [Cockpit UI for AMS](#ams-deployment). You can modify the generated policies according to your needs. For example, you can rename the policies to reflect appropriate job functions and adjust the referenced CAP roles: ```dcl [/ams/cap/basePolicies.dcl] POLICY StockManager { ASSIGN ROLE ManageBooks WHERE Genre IS NOT RESTRICTED; } POLICY ContentManager { ASSIGN ROLE ManageAuthors; ASSIGN ROLE ManageBooks; } ``` In contrast to a `StockManager` who is responsible for the books offering, a `ContentManager` additionally makes the author selection. In addition, a `StockManager` with CAP role `ManageBooks` may be restricted to specific genres by applying appropriate filters prepared in [custom policies](#local-testing). As a `ContentManager` there is no genre-based restriction. ::: info The attribute statement is defined in the scope of a dedicated CAP role and filters are applied on matching entites accordingly. ::: [Learn more about AMS policies](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/configuring-authorization-policies){.learn-more} ### Local Testing > Source: /docs/guides/security/cap-users#local-testing Although the AMS policies are not yet [deployed to the Cloud service](#ams-deployment), you can assign policies to mock users and run locally:
```yaml cds: security: mock: users: content-manager: // [!code ++:3] policies: - cap.ContentManager stock-manager: // [!code ++:3] policies: - cap.StockManager ```
```json [package.json] { "cds": { "requires": { "auth": { "[development]": { "kind": "mocked", "users": { "content-manager": { // [!code ++:5] "policies": [ "cap.ContentManager" ] }, "stock-manager": { // [!code ++:5] "policies": [ "cap.StockManager" ] } } } } } } ```
:::tip Don't forget to refer to fully qualified policy names including the package name (`cap` in this example). ::: Now (re)start the application with
```sh mvn spring-boot:run ```
```sh cds watch ```
and verify in the UI for `AdminService` (`http://localhost:8080/index.html#Books-manage`) that the the assigned policies imply the expected static access rules:
You can now verify that the assigned policies enforce the expected access rules:
- mock user `content-manager` has full access to `Books` and `Authors`. - mock user `stock-manager` can _read_ `Books` and `Authors` and can _edit_ `Books` (but _not_ `Authors`). For the advanced test scenario, you can define custom policies in pre-defined package `local` that is ignored during [deployment of the policies](#ams-deployment) to the Cloud service and hence will not show up in production. Let's add a custom policy `StockManagerFiction` which is based on base policy `cap.StockManager` restricting the assigned users to the genres `Mystery` and `Fantasy`: ```dcl [/ams/local/customPolicies.dcl] POLICY StockManagerFiction { USE cap.StockManager RESTRICT Genre IN ('Mystery', 'Fantasy'); } ``` You can define valid attribute values in complex [DCL expressions](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/condition-operators).
Don't miss to add the policy files in sub folders of `ams` reflecting the namespace properly: Policy `local.StockManagerFiction` is expected to be in a file within directory `/ams/local/*`. The assignment to mock users is done in the `policies` property: ```yaml cds: security: mock: users: stock-manager-test: // [!code ++:3] policies: - local.StockManagerFiction ``` You can verify in the UI that mock user `stock-manager-test` is restricted to books of genres `Mystery` and `Fantasy`.
::: tip Don't miss to add the policy files in sub folders of `ams/dcl` reflecting the namespace properly: Policy `local.StockManagerFiction` is expected to be in a file within directory `./ams/dcl/local/`. ::: ```json [package.json] { "cds": { "requires": { "auth": { "[development]": { "kind": "mocked", "users": { "stock-manager-test": { // [!code ++:5] "policies": [ "local.StockManagerFiction" ] } } } } } } } ```
[Learn more about AMS attribute filters with CAP](https://sap.github.io/cloud-identity-developer-guide/CAP/InstanceBasedAuthorization.html#instance-based-authorization){.learn-more} ### Cloud Deployment > Source: /docs/guides/security/cap-users#cloud-deployment If not done yet, prepare your project Cloud deployment as [explained before](./authentication#ias-ready). Policies can be automatically deployed to the AMS server during deployment of the application by means of AMS deployer provided by module `@sap/ams`. Enhancing the project by `cds add ams` automatically adds task for example in the MTA for AMS policy deployment.
::: details AMS policy deployer task in the MTA ::: code-group ```yaml [mta.yaml- deployer task] - name: bookshop-ams-policies-deployer type: javascript.nodejs path: srv/src/gen/policies # Node.js: gen/policies parameters: buildpack: nodejs_buildpack no-route: true no-start: true tasks: - name: deploy-dcl command: npm start memory: 512M requires: - name: bookshop-ias [...] ``` ```json [srv/src/gen/policies/package.json - deployer module] { "name": "ams-dcl-content-deployer", "version": "3.0.0", "dependencies": { "@sap/ams": "^3" }, [...] "scripts": { "start": "npx --package=@sap/ams deploy-dcl" } } ``` ::: Note that the policy deployer task requires a path to a directory structure containing the `ams` root folder with the policies to be deployed. By default, the path points to `srv/src/gen/policies` that is prepared automatically during build step with the appropriate policy-content copied from `srv/src/main/resources/ams`. In addition, `@sap/ams` needs to be referenced to add the deployer logic.
::: details AMS policy deployer task in the MTA ::: code-group ```yaml [mta.yaml - deployer task] - name: bookshop-ams-policies-deployer type: javascript.nodejs path: gen/policies parameters: buildpack: nodejs_buildpack no-route: true no-start: true tasks: - name: deploy-dcl command: npm start memory: 512M requires: - name: bookshop-ias [...] ``` ```json [gen/policies/package.json - deployer module] { "name": "ams-dcl-content-deployer", "version": "3.0.0", "dependencies": { "@sap/ams": "^3" }, [...] "scripts": { "start": "npx --package=@sap/ams deploy-dcl" } } ``` ::: Note that the policy deployer task requires a path to a directory structure containing the `ams/dcl` root folder with the policies to be deployed. By default, the path points to `gen/policies` that is prepared automatically during build step with the appropriate policy-content copied from `ams/dcl`. In addition, `@sap/ams` needs to be referenced to add the deployer logic.
::: tip Several microservices sharing the same IAS instance need a common folder structure the deployer task operates on. It contains the common view of policies applied to all services. ::: [Learn more about AMS deployer](https://sap.github.io/cloud-identity-developer-guide/Authorization/DeployDCL.html#ams-policies-deployer-app){.learn-more} Let's deploy and start the application with ```sh cds up ``` Afterwards, you can now perform the following tasks in the Administrative Console for the IAS tenant (see prerequisites [here](./authentication#ias-admin)): - Assign (base or custom) policies to IAS users - Create custom policies To create a custom policy with filter restrictions, follow these steps: 1. Select **Applications & Resources** > **Applications**. Pick the IAS application of your project from the list. 2. In **Authorization Policies** select **Create** > **Create Restriction**. Choose an appropriate policy name, for example, `StockManagerFiction`. 3. Customize the filter conditions for the available AMS attributes. 4. Confirm with **Save**. ::: details Create custom AMS policy with filter condition ![AMS custom policies in Administrative Console](assets/ams-custom-policy.jpg) ![AMS custom policy filters in Administrative Console](assets/ams-custom-policy-filter.jpg) ::: [Learn more about how to create custom AMS policies](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/create-authorization-policy){.learn-more} To assign a policy to an IAS user, follow these steps: 1. Select **Applications & Resources** > **Applications**. Pick the IAS application of your project from the list. 2. Switch to tab **Authorization Policies** and select the policy you want to assign. 3. In **Assignments**, add the IAS user of the tenant to which the policy should be assigned (you can review the policy definition in **Rules**). [Learn more about how to edit custom AMS policies](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/edit-authorization-policy){.learn-more} ::: details Assign AMS policy to an IAS user ![AMS base policies in Administrative Console](assets/ams-base-policies.jpg) ![AMS policy assignment in Administrative Console](assets/ams-policy-assignment.jpg) ::: You can log on to the bookshop test application with the test user and check that only books of dedicated genres can be modified. [Learn more about AMS policy assignment](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/assign-authorization-policies) {.learn-more} ### Tracing > Source: /docs/guides/security/cap-users#tracing-1 You can verify a valid configuration of the AMS plugin by the following log output:
```sh c.s.c.s.a.c.AmsRuntimeConfiguration : Configured AmsUserInfoProvider ```
```sh [ams] - AMS Plugin loaded. [ams] - Added AMS middleware after 'auth' middleware. ```
In addition, for detailed analysis of issues, you can set AMS logger to `DEBUG` level:
```yaml logging: level: com.sap.cloud.security.ams: DEBUG ```
```json { "cds": { "log": { "levels": { "ams": "DEBUG" } } } } ```
which gives you more information about the policy evaluation at request time:
```sh c.s.c.s.a.l.PolicyEvaluationSlf4jLogger : Policy evaluation result: {..., "unknowns":"[$app.Genre]", "$dcl.policies":"[local.StockManagerFiction]", ... "accessResult":"or( eq($app.Genre, "Mystery") eq($app.Genre, "Fantasy") )"}. ```
```sh [ams] - Determined potential actions for resource '$SCOPES': stock-manager { potentialActions: Set(1) { 'stock-manager' }, policies: [ 'local.StockManagerFiction' ], ... } [ams] - AMS user roles added to user.is: [ 'stock-manager' ] [ams] - Privilege check for 'stock-manager' on '$SCOPES' was conditional. { result: 'conditional', dcn: "$app.genre IN ['Fantasy', 'Mystery']", policies: [ 'local.StockManagerFiction' ], ... } [ams] - Resulting privileges for READ on AdminService.Books : [ { grant: 'READ', to: [ 'stock-manager' ], where: "genre.name IN ('Fantasy', 'Mystery')" } ] ```
You can add general user information by applying [user tracing](#user-tracing). ::: tip It might be useful to investigate the injected filter conditions by activating the query-trace (logger `com.sap.cds.persistence.sql`). ::: ## Role Assignment with XSUAA > Source: /docs/guides/security/cap-users#role-assignment-with-xsuaa Information about roles and attributes can be made available to the XSUAA platform service. This information enables the respective JWT tokens to be constructed and sent with the requests for authenticated users. In particular, the following happens automatically behind-the-scenes upon build: ### Generate Security Descriptor > Source: /docs/guides/security/cap-users#generate-security-descriptor Derive scopes, attributes, and role templates from the CDS model:
```sh cds add xsuaa ```
```sh cds add xsuaa --for production ```
This generates an _xs-security.json_ file: ::: code-group ```json [xs-security.json] { "scopes": [ { "name": "$XSAPPNAME.admin", "description": "admin" } ], "attributes": [ { "name": "level", "description": "level", "valueType": "s" } ], "role-templates": [ { "name": "admin", "scope-references": [ "$XSAPPNAME.admin" ], "description": "generated" } ] } ``` ::: For every role name in the CDS model, one scope and one role template are generated with the exact name of the CDS role. ::: tip Re-generate on model changes You can have such a file re-generated via ```sh cds compile srv --to xsuaa > xs-security.json ``` ::: See [Application Security Descriptor Configuration Syntax](https://help.sap.com/docs/HANA_CLOUD_DATABASE/b9902c314aef4afb8f7a29bf8c5b37b3/6d3ed64092f748cbac691abc5fe52985.html) in the SAP HANA Platform documentation for the syntax of the _xs-security.json_ and advanced configuration options. ::: warning Avoid invalid characters in your models Roles modeled in CDS may contain characters considered invalid by the XSUAA service. ::: If you modify the _xs-security.json_ manually, make sure that the scope names in the file exactly match the role names in the CDS model, as these scope names will be checked at runtime. ### Publish Security Descriptor > Source: /docs/guides/security/cap-users#publish-security-descriptor If there's no _mta.yaml_ present, run this command: ```sh cds add mta ``` ::: details See what this does in the background… 1. It creates an _mta.yaml_ file with an `xsuaa` service. 2. The created service added to the `requires` section of your backend, and possibly other services requiring authentication. ::: code-group ```yaml [mta.yaml] modules: - name: bookshop-srv requires: - bookshop-auth // [!code ++] resources: name: bookshop-auth // [!code ++] type: org.cloudfoundry.managed-service // [!code ++] parameters: // [!code ++] service: xsuaa // [!code ++] service-plan: application // [!code ++] path: ./xs-security.json # include cds managed scopes and role templates // [!code ++] config: // [!code ++] xsappname: bookshop-${org}-${space} // [!code ++] tenant-mode: dedicated # 'shared' for multitenant deployments // [!code ++] ``` ::: Inline configuration in the _mta.yaml_ `config` block and the _xs-security.json_ file are merged. If there are conflicts, the [MTA security configuration](https://help.sap.com/docs/HANA_CLOUD_DATABASE/b9902c314aef4afb8f7a29bf8c5b37b3/6d3ed64092f748cbac691abc5fe52985.html) has priority. [Learn more about **building and deploying MTA applications**.](/guides/deploy/){ .learn-more} ### Assign Roles in SAP BTP Cockpit > Source: /docs/guides/security/cap-users#assign-roles-in-sap-btp-cockpit This is a manual step a user administrator would do in SAP BTP Cockpit to setup and assign roles for the application: By creating a service instance of the `xsuaa` service, all the roles from the _xs-security.json_ file are already added to your subaccount. Next, you create a role collection that assigns these roles to your users. 1. Open the SAP BTP Cockpit. > For your trial account, this is: [https://cockpit.hanatrial.ondemand.com](https://cockpit.hanatrial.ondemand.com) 2. Navigate to your subaccount and then choose *Security* > *Role Collections*. 3. Choose *Create New Role Collection*: ![Create role collections in SAP BTP cockpit](./assets/create-role-collection.png) 4. Enter a *Name* for the role collection, for example `BookshopAdmin`, and choose *Create*. 5. Choose your new role collection to open it and switch to *Edit* mode. 6. Add the `admin` role for your bookshop application (application id `bookshop!a`) to the *Roles* list. 7. Add the email addresses for your users to the *Users* list. 8. Choose *Save* If a user attribute isn't set for a user in the identity provider of the SAP BTP Cockpit, this means that the user has no restriction for this attribute. For example, if a user has no value set for an attribute "Country", they're allowed to see data records for all countries. In the _xs-security.json_, the `attribute` entity has a property `valueRequired` where you as the developer can specify whether unrestricted access is possible by not assigning a value to the attribute. ## Developing with CAP Users > Source: /docs/guides/security/cap-users#developing-with-cap-users CAP is not tied to any specific authentication method, nor to concrete user information such as that provided by IAS or XSUAA. Instead, an abstract [user representation](cap-users#claims) is attached to the request which can be used to influence request processing. For example, both authorization enforcement and domain logic can depend on properties of the the current user. ::: warning Avoid writing custom code against the raw authentication information such as dedicated XSUAA properties. This undermines the decoupling between authentication strategy and your business logic. ::: ::: tip In most cases, there is no need to write custom code dependent on the CAP user - **leverage CDS modelling whenever possible**. ::: ### Reflection > Source: /docs/guides/security/cap-users#reflection
The CAP user of a request is represented by a [UserInfo](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/request/UserInfo.html) object that can be retrieved from the [RequestContext](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/request/RequestContext.html) of a handler in different ways. Either by directly requesting from the context like in the example: ```java @Before(entity = Books_.CDS_NAME) public void beforeReadBooks(CdsReadEventContext context) { UserInfo userInfo = context.getUserInfo(); String name = userInfo.getName(); // [...] } ``` or by Spring dependency injection within a handler bean: ```java @Autowired UserInfo userInfo; @After(event = CqnService.EVENT_READ) public void discountBooks(Stream books) { String name = userInfo.getName(); // [...] } ``` There is always an `UserInfo` attached to the current `RequestContext`, reflecting any type of [users](#user-types). The `UserInfo` object is not modifyable, but during request processing, a new `RequestContext` can be spawned and may be accompanied by a [switch of the current user](#switching-users). Depending on the configured [authentication](./authentication) strategy, CAP derives a *default set* of user claims containing the user's name, tenant, attributes and assigned roles: | User Property | UserInfo Getter | XSUAA JWT Property | IAS JWT Property | `@restrict`-annotation | |---------------|-----------------------------------------|-----------------------------|---------------------------|------------------------| | _Logon name_ | `getName()` | `user_name` | `sub` | `$user` | | _Tenant_ | `getTenant()` | `zid` | `app_tid` | `$tenant` | | _Attributes_ | `getAttributeValues(String attr)` | `xs.user.attributes.` | _All non-meta attributes_ | `$user.` | | _Roles_ | `getRoles()` and `hasRole(String role)` | `scopes` | _n/a - injected via AMS_ | _String in `to`-clause_ | ::: info CAP does not make any assumptions on the presented claims given in the token. String values are copied as they are. ::: In addition, there are getters to retrieve information about [pseudo-roles](#pseudo-roles): | UserInfo method | Description | CAP Role | |:--------------------|:-------------------------------------------------------------------------------------------------------------------|----------------------| | `isAuthenticated()` | _True if the current user has been authenticated._ | `authenticated-user` | | `isSystemUser()` | _Indicates whether the current user has pseudo-role `system-user`._ | `system-user` | | `isInternalUser()` | _Indicates whether the current user has pseudo-role `internal-user`._ | `internal-user` | | `isPrivileged()` | _Returns `true` if the current user runs in [privileged mode](#switching-to-privileged-user), that is is unrestricted._ | - |
In CAP Node.js, the CAP user of a request is represented by a [`cds.User`](../../node.js/authentication#cds-user) object. An instance of `cds.User` representing the current principal is available from the current request context in `req.user`. Similarly, the identifier of the user's tenant is available from `req.tenant`. ```js srv.before('READ', srv.entities.Books, req => { const { user, tenant } = req // [...] }) ``` In addition to the request context, information about the current user can similarly be retrieved from the global [`cds.context`](../../node.js/events#cds-context), which provides access to the current [`cds.EventContext`](../../node.js/events#cds-event-context): ```js const cds = require('@sap/cds') const { user, tenant } = cds.context ``` :::tip Prefer local req objects in your handlers for accessing event context properties, as each access to `cds.context` happens through [AsyncLocalStorage.getStore()](https://nodejs.org/api/async_context.html#asynclocalstoragegetstore), which induces some minor overhead. ::: Setting `cds.context` usually happens in inbound authentication middlewares or in inbound protocol adapters. During processing, you can set it programmatically or spawn a new root transaction providing a context argument to achieve a [switch of the current user](#switching-users). Depending on the configured [authentication](./authentication) strategy, CAP derives a default set of user claims containing the user's name, tenant, attributes and assigned roles: | User Property | UserInfo Getter | XSUAA JWT Property | IAS JWT Property | `@restrict`-annotation | |---------------|-------------------------------------|-----------------------------|-------------------------|------------------------| | _Logon name_ | `user.id` | `user_name` | `sub` | `$user` | | _Tenant_ | `req.tenant` / `cds.context.tenant` | `zid` | `app_tid` | `$tenant` | | _Attributes_ | `attr(attr)` | `xs.user.attributes.` | All non-meta attributes | `$user.` | | _Roles_ | `is(role)` | `scopes` | n/a - injected via AMS | String in `to`-clause |
### Customizing Users > Source: /docs/guides/security/cap-users#customizing-users
In most cases, CAP's default mapping to the CAP user matches your requirements, but CAP also allows you to customize the mapping according to specific needs. For instance, the logon name as injected by standard XSUAA integration might not be unique if several customer identity providers are connected to the underlying identity service. Here a combination of `user_name` and `origin` mapped to `$user` might be a feasible solution that you can implement in a custom adaptation. This is done by means of a custom [UserInfoProvider](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/runtime/UserInfoProvider.html) interface that can be implemented as Spring bean as demonstrated in [Registering Global Parameter Providers](../../java/event-handlers/request-contexts#global-providers): ::: details Sample implementation to override the user name ```java @Component @Order(1) public class UniqeNameUserInfoProvider implements UserInfoProvider { private UserInfoProvider defaultProvider; @Override public UserInfo get() { ModifiableUserInfo userInfo = UserInfo.create(); if (defaultProvider != null) { UserInfo prevUserInfo = defaultProvider.get(); if (prevUserInfo != null) { userInfo = prevUserInfo.copy(); } } if (userInfo != null) { XsuaaUserInfo xsuaaUserInfo = userInfo.as(XsuaaUserInfo.class); userInfo.setName(xsuaaUserInfo.getEmail() + "/" + xsuaaUserInfo.getOrigin()); // adapt name } return userInfo; } @Override public void setPrevious(UserInfoProvider prev) { this.defaultProvider = prev; } } ``` ::: In the example, the `UniqeNameUserInfoProvider` defines an overlay on the default XSUAA-based provider (`defaultProvider`) by leveraging chaining technique (`@Order(1)` ensures proper ordering). `UserInfo.copy()` returns [`ModifiableUserInfo`](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/request/ModifiableUserInfo.html) interface which allows arbitrary modifications such as overriding the user's name by a combination of email and origin. ::: warning Be very careful when redefining `$user` The user name is frequently stored with business data (for example, `managed` aspect) and might introduce migration efforts. Also consider data protection and privacy regulations when storing user data. ::: There are multiple reasonable use cases in which user modification is a suitable approach: - Injecting or mixing user roles by calling `modifiableUserInfo.addRole(String role)` (In fact this is the base for [AMS plugin](#roles-assignment-ams) injecting user-specific roles). - Providing calculated attributes used for [instance-based authorization](./authorization#user-attrs) by invoking `modifiableUserInfo.setAttributeValues(String attribute, List values)`. - Constructing a request user based on forwarded (and trusted) header information, completely replacing default authentication. [See more examples for custom UserInfoProvider](https://pages.github.tools.sap/cap/docs/java/event-handlers/request-contexts#global-providers){.learn-more}
In most cases, CAP's default mapping to the CAP user will match your requirements, but CAP also allows you to customize the mapping according to specific needs. For instance, the logon name as injected by standard XSUAA integration might not be unique if several customer IdPs are connected to the underlying identity service. Here a combination of `user_name` and `origin` mapped to `$user` might be a feasible solution that you can implement in a custom adaptation. This can be done by modifying `cds.middlewares`. To modify the `cds.context.user` while still relying on existing generic middlewares, a new middleware must be registered after the `auth` middleware. If you intend to manipulate the `cds.context.tenant` as well, the new middleware must run before `cds.context.model` is set for the current request. ::: details Sample implementation to override the user id ```js cds.middlewares.before = [ cds.middlewares.context(), cds.middlewares.trace(), cds.middlewares.auth(), function ctx_user (_,__,next) { const ctx = cds.context ctx.user.id = ctx.user.attr('origin') + ctx.user.id next() }, cds.middlewares.ctx_model() ] ``` ::: There are multiple reasonable use cases in which user modification is a suitable approach: - Overriding user roles by calling `user.roles(roles)`. - Overriding user attributes and providing calculated attributes used for [instance-based authorization](./authorization#user-attrs) by invoking `user.attr(attributes)`. ::: warning Be very careful when redefining `$user` and customizing `cds.middlewares` The user name is frequently stored with business data (for example, `managed` aspect) and might introduce migration efforts. Also consider data protection and privacy regulations when storing user data. ::: :::tip Custom Authentication Middleware In case you require even more control, it is possible to replace the authentication middleware with a fully [Custom Authentication](../../node.js/authentication#custom). :::
### Switching Users > Source: /docs/guides/security/cap-users#switching-users
There are a few typical use cases in a (multitenant) application where switching the current user of the request is required. For instance, the business request on behalf of a named subscriber user needs to reach out to a platform service on behalf of the underlying technical user of the subscriber. These scenarios are identified by a combination of the user (*technical* or *named*) and the tenant (*provider* or *subscriber*): ![A named user can switch to a technical user in the same/subscriber tenant using the systemUser() method. Also, a named user can switch to a technical user in the provider tenant using the systemUserProvider() method. In addition technical users provider/subscriber tenants can switch to technical users on provider/subscriber tenants using the methods systemUserProvider() or systemUser(tenant).](./assets/requestcontext.drawio.svg) The user context can only be modified by explicitly opening an appropriate Request Context which ensures a well-defined scope for the changed settings. Services might, for example, trigger HTTP requests to external services by deriving the target tenant from the current Request Context. The `RequestContextRunner` API offers convenience methods that allow an easy transition from the current Request Context to a derived one according to the concrete scenario. | Method | Scenario | |----------------------|--------------------------------------------------------------------------------------------------------------------------------------| | `systemUser()` | _[Switches](#switching-to-technical-user) to the **technical user** and preserves the tenant from the current user._ | | `systemUserProvider()` | _[Switches](#switching-to-provider-tenant) to the **technical user of the provider account**._ | | `systemUser(tenant)` | _[Switches](#switching-to-subscriber-tenant) to a **technical user targeting a given subscriber account**._ | | `privilegedUser()` | _[Elevates](#switching-to-privileged-user) the current `UserInfo` to by-pass all authorization checks._ | | `anonymousUser()` | _[Switches](#switching-to-anonymous-user) to an anonymous user._ | Named user contexts are only created by the CAP Java framework as initial Request Context based on appropriate authentication information (for example, JWT token) attached to the incoming HTTP request. :::info - It is not possible to switch from technical user to a named user. - Asynchronous requests to CAP services are always on behalf of a technical user. :::
There are a few typical use cases in a (multitenant) application, where switching the current user of the request is required. For instance, the business request on behalf of a named subscriber user needs to reach out to a service on behalf of the subscribers technical user. These scenarios are identified by a combination of the user (*technical* or *named*) and the tenant (*provider* or *subscriber*): ![Typical Scenarios for a User Context Switch](./assets/requestcontext-node.drawio.svg) In CAP Node.js, the `cds.context` allows to access the current `cds.EventContext` and enables updating the principal of the context. The prefered method for switching users and executing code in a different context and for a different principal, is to spawn a new root transaction using [`cds/srv.tx()`](../../node.js/cds-tx#srv-tx). Providing a `ctx` argument when creating a new root transaction allows switching the user for nested operations. The `cds.User` class exposes convenience constructors and accessors for specialized `cds.User` instances that represent typical technical principals you may require. ```js const newUser = new cds.User({ id: '...', roles: [...], ...}) await srv.tx ({ user: newUser, tenant: '' }, async tx => { // Perform operations with a privileged principal }) ``` :::tip When creating new root transactions in calls to [`cds/srv.tx()`](../../node.js/cds-tx#srv-tx), all properties not specified in the `ctx` argument are inherited from `cds.context`, if set in the current continuation. :::
#### Switching to Technical User > Source: /docs/guides/security/cap-users#switching-to-technical-user
![The graphic is explained in the accompanying text.](./assets/nameduser.drawio.svg){} The incoming JWT token triggers the creation of an initial Request Context with a named user. Accesses to the database in the OData Adapter as well as the custom `On` handler are executed within tenant1 and authorization checks are performed for user JohnDoe. An additionally defined `After` handler wants to call out to an external service using a technical user without propagating the named user JohnDoe. To achieve this, it's required to call `requestContext()` on the current `CdsRuntime` and use the `systemUser()` method to remove the named user from the new Request Context: ```java @After(entity = Books_.CDS_NAME) public void afterHandler(EventContext context){ runtime.requestContext().systemUser().run(reqContext -> { // call technical service }); } ```
![The graphic is explained in the accompanying text.](./assets/nameduser-node.drawio.svg){} The incoming JWT token triggers the creation of an initial `cds.EventContext` with a named user. Accesses to the database in the OData Adapter as well as the custom `.on` handler are executed within _tenant1_ and authorization checks are performed for user _JohnDoe_. In addition, there is an `.after` handler that wants to call out to an external service using a technical user without propagating the named user _JohnDoe_. To achieve this, you can create a new root transaction using `srv.tx` and use it to connect to the external service from within a new context: ```js srv.after('*', srv.entities.Books, async (res, req) => { await srv.tx({ user: cds.User.privileged }, async tx => { // call technical service }) }) ```
#### Switching to Technical Provider Tenant > Source: /docs/guides/security/cap-users#switching-to-technical-provider-tenant
![The graphic is explained in the accompanying text.](./assets/switchprovidertenant.drawio.svg){} The application offers a bound action in a CDS entity. Within the action, the application communicates with a remote CAP service using an internal technical user from the provider account. The corresponding `on` handler of the action needs to create a new Request Context by calling `requestContext()`. Using the `systemUserProvider()` method, the existing user information is removed and the tenant is automatically set to the provider tenant. This allows the application to perform an HTTP call to the remote CAP service, which is secured using the pseudo-role `internal-user`. ```java @On(entity = Books_.CDS_NAME) public void onAction(AddToOrderContext context){ runtime.requestContext().systemUserProvider().run(reqContext -> { // call remote CAP service }); } ```
![The graphic is explained in the accompanying text.](./assets/switchprovidertenant-node.drawio.svg){} In this scenario the application offers a bound action in a CDS entity. Within the action, the application communicates with a remote CAP service using a privileged user and the provider tenant. The corresponding `.on` handler of the action needs to create a new root transaction by calling `srv.tx`. The user passed to `srv.tx` in the `ctx` attribute will be used as the principal for requests made within the new root transaction. ```js srv.on('action', srv.entities.Books, async req => { const systemUser = new cds.User({ id: 'system', roles: [ 'internal-user' ] }) await srv.tx({ user: systemUser , tenant: 'provider-tenant' }, async tx => { // call remote CAP service }) }) ```
#### Switching to a Specific Technical Tenant > Source: /docs/guides/security/cap-users#switching-to-a-specific-technical-tenant
![The graphic is explained in the accompanying text.](./assets/switchtenant.drawio.svg){} The application is using a job scheduler that needs to regularly perform tasks on behalf of a certain tenant. By default, background executions (for example in a dedicated thread pool) aren't associated to any subscriber tenant and user. In this case, it's necessary to explicitly define a new Request Context based on the subscribed tenant by calling `systemUser(tenantId)`. This ensures that the Persistence Service performs the query for the specified tenant. ```java runtime.requestContext().systemUser(tenant).run(reqContext -> { return persistenceService.run(Select.from(Books_.class)) .listOf(Books.class); }); ``` ::: warning Resource Bottlenecks in Tenant Looping Avoid iterating through all subscriber tenants to perform tenant-specific tasks. Instead, prefer a task-based approach which processes specific subscriber tenants selectively. :::
![The graphic is explained in the accompanying text.](./assets/switchtenant-node.drawio.svg){} The application is using [`cds.spawn`](../../node.js/cds-tx#cds-spawn) to regularly perform tasks on behalf of a certain tenant. By default, operations that are nested within `cds.spawn` will inherit the outer context. You can explicitly define the context `cds.spawn` should use by passing relevant information in a `ctx` argument. This enables to ensure that the Persistence Service performs the query for the specified tenant. ```js cds.spawn({ user: cds.User.privileged, tenant: 'tenant1', every: '1h' }, async tx => { await persistenceService.run(SELECT.from(Books)) }) ``` ::: warning Resource Bottlenecks in Tenant Looping Avoid iterating through all subscriber tenants to perform tenant-specific tasks. Instead, prefer a task-based approach which processes specific subscriber tenants selectively. :::
#### Switching to Privileged User > Source: /docs/guides/security/cap-users#switching-to-privileged-user Application services invoked within custom handlers enforce an authorization on second-layer, which is the preferred behaviour to ensure security by default. However, in certain situations, you might want to bypass additional authorization checks if the initial request authorization is deemed sufficient. Such service calls can be executed on behalf of a privileged user, acting as a superuser without restrictions: ```java cdsRuntime.requestContext().privilegedUser().run(privilegedContext -> { assert privilegedContext.getUserInfo().isPrivileged(); // service calls in this scope pass generic authorization handler }); ``` ::: warning Call application services on behalf of the privileged user only in case the service call is fully independent from the business user's actual restrictions. :::
#### Switching to Anonymous User > Source: /docs/guides/security/cap-users#switching-to-anonymous-user
In rare situations you might want to call a public service without sharing information of the current request user. In this case, you explicitly prevent user propagation. Such service calls can be executed on behalf of the anonymous user, acting as a public user without personal user claims: ```java cdsRuntime.requestContext().anonymousUser().run(privilegedContext -> { // ... Service calls in this scope pass generic authorization handler }); ```
In rare situations you might want to call a public service without sharing information about the current request user. In this case, you can explicitly prevent user propagation by running in a context whose principal is the `anonymous` user. ```js cds.tx({ user: cds.User.anonymous }, async tx => { // Perform operations anonymously }) ```
### User Propagation > Source: /docs/guides/security/cap-users#user-propagation
#### Between Threads > Source: /docs/guides/security/cap-users#between-threads Within the same Request Context, all CAP service calls share the same user information. By default, the Request Context of the current thread is not shared with spawned threads and hence user information is lost. If you want to avoid this, you can propagate the Request Context to spawned threads as described [here](https://pages.github.tools.sap/cap/docs/java/event-handlers/request-contexts#threading-requestcontext) and hence the same user context is applied.
#### Non-CAP Libraries > Source: /docs/guides/security/cap-users#non-cap-libraries
CAP plugins for IAS and XSUAA store the resolved user information in Spring's [`SecurityContext`](https://docs.spring.io/spring-security/reference/api/java/org/springframework/security/core/context/SecurityContext.html) which contains all relevant authentication information. Hence, library code can rely on standards to fetch the authentication information and restore the user information if needed. In addition, the [authentication information](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/authentication/AuthenticationInfo.html) is stored in the Request Context and can be fetched as sketched here: ```java AuthenticationInfo authInfo = context.getAuthenticationInfo(); JwtTokenAuthenticationInfo jwtTokenInfo = authInfo.as(JwtTokenAuthenticationInfo.class); String jwtToken = jwtTokenInfo.getToken(); ```
CAPs generic authentication middlewares for IAS and XSUAA maintain resolved authentication information in the `authInfo` attribute of `cds.context.user`. For `@sap/xssec`-based authentication strategies (`ias`, `jwt`, and `xsuaa`), `cds.context.user.authInfo` is an instance of `@sap/xssec`'s [`SecurityContext`](https://www.npmjs.com/package/@sap/xssec#securitycontext). You can retrieve available authentication information for use in a non-CAP library from the `SecurityContext`. ```js const authInfo = cds.context.user.authInfo // @sap/xssec SecurityContext const token = authInfo.token // @sap/xssec Token const jwtToken = token.jwt // string ``` ::: warning The `cds.User.authInfo` property depends on the authentication library that you use. CAP does not guarantee the content of this property. Use it with caution. Always pin your dependencies as described in the [best practices](../../node.js/best-practices#deploy). :::
#### Remote Services > Source: /docs/guides/security/cap-users#remote-services
Remote APIs can be invoked either on behalf of a named user or a technical user, depending on the callee's specification. Thus, a client executing a business request within a specific user context might need to explicitly adjust the user propagation strategy. CAP's [Remote Services](../services/consuming-services) offer an easy and declarative way to define client-side representations of remote service APIs. Such services integrate seamlessly with CAP, managing connection setup, including [authentication and user propagation](../../java/cqn-services/remote-services#configuring-the-authentication-strategy): ```yaml cds: remote.services: SomeReuseService: binding: name: reuse-service-instance onBehalfOf: systemUserProvider ``` The parameter `onBehalfOf` in the binding configuration section allows to define the following *user propagation* strategies: - `currentUser` (default): Propagate the user of the current Request Context. - `systemUser`: Propagate the (tenant-specific) technical user, based on the tenant set in the current Request Context. - `systemUserProvider`: Propagate the technical user of the provider tenant. ::: tip Remote Services configurations with `destination` section support `onBehalfOf` only in case of [IAS App-2-App flows](../../java/cqn-services/remote-services#consuming-apis-from-other-ias-applications). ::: [Learn more about Remote Services in CAP Java](../../java/cqn-services/remote-services#remote-services){.learn-more}
CAP's [Remote Services](../services/consuming-services) offer an easy and declarative way to define client-side representations of remote service APIs. Such services integrate seamlessly with CAP, managing connection setup, including authentication and user propagation. Under the hood CAP utilizes the [BTP Destinations](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/create-destinations-from-scratch) and [`@sap-cloud-sdk/connectivity`](https://www.npmjs.com/package/@sap-cloud-sdk/connectivity) to do most of the heavy lifting. ```json { "cds": { "requires": { "SomeReuseService": { "kind": "odata", "model": "srv/external/SomeReuseService", "credentials": { "destination": "some-reuse-service", "path": "/reuse/odata/api", } } } } } ``` ::: tip Always prefer using [Remote Services](#remote-services) over natively consuming [Cloud SDK](https://sap.github.io/cloud-sdk/). :::
#### Cloud SDK > Source: /docs/guides/security/cap-users#cloud-sdk On a programmatic level, the CAP runtime integrates with [Cloud SDK](https://sap.github.io/cloud-sdk/) offering an abstraction for connection setup with remote services, including authentication and user propagation. By default, - the *tenant* of the current Request Context is propagated under the hood. - the *user token* is propagated via Spring's [`SecurityContext`](#user-token). - *user propagation strategy* can be specified with parameter values [`OnBehalfOf`](https://sap.github.io/cloud-sdk/docs/java/features/connectivity/service-bindings#multitenancy-and-principal-propagation). ::: tip Prefer using [Remote Services](#remote-services) built on Cloud SDK rather than natively consuming the Cloud SDK. ::: [Learn more about Cloud SDK integration in CAP Java](../../java/cqn-services/remote-services#cloud-sdk-integration){.learn-more}
## Pitfalls > Source: /docs/guides/security/cap-users#pitfalls - **Don't write custom code against user types of an identity service (XSUAA / IAS)**. Instead, if it is required at all to code against user types, use CAP's user abstraction layer (`UserInfo` in Java or `req.user` in Node.js) to handle user-related logic. - **Don't try to propagate named user context in asynchronous requests**. This can happen when using the Outbox pattern or Messaging. Asynchronous tasks are typically executed outside the scope of the original request context, after successful authorization. Propagating the named user context can lead to inconsistencies or security issues. Instead, use technical users for such scenarios. - **Don't mix CAP Roles for business and technical users**. CAP roles should be clearly separated based on their purpose: Business user roles are designed to reflect how end users interact with the application. Technical user roles are intended for system-level operations, such as background tasks or service-to-service communication. Mixing these roles can lead to confusion and unintended access control issues. - **Don't mix AMS Policy level with CAP Role level**. AMS policies operate at the business level, while CAP roles are defined at the technical domain level. Avoid mixing these two layers, as this could undermine the clarity and maintainability of your authorization model. - **Don't choose entity attributes as AMS Attributes whose relevance is too small**. Such attributes should have a broad, domain-wide relevance and be applicable across multiple entities. Typically, only a limited number of attributes (less than 10) meet this criterion. Exposing entity-specific attributes as AMS attributes can lead to unnecessary complexity and reduced reusability. # Outbound Authentication > Source: /docs/guides/security/remote-authentication ## Remote Service Abstraction > Source: /docs/guides/security/remote-authentication#remote-service-abstraction According to the key concept of [pluggable building blocks](./overview#key-concept-pluggable), CAP's [Remote Services](../services/consuming-services#consuming-services) architecture decouples the protocol level (exchanged content) from the connection level (established connection channel). While the business context of the application impacts the protocol, the connectivity of the service endpoints is independent of it and mainly depends on platform-level capabilities. The latter is frequently subject to change and therefore should not introduce application dependencies. ![Remote Service stack architecture](./assets/remote-service-stack.drawio.svg){ } At the connectivity layer, the following basic tasks can be addressed generically: - Authentication (_how to set up a trusted channel_) - Destination (_how to find the target service_) - User propagation (_how to transport user information_) CAP's connectivity component handles authentication (IAS, XSUAA, X.509, ZTID, ...), destination (local destination, BTP Destination, BTP Service Binding), and user propagation (technical provider, technical subscriber, named user) transparently through configuration (although the configuration approach may differ between Java and Node.js). All three service scenarios can be addressed through configuration variants of the same remote service concept, as shown in the following sections. CAP supports out-of-the-box consumption of various types of [remote services](#remote-services): * [Co-located services](#co-located-services) as part of the same deployment and bound to the same identity instance (that is, belong to the same trusted [application zone](./overview#application-zone)). * [External services](#ias-app-2-app) that can be running on non-BTP platforms. ## Co-located Services > Source: /docs/guides/security/remote-authentication#co-located-services Co-located services do not run in the same microservice, but are typically part of the same deployment unit and hence reside within the same trust boundary of the [application zone](./overview#application-zone). Logically, such co-located services contribute to the application equally and could run as integrated services in the same microservice, but for technical reasons (for example, different runtime or scaling requirements) they are separated physically, often as a result of a [late-cut microservice approach](../deploy/microservices#late-cut-microservices). Technically, **they share the same identity instance, which allows direct token forwarding**: ![Co-located services](./assets/co-located-services.drawio.svg){ } [Learn more about how to configure co-located services in CAP Java](/java/cqn-services/remote-services#binding-to-a-service-with-shared-identity){.learn-more} [Learn more about how to configure remote services in CAP Node.js](/node.js/remote-services){.learn-more} You can test CAP's built-in support for co-located services in practice by modifying the sample applications: - **Java**: [`xflights-java`](https://github.com/capire/xflights-java/tree/main) and [`xtravels-java`](https://github.com/capire/xtravels-java/tree/main) - **Node.js**: [`xflights`](https://github.com/capire/xflights/tree/main) and [`xtravels`](https://github.com/capire/xtravels/tree/main) `xflights` acts as a master data provider exposing basic flight data in service `FlightsService` via different protocols. On the client side, `xtravels` imports this service as a CAP remote service and fetches data for federation. ::: tip CAP offers A simplified co-located service setup by leveraging remote services that require: - Shared identity instance - URL for the destination - Principal propagation mode (optional) ::: To combine both applications in a co-located setup, follow these steps: #### 1. Prepare the Cloud Foundry Environment > Source: /docs/guides/security/remote-authentication#1-prepare-the-cloud-foundry-environment Make sure that you've prepared a [local environment for CF deployments](../deploy/to-cf#prerequisites) and in addition: - A Cloud Foundry (CF) space in a subaccount. - [HANA Cloud instance](https://help.sap.com/docs/hana-cloud/sap-hana-cloud-administration-guide/create-sap-hana-database-instance-using-sap-hana-cloud-central) mapped to the CF space. - [IAS tenant](./authentication#ias-ready) mapped to the subaccount. #### 2. Prepare and Deploy the Consumer Application > Source: /docs/guides/security/remote-authentication#2-prepare-and-deploy-the-consumer-application As client, `xtravels` first needs a valid configuration for the remote service `FlightsService`: ::: code-group ```yaml [Java: application.yaml] --- spring: config.activate.on-profile: cloud cds: remote.services: xflights: type: hcql model: FlightsService http: suffix: /hcql binding: name: xtravels-ias onBehalfOf: systemUser options: url: https:// ``` ```json [Node.js: package.json] { "cds": { "requires": { "FlightsService": { "kind": "hcql", "[production]": { "credentials": { "url": "https:///hcql/data", "forwardAuthToken": true } } } } } } ``` ::: ::: details Java configuration explained The `type` property activates the protocol for exchanging business data and must be offered by the provider [CDS service](https://github.com/capire/xflights-java/blob/6fc7c665c63bb6d73e28c11b391b1ba965b8772c/srv/data-service.cds#L24). The `model` property needs to match the fully qualified name of the CDS service from the imported model. You can find CDS service definition of `FlightsService` in file `target/cds/capire/xflight-data/service.cds` resolved during CDS build step. The `binding.name` needs to point to the shared identity instance and `options.url` together with `http.suffix` provides the required location of the remote service endpoint. Finally, `onBehalfOf: systemUser` specifies that the remote call is invoked on behalf of a technical user in context of the tenant. ::: tip On behalf of `systemUser` (Java) works both in pure single tenant and in pure multitenant scenarios. If you are consuming a single tenant service from within a multitenant application choose on behalf of `systemUserProvider`. ::: ::: details Node.js configuration explained The configuration follows the standard pattern for [required services](../integration/reuse-and-compose#configuring-required-services) with [service bindings](../integration/reuse-and-compose#binding-required-services). For co-located services sharing the same identity instance, `forwardAuthToken: true` forwards the incoming JWT directly to the provider - no token exchange needed since the token is already valid. Unlike Java's `onBehalfOf` option, no additional configuration is required as the original user context is preserved in the forwarded token. ::: Now you are ready to deploy the application with ::: code-group ```sh [Java] cd ./xtravels-java cds up ``` ```sh [Node.js] cd ./xtravels cds up ``` ::: ❗Note that CF application `xtravels-srv` will not start successfully as long as `xflights` is not deployed yet (step 3). ::: tip For production deployment, we recommend combining both services with the shared identity instance in a [single MTA descriptor](../deploy/microservices#all-in-one-deployment). ::: #### 3. Prepare and Deploy the Provider Application > Source: /docs/guides/security/remote-authentication#3-prepare-and-deploy-the-provider-application As server, `xflights` needs to restrict service `FlightsService` to the technical client calling from the same application. This can be done by adding pseudo-role [`internal-user`](./cap-users#pseudo-roles) to the service: ::: code-group ```cds [xflights/srv/authorization.cds] using { sap.capire.flights.FlightsService as data } from './data-service'; annotate data with @(requires: 'internal-user'); ``` ::: ::: tip For different [user propagation](./cap-users#remote-services) modes the remote service can be configured appropriately. The provider service authorization needs to align with the configured user propagation. ::: Additionally, to establish the co-located setup, the microservice needs to share the same identity instance. This is configured in the MTA deployment descriptor: > The `mta.yaml`has been generated by `cds up`. ::: code-group ```yaml [mta.yaml] resources: - name: xflights-ias type: org.cloudfoundry.managed-service # [!code --] type: org.cloudfoundry.existing-service # [!code ++] parameters: service: identity # [!code --] service-name: xflights-ias # [!code --] service-name: xtravels-ias # [!code ++] service-plan: application # [!code --] config: # [!code --] display-name: xflights # [!code --] ``` ::: Finally, deploy and start the application with ::: code-group ```sh [Java] cd ./xflights-java cds up ``` ```sh [Node.js] cd ./xflights cds up ``` ::: #### 4. Verify the Deployment > Source: /docs/guides/security/remote-authentication#4-verify-the-deployment First, you can check the overall deployment status at the CF CLI level. Specifically, the application services must be started successfully and the shared identity instance must be verified. ::: details Verify: `cf apps` should show the following lines: ::: code-group ```sh name requested state processes routes xflights-db-deployer stopped web:0/1 xflights-srv started web:1/1 ... xtravels started web:1/1 ... xtravels-ams-policies-deployer stopped web:0/1 xtravels-db-deployer stopped web:0/1 xtravels-srv started web:1/1 ... ``` ::: ::: details Verify: `cf services` should show the following lines: ::: code-group ```sh xflights-ias identity application xtravels-ias identity application xtravels, xtravels-srv, xflights-srv, ... ``` ::: You can test the valid setup of the `xtravels` application by accessing the UI and logging in with an authorized test user of the IAS tenant. To do so, assign a proper AMS policy (for example, `admin`) to the test user as described in [CAP-level Users and Roles](./cap-users#ams-deployment). ::: tip The very same setup could be deployed for XSUAA-based services. ::: ## External Services > Source: /docs/guides/security/remote-authentication#external-services In contrast to [co-located services](#co-located-services), external services do not have strong dependencies as they have a fully decoupled lifecycle and are provided by different owners. As a consequence, external services can run cross-regionally; even non-BTP systems might be involved. A prerequisite for external service calls is a trust federation between the consumer and the provider system. A seamless integration experience for external service communication is provided by [IAS App-2-App](#ias-app-2-app) flows, which are offered by CAP via remote services. [BTP Destinations](../services/consuming-services#using-destinations) offer [various authentication strategies](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/http-destinations) such as SAML 2.0 as required by many S/4 system endpoints. Both CAP Java and CAP Node.js support IAS App-2-App via configuration to handle token exchange automatically - Java uses service bindings with `ias-dependency-name`, while Node.js uses BTP Destinations with `tokenService.body.resource`. ### IAS App-2-App > Source: /docs/guides/security/remote-authentication#ias-app-2-app As a first-class citizen, [IAS](./authentication#ias-auth) is positioned to simplify cross-regional requests with user propagation. Prerequisites are identity instances on both consumer and provider sides, plus a registered IAS dependency in the consumer instance. ![External services](./assets/external-services.drawio.svg){ } CAP supports communication between arbitrary IAS endpoints and remains transparent for applications as it builds on the same architectural pattern of [remote services](#remote-services). Technically, the connectivity component uses [IAS App-2-App flows](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/consume-apis-from-other-applications) in this scenario that requires a token exchange from a consumer token into a token for the provider. The latter is issued by IAS only if the consumer is configured with a valid IAS dependency pointing to the provider accordingly. :::tip CAP offers App-2-App setup by leveraging remote services that require: - Identity instances for provider and consumer - Configured IAS dependency from consumer to provider - URL pointing to the provider - Principal propagation mode (optional) ::: [Learn more about how to consume external application APIs with IAS](https://help.sap.com/docs/cloud-identity-services/cloud-identity-services/consume-apis-from-other-applications) {.learn-more} #### 1. Prepare and Deploy the Provider Application > Source: /docs/guides/security/remote-authentication#1-prepare-and-deploy-the-provider-application Assuming the same local CF environment setup as [here](#prepare), clone the sample application ([`xflights-java`](https://github.com/capire/xflights-java/tree/main) or [`xflights`](https://github.com/capire/xflights/tree/main) for Node.js), or if already cloned and modified locally, reset to the remote branch. Similar to the [co-located](#co-located-provider) variant, `xflights` needs to expose service `FlightsService` to technical clients. The difference is that the consumers are not known a priori and are not part of the same application deployment. To expose service APIs for consumption, you can enhance the identity instance of the provider by defining API identifiers that are listed in property `provided-apis`: ::: code-group ```yaml [mta.yaml] resources: - name: xflights-ias type: org.cloudfoundry.managed-service parameters: service: identity service-plan: application config: display-name: xflights oauth2-configuration: token-policy: access-token-format: jwt provided-apis: - name: data-consumer description: Grants technical access to data service API ``` ::: The entry with name `data-consumer` represents the consumption of service `FlightsService` and is exposed as IAS API. The description helps administrators to configure the consumer application with the proper provider API if done on UI level. > [!NOTE] How can proper authorization be configured for technical clients without user propagation? >OAuth tokens presented by valid consumer requests from an App-2-App flow will have API claim `data-consumer`, which is automatically mapped to a CAP role by the runtime. Therefore, you can protect the corresponding CDS service by CAP role `data-consumer` to authorize requests thoroughly: ::: code-group ```cds [/srv/authorization.cds] using { sap.capire.flights.FlightsService as data } from './data-service'; annotate data with @(requires: 'data-consumer'); ``` ::: For Node.js, additionally configure the authentication strategy in `package.json`: ::: code-group ```json [Node.js: package.json] { "cds": { "requires": { "auth": { "[production]": { "kind": "ias" } } } } } ``` ::: Finally, deploy and start the application with ::: code-group ```sh [Java] cd ./xflights-java cds up ``` ```sh [Node.js] cd ./xflights cds up ``` ::: ::: tip API as CAP role The API identifiers exposed by the IAS instance in list `provided-apis` are granted as CAP roles after successful authentication and can be used in `@requires` annotations. ::: ::: warning Use different roles for technical and business users Use different CAP roles for technical clients without user propagation and for named business users. Instead of using the same role, expose dedicated CDS services to technical clients that are not accessible to business users and vice versa. ::: #### 2. Prepare and Deploy the Consumer Application > Source: /docs/guides/security/remote-authentication#2-prepare-and-deploy-the-consumer-application-1 Like the provider application (xflights), clone the sample application ([`xtravels-java`](https://github.com/capire/xtravels-java/tree/main) or [`xtravels`](https://github.com/capire/xtravels/tree/main) for Node.js), or if already cloned and modified locally, reset to the remote branch. The remote service can be configured in a very similar way as with [co-located services](#co-located-consumer). You only need to add the information about the IAS dependency to be called. The name for the IAS dependency is flexible but **needs to match the chosen name in the next step** when [connecting consumer and provider in IAS](#connect). ::: code-group ```yaml [Java: application.yaml] spring: config.activate.on-profile: cloud cds: remote.services: xflights: type: hcql model: FlightsService http: suffix: /hcql binding: name: xtravels-ias onBehalfOf: systemUser options: url: https:// ias-dependency-name: data-consumer ``` ```json [Node.js: package.json] { "cds": { "requires": { "auth": { "[production]": { "kind": "ias" } }, "sap.capire.flights.FlightsService": { "kind": "hcql", "[production]": { "credentials": { "path": "/hcql/data", "destination": "xflights-ias-app2app" } } } } } } ``` ::: ::: details Java configuration explained The `ias-dependency-name` property configures the IAS App-2-App flow directly in `application.yaml`. This is all that's needed for Java - the CAP Java runtime handles the token exchange automatically. ::: **Node.js:** Configure a BTP Destination that handles the IAS token exchange. The destination references the IAS dependency name, which **must match** the name used when [connecting consumer and provider in IAS](#connect). ::: warning MTA cannot resolve cross-service credential references The destination must be created manually in BTP Cockpit or via Destination Service API, as MTA cannot reference IAS credentials like `${generated>xtravels-ias/clientid}`. ::: ::: details Node.js configuration explained CAP Node.js supports IAS App-2-App via BTP Destinations using standard remote service configuration. **1. Configuration** - Use a named destination in `credentials`: - `path`: The HCQL endpoint path on the provider - `destination`: Name of the BTP Destination configured for IAS App-2-App **2. BTP Destination** - Create a destination in BTP Cockpit with these properties: | Property | Value | |----------|-------| | Name | `xflights-ias-app2app` | | Type | `HTTP` | | URL | `https://` | | Proxy Type | `Internet` | | Authentication | `OAuth2ClientCredentials` or `OAuth2JWTBearer` (see below) | | Client ID | Consumer IAS client ID | | Client Secret | Consumer IAS client secret | | Token Service URL | `https:///oauth2/token` | | Token Service URL Type | `Dedicated` | Client ID and Client Secret are obtained from the consumer's IAS service key: ```sh cf create-service-key xtravels-ias xtravels-ias-key ``` **Additional Property (required for IAS App-2-App):** | Property | Value | |----------|-------| | `tokenService.body.resource` | `urn:sap:identity:application:provider:name:data-consumer` | The key is `tokenService.body.resource` which passes the `resource` parameter to IAS, triggering App-2-App token scoping and adding the `ias_apis` claim. **3. IAS App-2-App supports two authentication types:** - **OAuth2ClientCredentials**: For technical user scenarios (no user context) - **OAuth2JWTBearer**: For user propagation (requires user token from authorization_code flow) Both support `tokenService.body.resource` for IAS App-2-App scoping. **4. MTA descriptor** - bind the IAS and Destination services: ```yaml modules: - name: xtravels-srv requires: - name: xtravels-ias - name: xtravels-destination resources: - name: xtravels-ias type: org.cloudfoundry.managed-service parameters: service: identity service-plan: application config: display-name: xtravels oauth2-configuration: token-policy: access-token-format: jwt - name: xtravels-destination type: org.cloudfoundry.managed-service parameters: service: destination service-plan: lite ``` ::: Finally, deploy and start the application with ::: code-group ```sh [Java] cd ./xtravels-java cds up ``` ```sh [Node.js] cd ./xtravels cds up ``` ::: `xtravels-srv` is not expected to start successfully; instead, you should see error log messages like this: ```sh Remote HCQL service responded with HTTP status code '401', ... ``` You solve this in the next step, by connecting the consumer and the provider application. ::: details Technically, the App-2-App flow takes the token from the request and triggers... ... an IAS token exchange for the target [IAS dependency](#connect). In Java, CAP's remote service handles this automatically. In Node.js, the BTP Destination with `tokenService.body.resource` triggers the token exchange via the Destination Service. As the IAS dependency is not created yet, IAS rejects the token exchange request and the call to the provider fails with `401` (not authenticated). Note that property `oauth2-configuration.token-policy.access-token-format: jwt` is set in the identity instance to ensure the exchanged token has JWT format. ::: #### 3. Connect Consumer with Provider > Source: /docs/guides/security/remote-authentication#3-connect-consumer-with-provider Now create the missing IAS dependency to establish trust for the API service call targeting the provided API with ID `data-consumer`. Open the Administrative Console for the IAS tenant, check prerequisites [in Authentication](./authentication#ias-admin): 1. Select **Applications & Resources** > **Applications**. Choose the IAS application of the `xtravels` consumer from the list. 2. In **Application APIs** select **Dependencies** and click on **Add**. 3. Type `data-consumer` as dependency name and pick provided API `data-consumer` from the provider IAS application `xflights`. >The dependency name needs to match property value `ias-dependency-name` in Java, or the name suffix in the `tokenService.body.resource` URN for Node.js, for example, `urn:sap:identity:application:provider:name:` where `data-consumer` is the dependency name. 4. Confirm with **Save** ::: details Create IAS dependency in Administrative Console ![Manage IAS dependencies in Administrative Console](assets/ias-dependencies.png) { } ![Create a new IAS dependency in Administrative Console](assets/add-api.png) { } :::
Now restart the consumer application: ```sh cf restart xtravels-srv ``` This triggers a successful startup with valid flight data retrieved from the provider. You can now test the valid setup of the xtravels application by accessing the UI and logging in with an authorized test user of the IAS tenant. To do so, assign a proper AMS policy (for example, `admin`) to the test user as described [earlier](./cap-users#ams-deployment).
## Pitfalls > Source: /docs/guides/security/remote-authentication#pitfalls - **Don't write custom integration logic** for consumed services. Leverage CAP's remote service architecture instead to ensure a seamless integration experience. - **Don't implement connectivity layer code** (for example, to fetch or exchange tokens). Instead, rely on the shared connectivity component, which ensures centralized and generic processing of outbound requests. - **Don't treat co-located services as external services**. This introduces unnecessary communication overhead and increases total cost of ownership. # CAP-level Authorization > Source: /docs/guides/security/authorization This guide explains how to restrict access to data by adding respective declarations to CDS models, that are then enforced by CAP's generic service providers. ## Declarative Access Control > Source: /docs/guides/security/authorization#declarative-access-control In essence, [authentication](./authentication#authentication) verifies the user's identity and the presented claims. Briefly, authentication reveals _who_ is using the service. In contrast, **authorization controls _how_ the user may interact with the application's resources**. As access control depends on user information, authentication is a prerequisite for authorization. ![Authorization with CAP](./assets/authorization.drawio.svg){} CAP authorization modeling means restricting user access to application resources in a declarative way. The decisive point here is that the application logic does not need to contribute any security-critical code for this, but can rely on the generic framework. There are several ways to define access rules on CDS resources: - [Static access control](#static-access-control) limits access to CDS services on a general level independently of the request user. - [Role-based access control](#role-based-access-control) derives resource access rules from roles granted by user administrators. - [Instance-based access control](#instance-based-auth) allows entity-level filters that usually depend on user criteria. **By default, CDS services have no access control**, which means that without authorization modeling, authenticated users have access to all entities. ::: warning **Applications must implement proper authorization.** CAP cannot enforce this automatically as it depends entirely on the specific domain model. ::: Finally, according to the key concept [Customizable Security](./overview#key-concept-customizable), applications can implement custom authorization logic for exceptional scenarios when declarative approaches are insufficient. ## Static Access Control > Source: /docs/guides/security/authorization#static-access-control ### Internal Services > Source: /docs/guides/security/authorization#internal-services CDS services that are only meant for *internal* usage shouldn't be exposed via protocol adapters. To prevent access from *any* external clients, annotate those services with `@protocol: 'none'`: ```cds @protocol: 'none' service InternalService { ... } ``` `InternalService` can only receive events sent by in-process handlers. ### @readonly and @insertonly > Source: /docs/guides/security/authorization#readonly-and-insertonly Annotate entities with `@readonly` or `@insertonly` to statically restrict allowed operations for **all** users as demonstrated in the example: ```cds service BookshopService { @readonly entity Books {...} @insertonly entity Orders {...} } ``` Note that both annotations introduce access control on an entity level. In contrast, for the sake of [input validation](../services/constraints), you can also use `@readonly` on a property level. In addition, annotation `@Capabilities` from standard OData vocabulary is enforced by the runtimes analogously: ```cds service SomeService { @Capabilities: { InsertRestrictions.Insertable: true, UpdateRestrictions.Updatable: true, DeleteRestrictions.Deletable: false } entity Foo { key ID : UUID } } ``` #### Events to Auto-Exposed Entities > Source: /docs/guides/security/authorization#events-to-auto-exposed-entities In general, entities can be exposed in services in different ways: they can be **explicitly exposed** by the modeler (for example, by a projection), or they can be [**auto-exposed**](../../cds/cdl#auto-exposed-entities) by the CDS compiler in certain circumstances. Access to auto-exposed entities needs to be controlled in a specific way. Consider the following example: ```cds context db { @cds.autoexpose entity Categories : cuid { // explicitly auto-exposed (by @cds.autoexpose) ... } entity Issues : cuid { // implicitly auto-exposed (by composition in Components) category: Association to Categories; ... } entity Components : cuid { // explicitly exposed (by projection in IssuesService) issues: Composition of many Issues; ... } } service IssuesService { entity Components as projection on db.Components; } ``` As a result, the `IssuesService` service actually exposes *all* three entities from the `db` context: * `db.Components` is explicitly exposed due to the projection in the service. * `db.Issues` is implicitly auto-exposed by the compiler as it is a composition entity of `Components`. * `db.Categories` is explicitly auto-exposed due to the `@cds.autoexpose` annotation. In general, **implicitly auto-exposed entities cannot be accessed directly**, which means only access via a navigation path (starting from an explicitly exposed entity) is allowed. In contrast, **explicitly auto-exposed entities can be accessed directly, but only as `@readonly`**. The rationale behind that is that entities representing value lists need to be readable at the service level, for instance to support value help lists. See details about `@cds.autoexpose` in [Auto-Exposed Entities](../services/providing-services#auto-exposed-entities). This results in the following access matrix: | Request | `READ` | `WRITE` | |--------------------------------------------------------|:------:|:-------:| | `IssuesService.Components` | | | | `IssuesService.Issues` | | | | `IssuesService.Categories` | | | | `IssuesService.Components[].issues` | | | | `IssuesService.Components[].issues[].category` | | | ::: tip CodeLists such as `Languages`, `Currencies`, and `Countries` from `sap.common` are annotated with `@cds.autoexpose` and so are explicitly auto-exposed. ::: ## Role-Based Access Control > Source: /docs/guides/security/authorization#role-based-access-control To protect resources according to your business needs, you can declaratively restrict access according to a [CAP role](./cap-users#roles) by adding [@requires](#requires) or [@restrict](#restrict-annotation) annotations. Restrictions can be defined on *different CDS resources*: - Services - Entities - (Un)bound actions and functions You can influence the scope of a restriction by choosing an adequate hierarchy level in the CDS model. For instance, a restriction on the service level applies to all entities in the service. Additional restrictions on entities or actions can further limit authorized requests. See [combined restrictions](#combined-restrictions) for more details. Beside the scope, restrictions can limit access to resources with regards to *different dimensions*: - The [event](#restricting-events) of the request, that is, the type of the operation (what?) - The [roles](cap-users#roles) of the user (who?) - [Filter-condition](#instance-based-auth) on instances to operate on (which?) ### @requires > Source: /docs/guides/security/authorization#requires You can use the `@requires` annotation to control which (pseudo-)role a user requires to access a resource: ```cds annotate BrowseBooksService with @(requires: 'authenticated-user'); annotate ShopService.Books with @(requires: ['Vendor', 'ProcurementManager']); annotate ShopService.ReplicationAction with @(requires: 'system-user'); ``` In this example, the `BrowseBooksService` service is open for authenticated but not for anonymous users. A user who has the `Vendor` _or_ `ProcurementManager` role is allowed to access the `ShopService.Books` entity. Unbound action `ShopService.ReplicationAction` can only be triggered by a technical user. ::: tip When restricting service access through `@requires`, the service's metadata endpoints (that is, `/$metadata` as well as the service root `/`) are restricted by default as well. If you require public metadata, you can disable the check with [a custom express middleware](../../node.js/cds-serve#addmw-pos) using the [privileged user](../../node.js/authentication#privileged-user) (Node.js) or through config cds.security.authentication.authenticateMetadataEndpoints = false (Java), respectively. Please be aware that the `/$metadata` endpoint is *not* checking for authorizations implied by `@restrict` annotation. ::: ### @restrict > Source: /docs/guides/security/authorization#restrict You can use the `@restrict` annotation to define authorizations on a fine-grained level. In essence, all kinds of restrictions that are based on static user roles, the request operation, and instance filters can be expressed by this annotation.
The building block of such a restriction is a single **privilege**, which has the general form: ```cds { grant:, to:, where: } ``` whereas the properties are: * `grant`: one or more events that the privilege applies to * `to`: one or more [user roles](cap-users#roles) that the privilege applies to (optional) * `where`: a filter condition that further restricts access on an instance level (optional). The following values are supported: - `grant` accepts all standard [CDS events](../../get-started/concepts#events) (such as `READ`, `CREATE`, `UPDATE`, and `DELETE`) as well as action and function names. `WRITE` is a virtual event for all standard CDS events with write semantic (`CREATE`, `DELETE`, `UPDATE`, `UPSERT`) and `*` is a wildcard for all events. - The `to` property lists all [user roles](cap-users#roles) or [pseudo roles](cap-users#pseudo-roles) that the privilege applies to. Note that the `any` pseudo-role applies for all users and is the default if no value is provided. - The `where`-clause can contain a Boolean expression in [CQL](../../cds/cql)-syntax that filters the instances that the event applies to. As it allows user values (name, attributes, etc.) and entity data as input, it's suitable for *dynamic authorizations based on the business domain*. Supported expressions and typical use cases are presented in [instance-based access control](#instance-based-auth). A privilege is met, if and only if **all properties are fulfilled** for the current request. In the following example, orders can only be read by an `Auditor` who meets `AuditBy` element of the instance: ```cds entity Orders @(restrict: [ { grant: 'READ', to: 'Auditor', where: (AuditBy = $user) } ]) {/*...*/} ``` If a privilege contains several events, only one of them needs to match the request event to comply with the privilege. The same holds, if there are multiple roles defined in the `to` property: ```cds entity Reviews @(restrict: [ { grant:['READ', 'WRITE'], to: ['Reviewer', 'Customer'] } ]) {/*...*/} ``` In this example, all users that have the `Reviewer` *or* `Customer` role can read *or* write to `Reviews`. You can build restrictions based on *multiple privileges*: ```cds entity Orders @(restrict: [ { grant: ['READ','WRITE'], to: 'Admin' }, { grant: 'READ', where: (buyer = $user) } ]) {/*...*/} ``` A request passes such a restriction **if at least one of the privileges is met**. In this example, `Admin` users can read and write the `Orders` entity. But a user can also read all orders that have a `buyer` property that matches the request user. Similarly, the filter conditions of matched privileges are combined with logical OR: ```cds entity Orders @(restrict: [ { grant: 'READ', to: 'Auditor', where: (country = $user.country) }, { grant: ['READ','WRITE'], where: (CreatedBy = $user) }, ]) {/*...*/} ``` Here, users can read and write orders they've created, and `Auditor` users can read all orders with matching `country`. > Annotations such as @requires or @readonly are just convenience shortcuts for @restrict, for example: - `@requires: 'Viewer'` is equivalent to `@restrict: [{grant:'*', to: 'Viewer'}]` - `@readonly` is the same as `@restrict: [{ grant:'READ' }]` #### Supported Combinations with CDS Resources > Source: /docs/guides/security/authorization#supported-combinations-with-cds-resources Restrictions can be defined on different types of CDS resources, but there are some limitations with regards to supported privileges: | CDS Resource | `grant` | `to` | `where` | Remark | |-----------------|:-------:|:----:|:-----------------:|---------------| | service | | | | = `@requires` | | entity | | | 1 | | | action/function | | | 2 | = `@requires` | > 1For bound actions and functions that are not bound against a collection, Node.js supports instance-based authorization at the entity level. For example, you can use `where` clauses that *contain references to the model*, such as `where: CreatedBy = $user`. For all bound actions and functions, Node.js supports simple static expressions at the entity level that *don't have any reference to the model*, such as `where: $user.level = 2`. > 2 For unbound actions and functions, Node.js supports simple static expressions that *don't have any reference to the model*, such as `where: $user.level = 2`. Unsupported privilege properties are ignored by the runtime. Especially, for bound or unbound actions, the `grant` property is implicitly removed (assuming `grant: '*'` instead). The same also holds for functions: ```cds service CatalogService { entity Products as projection on db.Products { ... } actions { @(requires: 'Admin') action addRating (stars: Integer); } function getViewsCount @(restrict: [{ grant: 'READ', to: 'Admin' }]) () returns Integer; } ``` ### Combined Restrictions > Source: /docs/guides/security/authorization#combined-restrictions Restrictions can be defined on different levels in the CDS model hierarchy. Bound actions and functions refer to an entity, which in turn refers to a service. Unbound actions and functions refer directly to a service. As a general rule, **all authorization checks of the hierarchy need to be passed** (logical AND). This is illustrated in the following example: ```cds service CustomerService @(requires: 'authenticated-user') { entity Products @(restrict: [ { grant: 'READ' }, { grant: 'WRITE', to: 'Vendor' }, { grant: 'addRating', to: 'Customer'} ]) {/*...*/} actions { action addRating (stars: Integer); } entity Orders @(restrict: [ { grant: '*', to: 'Customer', where: (CreatedBy = $user) } ]) {/*...*/} action monthlyBalance @(requires: 'Vendor') (); } ``` > The privilege for the `addRating` action is defined on an entity level. The resulting authorizations are illustrated in the following access matrix: | Operation | `Vendor` | `Customer` | `authenticated-user` | not authenticated | |--------------------------------------|:--------:|:----------------:|:--------------------:|-------------------| | `CustomerService.Products` (`READ`) | | | | | | `CustomerService.Products` (`WRITE`) | | | | | | `CustomerService.Products.addRating` | | | | | | `CustomerService.Orders` (*) | | 1 | | | | `CustomerService.monthlyBalance` | | | | | > 1 A `Vendor` user can only access the instances that they created.
The example models access rules for different roles in the same service. In general, this is _not recommended_ due to the high complexity. See [best practices](#dedicated-services) for information about how to avoid this. ### Propagation of Restrictions > Source: /docs/guides/security/authorization#propagation-of-restrictions Service entities inherit the restriction from the database entity, on which they define a projection. An explicit restriction defined on a service entity *replaces* inherited restrictions from the underlying entity. Entity `Books` on a database level: ```cds namespace db; entity Books @(restrict: [ { grant: 'READ', to: 'Buyer' }, ]) {/*...*/} ``` Services `BuyerService` and `AdminService` on a service level: ```cds service BuyerService @(requires: 'authenticated-user'){ entity Books as projection on db.Books; /* inherits */ } service AdminService @(requires: 'authenticated-user'){ entity Books @(restrict: [ { grant: '*', to: 'Admin'} /* overrides */ ]) as projection on db.Books; } ``` | Events | `Buyer` | `Admin` | `authenticated-user` | |-------------------------------|:-------:|:-------:|:--------------------:| | `BuyerService.Books` (`READ`) | | | | | `AdminService.Books` (`*`) | | | | ::: tip We recommend defining restrictions on a database entity level only in exceptional cases. Inheritance and override mechanisms can lead to an unclear situation. ::: ::: warning _Warning_ A service level entity can't inherit a restriction with a `where` condition that doesn't match the projected entity. The restriction has to be overridden in this case. ::: ### Draft Mode > Source: /docs/guides/security/authorization#draft-mode Basically, the access control for entities in draft mode differs from the [general restriction rules](#restrict-annotation) that apply to (active) entities. A user, who has created a draft, should also be able to edit (`UPDATE`) or cancel the draft (`DELETE`). The following rules apply: - If a user has the privilege to create an entity (`CREATE`), he or she also has the privilege to create a **new** draft entity and update, delete, and activate it. - If a user has the privilege to update an entity (`UPDATE`), he or she also has the privilege to **put it into draft mode** and update, delete, and activate it. - Draft entities can only be edited by the creator user. + In the Node.js runtime, this includes calling bound actions/functions on the draft entity. ::: tip As a result of the derived authorization rules for draft entities, you don't need to take care of draft events when designing the CDS authorization model. ::: ### Auto-Exposed and Generated Entities > Source: /docs/guides/security/authorization#auto-exposed-and-generated-entities In general, **a service actually exposes more than the explicitly modeled entities from the CDS service model**. This stems from the fact that the compiler auto-exposes entities for the sake of completeness, for example, by adding composition entities. Another reason is generated entities for localization or draft support that need to appear in the service. Typically, such entities don't have restrictions. The emerging question is, how can requests to these entities be authorized? For illustration, let's extend the service `IssuesService` from [Events to Auto-Exposed Entities](#events-and-auto-expose) by adding a restriction to `Components`: ```cds annotate IssuesService.Components with @(restrict: [ { grant: '*', to: 'Supporter' }, { grant: 'READ', to: 'authenticated-user' } ]); ``` Basically, users with the `Supporter` role aren't restricted, whereas authenticated users can only read the `Components`. But what about the auto-exposed entities such as `IssuesService.Issues` and `IssuesService.Categories`? They could be a target of an (indirect) request as outlined in [Events to Auto-Exposed Entities](#events-and-auto-expose), but none of them are annotated with a concrete restriction. In general, the same also holds for service entities that are generated by the compiler, for example, for localization or draft support. To close the gap with auto-exposed and generated entities, the authorization of such entities is delegated to a so-called **authorization entity**, which is the last entity in the request path, that bears authorization information, that means, that fulfills at least one of the following properties: - Explicitly exposed in the service - Annotated with a concrete restriction - Annotated with `@cds.autoexpose` So, the authorization for the requests in the example is delegated as follows: | Request Target | Authorization Entity | |--------------------------------------------------------|:--------------------------------------:| | `IssuesService.Components` | `IssuesService.Components`3 | | `IssuesService.Issues` | 1 | | `IssuesService.Categories` | `IssuesService.Categories`2 | | `IssuesService.Components[].issues` | `IssuesService.Components`3 | | `IssuesService.Components[].issues[].category` | `IssuesService.Categories`2 | > 1 Request is rejected.
> 2 `@readonly` due to `@cds.autoexpose`
> 3 According to the restriction. `` is relevant for instance-based filters. ## Instance-Based Access Control > Source: /docs/guides/security/authorization#instance-based-access-control The [restrict annotation](#restrict-annotation) for an entity allows you to enforce authorization checks that statically depend on the event type and user roles. In addition, you can define a `where`-condition that further limits the set of accessible instances. This condition, that acts like a filter, establishes *instance-based authorization*. ### Filter Conditions > Source: /docs/guides/security/authorization#filter-conditions For instance, a user is allowed to read or edit `Orders` (defined with the `managed` aspect) that they have created: ```cds annotate Orders with @(restrict: [ { grant: ['READ', 'UPDATE', 'DELETE'], where: (CreatedBy = $user) } ]); ``` Or a `Vendor` can only edit articles on stock (that means `Articles.stock` positive): ```cds annotate Articles with @(restrict: [ { grant: ['UPDATE'], to: 'Vendor', where: (stock > 0) } ]); ``` ::: tip Filter conditions declared as **compiler expressions** ensure validity at compile time and therefore strengthen security. ::: The condition defined in the `where` clause typically associates domain data with static [user claims](cap-users#claims). Basically, it *either filters the result set in queries or accepts only write operations on instances that meet the condition*. This means that, the condition applies to following standard CDS events only: - `READ` (as result filter) - `UPDATE` (as reject condition) - `DELETE` (as reject condition)
In addition, the runtime [checks the filter condition of the input data](#input-data-auth) for following standard CDS events: - `CREATE` (input filter) - `UPDATE` (input filer)
You can define filter conditions in the `where`-clause of restrictions based on [CQL](/cds/cql)-predicates, declared as [compiler expressions](../../cds/cdl#expressions-as-annotation-values): * Predicates with arithmetic operators. * Combining predicates to expressions with `and` and `or` logical operators. * Value references to constants, [user attributes](#user-attrs), and entity data (elements including [association paths](#association-paths)) * [Exists predicate](#exists-predicate) based on subselects.
* [Exists with a subquery](#exists-subquery) for access to ACL like entities.
At runtime you'll find filter predicates attached to the appropriate CQN queries matching the instance-based condition. :::warning Modification of Statements Be careful when you modify or extend the statements in custom handlers. Make sure you keep the filters for authorization. ::: #### User Attributes > Source: /docs/guides/security/authorization#user-attributes To refer to attribute values from the user claim, prefix the attribute name with '`$user.`' as outlined in [static user claims](cap-users#claims). For instance, `$user.country` refers to the attribute with the name `country`. In general, `$user.` contains a **list of attribute values** that are assigned to the user. The following rules apply: * A predicate in the `where` clause evaluates to `true` if one of the attribute values from the list matches the condition. * An empty (or not defined) list means that the user is fully restricted with regard to this attribute (that is, the predicate evaluates to `false`). For example, the condition `where: $user.country = countryCode` will grant a user with attribute values `country = ['DE', 'FR']` access to entity instances that have `countryCode = DE` _or_ `countryCode = FR`. In contrast, the user has no access to any entity instances if the value list of country is empty or the attribute is not available at all. ##### Unrestricted XSUAA Attributes > Source: /docs/guides/security/authorization#unrestricted-xsuaa-attributes By default, all attributes defined in [XSUAA instances](./cap-users#xsuaa-roles) require a value (`valueRequired:true`), which is well-aligned with the CAP runtime that enforces restrictions on empty attributes. If you explicitly want to offer unrestricted attributes to customers, you need to do the following: 1. Switch your XSUAA configuration to `valueRequired:false` 2. Adjust the filter-condition accordingly, for example: `where: $user.country = countryCode or $user.country is null`. > If `$user.country` is undefined or empty, the overall expression evaluates to `true`, reflecting the unrestricted attribute. ::: warning Refrain from unrestricted XSUAA attributes as they need to be designed very carefully as shown in the following example. ::: Consider this bad example with *unrestricted* attribute `country` (assuming `valueRequired:false` in XSUAA configuration): ```cds service SalesService @(requires: ['SalesAdmin', 'SalesManager']) { entity SalesOrgs @(restrict: [ { grant: '*', to: ['SalesAdmin', 'SalesManager'], where: ($user.country = countryCode or $user.country is null) } ]) { countryCode: String; /*...*/ } } ``` Let's assume a customer creates XSUAA roles `SalesManagerEMEA` with dedicated values (`['DE', 'FR', ...]`) and `SalesAdmin` with *unrestricted* values. As expected, a user assigned only to `SalesAdmin` has access to all `SalesOrgs`. But when role `SalesManagerEMEA` is added, *only* EMEA organizations are accessible suddenly! The preferred way is to model with restricted attribute `country` (`valueRequired:true`) and an additional grant: ```cds service SalesService @(requires: ['SalesAdmin', 'SalesManager']) { entity SalesOrgs @(restrict: [ { grant: '*', to: 'SalesManager', where: ($user.country = countryCode) }, { grant: '*', to: 'SalesAdmin' } ]) { countryCode: String; /*...*/ } } ``` #### Exists Predicate > Source: /docs/guides/security/authorization#exists-predicate In many cases, the authorization of an entity needs to be derived from entities reachable via association path. See [domain-driven authorization](#domain-driven-authorization) for more details. You can leverage the `exists` predicate in `where` conditions to define filters that directly apply to associated entities defined by an association path: ```cds service ProjectService @(requires: 'authenticated-user') { entity Projects @(restrict: [ { grant: ['READ', 'WRITE'], where: (exists members[userId = $user and role = 'Editor']) } ]) { members: Association to many Members; /*...*/ } @readonly entity Members { key userId : User; key role: String enum { Viewer; Editor; }; /*...*/ } } ``` In the `ProjectService` example, only projects for which the current user is a member with role `Editor` are readable and editable. Note that with exception of the user ID (`$user`) **all authorization information originates from the business data**. Supported features of `exists` predicate: * Combine with other predicates in the `where` condition (`where: 'exists a1[...] or exists a2[...]`). * Define recursively (`where: 'exists a1[exists b1[...]]`). * Use target paths (`where: 'exists a1.b1[...]`). * Usage of [user attributes](#user-attrs). ::: warning Paths *inside* the filter (`where: (exists a1[b1.c = ...])`) are not yet supported. ::: The following example demonstrates the last two features: ```cds service ProductsService @(requires: 'authenticated-user') { entity Products @(restrict: [ { grant: '*', where: (exists producers.division[$user.division = name])}]): cuid { producers : Association to many ProducingDivisions on producers.product = $self; } @readonly entity ProducingDivisions { key product : Association to Products; key division : Association to Divisions; } @readonly entity Divisions : cuid { name : String; producedProducts : Association to many ProducingDivisions on producedProducts.division = $self; } } ``` Here, the authorization of `Products` is derived from `Divisions` by leveraging the *n:m relationship* via entity `ProducingDivisions`. Note that the path `producers.division` in the `exists` predicate points to target entity `Divisions`, where the filter with the user-dependent attribute `$user.division` is applied. ::: warning Consider Access Control Lists Be aware that deep paths might introduce a performance bottleneck. Access Control List (ACL) tables, managed by the application, allow efficient queries and might be the better option in this case. ::: ### Association Paths > Source: /docs/guides/security/authorization#association-paths The `where`-condition in a restriction can also contain [CQL path expressions](../../cds/cql#path-expressions) that navigate to elements of associated entities: ```cds service SalesOrderService @(requires: 'authenticated-user') { entity SalesOrders @(restrict: [ { grant: 'READ', where: (product.productType = $user.productType) } ]) { product: Association to one Products; } entity Products { productType: String(32); /*...*/ } } ``` Paths on 1:n associations (`Association to many`) evaluate to `true`, _if the condition selects at most one associated instance_ (`exists` semantic).
### Checking Input Data > Source: /docs/guides/security/authorization#checking-input-data Input data of `CREATE` and `UPDATE` events is also validated with regards to instance-based authorization conditions. Invalid input that does not meet the condition is rejected with response code `400`. Let's assume an entity `Orders` that restricts access to users classified by assigned accounting areas: ```cds annotate Orders with @(restrict: [ { grant: '*', where: 'accountingArea = $user.accountingAreas' } ]); ``` A user with accounting areas `[Development, Research]` is not able to send an `UPDATE` request, that changes `accountingArea` from `Research` or `Development` to `CarFleet`, for example. Note that the `UPDATE` on instances _not matching the request user's accounting areas_ (for example, `CarFleet`) are rejected by standard instance-based authorization checks. Starting with CAP Java `4.0`, deep authorization is active by default. It can be disabled by setting cds.security.authorization.instanceBased.checkInputData: false. ### Rejected Entity Selection > Source: /docs/guides/security/authorization#rejected-entity-selection Entities that have an instance-based authorization condition, that is [`@restrict.where`](/guides/security/authorization#restrict-annotation), are guarded by the CAP Java runtime by adding a filter condition to the DB query **excluding not matching instances from the result**. Hence, if the user isn't authorized to query an entity, requests targeting a *single* entity return *404 - Not Found* response and not *403 - Forbidden*. To allow the UI to distinguish between *not found* and *forbidden*, CAP Java can detect this situation and rejects `UPDATE` and `DELETE` requests to single entities with forbidden accordingly. The additional authorization check might affect performance. ::: warning Avoid enumerable keys To avoid disclosure of the existence of such entities to unauthorized users, make sure that the key is not efficiently enumerable or add custom code to overrule the default behavior otherwise. ::: Starting with CAP Java `4.0`, the reject behaviour is active by default. It can be disabled by setting cds.security.authorization.instance-based.reject-selected-unauthorized-entity.enabled: false. ## Limitations > Source: /docs/guides/security/authorization#limitations Currently, the security annotations **are only evaluated on the target entity of the request**. Restrictions on associated entities touched by the operation are not regarded. This has the following implications: - Restrictions of (recursively) expanded or inlined entities of a `READ` request aren't checked. - Deep inserts and updates are checked on the root entity only. See [solution sketches](#limitation-deep-authorization) for information about how to deal with that. ## Deep Authorizations > Source: /docs/guides/security/authorization#deep-authorizations ### Associations > Source: /docs/guides/security/authorization#associations Queries to Application Services are not only authorized by the target entity that has a `@restrict` or `@requires` annotation, but also for all __associated entities__ that are used in the statement. For instance, consider the following model: ```cds @(restrict: [{ grant: 'READ', to: 'Manager' }]) entity Books {...} @(restrict: [{ grant: 'READ', to: 'Manager' }]) entity Orders { key ID: String; items: Composition of many { key book: Association to Books; quantity: Integer; } } ``` For the following OData request `GET Orders(ID='1')/items?$expand=book`, authorizations for `Orders` and for `Books` are checked. If the entity `Books` has a `where` clause for instance-based authorization, it will be added as a filter to the sub-request with the expand. Custom CQL statements submitted to the [Application Service](../../java/cqn-services/application-services) instances are also authorized by the same rules including the path expressions and subqueries used in them. For example, the following statement checks role-based authorizations for both `Orders` and `Books`, because the association to `Books` is used in the select list. ```java Select.from(Orders_.class, f -> f.filter(o -> o.ID().eq("1")).items()) .columns(c -> c.book().title()); ``` For modification statements with associated entities used in infix filters or where clauses, role-based authorizations are checked as well. Associated entities require `READ` authorization, in contrast to the target of the statement itself. The following statement requires `UPDATE` authorization on `Orders` and `READ` authorization on `Books` because an association from `Orders.items` to the book is used in the where condition. ```java Update.entity(Orders_.class, f -> f.filter(o -> o.ID().eq("1")).items()) .data("quantity", 2) .where(t -> t.book().ID().eq(1)); ``` Starting with CAP Java `4.0`, deep authorization is active by default. It can be disabled by setting cds.security.authorization.deep.enabled: false. ### Compositions > Source: /docs/guides/security/authorization#compositions Restrictions on associated composition entities touched by the request are **not** regarded by the runtime. The rational behind that is that authorization rules are [implicitly defined by the root entity of the document](#autoexposed-restrictions) and therefore security annotations **of the composition root entity are evaluated**. This has the following implications: - Restrictions of (recursively) expanded or inlined entities of a `READ` request aren't checked. - Deep `INSERT`s and `UPDATE`s are checked on the root entity only. ::: warning **Restrictions on compositions are not checked by the runtime**. If you model dedicated restriction rules on child entity level, you need to add custom authorization handlers accordingly. ::: ## Best Practices > Source: /docs/guides/security/authorization#best-practices CAP authorization allows you to control access to your business data on a fine granular level. But keep in mind that the high flexibility can end up in security vulnerabilities if not applied appropriately. In this perspective, lean and straightforward models are preferred. When modeling your access rules, the following recommendations can support you to design such models. ### Choose Conceptual Roles > Source: /docs/guides/security/authorization#choose-conceptual-roles When defining user roles, one of the first options could be to align roles to the available *operations* on entities, which results in roles such as `SalesOrders.Read`, `SalesOrders.Create`, `SalesOrders.Update`, and `SalesOrders.Delete`. What is the problem with this approach? Think about the resulting number of roles that the user administrator has to handle when assigning them to business users. The administrator would also have to know the domain model precisely and understand the result of combining the roles. Similarly, assigning roles to operations only (`Read`, `Create`, `Update`, ...) typically doesn't fit your business needs.
We strongly recommend defining roles that describe **how a business user interacts with the system**. Roles like `Vendor`, `Customer`, or `Accountant` can be appropriate. With this approach, you as the application developer define the set of accessible resources in the CDS model for each role - and not the user administrator. ### Prefer Single-Purposed, Use-Case Specific Services > Source: /docs/guides/security/authorization#prefer-single-purposed-use-case-specific-services Have a closer look at this example: ```cds service CatalogService @(requires: 'authenticated-user') { entity Books @(restrict: [ { grant: 'READ' }, { grant: 'WRITE', to: 'Vendor', where: ($user.publishers = publisher) }, { grant: 'WRITE', to: 'Admin' } ]) as projection on db.Books; action doAccounting @(requires: ['Accountant', 'Admin']) (); } ``` Four different roles (`authenticated-user`, `Vendor`, `Accountant`, `Admin`) *share* the same service - `CatalogService`. As a result, it's confusing how a user can use `Books` or `doAccounting`. Considering the complexity of this small example (4 roles, 1 service, 2 resources), this approach can introduce a security risk, especially if the model is larger and subject to adaptation. Moreover, UIs defined for this service will likely appear unclear as well.
The fundamental purpose of services is to expose business data in a specific way. Hence, the more straightforward way is to **use a service for each role**: ```cds @path:'browse' service CatalogService @(requires: 'authenticated-user') { @readonly entity Books as select from db.Books { title, publisher, price }; } @path:'internal' service VendorService @(requires: 'Vendor') { entity Books @(restrict: [ { grant: 'READ' }, { grant: 'WRITE', to: 'vendor', where: ($user.publishers = publisher) } ]) as projection on db.Books; } @path:'internal' service AccountantService @(requires: 'Accountant') { @readonly entity Books as projection on db.Books; action doAccounting(); } /*...*/ ``` ::: tip You can tailor the exposed data according to the corresponding role, even on the level of entity elements like in `CatalogService.Books`. ::: ### Prefer Dedicated Actions for Specific Use-Cases > Source: /docs/guides/security/authorization#prefer-dedicated-actions-for-specific-use-cases In some cases it can be helpful to restrict entity access as much as possible and create actions with dedicated restrictions for specific use cases, like in the following example: ```cds service GitHubRepositoryService @(requires: 'authenticated-user') { @readonly entity Organizations as projection on GitHub.Organizations actions { @(requires: 'Admin') action rename(newName : String); @(requires: 'Admin') action delete(); }; } ``` This service allows querying organizations for all authenticated users. In addition, `Admin` users are allowed to rename or delete. Granting `UPDATE` to `Admin` would allow administrators to change organization attributes that are not meant to change. ### Think About Domain-Driven Authorization > Source: /docs/guides/security/authorization#think-about-domain-driven-authorization Static roles often don't fit into an intuitive authorization model. Instead of making authorization dependent on static properties of the user, it's often more appropriate to derive access rules from the business domain. For instance, all users assigned to a department (in the domain) are allowed to access the data of the organization comprising the department. Relationships in the entity model (for example, a department assignment to organization) influence authorization rules at runtime. In contrast to static user roles, **dynamic roles** are fully domain-driven. Revisit the [ProjectService example](#exists-predicate), which demonstrates how to leverage instance-based authorization to induce dynamic roles. Advantages of dynamic roles are: - The most flexible way to define authorizations - Authorizations induced according to business domain - Application-specific authorization model and intuitive UIs - Decentralized role management for application users (no central user administrator required) Drawbacks to be considered are: - Additional effort for modeling and designing application-specific role management (entities, services, UI) - Potentially higher security risk due to lower use of framework functionality - Sharing authorization management with other (non-CAP) applications is harder to achieve - Dynamic role enforcement can introduce a performance penalty ### Control Exposure of Associations and Compositions > Source: /docs/guides/security/authorization#control-exposure-of-associations-and-compositions Note that exposed associations (and compositions) can disclose unauthorized data. Consider the following scenario: ```cds namespace db; entity Employees : cuid { // autoexposed! name: String(128); team: Association to Teams; contract: Composition of Contracts; } entity Contracts @(requires:'Manager') : cuid { // autoexposed! salary: Decimal; } entity Teams : cuid { members: Composition of many Employees on members.team = $self; } service ManageTeamsService @(requires:'Manager') { entity Teams as projection on db.Teams; } service BrowseEmployeesService @(requires:'Employee') { @readonly entity Teams as projection on db.Teams; // navigate to Contracts! } ``` A team (entity `Teams`) contains members of type `Employees`. An employee refers to a single contract (entity `Contracts`) that contains sensitive information that should be visible only to `Manager` users. `Employee` users should be able to browse the teams and their members but are not allowed to read or even edit their contracts.
As `db.Employees` and `db.Contracts` are auto-exposed, managers can navigate to all instances through the `ManageTeamsService.Teams` service entity (for example, OData request `/ManageTeamsService/Teams?$expand=members($expand=contract)`).
It's important to note that this also holds for an `Employee` user, as **only the target entity** `BrowseEmployeesService.Teams` **has to pass the authorization check in the generic handler, and not the associated entities**.
To solve this security issue, introduce a new service entity `BrowseEmployeesService.Employees` that removes the navigation to `Contracts` from the projection: ```cds service BrowseEmployeesService @(requires:'Employee') { @readonly entity Employees as projection on db.Employees excluding { contracts }; // hide contracts! @readonly entity Teams as projection on db.Teams; } ``` Now, an `Employee` user cannot expand the contracts as the composition is not reachable anymore from the service. ::: tip Associations without navigation links (for example, when you don't expose an associated entity) are still critical with regard to security. ::: ### Design Authorization Models from the Start > Source: /docs/guides/security/authorization#design-authorization-models-from-the-start As shown before, defining an adequate authorization strategy has a deep impact on the service model. Apart from the fundamental decision of whether you want to build your authorizations on [dynamic roles](#domain-driven-authorization), authorization requirements can result in completely rearranging service and entity definitions. For this reason, it's *strongly* recommended to take security design into consideration at an early stage of your project. ### Keep it as Simple as Possible > Source: /docs/guides/security/authorization#keep-it-as-simple-as-possible * If different authorizations are needed for different operations, it's easier to have them defined at the service level. If you start defining them at the entity level, all possible operations must be specified; otherwise, the operations not mentioned are automatically forbidden. * If possible, try to define your authorizations either on the service or on the entity level. Mixing both variants increases complexity, and not all combinations are supported either. ### Separation of Concerns > Source: /docs/guides/security/authorization#separation-of-concerns Consider using [CDS Aspects](../../cds/cdl#aspects) to separate the actual service definitions from authorization annotations as follows: ::: code-group ```cds [services.cds] service ReviewsService { /*...*/ } service CustomerService { entity Orders {/*...*/} entity Approval {/*...*/} } ``` ::: ::: code-group ```cds [services-auth.cds] annotate ReviewsService with @(requires: 'authenticated-user'); annotate CustomerService with @(requires: 'authenticated-user'); annotate CustomerService.Orders with @(restrict: [ { grant: ['READ','WRITE'], to: 'admin' }, { grant: 'READ', where: 'buyer = $user' }, ]); annotate CustomerService.Approval with @(restrict: [ { grant: 'WRITE', where: '$user.level > 2' } ]); ``` ::: This keeps your actual service definitions concise and focused on structure only. It also allows you to give authorization models separate ownership and lifecycle. # Data Privacy Overview > Source: /docs/guides/security/data-privacy This guide discusses how CAP helps applications to comply with data privacy regulations imposed by various laws and standards. ::: warning SAP does not give any advice on whether the features and functions provided to facilitate meeting data privacy obligations are the best method to support company, industry, regional, or country/region-specific requirements. Furthermore, this information should not be taken as advice or a recommendation regarding additional features that would be required in specific IT environments. Decisions related to data protection must be made on a case-by-case basis, considering the given system landscape and the applicable legal requirements. ::: ## Introduction to Data Privacy > Source: /docs/guides/security/data-privacy#introduction-to-data-privacy Data protection is associated with numerous legal requirements and privacy concerns, such as the EU's [General Data Protection Regulation](https://en.wikipedia.org/wiki/General_Data_Protection_Regulation). In addition to compliance with general data protection and privacy acts regarding [personal data](https://en.wikipedia.org/wiki/Personal_data), you need to consider compliance with industry-specific legislation in different countries/regions. CAP supports applications in their obligations to comply to data privacy regulations, by automating tedious tasks as much as possible based on annotated models. That is, CAP provides easy ways to designate personal data, as well as out-of-the-box integration with SAP BTP services, which enable you to fulfill specific data privacy requirements in your application. This relieves application developers of these tedious tasks and related efforts. ![Shows with which solutions CAP annotations can be used out of the box, as described in the following table.](./assets/data-privacy/Data-Privacy.drawio.svg){} > For general information about data protection and privacy (DPP) on SAP BTP, see the SAP BTP documentation under [Data Protection and Privacy](https://help.sap.com/docs/btp/sap-business-technology-platform/data-protection-and-privacy). ### In a Nutshell > Source: /docs/guides/security/data-privacy#in-a-nutshell The most essential requests you have to answer are those in the following table. The table also shows the basis of the requirement and the corresponding discipline for the request: | Question / Request | Obligation | Solution | | ------------------------------------------- | ----------------------------------------------- | ----------------------------------- | | *What data about me do you have stored?* | [Right of access](#right-of-access) | [Personal Data Mgmt](dpp-pdm.md) | | *Delete all personal data about me!* | [Right to be forgotten](#right-to-be-forgotten) | [Data Retention Mgmt](dpp-drm.md) | | *When was personal data stored/changed?* | [Transparency](#transparency) | [Audit Logging](dpp-audit-logging.md) | ## Annotating Personal Data > Source: /docs/guides/security/data-privacy#annotating-personal-data The first and frequently only task to do as an application developer is to identify entities and elements (potentially) holding personal data using `@PersonalData` annotations. These are used to automate CAP-facilitated audit logging, personal data management, and data retention management as much as possible. [Learn more in the *Annotating Personal Data* chapter](dpp-annotations) {.learn-more} ## Automatic Audit Logging > Source: /docs/guides/security/data-privacy#automatic-audit-logging The **Transparancy** obligation, requests to be able to report with whom data stored about an individual is shared and where that came from (for example, [EU GDPR Article 15(1)(c,g)](https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=CELEX:02016R0679-20160504&qid=1692819634946#tocId22)). The [SAP Audit Log Service](https://help.sap.com/docs/btp/sap-business-technology-platform/audit-logging-in-cloud-foundry-environment) stores all audit logs for a tenant in a common, compliant data store and allows auditors to search through and retrieve the respective logs when necessary. [Learn more in the *Audit Logging* guide](dpp-audit-logging) {.learn-more} ## Personal Data Management > Source: /docs/guides/security/data-privacy#personal-data-management The [**Right of Access** to personal data](https://en.wikipedia.org/wiki/Right_of_access_to_personal_data) "gives people the right to access their personal data and information about how this personal data is being processed". The [SAP Personal Data Manager](https://help.sap.com/docs/personal-data-manager) allows you to inform individuals about the data you have stored regarding them. [Learn more in the *Personal Data Management* guide](dpp-pdm) {.learn-more} ## Data Retention Management > Source: /docs/guides/security/data-privacy#data-retention-management The [**Right to be Forgotten**](https://en.wikipedia.org/wiki/Right_to_be_forgotten) gives people "the right to request erasure of personal data related to them on any one of a number of grounds [...]". The [SAP Data Retention Manager](https://help.sap.com/docs/data-retention-manager) allows you to manage retention and residence rules to block or destroy personal data. ## Personal Data stored by CAP > Source: /docs/guides/security/data-privacy#personal-data-stored-by-cap CAP doesn't store or manage any personal data on its own with some exceptions, which are mandatory to operate the applications properly: - Log outputs on verbose level might contain personal data such as user names and IP addresses. Connect an adequate logging service to meet compliance requirements such as [SAP Application Logging Service](https://help.sap.com/docs/application-logging-service/sap-application-logging-service/sap-application-logging-service-for-cloud-foundry-environment). - Draft-enabled entities store user information for the time periods when drafts are created or modified. - When using the [managed](../domain/index#managed-data) aspect, you decided to store metadata such as who created or modified an entity instance. - Messages temporarily written to transaction outbox might contain personal data. If necessary, applications can process these messages by standard CAP functionality (CDS model `@sap/cds/srv/outbox`). Also refer to related guides of most important platform services: [SAP Cloud Identity Services - Configuring Privacy Policies](https://help.sap.com/docs/IDENTITY_AUTHENTICATION/6d6d63354d1242d185ab4830fc04feb1/ed48466d770f4519aa23bba754851fbd.html){.learn-more} [SAP HANA Cloud - Data Protection and Privacy](https://help.sap.com/docs/HANA_CLOUD_DATABASE/c82f8d6a84c147f8b78bf6416dae7290/ad9588189e844092910103f2f7b1c968.html){.learn-more} # Annotating Personal Data > Source: /docs/guides/security/dpp-annotations In order to automate audit logging, personal data management, and data retention management as much as possible, the first and frequently only task to do as an application developer is to identify entities and elements (potentially) holding personal data using `@PersonalData` annotations. ## Reference App Sample > Source: /docs/guides/security/dpp-annotations#reference-app-sample In the remainder of this guide, we use the [Incidents Management reference sample app](https://github.com/cap-js/incidents-app) as the base to add data privacy and audit logging to. ![Shows the connections between the entities in the sample app.](./assets/data-privacy/Incidents-App.drawio.svg){} So, let's annotate the data model to identify personal data. In essence, in all our entities we search for elements which carry personal data, such as person names, birth dates, etc., and tag them accordingly. All found entities are classified as either *Data Subjects*, *Subject Details* or *Related Data Objects*. Following the [best practice of separation of concerns](../domain/index#separation-of-concerns), we annotate our domain model in a separate file *srv/data-privacy.cds*, which we add to our project and fill it with the following content: > For the time beeing also replace the data in _data/sap.capire.incidents-Customers.csv_. ::: code-group ```cds [db/data-privacy.cds] using { sap.capire.incidents as my } from '../db/schema'; extend my.Customers with { dateOfBirth : Date; }; annotate my.Customers with @PersonalData : { DataSubjectRole : 'Customer', EntitySemantics : 'DataSubject' } { ID @PersonalData.FieldSemantics: 'DataSubjectID'; firstName @PersonalData.IsPotentiallyPersonal; lastName @PersonalData.IsPotentiallyPersonal; email @PersonalData.IsPotentiallyPersonal; phone @PersonalData.IsPotentiallyPersonal; dateOfBirth @PersonalData.IsPotentiallyPersonal; creditCardNo @PersonalData.IsPotentiallySensitive; }; annotate my.Addresses with @PersonalData: { EntitySemantics : 'DataSubjectDetails' } { customer @PersonalData.FieldSemantics: 'DataSubjectID'; city @PersonalData.IsPotentiallyPersonal; postCode @PersonalData.IsPotentiallyPersonal; streetAddress @PersonalData.IsPotentiallyPersonal; }; annotate my.Incidents with @PersonalData : { EntitySemantics : 'Other' } { customer @PersonalData.FieldSemantics: 'DataSubjectID'; }; ``` ```csv [data/sap.capire.incidents-Customers.csv] ID,firstName,lastName,email,phone,dateOfBirth 1004155,Daniel,Watts,daniel.watts@demo.com,+44-555-123,1996-01-01 1004161,Stormy,Weathers,stormy.weathers@demo.com,,1981-01-01 1004100,Sunny,Sunshine,sunny.sunshine@demo.com,+01-555-789,1965-01-01 ``` ::: ## @PersonalData... > Source: /docs/guides/security/dpp-annotations#personaldata Let's break down the annotations to identify personal data, shown in the sample above. These annotations fall into three categories: - **Entity-level annotations** signify relevant entities as *Data Subjects*, *Data Subject Details*, or *Related Data Objects* in data privacy terms, as depicted in the graphic below. - **Key-level annotations** signify object primary keys, as well as references to data subjects (which have to be present on each object). - **Field-level annotations** identify elements containing personal data. Learn more about these annotations in the [@PersonalData OData vocabulary](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/PersonalData.md). {.learn-more} ### .EntitySemantics > Source: /docs/guides/security/dpp-annotations#entitysemantics The entity-level annotation `@PersonalData.EntitySemantics` signifies relevant entities as *Data Subject*, *Data Subject Details*, or *Other* in data privacy terms, as depicted in the following graphic. ![Shows the connections between the entities in the sample app. In addition via color coding it makes clear how entities are annotated: customers are data subject, addresses are data subject details and incidents are other.](./assets/data-privacy/Data-Subjects.drawio.svg){} The following table provides some further details. Annotation | Description --------------------- | ------------- `DataSubject` | The entities of this set describe a data subject (an identified or identifiable natural person), for example, Customer or Vendor. `DataSubjectDetails` | The entities of this set contain details of a data subject (an identified or identifiable natural person) but do not by themselves identify/describe a data subject, for example, Addresses. `Other` | Entities containing personal data or references to data subjects, but not representing data subjects or data subject details by themselves. For example, customer quote, customer order, or purchase order with involved business partners. These entities are relevant for audit logging. There are no restrictions on their structure. The properties should be annotated suitably with `FieldSemantics`. Hence, we annotate our model as follows: ```cds annotate my.Customers with @PersonalData: { EntitySemantics: 'DataSubject' // [!code focus] }; annotate my.Addresses with @PersonalData: { EntitySemantics: 'DataSubjectDetails' // [!code focus] }; annotate my.Incidents with @PersonalData: { EntitySemantics: 'Other' // [!code focus] }; ``` ### .DataSubjectRole > Source: /docs/guides/security/dpp-annotations#datasubjectrole Can be added to `@PersonalData.EntitySemantics: 'DataSubject'`. It's a user-chosen string specifying the role name to use. If omitted, the default is the entity name. Use case is similar to providing user-friendly labels for the UI, although in this case there's no i18n. In our model, we can add the `DataSubjectRole` as follows: ```cds annotate my.Customers with @PersonalData: { EntitySemantics: 'DataSubject', DataSubjectRole: 'Customer' // [!code focus] }; ``` ### .FieldSemantics: DataSubjectID > Source: /docs/guides/security/dpp-annotations#fieldsemantics-datasubjectid Use this annotation to identify data subject's unique key, or a reference to it. References are commonly associations or foreign keys in subject details entities, or related ones, referring to a subject entity. - Each `@PersonalData` entity needs to identify a `DataSubjectID` element. - For entities with `DataSubject` semantics, this is typically the primary key. - For entities with `DataSubjectDetails` or `Other` semantics, this is usually an association to the data subject. - Fields marked as `DataSubjectID` should use [`not null`](../databases/cdl-to-ddl#not-null-constraints) to guarantee a value is present at all times. Hence, we annotate our model as follows: ```cds annotate my.Customers with { ID @PersonalData.FieldSemantics: 'DataSubjectID' // [!code focus] }; annotate my.Addresses with { customer @PersonalData.FieldSemantics: 'DataSubjectID' // [!code focus] }; annotate my.Incidents with { customer @PersonalData.FieldSemantics: 'DataSubjectID' // [!code focus] }; ``` ### .IsPotentiallyPersonal > Source: /docs/guides/security/dpp-annotations#ispotentiallypersonal `@PersonalData.IsPotentiallyPersonal` tags which fields are personal and, for example, require audit logs if modified. ```cds annotate my.Customers with { firstName @PersonalData.IsPotentiallyPersonal; // [!code focus] lastName @PersonalData.IsPotentiallyPersonal; // [!code focus] email @PersonalData.IsPotentiallyPersonal; // [!code focus] phone @PersonalData.IsPotentiallyPersonal; // [!code focus] }; ``` ### .IsPotentiallySensitive > Source: /docs/guides/security/dpp-annotations#ispotentiallysensitive `@PersonalData.IsPotentiallySensitive` tags which fields are sensitive and, for example, require audit logs in case of access. ```cds annotate my.Customers with { creditCardNo @PersonalData.IsPotentiallySensitive; // [!code focus] }; ``` ## Next Steps... > Source: /docs/guides/security/dpp-annotations#next-steps Having annotated your data model with `@PersonalData` annotations, you can now go on to the respective tasks that leverage these annotations to automate as much as possible: - [*Automated Audit Logging*](dpp-audit-logging) - [*Personal Data Management*](dpp-pdm) - [*Data Retention Management*](dpp-drm) # Audit Logging > Source: /docs/guides/security/dpp-audit-logging The [`@cap-js/audit-logging`](https://www.npmjs.com/package/@cap-js/audit-logging) plugin provides out-of-the box support for automatic audit logging of data privacy-related events, in particular changes to *personal data* and reads of *sensitive* data. Find here a step-by-step guide how to use it. :::warning _The following is mainly written from a Node.js perspective. For Java's perspective, please see [Java - Audit Logging](../../java/auditlog.md)._ ::: ## Annotate Personal Data > Source: /docs/guides/security/dpp-audit-logging#annotate-personal-data First identify entities and elements (potentially) holding personal data using `@PersonalData` annotations, as explained in detail in the [*Annotating Personal Data* chapter](dpp-annotations) of these guides. > We keep using the [Incidents Management reference sample app](https://github.com/cap-js/incidents-app). ## Add the Plugin > Source: /docs/guides/security/dpp-audit-logging#add-the-plugin To enable automatic audit logging simply add the [`@cap-js/audit-logging`](https://www.npmjs.com/package/@cap-js/audit-logging) plugin package to your project like so: ```sh npm add @cap-js/audit-logging ``` ::: details Behind the Scenes… [CDS Plugin Packages](../../node.js/cds-plugins) are self-contained extensions. They not only include the relevant code but also bring their own default configuration. In our case, next to bringing the respective code, the plugin does the following: 1. Sets cds.requires.audit-log: true 2. Which in turn activates the effective `audit-log` configuration via **presets**: ```jsonc { "audit-log": { "handle": ["READ", "WRITE"], "outbox": true, "[development]": { "kind": "audit-log-to-console" }, "[hybrid]": { "kind": "audit-log-to-restv2" }, "[production]": { "kind": "audit-log-to-restv2" } }, "kinds": { "audit-log-to-console": { "impl": "@cap-js/audit-logging/srv/log2console" }, "audit-log-to-restv2": { "impl": "@cap-js/audit-logging/srv/log2restv2", "vcap": { "label": "auditlog" } } } } ``` **The individual configuration options are:** - `impl` — the service implementation to use - `outbox` — whether to use transactional outbox or not - `handle` — which events (`READ` and/or `WRITE`) to intercept and generate log messages from **The preset uses profile-specific configurations** for (hybrid) development and production. Use the `cds env` command to find out the effective configuration for your current environment: ::: code-group ```sh [w/o profile] cds env requires.audit-log ``` ```sh [production profile] cds env requires.audit-log --profile production ``` ::: ## Test-drive Locally > Source: /docs/guides/security/dpp-audit-logging#test-drive-locally The previous step is all we need to do to automatically log personal data-related events. Let's see that in action… 1. **Start the server** as usual: ```sh cds watch ``` 2. **Send an update** request that changes personal data: ::: code-group ```http [test/audit-logging.http] PATCH http://localhost:4004/admin/Customers(2b87f6ca-28a2-41d6-8c69-ccf16aa6389d) HTTP/1.1 Authorization: Basic alice:in-wonderland Content-Type: application/json { "firstName": "Jane", "lastName": "Doe" } ``` ::: [Find more sample requests in the Incident Management sample.](https://github.com/cap-js/incidents-app/blob/attachments/test/audit-logging.http){.learn-more} 3. **See the audit logs** in the server's console output: ```js { data_subject: { id: { ID: '2b87f6ca-28a2-41d6-8c69-ccf16aa6389d' }, role: 'Customer', type: 'AdminService.Customers' }, object: { type: 'AdminService.Customers', id: { ID: '2b87f6ca-28a2-41d6-8c69-ccf16aa6389d' } }, attributes: [ { name: 'firstName', old: 'Sunny', new: 'Jane' }, { name: 'lastName', old: 'Sunshine', new: 'Doe' } ], uuid: '5cddbc91-8edf-4ba2-989b-87869d94070d', tenant: 't1', user: 'alice', time: 2024-02-08T09:21:45.021Z } ``` ## Use SAP Audit Log Service > Source: /docs/guides/security/dpp-audit-logging#use-sap-audit-log-service While we simply dumped audit log messages to stdout in local development, we'll be using the SAP Audit Log Service on SAP BTP in production. Following is a brief description of the necessary steps for setting this up. A more comprehensive guide, incl. tutorials, is currently under development. ### Setup Instance and Deploy App > Source: /docs/guides/security/dpp-audit-logging#setup-instance-and-deploy-app For deployment in general, please follow the [deployment guide](../deploy/index.md). Check the rest of this guide before actually triggering the deployment (that is, executing `cf deploy`). Here is what you need to do additionally, to integrate with SAP Audit Log Service: 1. In your space, create a service instance of the _SAP Audit Log Service_ (`auditlog`) service with plan `premium`. 2. Add the service instance as _existing resource_ to your `mta.yml` and bind it to your application in its _requires_ section. Existing resources are defined like this: ```yml resources: - name: my-auditlog-service type: org.cloudfoundry.existing-service ``` [Learn more about *Audit Log Write API for Customers*](https://help.sap.com/docs/btp/sap-business-technology-platform/audit-log-write-api-for-customers?version=Cloud){.learn-more} ### Accessing Audit Logs > Source: /docs/guides/security/dpp-audit-logging#accessing-audit-logs There are two options to access audit logs: 1. Create an instance of service `auditlog-management` to retrieve audit logs via REST API, see [Audit Log Retrieval API Usage for the Cloud Foundry Environment](https://help.sap.com/docs/btp/sap-business-technology-platform/audit-log-retrieval-api-usage-for-subaccounts-in-cloud-foundry-environment). 2. Use the SAP Audit Log Viewer, see [Audit Log Viewer for the Cloud Foundry Environment](https://help.sap.com/docs/btp/sap-business-technology-platform/audit-log-viewer-for-cloud-foundry-environment). ## Generic Audit Logging > Source: /docs/guides/security/dpp-audit-logging#generic-audit-logging ### Behind the Scenes... > Source: /docs/guides/security/dpp-audit-logging#behind-the-scenes For all [defined services](../services/providing-services), the generic audit logging implementation does the following: - Intercept all write operations potentially involving personal data. - Intercept all read operations potentially involving sensitive data. - Determine the affected fields containing personal data, if any. - Construct log messages, and send them to the connected audit log service. - All emitted log messages are sent through the [transactional outbox](#transactional-outbox). - Apply resiliency mechanisms like retry with exponential backoff, and more. ## Custom Audit Logging > Source: /docs/guides/security/dpp-audit-logging#custom-audit-logging In addition to the generic audit logging provided out of the box, applications can also log custom events with custom data using the programmatic API. Connecting to the service: ```js const audit = await cds.connect.to('audit-log') ``` Sending log messages: ```js await audit.log('Foo', { bar: 'baz' }) ``` ::: tip Audit Logging as Just Another CAP Service The Audit Log Service API is implemented as a CAP service, with the service API defined in CDS as shown in the next section. In effect, the common patterns of [*CAP Service Consumption*](../services/consuming-services) apply, as well as all the usual benefits like *mocking*, *late-cut µ services*, *resilience* and *extensibility*. ::: ### Service Definition > Source: /docs/guides/security/dpp-audit-logging#service-definition Below is the complete reference modeling as contained in `@cap-js/audit-logging`. The individual operations and events are briefly discussed in the following sections. The service definition declares the generic `log` operation, which is used for all kinds of events, as well as the common type `LogEntry`, which declares the common fields of all log messages. These fields are filled in automatically by the base service and any values provided by the caller are ignored. Further, the service has pre-defined event payloads for the four event types: 1. _Log read access to sensitive personal data_ 1. _Log changes to personal data_ 1. _Security event log_ 1. _Configuration change log_ These payloads are based on [SAP Audit Log Service's REST API](https://help.sap.com/docs/btp/sap-business-technology-platform/audit-log-write-api-for-customers), which maximizes performance by omitting any intermediate data structures. ```cds namespace sap.auditlog; service AuditLogService { action log(event : String, data : LogEntry); event SensitiveDataRead : LogEntry { data_subject : DataSubject; object : DataObject; attributes : many { name : String; }; attachments : many { id : String; name : String; }; channel : String; }; event PersonalDataModified : LogEntry { data_subject : DataSubject; object : DataObject; attributes : many Modification; success : Boolean default true; }; event ConfigurationModified : LogEntry { object : DataObject; attributes : many Modification; }; event SecurityEvent : LogEntry { data : {}; ip : String; }; } /** Common fields, filled in automatically */ type LogEntry { uuid : UUID; tenant : String; user : String; time : Timestamp; } type DataObject { type : String; id : {}; } type DataSubject : DataObject { role : String; } type Modification { name : String; old : String; new : String; } ``` ### Sensitive Data Read > Source: /docs/guides/security/dpp-audit-logging#sensitive-data-read ```cds event SensitiveDataRead : LogEntry { data_subject : DataSubject; object : DataObject; attributes : many { name : String; }; attachments : many { id : String; name : String; }; channel : String; } type DataObject { type : String; id : {}; } type DataSubject : DataObject { role : String; } ``` Send `SensitiveDataRead` event log messages like that: ```js await audit.log ('SensitiveDataRead', { data_subject: { type: 'sap.capire.bookshop.Customers', id: { ID: '1923bd11-b1d6-47b6-a91b-732e755fa976' }, role: 'Customer', }, object: { type: 'sap.capire.bookshop.BillingData', id: { ID: '399a2704-3d2d-4fa1-9e7d-a4e45c67749b' } }, attributes: [ { name: 'creditCardNo' } ] }) ``` ### Personal Data Modified > Source: /docs/guides/security/dpp-audit-logging#personal-data-modified ```cds event PersonalDataModified : LogEntry { data_subject : DataSubject; object : DataObject; attributes : many Modification; success : Boolean default true; } type Modification { name : String; old : String; new : String; } ``` Send `PersonalDataModified` event log messages like that: ```js await audit.log ('PersonalDataModified', { data_subject: { type: 'sap.capire.bookshop.Customers', id: { ID: '1923bd11-b1d6-47b6-a91b-732e755fa976' }, role: 'Customer', }, object: { type: 'sap.capire.bookshop.Customers', id: { ID: '1923bd11-b1d6-47b6-a91b-732e755fa976' } }, attributes: [ { name: 'emailAddress', old: 'foo@example.com', new: 'bar@example.com' } ] }) ``` ### Configuration Modified > Source: /docs/guides/security/dpp-audit-logging#configuration-modified ```cds event ConfigurationModified : LogEntry { object : DataObject; attributes : many Modification; } ``` Send `ConfigurationModified` event log messages like that: ```js await audit.log ('ConfigurationModified', { object: { type: 'sap.common.Currencies', id: { ID: 'f79ba248-c348-4962-9fef-680c3b88807c' } }, attributes: [ { name: 'symbol', old: 'EUR', new: '€' } ] }) ``` ### Security Events > Source: /docs/guides/security/dpp-audit-logging#security-events ```cds event SecurityEvent : LogEntry { data : {}; ip : String; } ``` Send `SecurityEvent` log messages like that: ```js await audit.log ('SecurityEvent', { data: { user: 'alice', action: 'Attempt to access restricted service "PDMService" with insufficient authority' }, ip: '127.0.0.1' }) ``` > In the SAP Audit Log Service REST API, `data` is a String. For ease of use, the default implementation stringifies `data`, if it is provided as an object. [Custom implementations](#custom-implementation) should also handle both. ## Custom Implementation > Source: /docs/guides/security/dpp-audit-logging#custom-implementation In addition, everybody could provide new implementations in the same way as we implement the mock variant: ```js const { AuditLogService } = require('@cap-js/audit-logging') class MyAuditLogService extends AuditLogService { async init() { this.on('*', function (req) { // [!code focus] const { event, data } = req console.log(`[my-audit-log] - ${event}:`, data) }) return super.init() } } module.exports = MyAuditLogService ``` As always, custom implementations need to be configured in `cds.requires.<>.impl`: ```json { "cds": { "requires": { "audit-log": { "impl": "lib/MyAuditLogService.js" } } } } ``` ## Transactional Outbox > Source: /docs/guides/security/dpp-audit-logging#transactional-outbox By default, all log messages are sent through a transactional outbox. This means, when sent, log messages are first stored in a local outbox table, which acts like a queue for outbound messages. Only when requests are fully and successfully processed, these messages are forwarded to the audit log service. ![This graphic is explained in the accompanying text.](./assets/data-privacy/Transactional-Outbox.drawio.svg) This provides an ultimate level of resiliency, plus additional benefits: - **Audit log messages are guaranteed to be delivered** — even if the audit log service should be down for a longer time period. - **Asynchronous delivery of log messages** — the main thread doesn't wait for requests being sent and successfully processed by the audit log service. - **False log messages are avoided** — messages are forwarded to the audit log service on successfully committed requests; and skipped in case of rollbacks. This transparently applies to all implementations, even [custom implementations](#custom-implementation). You can opt out of this default by configuring cds.audit-log.\[development\].outbox = false. # Personal Data Management > Source: /docs/guides/security/dpp-pdm The Personal Data Management (PDM) guide explains how to integrate your CAP application with the SAP Personal Data Manager service to respond to data subject requests about their personal data stored in your application. :::warning To follow this cookbook hands-on you need an enterprise account. The SAP Personal Data Manager service is currently only available for [enterprise accounts](https://discovery-center.cloud.sap/missiondetail/3019/3297/). An entitlement in trial accounts is not possible. ::: SAP BTP provides the [*SAP Personal Data Manager (PDM)*](https://help.sap.com/docs/PERSONAL_DATA_MANAGER) which allows administrators to respond to the question "What data of me do you have?". To answer this question, the PDM service needs to fetch all personal data using an OData endpoint. That endpoint has to be provided by the application as follows. ## Annotate Personal Data > Source: /docs/guides/security/dpp-pdm#annotate-personal-data First identify entities and elements (potentially) holding personal data using `@PersonalData` annotations, as explained in detail in the [*Annotating Personal Data* chapter](dpp-annotations) of these guides. > We keep using the [Incidents Management reference sample app](https://github.com/cap-js/incidents-app). ## Provide a Service Interface to SAP Personal Data Manager > Source: /docs/guides/security/dpp-pdm#provide-a-service-interface-to-sap-personal-data-manager SAP Personal Data Manager needs to call into your application to read personal data so you have to define a respective service endpoint, complying to the interface required by SAP Personal Data Manager. Following the CAP principles, we recommend adding a new dedicated CAP service that handles all the personal data manager requirements for you. This keeps the rest of your data model clean and enables reuse, just as CAP promotes it. ### CAP Service Model for SAP Personal Data Manager > Source: /docs/guides/security/dpp-pdm#cap-service-model-for-sap-personal-data-manager Following the [best practice of separation of concerns](../domain/index#separation-of-concerns), we create a dedicated service for the integration with SAP Personal Data Manager: ::: code-group ```cds [srv/pdm-service.cds] using {sap.capire.incidents as db} from '../db/schema'; @requires: 'PersonalDataManagerUser' // security check service PDMService @(path: '/pdm') { // Data Privacy annotations on 'Customers' and 'Addresses' are derived from original entity definitions entity Customers as projection on db.Customers; entity Addresses as projection on db.Addresses; entity Incidents as projection on db.Incidents // create view on Incidents and Conversations as flat projection entity IncidentConversationView as select from Incidents { ID, title, urgency, status, key conversation.ID as conversation_ID, conversation.timestamp as conversation_timestamp, conversation.author as conversation_author, conversation.message as conversation_message, customer.ID as customer_ID, customer.email as customer_email }; // annotate new view annotate PDMService.IncidentConversationView with @(PersonalData.EntitySemantics: 'Other') { customer_ID @PersonalData.FieldSemantics: 'DataSubjectID'; }; // annotations for Personal Data Manager - Search Fields annotate Customers with @(Communication.Contact: { n : { surname: lastName, given : firstName }, bday : dateOfBirth, email: [{ type : #preferred, address: email}] }); }; ``` ::: ::: tip Make sure to have [indicated all relevant entities and elements in your domain model](dpp-annotations). ::: ### Provide Flat Projections > Source: /docs/guides/security/dpp-pdm#provide-flat-projections As an additional step, you have to create flat projections on the additional business data, like transactional data. In our model, we have `Incidents` and `Conversations`, which are connected via a composition. Since SAP Personal Data Manager needs flattened out structures, we define a helper view `IncidentConversationView` to flatten this out. We have to then add data privacy-specific annotations to this new view as well. The `IncidentConversationView` as transactional data is marked as `Other`. In addition, it is important to tag the correct field, which defines the corresponding data subject, in our case that is `customer_ID @PersonalData.FieldSemantics: 'DataSubjectID';` ### Annotating Search Fields > Source: /docs/guides/security/dpp-pdm#annotating-search-fields In addition, the most important search fields of the data subject have to be annotated with the corresponding annotation `@Communication.Contact`. To perform a valid search in the SAP Personal Data Manager application, you will need _Surname_, _Given Name_, and _Email_ or the _Data Subject ID_. Details about this annotation can be found in [Communication Vocabulary](https://github.com/SAP/odata-vocabularies/blob/main/vocabularies/Communication.md). Alternatively to the tuple _Surname_, _Given Name_, and _Email_, you can also use _Surname_, _Given Name_, and _Birthday_ (called `bday`), if available in your data model. Details about this can be found in [SAP Personal Data Manager - Developer Guide](https://help.sap.com/docs/personal-data-manager/4adcd96ce00c4f1ba29ed11f646a5944/v4-annotations?q=Contact&locale=en-US). ### Restrict Access Using the `@requires` Annotation > Source: /docs/guides/security/dpp-pdm#restrict-access-using-the-requires-annotation To restrict access to this sensitive data, the `PDMservice` is protected by the `@requires: 'PersonalDataManagerUser'` annotation. Calling the `PDMservice` externally without the corresponding permission is forbidden. The Personal Data Manager service calls the `PDMservice` with the needed role granted. This is configured in the _xs-security.json_ file, which is explained later. [Learn more about security configuration and the SAP Personal Data Manager.](https://help.sap.com/docs/PERSONAL_DATA_MANAGER/620a3ea6aaf64610accdd05cca9e3de2/4ee5705b8ded43e68bde610223722971.html#loio8eb6d9f889594a2d98f478bd57412ceb){.learn-more} At this point, you are done with your application. Let's set up the SAP Personal Data Manager and try it out. ## Connecting SAP Personal Data Manager > Source: /docs/guides/security/dpp-pdm#connecting-sap-personal-data-manager Next, we will briefly detail the integration to SAP Personal Data Manager. A more comprehensive guide, incl. tutorials, is currently under development. For further details, see the [SAP Personal Data Manager Developer Guide](https://help.sap.com/docs/personal-data-manager/4adcd96ce00c4f1ba29ed11f646a5944/what-is-personal-data-manager). ### Activate Access Checks in _xs-security.json_ > Source: /docs/guides/security/dpp-pdm#activate-access-checks-in-xs-securityjson Because we protected the `PDMservice`, we need to establish the security check properly. In particular, you need the _xs-security.json_ file to make the security check active. The following _xs-security.json_ is from our sample. ```json { "xsappname": "incidents-mgmt", "tenant-mode": "shared", "scopes": [ { "name": "$XSAPPNAME.PersonalDataManagerUser", "description": "Authority for Personal Data Manager", "grant-as-authority-to-apps": [ "$XSSERVICENAME(pdm)" ] } ] } ``` Here you define that your personal data manager service instance, called `pdm`, is allowed to access your CAP application granting the `PersonalDataManagerUser` role. ### Add `@sap/xssec` Library > Source: /docs/guides/security/dpp-pdm#add-sapxssec-library To make the authentication work, you have to enable the security strategy by installing the `@sap/xssec` package: ```sh npm install @sap/xssec ``` [Learn more about authorization in CAP using Node.js.](../../node.js/authentication#jwt){.learn-more} ### Build and Deploy Your Application > Source: /docs/guides/security/dpp-pdm#build-and-deploy-your-application The Personal Data Manager can't connect to your application running locally. Therefore, you first need to deploy your application. In our sample, we added two manifest files using `cds add cf-manifest` and SAP HANA configuration using `cds add hana`. The general deployment is described in detail in [Deploy Using Manifest Files](../deploy/to-cf). Make a production build: ```sh cds build --production ``` Deploy your application: ```sh cf create-service-push ``` ### Subscribe to SAP Personal Data Manager Service > Source: /docs/guides/security/dpp-pdm#subscribe-to-sap-personal-data-manager-service [Subscribe to the service](https://help.sap.com/docs/PERSONAL_DATA_MANAGER/620a3ea6aaf64610accdd05cca9e3de2/ef10215655a540b6ba1c02a96e118d66.html) from the _Service Marketplace_ in the SAP BTP cockpit. ![A screenshot of the tile in the cockpit for the SAP Personal Data Manager service.](assets/data-privacy/pdmCockpitCreate.png){} Follow the wizard to create your subscription. ### Create Role Collections > Source: /docs/guides/security/dpp-pdm#create-role-collections SAP Personal Data Manager comes with the following roles: Role Name | Role Template ----------|------ PDM_Administrator | PDM_Administrator PDM_CustomerServiceRepresentative | PDM_CustomerServiceRepresentative PDM_OperatorsClerk | PDM_OperatorsClerk All of these roles have two different _Application Identifiers_. ::: tip Application identifiers with **!b** are needed for the UI, and identifiers with **!t** are needed for executing the Postman collection. ::: [Learn more about defining a role collection in SAP BTP cockpit](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/4b20383efab341f181becf0a947a5498.html){.learn-more} ### Create a Service Instance > Source: /docs/guides/security/dpp-pdm#create-a-service-instance You need a configuration file, like the following, to create a service instance for the Personal Data Manager. `pdm-instance-config.json` ```json { "xs-security": { "xsappname": "incidents-mgmt", "authorities": ["$ACCEPT_GRANTED_AUTHORITIES"] }, "fullyQualifiedApplicationName": "incidents-mgmt", "appConsentServiceEnabled": true } ``` Create a service instance using the SAP BTP cockpit or execute the following command: ```sh cf create-service personal-data-manager-service standard incidents-mgmt-pdm -c ./pdm-instance-config.json ``` ### Bind the Service Instance to Your Application. > Source: /docs/guides/security/dpp-pdm#bind-the-service-instance-to-your-application With both the application deployed and the SAP Personal Data Manger service set up, you can now bind the service instance of the Personal Data Manager to your application. Use the URL of your application in a configuration file, such as the following example, which you need when binding a service instance. `pdm-binding-config.json` ```json { "fullyQualifiedApplicationName": "incidents-mgmt", "fullyQualifiedModuleName": "incidents-mgmt-srv", "applicationTitle": "PDM Incidents", "applicationTitleKey": "PDM Incidents", "applicationURL": "https://incidents-mgmt-srv.cfapps.eu10.hana.ondemand.com/", // get the URL from the CF CLI command: cf apps "endPoints": [ { "type": "odatav4", "serviceName": "pdm-service", "serviceTitle": "Incidents Management", "serviceTitleKey": "IncidentsManagement", "serviceURI": "pdm", "hasGdprV4Annotations": true, "cacheControl": "no-cache" } ] } ``` Here the `applicationURL`, the `fullyQualifiedModuleName`, and the `serviceURI` have to be those of your Cloud Foundry deployment and your CAP service definition (_services-manifest.yaml_). Bind the service instance using the SAP BTP cockpit or execute the following command: ```sh cf bind-service incidents-mgmt-srv incidents-mgmt-pdm -c ./pdm-binding-config.json ``` ## Using the SAP Personal Data Manager Application > Source: /docs/guides/security/dpp-pdm#using-the-sap-personal-data-manager-application Open the SAP Personal Data Manager application from the _Instances and Subscriptions_ page in the SAP BTP cockpit. ![To open the application, open the three dot menu and select "Go to Application".](assets/data-privacy/pdmCockpit.png){} In the personal data manager application you can search for data subjects with _First Name_, _Last Name_, and _Date of Birth_, or alternatively with their _ID_. ![A screenshot of the SAP Personal Data Manager application.](assets/data-privacy/pdmApplication.png){} # Product Security Overview > Source: /docs/guides/security/data-protection CAP provides several features to ensure data protection that meet industry standards and regulatory requirements. ## Secure Communications > Source: /docs/guides/security/data-protection#secure-communications ### Encrypted Communication Channels > Source: /docs/guides/security/data-protection#encrypted-communication-channels *Integrity* and *confidentiality* of data being transferred between any communication endpoints needs to be guaranteed. In particular, this holds true for communication between client and server ([public zone](./overview#public-zone) resp. [platform zone](./overview#platform-zone)), but also for service-to-service communication (within a platform zone). That means the communication channels are established in a way that rules out undetected data manipulation or disclosure. #### Inbound Communication (Server) > Source: /docs/guides/security/data-protection#inbound-communication-server [SAP BTP](https://help.sap.com/docs/btp/sap-business-technology-platform/btp-security) exclusively establishes encrypted communication channels based on HTTPS/TLS as shown in the [architecture overview](./overview#architecture-overview) and hence fulfills the requirements out of the box. For all deployed (CAP) applications and platform services, the platform's API gateway resp. ingress router provides TLS endpoints accepting incoming request and forwards to the backing services via HTTP. The HTTP endpoints of microservices are only accessible for the router in terms of network technology (perimeter security) and therefore aren't visible for clients in public and platform zone. Likewise microservices can only serve a single network port, which the platform has opened for the hosting container. The router endpoints are configured with an up to date TLS protocol version containing a state-of-the-art cipher suite. Server authentication is given by X.509 server certificates signed by a trusted certificate authority. ::: tip It's mandatory for public clients to authenticate the server and to verify the server's identity by matching the target host name with the host name in the server certificate. ::: ::: tip Manually provided certificates for [custom domains](https://help.sap.com/docs/CUSTOM_DOMAINS/6f35a23466ee4df0b19085c9c52f9c29/4f4c3ff62fd2413089dce8a973620167.html) need to be signed by a [trusted certificate authority](https://help.sap.com/docs/btp/sap-business-technology-platform/trusted-certificate-authentication). ::: #### Outbound Communication (Client) > Source: /docs/guides/security/data-protection#outbound-communication-client As platform services and other applications deployed to BTP are only accessible via exposed TLS router endpoints, outbound connections are automatically secured as well. Consequently, technical clients have to [validate the server certificate](#inbound) for proper server authentication. Also here CAP application developers don't need to deal with HTTPS/TLS connection setup provided the client code is build on CAP offerings such as HANA Cloud Service or CloudSDK integration. ::: warning The **CAP application needs to ensure adequate protection of secrets** that are injected into CAP microservices, for example: - [mTLS authentication is enabled](https://help.sap.com/docs/btp/sap-business-technology-platform/enable-mtls-authentication-to-sap-authorization-and-trust-management-service-for-your-application) in the XSUAA service instance of your application and also for XSUAA reuse instances of platform services. - Ensure that [service bindings and keys](https://help.sap.com/docs/btp/sap-business-technology-platform/using-services-in-cloud-foundry-environment) aren't compromised (rotate regularly). - SAP BTP Connectivity services are maintained [securely](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/connectivity-security). ::: #### Internal Communication (Client and Server) > Source: /docs/guides/security/data-protection#internal-communication-client-and-server Depending on the target platform, closely coupled microservices of the application zone might also communicate via trusted network channels instead of using [outbound connections](#outbound). For instance, a CAP service could communicate to a CAP sidecar, which is deployed to the same container via localhost HTTP connection. ::: tip CAP allows to use alternative communication channels, but application operators are responsible to set up them in a secure manner. ::: ::: tip CAP applications don't have to deal with TLS, communication encryption, or certificates, for inbound as well as outbound connections. ::: ### Filtering Internet Traffic > Source: /docs/guides/security/data-protection#filtering-internet-traffic Reducing attack surface by filtering communication from or to public zone increases the overall security protection level. By default, the platform comes with a standard set of services and configurations to protect network communication building on security features of the underlying hyperscaler. ::: warning Measures to further **restrict web access to your application** can be applied at platform level and aren't offered by CAP. For instance, [CF Route service](https://docs.cloudfoundry.org/services/route-services.html) can be used to implement route-specific restriction rules. ::: ## Secure Authentication > Source: /docs/guides/security/data-protection#secure-authentication None-public resources may only be accessed by authenticated users. Hence, authentication plays a key role for product security on different levels: - **Business users** consume the application via web interface. In multitenant applications, they come from different subscriber tenants that need to be isolated from each other. - **Platform users** operate the application and have privileged access to its components on OS level (containers, configurations, logs etc.). Platform users come from the provider tenant. Managing user pools, providing a logon flow, and processing authentication are complex and highly security-critical tasks **that shouldn't be tackled by applications**. Instead, applications should rely on an identity service provided by the platform which is [seamlessly integrated by CAP](#authenticate-requests). Find more about platform and business users: [SAP BTP User and Member Management](https://help.sap.com/docs/btp/sap-business-technology-platform/user-and-member-management){.learn-more} ### Server Requests > Source: /docs/guides/security/data-protection#server-requests SAP BTP offers central identity services [SAP Cloud Identity Services - Identity Authentication](https://help.sap.com/docs/IDENTITY_AUTHENTICATION) resp. [SAP Authorization and Trust Management Service](https://help.sap.com/docs/CP_AUTHORIZ_TRUST_MNG) for managing and authenticating platform and business users providing: - User authentication flows (OpenID connect), for example, multifactor authentication - Federation of custom identity providers (IdPs) - Single-sign on - Principal propagation - Password and session policies etc. The central platform service provides applications with a large set of industry-proven security features, which is why applications don't have to develop their own extensions and run the risk of security flaws. CAP doesn't require any specific authentication strategy, but it provides out of the box integration with the platform identity service. On configured authentication, *all CAP endpoints are authenticated by default*. ::: warning ❗ **CAP applications need to ensure that an appropriate [authentication method](authentication) is configured**. It's highly recommended to establish integration tests to safeguard a valid configuration. ::: Learn more about user model and identity providers here: [SAP BTP Security](https://help.sap.com/docs/btp/sap-business-technology-platform/btp-security){.learn-more} ### Remote Services > Source: /docs/guides/security/data-protection#remote-services CAP microservices consume remote services and hence need to be authenticated as technical client as well. Similar to [request authentication](#authenticate-requests), CAP saves applications from having to implement secure setup of service to service communication: - CAP interacts with platform services such as [Event Mesh](../events/index.md) or [SaaS Provisioning Service](../deploy/to-cf) on basis of platform-injected service bindings. - CAP offers consumption of [Remote Services](../services/consuming-services) on basis of SAP BTP destinations. Note that the applied authentication strategy is specified by server offering and resp. configuration and not limited by CAP.
### Maintaining Sessions > Source: /docs/guides/security/data-protection#maintaining-sessions CAP microservices require [authentication](#authenticate-requests) of all requests, but they don't support logon flows for UI clients. Being stateless, they neither establish a session with the client to store login information such as an OAuth 2 token that needs to be passed in each server request. To close this gap, UI-based CAP applications can use an [Application Router](https://help.sap.com/docs/btp/sap-business-technology-platform/application-router) instance or service as reverse proxy as depicted in the [diagram](./overview#architecture-overview). The Application Router redirects the login to the identity service, fetches an OAuth2 token, and stores it into a secure session cookie. ::: warning ❗ The **Application Router endpoints don't hide CAP endpoints** in the service backend. Hence, authentication is still mandatory for CAP microservices. ::: ### Maintaining Secrets > Source: /docs/guides/security/data-protection#maintaining-secrets To run a CAP application that authenticates users and consumes remote services, **it isn't required to manage any secrets such as keys, tokens, or passwords**. Also CAP doesn't store any of them, but relies on platform [injection mechanisms](./overview#platform-environment) or destinations. ::: tip In case you still need to store any secrets, use a platform service [SAP Credential Store](https://help.sap.com/docs/CREDENTIAL_STORE). ::: ## Secure Authorization > Source: /docs/guides/security/data-protection#secure-authorization According to segregation of duties paradigm, user administrators need to control how different users may interact with the application. Critical combinations of authorizations must be avoided. Basically, access rules for [business users](#business-authz) are different from [platform users](#platform-authz). ### Business Users > Source: /docs/guides/security/data-protection#business-users To align with the principle of least privilege, applications need to enforce fine-grained access control for business users from the subscriber tenants. Depending from the business scenario, users need to be restricted to operations they perform on server resources, for example, reading an entity collection. Moreover, they might also be limited to a subset of data entries, that is, they may only operate on a filtered view on the data. The set of rules that apply to a user reflects a specific conceptual role that describes the interaction with the application to fulfill a business scenario. Obviously, the business roles are dependent from the scenarios and hence *need to be defined by the application developers*. Enforcing authorization rules at runtime is highly security-critical and shouldn't be implemented by the application as this would introduce the risk of security flaws. Instead, [CAP authorizations](authorization) follow a declarative approach allowing applications to design comprehensive access rules in the CDS model. Resources in the model such as services or entities can be restricted to users that fulfill specific conditions as declared in `@requires` or `@restrict` [annotations](authorization#restrictions). According to the declarations, server-side authorization enforcement is guaranteed for all requests. It's executed close before accessing the corresponding resources. ::: warning ❗ **By default, CAP services and entities aren't authorized**. Application developers need to **design and test access rules** according to the business need. ::: ::: tip To verify CAP authorizations in your model, it's recommended to use [CDS lint rules](../../tools/cds-lint/rules/index.md). ::: The rules prepared by application developers are applied to business users according to grants given by the subscribers user administrator, that is, they're applied tenant-specific. CAP authorizations can be defined dependently from [user claims](cap-users#claims) such as [XSUAA scopes or attributes](https://help.sap.com/docs/btp/sap-business-technology-platform/application-security-descriptor-configuration-syntax) that are deployed by application developers and granted by the user administrator of the subscriber. Hence, CAP provides a seamless integration of central identity service without technical lock-in. ::: tip You can generate the `xs-security.json` [descriptor file](https://help.sap.com/docs/btp/sap-business-technology-platform/protecting-your-application) of the application's XSUAA instance by executing `cds add xsuaa` in the project root folder. The XSUAA scopes, roles, and attributes are derived from the CAP authorization model. ::: ::: warning CAP authorization enforcement doesn't automatically log successful and unsuccessful authorization checks. Applications need to add corresponding custom handlers to support it. ::: #### Authorization of CAP Endpoints > Source: /docs/guides/security/data-protection#authorization-of-cap-endpoints In general, responses created by *standard* CAP handlers and services are created on need-to-know basis. This means, authorized users only receive server information according to their privilege. Therefore, business users won't gain information about server host names, any version of application server component, generated queries etc. Based on the CDS model and configuration of CDS services, the CAP runtime exposes following endpoints: | Name | Configuration | URL | Authorization | |-------------------|------------------|-------------------------------------------|-----------------------------------------------| | CDS Service `Foo` | `service Foo {}` | `//Foo/**`1 | `@restrict`/`@requires`2 | | | OData v2/v4 | `//Foo/$metadata`1 | See [here](authorization#requires) | | Index page | | `/index.html` | none, but disabled in production | > 1 See [protocols and paths](../../java/cqn-services/application-services#configure-path-and-protocol) > 2 No authorization by default Based on configured features, the CAP runtime exposes additional callback endpoints for specific platform service:
| Platform service | URL | Authorization | |------------------------------|-----------------------------|--------------------------------------------------------------------------------------------------------| | Multitenancy (SaaS Registry) | `/mt/v1.0/subscriptions/**` | Technical role `mtcallback` |
| Platform service | URL | Authorization | |------------------------------|-------------------------|---------------| | Multitenancy (SaaS Registry) | none so far for Node.js | |
Moreover, technical [MTXs CAP services](../multitenancy/mtxs) may be configured, for example, as sidecar microservice to support higher-level features such as Feature Toggles or Multitenancy: | CAP service | URL | Authorization | ----------- | --- | ------------- | [cds.xt.ModelProviderService](../multitenancy/mtxs#modelproviderservice) | `/-/cds/model-provider/**` | Internal, technical user1 | [cds.xt.DeploymentService](../multitenancy/mtxs#deploymentservice) | `/-/cds/deployment/**` | | Internal, technical user1, or technical role `cds.Subscriber` | [cds.xt.SaasProvisioningService](../multitenancy/mtxs#saasprovisioningservice) | `/-/cds/saas-provisioning/**` | Internal, technical user1, or technical roles `cds.Subscriber` resp. `mtcallback` | [cds.xt.ExtensibilityService](../multitenancy/mtxs#extensibilityservice) | `/-/cds/extensibility/**` | Internal, technical user1, or technical roles `cds.ExtensionDeveloper` > 1 The microservice running the MTXS CAP service needs to be deployed to the [application zone](./overview#application-zone) and hence has established trust with the CAP application client, for instance given by shared XSUAA instance. Authentication for a CAP sidecar needs to be configured just like any other CAP application. ::: warning ❗ Ensure that technical roles such as `cds.Subscriber`, `mtcallback`, or `emcallback` **are never included in business roles**. ::: ### Platform Users > Source: /docs/guides/security/data-protection#platform-users Similar to [business consumption](#business-authz), different scenarios apply on operator level that need to be separated by dedicated access rules: deployment resp. configuration, monitoring, support, audit logs etc. *CAP doesn't cover authorization of platform users*. Please refer to security documentation of the underlying SAP BTP runtime environment: - [Roles in the Cloud Foundry Environment](https://help.sap.com/docs/btp/sap-business-technology-platform/about-roles-in-cloud-foundry-environment) - [Roles in the Kyma Environment](https://help.sap.com/docs/btp/sap-business-technology-platform/assign-roles-in-kyma-environment) ## Secure Multi-Tenancy > Source: /docs/guides/security/data-protection#secure-multi-tenancy Multitenant SaaS-applications need to take care for security aspects on a higher level. Different subscriber tenants share the same runtime stack to interact with the CAP application. Ideally, from perspective of a single tenant, the runtime should look like a self-contained virtual system that doesn't interfere with any other tenant. All directly or indirectly involved services that process the business request require to isolate with regards to several dimensions: - No breakout to [persisted data](#isolated-persistent-data) - No breakout to [transient data](#isolated-transient-data) - Limited [resource consumption](#limiting-resource-consumption) The CAP runtime is designed from scratch to support tenant isolation: ### Isolated Persistent Data > Source: /docs/guides/security/data-protection#isolated-persistent-data Having configured [Multitenancy in CAP](../multitenancy/index.md), when serving a business request, CAP automatically targets an isolated HDI container dedicated for the request tenant to execute DB statements. Here, CAP's data query API based on [CQN](../../cds/cqn) is orthogonal to multitenancy, that is, custom CAP handlers can be implemented agnostic to MT. During tenant onboarding process, CAP triggers the HDI container creation via [SAP HANA Cloud Services](https://help.sap.com/docs/HANA_SERVICE_CF/cc53ad464a57404b8d453bbadbc81ceb/f70399be7fca4508aa0e33e138dbd84d.html). The containers have separated DB schemas and dedicated technical DB users for access. CAP guarantees that code for business requests runs on a DB connection opened for the technical user of the tenant's container. ### Isolated Transient Data > Source: /docs/guides/security/data-protection#isolated-transient-data Although CAP microservices are stateless, the CAP Java runtime (generic handlers inclusive) needs to cache data in-memory for performance reasons. For instance, filters for [instance-based authorization](authorization#instance-based-auth) are constructed only once and are reused in subsequent requests.
To minimize risk of a data breach by exposing transient data at runtime, the CAP Java runtime explicitly refrains from declaring and using static mutable objects in Java heap. Instead, request-related data such as the [EventContext](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/EventContext.html) is provided via thread-local storage. Likewise, data is stored in tenant-maps that are transitively referenced by the [CdsRuntime](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/CdsRuntime.html) instance. ::: warning Make sure that custom code doesn't break tenant data isolation. :::
Request-related data is propagated down the call stack via the continuation-local variable [cds.context](../../node.js/events#cds-context). ::: warning Make sure that custom code doesn't break tenant data isolation or leak data across concurrent requests. ::: As a best practice, you should not put any non-static variables in the closures of your service implementations. ##### **Bad example:** > Source: /docs/guides/security/data-protection#bad-example ::: code-group ```js [srv/cat-service.js] module.exports = srv => { let books // <- leaks data across tenants and concurrent requests // [!code error] srv.on('READ', 'Books', async function(req, next) { if (books) return books return books = await next() }) } ``` :::
### Limiting Resource Consumption > Source: /docs/guides/security/data-protection#limiting-resource-consumption Tenant-aware microservices also need to handle resource consumption of tenants, in particular with regards to CPU, memory, and network connections. Excessive use of resources requested by a single tenant could cause runtime problems for other consumers (noisy neighbor problem). CAP helps to control resource usage:
- Business request run in isolated Java threads and hence OS thread scheduling ensures fair distribution of CPU shares. - By default, tenants have dedicated DB connection pools.
- Fine granular processing of request (CAP handlers) to avoid disproportionate blocking times of the event loop. - Tenants have dedicated DB connection pools.
::: tip Make sure that custom code doesn't introduce excessive memory or CPU consumption within a single request. ::: Because OS resources are strictly limited in a virtualized environment, a single microservice instance can handle load of a limited set of tenants, only. [**Adequate sizing**](#dos-attacks) of your microservice is mandatory, that is, adjusting memory settings, connection pool sizes, request size limits etc. according to the business needs. Last but not least you need to implement a **scaling strategy** to meet increasing load requirements by additional microservice instances. ::: warning ❗ **Sizing and scaling** is up to application developers and operators. CAP default values aren't suitable for all applications. ::: ## Secure Against Untrusted Input > Source: /docs/guides/security/data-protection#secure-against-untrusted-input Without protection mechanism in place, a malicious user could misuse a valid (that is, authenticated) session with the server and attack valuable business assets. ### Injection Attacks > Source: /docs/guides/security/data-protection#injection-attacks Attackers can send malicious input data in a regular request to make the server perform unintended actions that can lead to serious data exploits. #### Common Attack Patterns > Source: /docs/guides/security/data-protection#common-attack-patterns - CAP's intrinsic data querying engine is immune with regards to [SQL injections](https://owasp.org/www-community/attacks/SQL_Injection) that are introduced by query parameter values that are derived from malicious user input. CQL statements are transformed into prepared statements that are executed in SQL databases such as SAP HANA. Be aware that injections are still possible even via CQL when the query structure (target entity, columns and so on) is based on user input:
```java String entity = ...; // from user input; String column = ...; // from user input; validate(entity, column); // for example, by comparing with positive list Select.from(entity).columns(b -> b.get(column)); ```
```js const entity = const column = validate(entity, column) // for example, by comparing with positive list SELECT.from(entity).columns(column) ```
::: warning Be careful with custom code when creating or modifying CQL queries. Additional input validation is needed when the query structure depends on the request's input. ::: - [Cross Site Scripting (XSS)](https://owasp.org/www-community/attacks/xss) is used by attackers to inject a malicious script, which is executed in the browser session of an unsuspecting user. By default, there are some protection mechanisms in place. For instance, CAP OData V4 adapter renders responses with HTTP, which prevents the browser from misinterpreting the context. On the client side, SAPUI5 provides input validation for all typed element properties and automatic output encoding in all standard controls. - Untrusted data being transferred may contain malware. [SAP Malware Scanning Service](https://help.sap.com/docs/MALWARE_SCANNING) is capable to scan provided input streams for viruses and is regularly updated. ::: warning ❗ Currently, CAP applications need to add custom handlers to **scan data being uploaded or downloaded**. ::: - [Path traversal](https://owasp.org/www-community/attacks/Path_Traversal) attacks aim to access parts of the server's file system outside the web root folder. As part of the [application zone](./overview#application-zone), an Application Router serves the static UI content of the application. The CAP microservice doesn't need to serve web content from file system. Apart from that the used web server frameworks such as Spring or Express already have adequate protection mechanisms in place. - [CLRF injections](https://owasp.org/www-community/vulnerabilities/CRLF_Injection) or [log injections](https://owasp.org/www-community/attacks/Log_Injection) can occur when untrusted user input is written to log output.
CAP Node.js offers a CLRF-safe [logging API](../../node.js/cds-log#logging-in-production) that should be used for application logs.
::: warning Currently, CAP applications need to care for escaping user data that is used as input parameter for application logging. It's recommended to make use of an existing Encoder such as OWASP [ESAPI](https://www.javadoc.io/doc/org.owasp.esapi/esapi/2.0.1/org/owasp/esapi/Encoder.html). :::
- [Deserialization of untrusted data](https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data) can lead to serious exploits including remote code execution. The OData adapter converts JSON payload into an object representation. Here it follows a hardened deserialization process where the deserializer capabilities (for example, no default types in Jackson) are restricted to a minimum. A strong input validation based on EDMX model is done as well. Moreover, deserialization errors terminate the request and are tracked in the application log. #### General Recommendations Against Injections > Source: /docs/guides/security/data-protection#general-recommendations-against-injections In general, to achieve perfect injection resistance, applications should have input validation, output validation, and a proper Content-Security-Policy in place. - CAP provides built-in support for **input validation**. Developers can use the [`@assert`](../services/constraints) annotation to define field-specific input checks. ::: warning Applications need to validate or sanitize all input variables according to the business context. ::: - With respect to **output encoding**, CAP OData adapters have proper URI encoding for all resource locations in place. Moreover, OData validates the JSON response according to the given EDMX schema. In addition, client-side protection is given by [SAPUI5](https://pages.community.sap.com/topics/ui5) standard controls - Applications should meet basic [Content Security Policy (CSP)](https://www.w3.org/TR/CSP2/) compliance rules to further limit the attack vector on client side. CSP-compatible browsers only load resources from web locations that are listed in the allowlist defined by the server. `Content-Security-Policy` header can be set as route-specific response header in the [Application Router](https://help.sap.com/docs/btp/sap-business-technology-platform/responseheaders). SAPUI5 is [CSP-compliant](https://sapui5.hana.ondemand.com/sdk/#/topic/fe1a6dba940e479fb7c3bc753f92b28c.html) as well. ::: warning Applications have to **configure Content Security Policy** to meet basic compliance. ::: ### Service Misuse Attacks > Source: /docs/guides/security/data-protection#service-misuse-attacks - [Server Side Request Forgery (SSRF)](https://owasp.org/www-community/attacks/Server_Side_Request_Forgery) abuses server functionality to read or update resources from a secondary system. CAP microservices are protected from this kind of attack if they use the [CAP standard mechanisms](#authenticate-remote) for service to service communication. - [Cross-Site Request Forgery (CSRF)](https://owasp.org/www-community/attacks/csrf) attacks make end users executing unwanted actions on the server while having established a valid web session. By default, the Application Router, which manages the session with the client, enforces a CSRF token protection (on basis of `x-csrf-token` headers). Hence, CAP services don't have to deal with CSRF protection as long as they don't maintain sessions with the client. SAPUI5 supports CSRF tokens on client side out of the box. - [Clickjacking](https://owasp.org/www-community/attacks/Clickjacking) is an attack on client side where end users are tricked to open foreign pages. SAPUI5 provides [protection mechanisms](https://sapui5.hana.ondemand.com/sdk/#/topic/62d9c4d8f5ad49aa914624af9551beb7.html) against this kind of attack. ::: warning To protect SAPUI5 applications against clickjacking, configure `frame options`. ::: ### Denial-of-Service Attacks > Source: /docs/guides/security/data-protection#denial-of-service-attacks [Denial-of-service (DoS)](https://owasp.org/www-community/attacks/Denial_of_Service) attacks attempt to reduce service availability for legitimate users. This can happen by erroneous server behavior upon a single large or a few specially crafted malicious requests that bind an excessive amount of shared OS resources such as CPU, memory, or network connections. Since OS resource allocations are distributed over the entire request, DoS-prevention needs to be addressed in all different layers of the runtime stack: #### HTTP Server and CAP Protocol Adapter > Source: /docs/guides/security/data-protection#http-server-and-cap-protocol-adapter The used web server frameworks such as [Spring/Tomcat](https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties.server) or [Express](https://expressjs.com/) start with reasonable default limits, for example: - Maximum size of the HTTP request header. - Maximum size of the HTTP request body. - Maximum queue length for incoming connection requests. - Maximum number of connections that the server accepts and processes at any given time. - Connection timeout. Additional size limits and timeouts (request timeout) are established by the reverse proxy components, API Gateway and Application Router. ::: tip If you want to apply an application-specific sizing, consult the corresponding framework documentation. See section [Maximum Request Body Size](../../node.js/cds-server#maximum-request-body-size) to find out how to restrict incoming requests to a CAP Node.js application depending on the body size. ::: Moreover, CAP adapters automatically introduce query results pagination in order to limit memory peaks (customize with [`@cds.query.limit`](../services/served-ootb#annotation-cds-query-limit)). The total number of request of OData batches can be limited by application configuration.
To limit the _amount of queries_ per OData `$batch`, use cds.odataV4.batch.maxRequests or. cds.odataV2.batch.maxRequests To prevent clients from _requesting too much data_, you can define restrictions on `$expands` for your entities: - Prevent any expands from the entity: - `@Capabilities.ExpandRestrictions.Expandable: false` - Restrict expands for certain properties: - `@Capabilities.ExpandRestrictions.NonExpandableProperties: [...]` - Set maximum allowed depth of an `$expand` from this entity: - `@Capabilities.ExpandRestrictions.MaxLevels: ...` - Or you can set an **application-wide limit** with cds.query.restrictions.expand.maxLevels = \ that applies to all entities. Value `-1` indicates absence of limit. :::warning These restrictions are enforced on 'READ' events on [Application services](../../java/cqn-services/#application-services). ::: Good candidates for expand restrictions are associations to the same type (for example, when your entity represents a tree or a hierarchy1), backlink associations of compositions, or many-to-many associations. > 1Hierarchical requests from the UI5 tree table do not use expand and are not affected by expand restriction. To restrict clients to filter (or not to filter) the data, you can define restrictions on `$filter`: - Prevent filtering on the entity: - `@Capabilities.FilterRestrictions.Filterable: false` - Indicate that clients must send requests with `$filter`: - `@Capabilities.FilterRestrictions.RequiresFilter: true` - Indicate that `$filter` must contain certain properties: - `@Capabilities.FilterRestrictions.RequiredProperties: [...]` - Indicate that certain properties are non-filterable: - `@Capabilities.FilterRestrictions.NonFilterableProperties: [...]`
::: warning ❗ CAP applications have to limit the amount of `$expands` per request in a custom handler. Also, the maximum amount of requests per `$batch` request need to be configured with cds.odata.batch_limit = \ :::
::: tip Design your CDS services exposed to web adapters on need-to-know basis. Be especially careful when exposing associations. ::: #### CAP Service Runtime > Source: /docs/guides/security/data-protection#cap-service-runtime Open transactions are expensive as they bind many resources such as a database connection as well as memory buffers. To minimize the amount of time a transaction must be kept open, the CAP runtime offers an [Outbox Service](../../java/event-queues) that allows you to schedule asynchronous remote calls in the business transaction. Hence, the request time to process a business query, which requires a remote call (such as to an audit log server or messaging broker), is minimized and independent from the response time of the remote service. ::: tip Avoid synchronous requests to remote systems during a transaction. ::: [See why CPU time is fairly distributed among business requests](#limiting-resource-consumption){.learn-more} #### Database > Source: /docs/guides/security/data-protection#database As already outlined, database connections are a expensive resource. To limit overall usage, by default, the CAP runtime creates connection pools per subscriber tenant. Similarly, the DB driver settings such as SQL query timeout and buffer size have reasonable values. ::: tip
In case the default setting doesn't fit, connection pool properties and driver settings can be customized, respectively.
In case the default setting doesn't fit, connection pool properties and driver settings can be customized, respectively.
::: ::: warning ❗ Applications need to establish an adequate [Workload Management](https://help.sap.com/docs/HANA_CLOUD_DATABASE/f9c5015e72e04fffa14d7d4f7267d897/30f2e9cb92aa4f358dda4ac58e062d83.html) that controls DB resource usage. ::: #### Supplementary Measures > Source: /docs/guides/security/data-protection#supplementary-measures As outlined before, a well-sized microservice instance doesn't help to protect from service downtimes when excessive workload initiated by an attacker exceeds the available capacity. [Rate limiting](https://help.sap.com/docs/btp/developing-resilient-apps-on-sap-btp/rate-limiting-c56d72711eec41118f243054f2e92f94) is a possible counter measure to restrict the frequency of calls of a client. ::: warning ❗ Applications need to establish an adequate **rate limiting** strategy. ::: There's also the possibility to introduce request filtering and rate limiting on platform level via [Route Service](https://docs.cloudfoundry.org/services/route-services.html). It has the advantage that the requests can be controlled centrally before touching application service instances. In addition, the number of instances need to be **scaled horizontally** according to current load requirements. This can be achieved automatically by consuming [Application Autoscaler](https://help.sap.com/docs/Application_Autoscaler). ### Additional Protection Mechanisms > Source: /docs/guides/security/data-protection#additional-protection-mechanisms There are additional attack vectors to consider. For instance, naive URL handling in the server endpoints frequently introduces security gaps. Luckily, CAP applications don't have to implement HTTP/URL processing on their own as CAP offers sophisticated [protocol adapters](../../get-started/feature-matrix#consuming-services) such as OData V2/V4 that have the necessary security validations in place. The adapters also transform the HTTP requests into a corresponding CQN statement. Access control is performed on basis of CQN level according to the CDS model and hence HTTP Verb Tampering attacks are avoided. Also HTTP method override, using `X-Http-Method-Override` or `X-Http-Method` header, is not accepted by the runtime. The OData protocol allows to encode field values in query parameters of the request URL or in the response headers. This is, for example, used to specify: - [Pagination (implicit sort order)](../services/served-ootb#pagination--sorting) - [Searching Data](../services/served-ootb#searching-data) - Filtering ::: warning Applications need to ensure by means of CDS modeling that fields reflecting sensitive data are excluded and don't appear in URLs. ::: ::: tip It's recommended to serve all application endpoints via CAP adapters. Securing custom endpoints is left to the application. ::: In addition, CAP runs on a virtual machine with a managed heap that protects from common memory corruption vulnerabilities such as buffer overflow or range overflows. CAP also brings some tools to effectively reduce the attack vector of race condition vulnerabilities. These might be exposed when the state of resources can be manipulated concurrently and a consumer faces an unexpected state. CAP provides basic means of [concurrency control](../services/served-ootb#concurrency-control) on different layers, for example [ETags](../services/served-ootb#etag) and [pessimistic locks](../services/served-ootb#select-for-update). Moreover, Messages received from the [message queue](../events/index.md) are always in order. ::: tip Applications have to ensure a consistent data processing taking concurrency into account. :::
## Secure by Default and by Design > Source: /docs/guides/security/data-protection#secure-by-default-and-by-design ### Secure Default Configuration > Source: /docs/guides/security/data-protection#secure-default-configuration Where possible, CAP default configuration matches the secure by default principle: - There's no need to provide any password, credentials, or certificates to [protect communication](#secure-communications). - A CAP application bound to an XSUAA instance authenticates all endpoints [by default](#secure-authentication). Developers have to explicitly configure public endpoints if necessary. - Isolated multitenancy is provided out of the box. - Application logging has `INFO` level to avoid potential information disclosures. - CAP also has first-class citizen support for [Fiori UI](../uis/fiori) framework that brings a lot secure by default features in the UI client. Of course, several security aspects need application-specific configuration. For instance, this is true for [authorizations](#secure-authorization) or application [sizing](#dos-attacks). ::: tip It's recommended to ensure security settings by automated integration tests. ::: CAP provides some features that are suitable for development only such as - Index Page - Mock Users - Developer Dashboard (Java only) These features are deactivated in the production profile by default. ::: warning **Do not manually enable features for production that are disabled by the production profile**, as this could introduce serious security vulnerabilities. ::: [Learn more about production profiles in Java](../../java/developing-applications/configuring#production-profile){.learn-more} [Learn more about production profiles in Node.js](../../node.js/cds-env#profiles){.learn-more} ### Fail Securely > Source: /docs/guides/security/data-protection#fail-securely CAP runtime differentiates several types of error situations during request processing: - Exceptions because of invalid user input (HTTP 4xx). - Exceptions because of unexpected server behaviour, for example, network issues. - Unrecoverable errors due to serious issues in the VM (for example, lack of memory) or program flaws. In general, **exceptions immediately stop the execution of the current request**. In Java, the thrown [ServiceException](https://www.javadoc.io/doc/com.sap.cds/cds-services-api/latest/com/sap/cds/services/EventContext.html) is automatically scoped to the current request by means of thread isolation. { .java } CAP Node.js adds an exception wrapper to ensure that only the failing request is affected by the exception. { .node } Customers can react in dedicated exception handlers if necessary. In contrast, **errors stop the overall microservice** to ensure that security measures aren't weakened. ::: tip Align the exception handling in your custom coding with the provided exception handling capabilities of the CAP runtime. :::
# Extensibility > Source: /docs/guides/extensibility/ Learn here about intrinsic capabilities to extend your applications in verticalization and customization scenarios. Extensibility of CAP applications is greatly fueled by **CDS Aspects**, which allow to easily extend existing models with new fields, entities, relationships, or new or overridden annotations [→ Learn more about using CDS Aspects in the Domain Modeling guide](../domain/index#separation-of-concerns). ![This screenshot is explained in the accompanying text.](assets/extensibility.drawio.svg) As illustrated in the graphic above, different parties can build and deploy CDS Aspects-based extensions: - **Customizations** – Customers/Subscribers of SaaS solutions need options to tailor these to their needs, again using CDS Aspects to add custom fields and entities. - **Toggled Features** – SaaS providers can offer pre-built enhancement features, which can be switched on selectively per tenant using Feature Toggles, for example, specialization for selected industries. - **Composition** – Finally, 3rd parties can provide pre-built extension packages for reuse, which customers can pick and compose into own solutions. - **Verticalization** – 3rd parties can provide verticalized versions of a given base application, which they can in turn operate as verticalized SaaS apps.
The following guides give detailed information to each of these options. # Extending SaaS Applications > Source: /docs/guides/extensibility/customization ## Introduction & Overview > Source: /docs/guides/extensibility/customization#introduction--overview Subscribers (customers) of SaaS solutions frequently need to tailor these to their specific needs, for example, by adding specific extension fields and entities. All CAP-based applications intrinsically support such **SaaS extensions** out of the box. The overall process is depicted in the following figure: ![The graphic shows the three parts that are also discussed in this guide. Each part has it's steps. The first part is the one of the SaaS provider. As SaaS provider you need to deploy an extensible application and provide a guide that explains how to extend your application. In addition the SaaS provider should provide a project template for extension projects. The next part is for the SaaS customer. In this role you need to setup a tenant landscape for your extension, subscribe to the application you want to extend and authorize the extension developers. The last part is for the extension developer. As such, you start an extension project, develop and test your extension and then activate it.](assets/process_SAP_BTP.drawio.svg) In this guide, you will learn the following: - How to enable extensibility as a **SaaS provider**. - How to develop SaaS extensions as a **SaaS customer**. ## Prerequisites > Source: /docs/guides/extensibility/customization#prerequisites Before we start, you'll need a **CAP-based [multitenant SaaS application](../multitenancy/)** that you can modify and deploy. ::: tip Jumpstart You can download the ready-to-use [Orders Management application](https://github.com/capire/orders): ```sh git clone https://github.com/capire/orders cd orders cds add multitenancy ``` Also, ensure you have the latest version of `@sap/cds-dk` installed globally: ```sh npm update -g @sap/cds-dk ``` ::: ## As a SaaS Provider > Source: /docs/guides/extensibility/customization#as-a-saas-provider CAP provides intrinsic extensibility, which means all your entities and services are extensible by default. Your SaaS app becomes the **base app** for extensions by your customers, and your data model the **base model**. ### 1. Enable Extensibility > Source: /docs/guides/extensibility/customization#1-enable-extensibility Extensibility is enabled by running this command in your project root: ```sh cds add extensibility ``` ::: details Essentially, this automates the following steps… 1. It adds an `@sap/cds-mtxs` package dependency: ```sh npm add @sap/cds-mtxs ``` 2. It switches on cds.requires.extensibility: true in your _package.json_: ::: code-group ```json [package.json] { "name": "@capire/orders", "version": "1.0.0", "dependencies": { "@capire/common": "*", "@sap/cds": "^10", "@sap/cds-mtxs": "^4" }, "cds": { "requires": { "extensibility": true // [!code focus] } } } ``` ::: If `@sap/cds-mtxs` is newly added to your project install the dependencies: ```sh npm i ``` ### 2. Restrict Extension Points > Source: /docs/guides/extensibility/customization#2-restrict-extension-points Normally, you'll want to restrict which services or entities your SaaS customers are allowed to extend and to what degree they may do so. Take a look at the following configuration: ::: code-group ```jsonc [mtx/sidecar/package.json] { "cds": { "requires": { "cds.xt.ExtensibilityService": { "element-prefix": ["x_"], "extension-allowlist": [ { "for": ["sap.capire.orders"], "kind": "entity", "new-fields": 2 }, { "for": ["OrdersService"], "new-entities": 2 } ] } } } } ``` ::: This enforces the following restrictions: - All new elements have to start with `x_` → to avoid naming conflicts. - Only entities in namespace `sap.capire.orders` can be extended, with a maximum 2 new fields allowed. - Only the `OrdersService` can be extended, with a maximum of 2 new entities allowed. [Learn more about extension restrictions.](../multitenancy/mtxs#extension-restrictions){.learn-more} ### 3. Provide Template Projects > Source: /docs/guides/extensibility/customization#3-provide-template-projects To jumpstart your customers with extension projects, it's beneficial to provide a template project. Including this template with your application and making it available as a downloadable archive not only simplifies their work but also enhances their experience. #### Create an Extension Project (Template) > Source: /docs/guides/extensibility/customization#create-an-extension-project-template Extension projects are standard CAP projects extending the SaaS application. Create one for your SaaS app following these steps: 1. Create a new CAP project — `orders-ext` in our walkthrough: ```sh cd .. cds init orders-ext --nodejs code orders-ext # open in VS Code ``` 2. Add this to your _package.json_: ::: code-group ```jsonc [package.json] { "name": "@capire/orders-ext", "extends": "@capire/orders", "workspaces": [ ".base" ] } ``` ::: - `name` identifies the extension within a SaaS subscription; extension developers can choose the value freely. - `extends` is the name by which the extension model will refer to the base model. This must be a valid npm package name as it will be used by `cds pull` as a package name for the base model. It doesn't have to be a unique name, nor does it have to exist in a package registry like npmjs, as it will only be used locally. - `workspaces` is a list of folders including the one where the base model is stored. `cds pull` will add this property automatically if not already present. ::: details Uniqueness of base-model name… You use the `extends` property as the name of the base model in your extension project. Currently, it's not an issue if the base model name isn't unique. However, to prevent potential conflicts, we recommend using a unique name for the base model. ::: #### Add Sample Content > Source: /docs/guides/extensibility/customization#add-sample-content Create a new file _app/extensions.cds_ and fill in this content: ::: code-group ```cds [app/extensions.cds] namespace x_orders.ext; // only applies to new entities defined below using { OrdersService, sap.capire.orders.Orders } from '@capire/orders'; extend Orders with { x_new_field : String; } // ------------------------------------------- // Fiori Annotations annotate Orders:x_new_field with @title: 'New Field'; annotate OrdersService.Orders with @UI.LineItem: [ ... up to { Value: OrderNo }, { Value : x_new_field }, ... ]; ``` ::: The name of the _.cds_ file can be freely chosen. Yet, for the build system to work out of the box, it must be in either the `app`, `srv`, or `db` folder. ::: tip Keep it simple We recommend putting all extension files into `./app` and removing `./srv` and `./db` from extension projects. You may want to consider [separating concerns](../domain/index#separation-of-concerns) by putting all Fiori annotations into a separate _./app/fiori.cds_. ::: #### Add Test Data > Source: /docs/guides/extensibility/customization#add-test-data To support [quick-turnaround tests of extensions](#test-locally) using `cds watch`, add some test data. In your template project, create a file _test/data/sap.capire.orders-Orders.csv_ like that: ::: code-group ```csv [test/data/sap.capire.orders-Orders.csv] ID,createdAt,buyer,OrderNo,currency_code 7e2f2640-6866-4dcf-8f4d-3027aa831cad,2019-01-31,john.doe@test.com,1,EUR 64e718c9-ff99-47f1-8ca3-950c850777d4,2019-01-30,jane.doe@test.com,2,EUR ``` ::: #### Add a Readme > Source: /docs/guides/extensibility/customization#add-a-readme Include additional documentation for the extension developer in a _README.md_ file inside the template project. ::: code-group ```md [README.md] # Getting Started > Source: /docs/guides/extensibility/customization#getting-started Welcome to your extension project to `@capire/orders`. It contains these folders and files, following our recommended project layout: | File or Folder | Purpose | |----------------|--------------------------------| | `app/` | all extensions content is here | | `test/` | all test content is here | | `package.json` | project configuration | | `readme.md` | this getting started guide | ## Next Steps > Source: /docs/guides/extensibility/customization#next-steps - `cds pull` the latest models from the SaaS application - edit [`./app/extensions.cds`](./app/extensions.cds) to add your extensions - `cds watch` your extension in local test-drives - `cds push` your extension to **test** tenant - `cds push` your extension to **prod** tenant ## Learn More > Source: /docs/guides/extensibility/customization#learn-more Learn more at https://cap.cloud.sap/docs/guides/extensibility/customization. ``` ::: ### 4. Provide Extension Guides > Source: /docs/guides/extensibility/customization#4-provide-extension-guides You should provide documentation to guide your customers through the steps to add extensions. This guide should provide application-specific information along the lines of the walkthrough steps presented in this guide. Here's a rough checklist what this guide should cover: - [How to set up test tenants](#prepare-an-extension-tenant) for extension projects - [How to assign requisite roles](#prepare-an-extension-tenant) to extension developers - [How to start extension projects](#start-ext-project) from [provided templates](#templates) - [How to find deployed app urls](#pull-base) of test and prod tenants - [What can be extended?](#about-extension-models) → which services, entities, ... - [With enclosed documentation](../../cds/cdl#doc-comments) to the models for these services and entities. ### 5. Deploy Application > Source: /docs/guides/extensibility/customization#5-deploy-application Before deploying your SaaS application to the cloud, you can [test-drive it locally](../multitenancy/index#test-drive-locally). Prepare this by going back to your app with `cd orders`. With your application enabled and prepared for extensibility, you are ready to deploy the application as described in the [Deployment Guide](../deploy/). ## As a SaaS Customer > Source: /docs/guides/extensibility/customization#as-a-saas-customer The following sections provide step-by-step instructions on adding extensions. All steps are based on our Orders Management sample which can be [started locally for testing](../multitenancy/index#test-drive-locally). ::: details On BTP… To extend a SaaS app deployed to BTP, you'll need to subscribe to it [through the BTP cockpit](../multitenancy/index#subscribe-via-btp-cockpit). Refer to the [Deployment Guide](../deploy/to-cf) for more details on remote deployments. Also, you have to replace local URLs used in `cds` commands later with the URL of the deployed App Router. Use a passcode to authenticate and authorize you. Refer to the section on [`cds login`](#cds-login) for a simplified workflow. ::: ### 1. Subscribe to SaaS App > Source: /docs/guides/extensibility/customization#1-subscribe-to-saas-app It all starts with a customer subscribing to a SaaS application. In a productive application this is usually triggered by the platform to which the customer is logged on. The platform is using a technical user to call the application subscription API. In your local setup, you can simulate this with a [mock user](../../node.js/authentication#mock-users) `yves`. 1. In a new terminal, subscribe as tenant `t1`: ```sh cds subscribe t1 --to http://localhost:4005 -u yves: ``` Please note that the URL used for the subscription command is the sidecar URL, if a sidecar is used. Learn more about tenant subscriptions [via the MTX API for local testing](../multitenancy/mtxs#put-tenant).{.learn-more} 2. Verify that it worked by opening the [Orders Management Fiori UI](http://localhost:4004/orders/index.html#manage-orders) in a **new private browser window** and log in as `carol`, which is assigned to tenant `t1`. ![A screenshot of an SAP Fiori UI on the orders management example. It shows a table with the columns order number, customer, currency and date. The table contains two orders.](assets/image-20221004054556898.png){.mute-dark} ### 2. Prepare an Extension Tenant > Source: /docs/guides/extensibility/customization#2-prepare-an-extension-tenant In order to test-drive and validate the extension before activating to production, you'll first need to set up a test tenant. This is how you simulate it in your local setup: 1. Set up a **test tenant** `t1-ext` ```sh cds subscribe t1-ext --to http://localhost:4005 -u yves: ``` 2. Assign **extension developers** for the test tenant. > As you're using mocked auth, simulate this step by adding the following to the SaaS app's _package.json_, assigning user `bob` as extension developer for tenant `t1-ext`: ::: code-group ```json [package.json] { "cds": { "requires": { "auth": { "users": { "bob": { "tenant": "t1-ext", "roles": ["cds.ExtensionDeveloper"] } } } } } } ``` ::: ### 3. Start an Extension Project > Source: /docs/guides/extensibility/customization#3-start-an-extension-project Extension projects are standard CAP projects extending the subscribed application. SaaS providers usually provide **application-specific templates**, which extension developers can download and open in their editor. You can therefore use the extension template created in your walkthrough [as SaaS provider](#templates). Open the `orders-ext` folder in your editor. Here's how you do it using VS Code: ```sh code ../orders-ext ``` ![A screenshot of a readme.md file as it's described in the previous "Add a readme" section of this guide.](assets/orders-ext.png){.ignore-dark} ### 4. Pull the Latest Base Model > Source: /docs/guides/extensibility/customization#4-pull-the-latest-base-model Next, you need to download the latest base model. ```sh cds pull --from http://localhost:4005 -u bob: ``` > Run `cds help pull` to see all available options. This downloads the base model as a package into an npm workspace folder `.base`. The actual folder name is taken from the `workspaces` configuration. It also prepares the extension _package.json_ to reference the base model, if the extension template does not already do so. ::: details See what `cds pull` does… 1. Gets the base-model name from the extension _package.json_, property `extends`. If the previous value is not a valid npm package name, it gets changed to `"base-model"`. In this case, existing source files may have to be manually adapted. `cds pull` will notify you in such cases. 2. It fetches the base model from the SaaS app. 3. It saves the base model in a subdirectory `.base` of the extension project. This includes file _.base/package.json_ describing the base model as an npm package, including a `"name"` property set to the base-model name. 4. In the extension _package.json_: - It configures `.base` as an npm workspace folder. - It sets the `extends` property to the base-model name. ::: ### 5. Install the Base Model > Source: /docs/guides/extensibility/customization#5-install-the-base-model To make the downloaded base model ready for use in your extension project, install it as a package: ```sh npm install ``` This will link the base model in the workspace folder to the subdirectory `node_modules/@capire/orders` (in this example). ### 6. Write the Extension > Source: /docs/guides/extensibility/customization#6-write-the-extension Edit the file _app/extensions.cds_ and replace its content with the following: ::: code-group ```cds [app/extensions.cds] namespace x_orders.ext; // for new entities like SalesRegion below using { OrdersService, sap, sap.capire.orders.Orders } from '@capire/orders'; extend Orders with { // 2 new fields.... x_priority : String enum {high; medium; low} default 'medium'; x_salesRegion : Association to x_SalesRegion; } entity x_SalesRegion : sap.common.CodeList { // Value Help key code : String(11); } // ------------------------------------------- // Fiori Annotations annotate Orders:x_priority with @title: 'Priority'; annotate x_SalesRegion:name with @title: 'Sales Region'; annotate OrdersService.Orders with @UI.LineItem: [ ... up to { Value: OrderNo }, { Value: x_priority }, { Value: x_salesRegion.name }, ... ]; ``` ::: [Learn more about what you can do in CDS extension models](#about-extension-models){.learn-more} ::: tip Make sure **no syntax errors** are shown in the [CDS editor](../../tools/cds-editors#vscode) before going on to the next steps. ::: ### 7. Test-Drive Locally > Source: /docs/guides/extensibility/customization#7-test-drive-locally To conduct an initial test of your extension, run it locally with `cds watch`: ```sh cds watch --port 4006 ``` > This starts a local Node.js application server serving your extension along with the base model and supplied test data stored in an in-memory database.
> It does not include any custom application logic though. #### Add Local Test Data > Source: /docs/guides/extensibility/customization#add-local-test-data To improve local test drives, you can add _local_ test data for extensions. Edit the template-provided file `test/data/sap.capire.orders-Orders.csv` and add data for the new fields as follows: ::: code-group ```csv [test/data/sap.capire.orders-Orders.csv] ID,createdAt,buyer,OrderNo,currency_code,x_priority,x_salesRegion_code 7e2f2640-6866-4dcf-8f4d-3027aa831cad,2019-01-31,john.doe@test.com,1,EUR,high,EMEA 64e718c9-ff99-47f1-8ca3-950c850777d4,2019-01-30,jane.doe@test.com,2,EUR,low,APJ ``` ::: Create a new file `test/data/x_orders.ext-x_SalesRegion.csv` with this content: ::: code-group ```csv [test/data/x_orders.ext-x_SalesRegion.csv] code,name,descr AMER,"Americas","North, Central and South America" EMEA,"Europe, the Middle East and Africa","Europe, the Middle East and Africa" APJ,"Asia Pacific and Japan","Asia Pacific and Japan" ``` ::: #### Verify the Extension > Source: /docs/guides/extensibility/customization#verify-the-extension Verify your extensions are applied correctly by opening the [Orders Fiori Preview](http://localhost:4006/$fiori-preview/OrdersService/Orders#preview-app) in a **new private browser window**, log in as `bob`, and see columns _Priority_ and _Sales Region_ filled as in the following screenshot: ![This screenshot is explained in the accompanying text.](assets/image-20221004080722532.png){.mute-dark} > Note: the screenshot includes local test data, added as explained below. This test data will only be deployed to the local sandbox and not be processed during activation to the productive environment. ### 8. Push to Test Tenant > Source: /docs/guides/extensibility/customization#8-push-to-test-tenant Let's push your extension to the deployed application in your test tenant for final verification before pushing to production. ```sh cds push --to http://localhost:4005 -u bob: ``` ::: tip `cds push` runs a `cds build` on your extension project automatically. ::: ::: details Prepacked extensions To push a ready-to-use extension archive (.tar.gz or .tgz), run `cds push `. The argument can be a local path to the archive or a URL to download it from. Run `cds help push` to see all available options. ::: > You pushed the extension with user `bob`, which in your local setup ensures they are sent to your test tenant `t1-ext`, not the production tenant `t1`. ::: details Building extensions `cds build` compiles the extension model and validates the constraints defined by the SaaS application, for example, it checks if the entities are extendable. It will fail in case of compilation or validation errors, which will in turn abort `cds push`. _Warning_ messages related to the SaaS application base model are reclassified as _info_ messages. As a consequence they will not be shown by default. Execute `cds build --log-level info` to display all messages, although they should not be of interest for the extension developer. ::: #### Verify the Extension > Source: /docs/guides/extensibility/customization#verify-the-extension-1 Verify your extensions are applied correctly by opening the [Order Management UI](http://localhost:4004/orders/index.html#manage-orders) in a **new private browser window**, log in as `bob`, and check that columns _Priority_ and _Sales Region_ are displayed as in the following screenshot. Also, check that there's content with a proper label in the _Sales Region_ column. ![The screenshot is explained in the accompanying text.](assets/image-20221004081826167.png){.mute-dark} ### 9. Add Data > Source: /docs/guides/extensibility/customization#9-add-data After pushing your extension, you have seen that the column for _Sales Region_ was added, but is not filled. To change this, you need to provide initial data with your extension. Copy the data file that you created before from `test/data/` to `db/data/` and push the extension again. [Learn more about adding data to extensions](#add-data-to-extensions) {.learn-more} ### 10. Activate the Extension > Source: /docs/guides/extensibility/customization#10-activate-the-extension Finally, after all tests, verifications and approvals are in place, you can push the extension to your production tenant: ```sh cds push --to http://localhost:4005 -u carol: ``` > You pushed the extension with [mock user](../../node.js/authentication#mock-users) `carol`, which in your local setup ensures they are sent to your **production** tenant `t1`. ::: tip Simplify your workflow with `cds pull` and `cds push` Particularly when extending deployed SaaS apps, refer to [`cds login`](#cds-login) to save project settings and authentication data for later reuse. ::: # Appendices > Source: /docs/guides/extensibility/customization#appendices ## Configuring App Router > Source: /docs/guides/extensibility/customization#configuring-app-router In a deployed multitenant SaaS application, you need to set up the App Router correctly. This setup lets the CDS command-line utilities connect to the MTX Sidecar without needing to authenticate again. If you haven't used both the `cds add multitenancy` and `cds add approuter` commands, it's likely that you'll need to tweak the App Router configuration. You can do this by adding a route to the MTX Sidecar. ```json [app/router/xs-app.json] { "routes": [ { "source": "^/-/cds/.*", "destination": "mtx-api", "authenticationType": "none" } ] } ``` This ensures that the App Router doesn't try to authenticate requests to MTX Sidecar, which would fail. Instead, the Sidecar authenticates requests itself. ## About Extension Models > Source: /docs/guides/extensibility/customization#about-extension-models This section explains in detail about the possibilities that the _CDS_ languages provides for extension models. All names are subject to [extension restrictions defined by the SaaS app](../multitenancy/mtxs#extensibility-config). ### Extending the Data Model > Source: /docs/guides/extensibility/customization#extending-the-data-model Following [the extend directive](../../cds/cdl#extend) it is pretty straightforward to extend the application with the following new artifacts: - Extend existing entities with new (simple) fields. - Create new entities. - Extend existing entities with new associations. - Add compositions to existing or new entities. - Supply new or existing fields with default values, range checks, or value list (enum) checks. - Define a mandatory check on new or existing fields. - Define new unique constraints on new or existing entities. ```cds using {sap.capire.bookshop, sap.capire.orders} from '@capire/fiori'; using { cuid, managed, Country, sap.common.CodeList } from '@sap/cds/common'; namespace x_bookshop.extension; // extend existing entity extend orders.Orders with { x_Customer : Association to one x_Customers; x_SalesRegion : Association to one x_SalesRegion; x_priority : String @assert.range enum {high; medium; low} default 'medium'; x_Remarks : Composition of many x_Remarks on x_Remarks.parent = $self; } // new entity - as association target entity x_Customers : cuid, managed { email : String; firstName : String; lastName : String; creditCardNo : String; dateOfBirth : Date; status : String @assert.range enum {platinum; gold; silver; bronze} default 'bronze'; creditScore : Decimal @assert.range: [ 1.0, 100.0 ] default 50.0; PostalAddresses : Composition of many x_CustomerPostalAddresses on PostalAddresses.Customer = $self; } // new unique constraint (secondary index) annotate x_Customers with @assert.unique: { email: [ email ] } { email @mandatory; // mandatory check } // new entity - as composition target entity x_CustomerPostalAddresses : cuid, managed { Customer : Association to one x_Customers; description : String; street : String; town : String; country : Country; } // new entity - as code list entity x_SalesRegion: CodeList { key regionCode : String(11); } // new entity - as composition target entity x_Remarks : cuid, managed { parent : Association to one orders.Orders; number : Integer; remarksLine : String; } ``` ::: tip This example provides annotations for business logic handled automatically by CAP as documented in [_Providing Services_](../services/constraints). ::: Learn more about the [basic syntax of the `annotate` directive](../../cds/cdl#annotate) {.learn-more} ### Extending the Service Model > Source: /docs/guides/extensibility/customization#extending-the-service-model In the existing in `OrdersService`, the new entities `x_CustomerPostalAddresses` and `x_Remarks` are automatically included since they are targets of the corresponding _compositions_. The new entities `x_Customers` and `x_SalesRegion` are [autoexposed](../services/providing-services#auto-exposed-entities) in a read-only way as [CodeLists](../../cds/common#aspect-codelist). Only if wanted to _change_ it, you would need to expose them explicitly: ```cds using { OrdersService } from '@capire/fiori'; extend service OrdersService with { entity x_Customers as projection on extension.x_Customers; entity x_SalesRegion as projection on extension.x_SalesRegion; } ``` ### Extending UI Annotations > Source: /docs/guides/extensibility/customization#extending-ui-annotations The following snippet demonstrates which UI annotations you need to expose your extensions to the SAP Fiori elements UI. Add UI annotations for the completely new entities `x_Customers, x_CustomerPostalAddresses, x_SalesRegion, x_Remarks`: ```cds using { OrdersService } from '@capire/fiori'; // new entity -- draft enabled annotate OrdersService.x_Customers with @odata.draft.enabled; // new entity -- titles annotate OrdersService.x_Customers with { ID @( UI.Hidden, Common : {Text : email} ); firstName @title : 'First Name'; lastName @title : 'Last Name'; email @title : 'Email'; creditCardNo @title : 'Credit Card No'; dateOfBirth @title : 'Date of Birth'; status @title : 'Status'; creditScore @title : 'Credit Score'; } // new entity -- titles annotate OrdersService.x_CustomerPostalAddresses with { ID @( UI.Hidden, Common : {Text : description} ); description @title : 'Description'; street @title : 'Street'; town @title : 'Town'; country @title : 'Country'; } // new entity -- titles annotate x_SalesRegion : regionCode with @( title : 'Region Code', Common: { Text: name, TextArrangement: #TextOnly } ); // new entity in service -- UI annotate OrdersService.x_Customers with @(UI : { HeaderInfo : { TypeName : 'Customer', TypeNamePlural : 'Customers', Title : { Value : email} }, LineItem : [ {Value : firstName}, {Value : lastName}, {Value : email}, {Value : status}, {Value : creditScore} ], Facets : [ {$Type: 'UI.ReferenceFacet', Label: 'Main', Target : '@UI.FieldGroup#Main'}, {$Type: 'UI.ReferenceFacet', Label: 'Customer Postal Addresses', Target: 'PostalAddresses/@UI.LineItem'} ], FieldGroup #Main : {Data : [ {Value : firstName}, {Value : lastName}, {Value : email}, {Value : status}, {Value : creditScore} ]} }); // new entity -- UI annotate OrdersService.x_CustomerPostalAddresses with @(UI : { HeaderInfo : { TypeName : 'CustomerPostalAddress', TypeNamePlural : 'CustomerPostalAddresses', Title : { Value : description } }, LineItem : [ {Value : description}, {Value : street}, {Value : town}, {Value : country_code} ], Facets : [ {$Type: 'UI.ReferenceFacet', Label: 'Main', Target : '@UI.FieldGroup#Main'} ], FieldGroup #Main : {Data : [ {Value : description}, {Value : street}, {Value : town}, {Value : country_code} ]} }) {}; // new entity -- UI annotate OrdersService.x_SalesRegion with @( UI: { HeaderInfo: { TypeName : 'Sales Region', TypeNamePlural : 'Sales Regions', Title : { Value : regionCode } }, LineItem: [ {Value: regionCode}, {Value: name}, {Value: descr} ], Facets: [ {$Type: 'UI.ReferenceFacet', Label: 'Main', Target: '@UI.FieldGroup#Main'} ], FieldGroup#Main: { Data: [ {Value: regionCode}, {Value: name}, {Value: descr} ] } } ) {}; // new entity -- UI annotate OrdersService.x_Remarks with @( UI: { HeaderInfo: { TypeName : 'Remark', TypeNamePlural : 'Remarks', Title : { Value : number } }, LineItem: [ {Value: number}, {Value: remarksLine} ], Facets: [ {$Type: 'UI.ReferenceFacet', Label: 'Main', Target: '@UI.FieldGroup#Main'} ], FieldGroup#Main: { Data: [ {Value: number}, {Value: remarksLine} ] } } ) {}; ``` #### Extending Array Values > Source: /docs/guides/extensibility/customization#extending-array-values Extend the existing UI annotation of the existing `Orders` entity with new extension fields and new facets using the special [syntax for array-valued annotations](../../cds/cdl#extend-array-annotations). ```cds // extend existing entity Orders with new extension fields and new composition annotate OrdersService.Orders with @( UI: { LineItem: [ ... up to { Value: OrderNo }, // head {Value: x_Customer_ID, Label:'Customer'}, //> extension field {Value: x_SalesRegion.regionCode, Label:'Sales Region'}, //> extension field {Value: x_priority, Label:'Priority'}, //> extension field ..., // rest ], Facets: [..., {$Type: 'UI.ReferenceFacet', Label: 'Remarks', Target: 'x_Remarks/@UI.LineItem'} // new composition ], FieldGroup#Details: { Data: [..., {Value: x_Customer_ID, Label:'Customer'}, // extension field {Value: x_SalesRegion.regionCode, Label:'Sales Region'}, // extension field {Value: x_priority, Label:'Priority'} // extension field ] } } ); ``` The advantage of this syntax is that you do not have to replicate the complete array content of the existing UI annotation, you only have to add the delta. #### Semantic IDs > Source: /docs/guides/extensibility/customization#semantic-ids Finally, exchange the display ID (which is by default a GUID) of the new `x_Customers` entity with a human readable text which in your case is given by the unique property `email`. ```cds // new field in existing service -- exchange ID with text annotate OrdersService.Orders:x_Customer with @( Common: { //show email, not id for Customer in the context of Orders Text: x_Customer.email , TextArrangement: #TextOnly, ValueList: { Label: 'Customers', CollectionPath: 'x_Customers', Parameters: [ { $Type: 'Common.ValueListParameterInOut', LocalDataProperty: x_Customer_ID, ValueListProperty: 'ID' }, { $Type: 'Common.ValueListParameterDisplayOnly', ValueListProperty: 'email' } ] } } ); ``` ### Localizable Texts > Source: /docs/guides/extensibility/customization#localizable-texts To externalize translatable texts, use the same approach as for standard applications, that is, create a _i18n/i18n.properties_ file: ::: code-group ```properties [i18n/i18n.properties] SalesRegion_name_col = Sales Region Orders_priority_col = Priority ... ``` ::: Then replace texts with the corresponding `{i18n>...}` keys from the properties file. Make sure to run `cds build` again. Properties files must be placed in the `i18n` folder. If an entry with the same key exists in the SaaS application, the translation of the extension has preference. > This feature is available with `@sap/cds` 6.3.0 or higher. [Learn more about localization](../uis/i18n){.learn-more} ## Simplify Your Workflow With `cds login` > Source: /docs/guides/extensibility/customization#simplify-your-workflow-with-cds-login As a SaaS extension developer, you have the option to log in to the SaaS app and thus authenticate only once. This allows you to re-run `cds pull` and `cds push` against the app without repeating the same options over and over again – and you can avoid generating a passcode every time. Achieve this by running `cds login` once. This command fetches tokens using OAuth2 from XSUAA and saves them for later use. For convenience, further settings for the current project are also stored, so you don't have to provide them again (such as the app URL and tenant subdomain). ### Where Tokens Are Stored > Source: /docs/guides/extensibility/customization#where-tokens-are-stored Tokens are saved in the desktop keyring by default (libsecret on Linux, Keychain Access on macOS, or Credential Vault on Windows). Using the keyring is more secure because, depending on the platform, you can lock and unlock it, and data saved by `cds login` may be inaccessible to other applications you run. > For details, refer to the documentation of the keyring implementation used on your development machine. `cds login` therefore uses the keyring by default. To enable this, you need to install an additional Node.js module, [_keytar_](https://www.npmjs.com/package/keytar): ```sh npm i -g keytar ``` If you decide against using the keyring, you can request `cds login` to write to a plain-text file by appending `--plain`. ::: tip Switching to and from plain-text Once usage of the `--plain` option changes for a given SaaS app, `cds login` migrates pre-existing authentication data from the previous storage to the new storage. ::: ::: warning Handle secrets with caution Local storage of authentication data incurs a security risk: a potential malicious, local process might be able to perform actions you're authorized for, with the SaaS app, as your tenant. ::: > In SAP Business Application Studio, plain-text storage is enforced when using `cds login`, since no desktop keyring is available. The plain-text file resides in encrypted storage. ### How to Login > Source: /docs/guides/extensibility/customization#how-to-login If you work with Cloud Foundry (CF) and you have got the `cf` client installed, you can call `cds login` with just a passcode. The command runs the `cf` client to determine suitable apps from the org and space that you're logged in to. This allows you to interactively choose the login target from a list of apps and their respective URLs. To log in to the SaaS app in this way, first change to the folder you want to use for your extension project. Then run the following command (the one-time passcode will be prompted interactively if omitted): ```sh cds login [-p ] ``` :::details Advanced options If you need to call `cds login` automatically without user interaction, you may use the [Client Credentials](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/) grant, which does not require a passcode. You can then omit the `-p ` option but will instead have to provide the Client ID and a specific form of client secret to authenticate. Obtain these two from the `VCAP_SERVICES` environment variable in your deployed MTX server (`@sap/cds-mtxs`). In the JSON value, navigate to `xsuaa[0].credentials`. - If you find a `key` property (Private Key of the Client Certificate), XSUAA is configured to use X.509 (mTLS). Use this Private Key by specifying `cds login … -m [:key]`. - Otherwise, find the Client Secret in the `clientsecret` property and use `cds login … -c [:]` in an analogous way. **Note:** The `key` and `clientsecret` properties are secrets that should not be stored in an unsafe location in productive scenarios! [Learn more about environment variables / `VCAP_Services`.](../../node.js/cds-connect#bindings-in-cloud-platforms){.learn-more} If you leave out the respective secret (enclosed in square brackets above), you will be prompted to enter it interactively. This can be used to feed the secret from the environment to `cds login` via standard input, like so: ```sh echo $MY_KEY | cds login … -m ``` ::: For a synopsis of all options, run `cds help login`. :::details Login without CF CLI If you don't work with CF CLI, additionally provide the application URL and the subdomain as these can't be determined automatically: ```sh cds login [] -s ``` The `` is the URL that you get in your subscriber account when you subscribe to an application. You find the `` in the overview page of your subaccount in the SAP BTP Cockpit: ![Simplified UI, showing where to find the subdomain in the SAP BTP cockpit.](assets/subdomain-cockpit-sui.png) ::: ::: tip Multiple targets Should you later want to extend other SaaS applications, you can log in to them as well, and it won't affect your other logins. Logins are independent of each other, and `cds pull` etc. will be authenticated based on the requested target. ::: ### Simplified Workflow > Source: /docs/guides/extensibility/customization#simplified-workflow Once you've logged in to the SaaS app, you can omit the passcode, the app URL, and the tenant subdomain, so in your development cycle you can run: ```sh cds pull # develop your extension > Source: /docs/guides/extensibility/customization#develop-your-extension cds push # develop your extension > Source: /docs/guides/extensibility/customization#develop-your-extension-1 cds push # … > Source: /docs/guides/extensibility/customization# ``` ::: tip Override saved values with options For example, run `cds push -s -p ` to activate your extension in another subdomain. This usage of `cds push` may be considered a kind of cross-client transport mechanism. ::: ### Refreshing Tokens > Source: /docs/guides/extensibility/customization#refreshing-tokens Tokens have a certain lifespan, after which they lose validity. To save you the hassle, `cds login` also stores the refresh token sent by XSUAA alongside the token (depending on configuration) and uses it to automatically renew the token after it has expired. By default, refresh tokens expire much later than the token itself, allowing you to work without re-entering passcodes for multiple successive days. ### Cleaning Up > Source: /docs/guides/extensibility/customization#cleaning-up To remove locally saved authentication data and optionally, the project settings, run `cds logout` inside your extension project folder. Append `--delete-settings` to include saved project settings for the current project folder as well. `cds help logout` is available for more details. ::: tip Re-authenticate when your role-collection assignments have changed Run `cds logout` followed by `cds login` in order to fetch a token with the new scopes. ::: ### Debugging > Source: /docs/guides/extensibility/customization#debugging In case something unexpected happens, set the variable `DEBUG=cli` in your shell environment before re-running the corresponding command. ::: code-group ```sh [macOS/Linux] export DEBUG="cli" ``` ```cmd [Windows] set DEBUG=cli ``` ```powershell [Powershell] Set-Variable -Name "DEBUG" -Value "cli" ``` ::: ## Add Data to Extensions > Source: /docs/guides/extensibility/customization#add-data-to-extensions As described in [Add Data](#add-data), you can provide local test data and initial data for your extension. In this guide we copied local data from the `test/data` folder into the `db/data` folder. When using SQLite, this step can be further simplified. For `sap.capire.orders-Orders.csv`, just add the _new_ columns along with the primary key: ` ::: code-group ```csv [sap.capire.orders-Orders.csv] ID,x_priority,x_salesRegion_code 7e2f2640-6866-4dcf-8f4d-3027aa831cad,high,EMEA 64e718c9-ff99-47f1-8ca3-950c850777d4,low,APJ ``` ::: ::: warning Adding data only for missing columns doesn't work with SAP HANA With SAP HANA, you always have to provide the full set of data. ::: # Feature Toggles > Source: /docs/guides/extensibility/feature-toggles Toggled features are pre-built extensions built by the provider of a SaaS application, which can be switched on selectively per subscriber. ## Introduction and Overview > Source: /docs/guides/extensibility/feature-toggles#introduction-and-overview CAP feature-toggled aspects allow SaaS providers to create pre-built features as CDS models, extending the base models with new fields, entities, as well as annotations for SAP Fiori UIs. These features can be assigned to individual SaaS customers (tenants), users, and requests and are then activated dynamically at runtime, as illustrated in the following figure. ![This graphic shows an inbound request passing authentication and then the CAP runtime queries the database as well as the model provider service to know which features belong to the inbound request.](./assets/feature-toggles.drawio.svg) ### Get `cloud-cap-samples-java` for step-by-step Exercises > Source: /docs/guides/extensibility/feature-toggles#get-cloud-cap-samples-java-for-step-by-step-exercises The following steps will extend the [CAP samples for Java](https://github.com/SAP-samples/cloud-cap-samples-java) app to demonstrate how features can extend data models, services, as well as SAP Fiori UIs. If you want to exercise these steps, get [cloud-cap-samples-java](https://github.com/SAP-samples/cloud-cap-samples-java) before, and prepare to extend the *Fiori* app:
```sh git clone https://github.com/SAP-samples/cloud-cap-samples-java cd cloud-cap-samples-java mvn clean install ```
Now, open the app in your editor, for example, for VS Code type: ```sh code . ``` ### Get `cap/samples` for Step-By-Step Exercises > Source: /docs/guides/extensibility/feature-toggles#get-capsamples-for-step-by-step-exercises The following steps will extend the [cap/samples/bookstore](https://github.com/capire/bookstore) app to demonstrate how features can extend data models, services, as well as SAP Fiori UIs. If you want to exercise these steps, get [cap/samples](https://github.com/capire/samples) before, and prepare to extend the *bookstore* app: ```sh git clone --recurse-submodules https://github.com/capire/samples cd samples npm install ``` Now, open the `bookstore` app in your editor, for example, by this if you're using VS Code on macOS: ```sh code bookstore ``` ## Enable Feature Toggles > Source: /docs/guides/extensibility/feature-toggles#enable-feature-toggles ### Add `@sap/cds-mtxs` Package Dependency > Source: /docs/guides/extensibility/feature-toggles#add-sapcds-mtxs-package-dependency For example, like this: ```sh npm add @sap/cds-mtxs ``` ### Switch on `cds.requires.toggles` > Source: /docs/guides/extensibility/feature-toggles#switch-on-cdsrequirestoggles Switch on feature toggle support by adding cds.requires.toggles: true. ## Adding Features in CDS > Source: /docs/guides/extensibility/feature-toggles#adding-features-in-cds Add a subfolder per feature to folder *fts* and put `.cds` files into it. The name of the folder is the name you later on use in feature toggles to switch the feature on/off. In our samples app, we add two features `isbn` and `reviews` as depicted in the following screenshot: ![This screenshot is explained in the accompanying text.](./assets/image-20220628101642511.png){.ignore-dark} > The name of the *.cds* files within the *fts/* subfolders can be freely chosen. All *.cds* files found in there will be served, with special handling for *index.cds* files, as usual. ### Feature *fts/isbn* > Source: /docs/guides/extensibility/feature-toggles#feature-ftsisbn Create a file *fts/isbn/schema.cds* with this content: ```cds using { CatalogService, sap.capire.bookshop.Books } from '../../app/browse/fiori-service'; // Add new field `isbn` to Books extend Books with { isbn : String @title:'ISBN'; } // Display that new field in list on Fiori UI annotate CatalogService.Books with @( UI.LineItem: [... up to {Value:author}, {Value:isbn}, ...] ); ``` This feature adds a new field `isbn` to entity `Books` and extends corresponding SAP Fiori annotations to display this field in the *Browse Books* list view. ::: tip Note that all features will be deployed to each tenant database in order to allow toggling per user/request. ::: ### Feature *fts/reviews* > Source: /docs/guides/extensibility/feature-toggles#feature-ftsreviews Create a file *fts/reviews/schema.cds* with this content: ```cds using { CatalogService } from '../../app/browse/fiori-service'; // Display existing field `rating` in list on Fiori UI annotate CatalogService.Books with @( UI.LineItem: [... up to {Value:author}, {Value:rating}, ...] ); ``` This feature extends corresponding SAP Fiori annotations to display already existing field `rating` in the *Browse Books* list view. ### Limitations > Source: /docs/guides/extensibility/feature-toggles#limitations ::: warning Note the following limitations for `.cds` files in features: - no `.cds` files in subfolders, for example, `fts/isbn/sub/file.cds` - no `using` dependencies between features, any entity, service or type that you refer to or extend needs to be part of the base model - further limitations re `extend aspect` → to be documented ::: ## Toggling Features > Source: /docs/guides/extensibility/feature-toggles#toggling-features In principle, features can be toggled per request, per user, or per tenant; most commonly they'll be toggled per tenant, as demonstrated in the following. ### In Development > Source: /docs/guides/extensibility/feature-toggles#in-development
CAP Node.js' `mocked-auth` strategy has built-in support for toggling features per tenant, per user, or per request. To demonstrate toggling features per tenant, or user, you can add these lines of configuration to our `package.json` of the SAP Fiori app: ```json {"cds":{ "requires": { "auth": { "users": { "carol": { "tenant": "t1" }, "erin": { "tenant": "t2" }, "fred": { "tenant": "t2", "features":[] } }, "tenants": { "t1": { "features": ["isbn"] }, "t2": { "features": "*" } } } } }} ```
CAP Java's [Mock User Authentication with Spring Boot](../../java/security#mock-users) allows to assign feature toggles to users based on the mock user configuration. To demonstrate toggling features per user, you can add these lines to the mock user configuration in the `srv/src/main/resources/application.yaml` file: ```yaml cds: security.mock.users: - name: carol features: - isbn - name: erin features: - isbn - reviews - name: fred features: ```
In effect of this, for the user `carol` the feature `isbn` is enabled, for `erin`, the features `isbn` and `reviews` are enabled, and for the user `fred` all features are disabled. ### In Production > Source: /docs/guides/extensibility/feature-toggles#in-production
::: warning No features toggling for production yet Note that the previous sample is only for demonstration purposes. As user and tenant management is outside of CAP's scope, there's no out-of-the-box feature toggles provider for production yet. → Learn more about that in the following section [*Feature Vector Providers*](#feature-vector-providers). :::
For productive use, the mock user configuration must not be used. The set of active features is determined per request by the [Feature Toggles Info Provider](../../java/reflection-api#feature-toggles-info-provider). You can register a [Custom Implementation](../../java/reflection-api#custom-implementation) as a Spring bean that computes the active feature set based on the request's `UserInfo` and `ParameterInfo`.
## Test-Drive Locally > Source: /docs/guides/extensibility/feature-toggles#test-drive-locally To test feature toggles, just run your CAP server as usual, then log on with different users, assigned to different tenants, to see the effects. ### Run `cds watch` > Source: /docs/guides/extensibility/feature-toggles#run-cds-watch Start the CAP server with `cds watch` as usual: ```sh cds watch ``` → in the log output, note the line reporting: ```js [cds] - serving cds.xt.ModelProviderService { path: '/-/cds/model-provider', impl: '@sap/cds/srv/model-provider.js' } ``` > The `ModelProviderService` is used by the runtime to get feature-enhanced models. ### See Effects in SAP Fiori UIs > Source: /docs/guides/extensibility/feature-toggles#see-effects-in-sap-fiori-uis To see the effects in the UIs open three anonymous browser windows, one for each user to log in, and: 1. [Open SAP Fiori app in browser](http://localhost:4004/fiori-apps.html) and go to [Browse Books](http://localhost:4004/fiori-apps.html#Books-display). 2. Log in as `carol` and see `ISBN` column in list. 3. Log in as `erin` and see `Ratings` and `ISBN` columns in list. 4. Log in as `fred` and no features for *Fred*, even though same tenant as *Erin*. For example the displayed UI should look like that for `erin`: ![A standard SAP Fiori UI including the new columns ratings and isbn that are available to erin.](assets/image-20220630132726831.png) ## Model Provider in Sidecar > Source: /docs/guides/extensibility/feature-toggles#model-provider-in-sidecar The `ModelProviderService`, which is used for toggling features, is implemented in Node.js only. To use it with CAP Java apps, you run it in a so-called *MTX sidecar*. For a CAP Node.js project, this service is always run embedded with the main application. ### Create Sidecar as Node.js Project > Source: /docs/guides/extensibility/feature-toggles#create-sidecar-as-nodejs-project An MTX sidecar is a standard, yet minimalistic Node.js CAP project. By default it's added to a subfolder *mtx/sidecar* within your main project, containing just a *package.json* file:
::: code-group ```json [mtx/sidecar/package.json] { "name": "mtx-sidecar", "version": "0.0.0", "dependencies": { "@sap/cds": "^10", "@sap/cds-mtxs": "^4", }, "cds": { "profile": "mtx-sidecar" } } ``` :::
::: code-group ```json [mtx/sidecar/package.json] { "name": "mtx-sidecar", "version": "0.0.0", "dependencies": { "@sap/cds": "^10", "@sap/cds-mtxs": "^4", }, "cds": { "profiles": [ "mtx-sidecar", "java" ] } } ``` :::
[Learn more about setting up **MTX sidecars**.](../multitenancy/mtxs#sidecars){.learn-more} ### Add Remote Service Link to Sidecar > Source: /docs/guides/extensibility/feature-toggles#add-remote-service-link-to-sidecar
::: tip In Node.js apps you usually don't consume services from the sidecar. The *ModelProviderService* is served both, embedded in the main app as well as in the sidecar. The following is documented for the sake of completeness only... ::: You can use the `from-sidecar` preset to tell the CAP runtime to use the remote model provider from the sidecar: ```json "cds":{ "requires": { "toggles": true, "cds.xt.ModelProviderService": "from-sidecar" } } ``` [Learn more about configuring ModelProviderService.](../multitenancy/mtxs#model-provider-config){.learn-more}
You need to configure the CAP Java application to request the CDS model from the Model Provider Service. This is done in the `application.yaml` file of your application. To enable the Model Provider Service for local development, add the following configuration to the `default` profile: ```yaml cds: model: provider: url: http://localhost:4005 # remove, in case you need tenant extensibility extensibility: false ```
### Test-Drive Sidecar Locally > Source: /docs/guides/extensibility/feature-toggles#test-drive-sidecar-locally With the setup as described in place, you can run the main app locally with the Model Provider as sidecar. Simply start the main app and the sidecar in two separate shells: **First, start the sidecar** as the main app now depends on the sidecar: ```sh cds watch mtx/sidecar ``` **Then, start the main app** in the second shell:
```sh cds watch ```
```sh mvn spring-boot:run ```
#### Remote `getCsn()` Calls to Sidecar at Runtime > Source: /docs/guides/extensibility/feature-toggles#remote-getcsn-calls-to-sidecar-at-runtime When you now run and use our application again as described in the previous section [See Effects in SAP Fiori UIs](#test-fiori-node), you can see in the trace logs that the main app sends `getCsn` requests to the sidecar, which in response to that reads and returns the main app's models. That means, the models from two levels up the folder hierarchy as configured by `root: ../..` for development. ### See Effects in SAP Fiori UIs > Source: /docs/guides/extensibility/feature-toggles#see-effects-in-sap-fiori-uis-1 To see the effects in the UIs open three anonymous browser windows, one for each user to log in, and: 1. [Open SAP Fiori app in browser](localhost:8080/fiori.html) and go to [Browse Books](localhost:8080/fiori.html#browse-books). 2. Log in as `carol` and see `ISBN` column in list. 3. Log in as `erin` and see `Ratings` and `ISBN` columns in list. 4. Log in as `fred` and no features for *Fred*, even though same tenant as *Erin*. For example the displayed UI should look like that for `erin`: ![A standard SAP Fiori UI including the new columns ratings and isbn that are available to erin.](assets/image-20220630132726831.png) ## Feature Vector Providers > Source: /docs/guides/extensibility/feature-toggles#feature-vector-providers In principle, features can be toggled *per request* using the `req.features` property (`req` being the standard HTTP req object here, not the CAP runtimes `req` object). This property is expected to contain one of the following: - An array with feature names, for example, `['isbn','reviews']`. - A string with comma-separated feature names, for example, `'isbn,reviews'`. - An object with keys being feature names, for example, `{isbn:true,reviews:true}`. So, to add support for a specific feature toggles management you can add a simple Express.js middleware as follows, for example, in your `server.js`: ```js const cds = require ('@sap/cds') cds.middlewares.add((req,res,next) => { req.features = req.headers.features || 'isbn' next() }, { before: 'ctx_model' }) ``` ## Feature-Toggled Custom Logic > Source: /docs/guides/extensibility/feature-toggles#feature-toggled-custom-logic
[Evaluate the `FeatureTogglesInfo` in custom code](../../java/reflection-api#using-feature-toggles-in-custom-code) to check if a feature is enabled: ```java @Autowired FeatureTogglesInfo features; ... if (features.isEnabled("discount")) { // specific coding when feature 'discount' is enabled... } ```
Within your service implementations, you can react on feature toggles by inspecting `cds.context.features` like so: ```js const { features } = cds.context if ('isbn' in features) { // specific coding when feature 'isbn' is enabled... } if ('reviews' in features) { // specific coding when feature 'reviews' is enabled... } // common coding... ``` Or alternatively: ```js const { isbn, reviews } = cds.context.features if (isbn) { // specific coding when feature 'isbn' is enabled... } if (reviews) { // specific coding when feature 'reviews' is enabled... } // common coding... ```
# Deployment > Source: /docs/guides/deploy/ Learn here about deployment options for CAP application. # Deploy to Cloud Foundry > Source: /docs/guides/deploy/to-cf A comprehensive guide on deploying applications built with SAP Cloud Application Programming Model (CAP) to SAP BTP Cloud Foundry environment. ## Intro & Overview > Source: /docs/guides/deploy/to-cf#intro--overview After completing the functional implementation of your CAP application by following the [Getting Started](../../get-started/bookshop) or [Cookbook](../) guides, you finally deploy it to the cloud for production. The essential steps are illustrated in the following graphic: ![First prepare for production (once) and then freeze your dependencies (once and on upgrades). Next build and assemble and then deploy.](assets/deploy-setps.drawio.svg){} First, you apply these steps manually in an ad-hoc deployment, as described in this guide. Then, after successful deployment, you automate them using [CI/CD pipelines](cicd). ## Prerequisites > Source: /docs/guides/deploy/to-cf#prerequisites The following sections are based on a new project that you can create like this: ::: code-group ```sh [Node.js] cds init bookshop --nodejs --add sample cd bookshop ``` ```sh [Java] cds init bookshop --java --add sample cd bookshop ``` ::: ::: details Alternatively, use the ready-to-deploy sample project ::: code-group ```sh [Node.js] git clone https://github.com/capire/bookshop cd bookshop ``` ```sh [Java] git clone https://github.com/sap-samples/cloud-cap-samples-java cd cloud-cap-samples-java ``` :::
In addition, you need to prepare the following: #### 1. SAP BTP with SAP HANA Cloud Database Up and Running > Source: /docs/guides/deploy/to-cf#1-sap-btp-with-sap-hana-cloud-database-up-and-running - Access to [SAP BTP, for example a trial](https://developers.sap.com/tutorials/hcp-create-trial-account.html) - An [SAP HANA Cloud database running](https://help.sap.com/docs/hana-cloud/sap-hana-cloud-administration-guide/create-sap-hana-database-instance-using-sap-hana-cloud-central) in your subaccount - Entitlement for [`hdi-shared` service plan](https://help.sap.com/docs/hana-cloud/sap-hana-cloud-getting-started-guide/set-up-schema-or-hdi-container-cloud-foundry) for your subaccount - A [Cloud Foundry space](https://help.sap.com/docs/btp/sap-business-technology-platform/create-spaces?version=Cloud) ::: tip Starting the SAP HANA database takes several minutes Therefore, do these steps early on. In trial accounts, you need to start the database **every day**. ::: #### 2. Latest Versions of `@sap/cds-dk` > Source: /docs/guides/deploy/to-cf#2-latest-versions-of-sapcds-dk Ensure you have the latest version of `@sap/cds-dk` installed globally: ```sh npm -g outdated #> check whether @sap/cds-dk is listed npm i -g @sap/cds-dk #> if necessary ``` For Node.js projects, ensure that the latest version of `@sap/cds` is installed in your project: ```sh npm outdated #> check whether @sap/cds is listed npm i @sap/cds #> if necessary ``` #### 3. Cloud MTA Build Tool > Source: /docs/guides/deploy/to-cf#3-cloud-mta-build-tool - Run `mbt` in a terminal to check whether you've installed it. - If not, install it according to the [MTA Build Tool's documentation](https://sap.github.io/cloud-mta-build-tool/download). - For macOS/Linux machines, it's best to install using `npm`: ```sh npm i -g mbt ``` - For Windows, [please also install `GNU Make`](https://sap.github.io/cloud-mta-build-tool/makefile/). #### 4. Cloud Foundry CLI w/ MTA Plugins > Source: /docs/guides/deploy/to-cf#4-cloud-foundry-cli-w-mta-plugins - Run `cf -v` in a terminal to check whether you've installed version **8** or higher. - If not, install or update it according to the [Cloud Foundry CLI documentation](https://github.com/cloudfoundry/cli#downloads). - In addition, ensure to have the [MTA plugin for the Cloud Foundry CLI](https://github.com/cloudfoundry-incubator/multiapps-cli-plugin/tree/master/README.md) installed. ```sh cf add-plugin-repo CF-Community https://plugins.cloudfoundry.org cf install-plugin -f multiapps cf install-plugin -f html5-plugin ``` ## Prepare for Production > Source: /docs/guides/deploy/to-cf#prepare-for-production If you followed CAP's grow-as-you-go approach, you've developed your application with an in-memory database and basic (mocked) authentication. In the cloud, you typically use production-grade services like SAP HANA and authentication providers. The `cds add ` command ensures required services are configured correctly and their dependencies are added to your _package.json_. ### 1. SAP HANA Database > Source: /docs/guides/deploy/to-cf#1-sap-hana-database While you used SQLite (Node.js) or SQLite/H2 (Java) as a low-cost stand-in during development, you use an SAP HANA Cloud database for production: ```sh cds add hana ``` [Learn more about using SAP HANA for production.](../databases/hana){.learn-more} ### 2. Authorization/Authentication > Source: /docs/guides/deploy/to-cf#2-authorizationauthentication Configure your app for XSUAA-based authentication: ```sh cds add xsuaa ``` ::: tip This will also generate an `xs-security.json` file The roles/scopes are derived from authorization-related annotations in your CDS models. Ensure to rerun `cds compile --to xsuaa`, as documented in the [_Security_ guide](../security/cap-users#xsuaa-roles) whenever there are changes to these annotations. ::: [Learn more about SAP Authorization and Trust Management/XSUAA.](https://discovery-center.cloud.sap/serviceCatalog/authorization-and-trust-management-service?region=all){.learn-more} ### 3. Remote Service Consumption > Source: /docs/guides/deploy/to-cf#3-remote-service-consumption CAP supports two HTTP clients for remote service calls. #### SAP Cloud SDK > Source: /docs/guides/deploy/to-cf#sap-cloud-sdk If you intend to consume remote services in production, for example, via [BTP Destinations](https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/destination-service), add the requisite SAP Cloud SDK packages, like that for Node.js: ```shell npm add @sap-cloud-sdk/connectivity npm add @sap-cloud-sdk/http-client npm add @sap-cloud-sdk/resilience ``` [Learn more about consuming remote services with SAP Cloud SDK.](https://sap.github.io/cloud-sdk/docs/js/overview){.learn-more} #### Native Fetch Client > Source: /docs/guides/deploy/to-cf#native-fetch-client-beta- CAP provides a built-in remote client that uses the native Node.js `fetch` API. For limitations, see the warning below. During local development, you don't need SAP Cloud SDK, but you can still use it. For production, you still need SAP Cloud SDK. For example, you use it to resolve named destinations through the SAP BTP Destination service. CAP selects the native fetch client for each outgoing request according to the following rules: 1. If the destination requires features only available in SAP Cloud SDK (for example, SAP BTP Destination service resolution or non-basic authentication), CAP always uses SAP Cloud SDK. 2. If you explicitly set cds.remote.native_fetch to `true` or `false`, CAP uses that setting. 3. Otherwise, CAP uses native fetch when you haven't installed `@sap-cloud-sdk/http-client`. ::: warning Current limitations The native fetch client does not yet support named destinations using the SAP BTP Destination service. It supports only [application-defined destinations](../services/consuming-services#use-application-defined-destinations). In addition, it limits authentication to `NoAuthentication` and `BasicAuthentication`. ::: ### 4. MTA-Based Deployment > Source: /docs/guides/deploy/to-cf#4-mta-based-deployment You use the [Cloud MTA Build Tool](https://sap.github.io/cloud-mta-build-tool/) to execute the deployment. The modules and services are configured in an _mta.yaml_ deployment descriptor: ```sh cds add mta ``` [Learn more about MTA-based deployment.](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/d04fc0e2ad894545aebfd7126384307c.html?locale=en-US){.learn-more} ### 5. User Interfaces > Source: /docs/guides/deploy/to-cf#5-user-interfaces #### Option A: SAP Cloud Portal > Source: /docs/guides/deploy/to-cf#option-a-sap-cloud-portal If you intend to deploy **multitenant** applications with a UI, set up the [HTML5 Application Repository](https://discovery-center.cloud.sap/serviceCatalog/html5-application-repository-service) in combination with the [SAP Cloud Portal service](https://discovery-center.cloud.sap/serviceCatalog/cloud-portal-service): ```sh cds add portal ``` ::: tip `cds add portal` adds an _App Router_ configuration to your project The App Router acts as a single point-of-entry gateway to route requests to. In particular, it ensures user login and authentication in combination with XSUAA or IAS. ::: #### Option B: SAP BTP Application Frontend > Source: /docs/guides/deploy/to-cf#option-b-sap-btp-application-frontend-beta- For **single-tenant** applications, you can use the new [SAP BTP Application Frontend](https://help.sap.com/docs/application-frontend-service) service: ```sh cds add app-frontend ``` [Enable the service for consumption in your subaccount](https://help.sap.com/docs/application-frontend-service/application-frontend-service/enabling-service?locale=en-US){.learn-more} ::: details Other deployment variants... For **single-tenant** applications, you can integrate with SAP Build Work Zone, standard edition: ```sh cds add workzone ``` This approach uses the **managed App Router** provided by SAP Fiori Launchpad — you don't need to deploy your own. Instead, destinations are configured.
You might also use a custom App Router setup without SAP BTP Cloud Portal service: ```sh cds add approuter ``` [Learn more about the SAP BTP Application Router.](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/01c5f9ba7d6847aaaf069d153b981b51.html?locale=en-US){.learn-more}
However, in this case, you need to create symlinks from your _app_ folders to make them visible to the deployed App Router. The [samples _modulith_](https://github.com/capire/samples) project uses this setup for serving a static _index.html_ consuming Vue.js via CDN. [Find the symlink directory in the App Router's _resources_ folder](https://github.com/capire/samples/tree/main/.deploy/app-router/resources){.learn-more} ::: ### 6. Optional: Multitenancy > Source: /docs/guides/deploy/to-cf#6-optional-multitenancy To enable multitenancy for production, run the following command: ```sh cds add multitenancy ```
::: tip You're set! The previous steps are required _only once_ in a project's lifetime. With that done, we can repeatedly deploy the application. :::
## Build and Deploy > Source: /docs/guides/deploy/to-cf#build-and-deploy Make sure you are logged in to Cloud Foundry and target the space you want to deploy to: ```sh cf login --sso # to log on with SAP Universal ID cf target ``` [Learn more about `cf login`](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/7a37d66c2e7d401db4980db0cd74aa6b.html){.learn-more} If your project already includes a _package-lock.json_, freeze your updated dependencies: ```sh npm install --package-lock-only ``` You can now build and deploy the application: ```sh cds up ``` ::: details Essentially, this automates the following steps... ```sh # Installing app dependencies, e.g. > Source: /docs/guides/deploy/to-cf#installing-app-dependencies-eg npm i app/browse npm i app/admin-books # If project is monorepo > Source: /docs/guides/deploy/to-cf#if-project-is-monorepo ln -sf ../package-lock.json # If project is multitenant > Source: /docs/guides/deploy/to-cf#if-project-is-multitenant npm i --package-lock-only --prefix mtx/sidecar # If package-lock.json doesn't exist > Source: /docs/guides/deploy/to-cf#if-package-lockjson-doesnt-exist npm i --package-lock-only # Final assembly and deployment... > Source: /docs/guides/deploy/to-cf#final-assembly-and-deployment mbt build -t gen --mtar mta.tar cf deploy gen/mta.tar -f ``` ::: ::: details Test with `cds build` While `cds build` is already run as part of `mbt build` in `cds up`, you can also run it standalone to inspect what is generated for production: ```sh cds build --production ``` [Learn more about running and customizing `cds build`.](build){.learn-more} ::: [Got errors? See the troubleshooting guide.](../../get-started/get-help#mta){.learn-more} [Learn how to reduce the MTA archive size **during development**.](../../get-started/get-help#reduce-mta-size){.learn-more} This process can take some minutes and finally logs an output like this: ```log […] Application "bookshop" started and available at "[org]-[space]-bookshop..com" […] ``` You can use this URL to access the App Router as the entry point of your application. For **multitenant applications**, you have to subscribe a tenant first. The application is accessible via a tenant-specific App Router URL after subscription. ::: info SaaS Extensibility Share the generic App-Router URL with SaaS consumers for logging in as extension developers using `cds login` or other [extensibility-related commands](../extensibility/customization#prep-as-operator). ::: ::: tip No index page and SAP Fiori preview in the cloud The default index page and [SAP Fiori preview](../uis/fiori#fiori-preview), that you're used to seeing during local development, are meant only for the development profile and aren't available in the cloud. For productive applications, you should add a proper SAP Fiori elements application through one of the [user interface options](#add-ui) outlined before. ::: ### Inspect Apps in BTP Cockpit > Source: /docs/guides/deploy/to-cf#inspect-apps-in-btp-cockpit Visit the "Applications" section in your [SAP BTP cockpit](https://help.sap.com/docs/BTP/65de2977205c403bbc107264b8eccf4b/144e1733d0d64d58a7176e817fa6aeb3.html) to see the deployed apps: ![The screenshot shows the SAP BTP cockpit, when a user navigates to their dev space in the trial account and views all deployed applications.](./assets/apps-cockpit.png) ::: tip Next up: Assign the _admin_ role To access the admin APIs, assign the _admin_ role required by the `AdminService`. By default, CAP creates a **role collection** named _admin‑\‑\_. [Assign it to your user](https://help.sap.com/docs/btp/sap-business-technology-platform/assign-user-groups-to-role-collections) to get access. ::: ### Use MTA Extensions with `cds up` > Source: /docs/guides/deploy/to-cf#use-mta-extensions-with-cds-up For Cloud Foundry deployments, you can pass an [MTA extension descriptor](https://help.sap.com/docs/btp/sap-business-technology-platform/defining-mta-extension-descriptors) to `cds up` using `--overlay`: ```sh cds up --overlay .deploy/eu10-prod.mtaext ``` This allows you to keep landscape-specific deployment settings outside your base _mta.yaml_, for example, scaling parameters: ```yaml [eu10-prod.mtaext] _schema-version: 3.3.0 ID: bookshop-eu10-prod extends: bookshop modules: - name: bookshop-srv parameters: instances: 2 ``` ## Staying Up-to-date > Source: /docs/guides/deploy/to-cf#staying-up-to-date Deployed applications should freeze all their dependencies, including transient ones. Therefore, on first execution, `cds up` creates a _package-lock.json_ file for all application modules. It is **essential to regularly update dependencies** to consume latest bug fixes and improvements. Not doing so will increase the risk of **security vulnerabilities**, expose your application to **known bugs**, and make future upgrades significantly harder and more time-consuming. We recommend setting up [Dependabot](https://docs.github.com/en/code-security/dependabot), [Renovate](https://docs.renovatebot.com/) or similar automated solutions to update dependencies **one-by-one** to easily identify breaking changes, minimize risks, and ensure continuous compatibility and **stability of your application**. ## Upgrade Tenants in Java > Source: /docs/guides/deploy/to-cf#upgrade-tenants-in-java The CAP Java SDK offers `main` methods for Subscribe/Unsubscribe in the classes `com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe` that can be called from the command line. This way, you can run the tenant subscribe/unsubscribe for the specified tenant. This triggers your custom handlers, which is useful for local testing scenarios. To register all handlers of the application properly during the execution of a tenant operation `main` method, the component scan package must be configured. To set the component scan, the property cds.multitenancy.component-scan must be set to the package name of your application. The handler registration provides additional information that is used for the tenant subscribe, for example, messaging subscriptions that are created. ::: warning The MTX sidecar must be running You can stop the CAP Java backend when you call this method, but the MTX sidecar application must be running! ::: You can also automate this synchronization, for example using [Cloud Foundry Tasks](https://docs.cloudfoundry.org/devguide/using-tasks.html) on SAP BTP and [Module Hooks](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/b9245ba90aa14681a416065df8e8c593.html) in your MTA. The `main` method optionally takes tenant ID (string) as the first input argument and tenant options (JSON string) as the second input argument. Alternatively, you can use the environment variables `MTCOMMAND_TENANTS` and `MTCOMMAND_OPTIONS` instead of arguments. The command-line arguments have higher priority, so you can use them to override the environment variables. The method returns the following exit codes. | Exit Code | Result | |-----------:|---------------------------------------------------------------------------------------------------------------------| | 0 | Tenant subscribed/unsubscribed successfully. | | 3 | Failed to subscribe/unsubscribe the tenant. Rerun the procedure to make sure the tenant is subscribed/unsubscribed. | To run this method locally, use the following command where `` is the one of your applications: ::: code-group ```sh [>= Spring Boot 3.2.0] java -cp -Dloader.main=com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe org.springframework.boot.loader.launch.PropertiesLauncher [] ``` ```sh [< Spring Boot 3.2.0] java -cp -Dloader.main=com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe org.springframework.boot.loader.PropertiesLauncher [] ``` ::: In the SAP BTP, Cloud Foundry environment, it can be tricky to construct such a command. The reason is that the JAR file is extracted by the Java buildpack and the place of the Java executable isn't easy to determine. Also the place differs for different Java versions. Therefore, we recommend adapting the start command that is generated by the buildpack and run the adapted command: ::: code-group ```sh [>= Spring Boot 3.2.0] sed -i 's/org.springframework.boot.loader.launch.JarLauncher/org.springframework.boot.loader.launch.PropertiesLauncher/g' /home/vcap/staging_info.yml && sed -i 's/-Dsun.net.inetaddr.negative.ttl=0/-Dsun.net.inetaddr.negative.ttl=0 -Dloader.main=com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe/g' /home/vcap/staging_info.yml && jq -r .start_command /home/vcap/staging_info.yml | sed 's/^/ MTCOMMAND_TENANTS=my-tenant [MTCOMMAND_TENANTS=]/' | bash ``` ```sh [< Spring Boot 3.2.0] sed -i 's/org.springframework.boot.loader.JarLauncher/org.springframework.boot.loader.PropertiesLauncher/g' /home/vcap/staging_info.yml && sed -i 's/-Dsun.net.inetaddr.negative.ttl=0/-Dsun.net.inetaddr.negative.ttl=0 -Dloader.main=com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe/g' /home/vcap/staging_info.yml && jq -r .start_command /home/vcap/staging_info.yml | sed 's/^/ MTCOMMAND_TENANTS=my-tenant [MTCOMMAND_TENANTS=]/' | bash ``` ```sh [Java 8] sed -i 's/org.springframework.boot.loader.JarLauncher/-Dloader.main=com.sap.cds.framework.spring.utils.Subscribe/Unsubscribe org.springframework.boot.loader.PropertiesLauncher/g' /home/vcap/staging_info.yml && jq -r .start_command /home/vcap/staging_info.yml | sed 's/^/ MTCOMMAND_TENANTS=my-tenant [MTCOMMAND_TENANTS=]/' | bash ``` ::: # Deploy to Kyma > Source: /docs/guides/deploy/to-kyma You can run your CAP application in the [SAP BTP Kyma Runtime](https://discovery-center.cloud.sap/serviceCatalog/kyma-runtime?region=all), the SAP-managed offering for the [Kyma project](https://kyma-project.io/). ## Overview > Source: /docs/guides/deploy/to-kyma#overview Kyma is a Kubernetes-based runtime for deploying and managing containerized applications. Applications are packaged as container images—typically Docker images—and their deployment and operations are defined using Kubernetes resource configurations. Deploying apps on the SAP BTP Kyma Runtime requires two main artifact types: 1. **Container Images** – Your application packaged in a container 2. **Kubernetes Resources** – Configurations for deployment and scaling The following diagram illustrates the deployment workflow: ![A CAP Helm chart is added to your project. Then you build your project as container images and push those images to a container registry of your choice. As a last step the Helm chart is deployed to your Kyma cluster, where service instances of SAP BTP services are created and pods pull the previously created container images from the container registry.](assets/deploy-kyma.drawio.svg) ## Prerequisites > Source: /docs/guides/deploy/to-kyma#prerequisites + Use a Kyma-enabled [Trial Account](https://account.hanatrial.ondemand.com/) or purchase a Kyma cluster from SAP + You need a [Container Image Registry](#get-access-to-a-container-registry) + Get the required SAP BTP service entitlements + Install [Docker Desktop or Docker for Linux](https://docs.docker.com/get-docker/) + Download and install the following command line tools: + [`kubectl` command line client](https://kubernetes.io/docs/tasks/tools/) for Kubernetes + [`pack` command line tool](https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/) + [`helm` command line tool](https://helm.sh/docs/intro/install/) + Make sure your SAP HANA Cloud is [mapped to your namespace](https://community.sap.com/t5/technology-blogs-by-sap/consuming-sap-hana-cloud-from-the-kyma-environment/ba-p/13552718#toc-hId-569025164) + Ensure SAP HANA Cloud is accessible from your Kyma cluster by [configuring trusted source IPs](https://help.sap.com/docs/HANA_CLOUD/9ae9104a46f74a6583ce5182e7fb20cb/0610e4440c7643b48d869a6376ccaecd.html) #### Configure Kubernetes > Source: /docs/guides/deploy/to-kyma#configure-kubernetes Download the Kubernetes configuration from SAP BTP and move it to _$HOME/.kube/config_. [Learn more in the SAP BTP Kyma documentation](https://help.sap.com/docs/btp/sap-business-technology-platform/access-kyma-instance-using-kubectl){.learn-more} #### Get Access to a Container Registry > Source: /docs/guides/deploy/to-kyma#get-access-to-a-container-registry SAP BTP doesn't provide a container image registry (or container repository), but you can choose from offerings of hosted open source and private container image registries, as well as solutions that can be run on premise or in your own cloud infrastructure. ::: tip Ensure network access Verify the Kubernetes cluster has network access to the container registry, especially if hosted behind a VPN or within a restricted network environment. ::: #### Set Up Your Cluster for a Private Container Registry > Source: /docs/guides/deploy/to-kyma#set-up-your-cluster-for-a-private-container-registry To use a docker image from a private repository, you need to [create an image pull secret](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) and configure this secret for your containers. :::info Interactive setup If a pull secret does not exist for your namespace when deploying your application, the CLI will prompt you to set up the pull secret interactively. ::: ::: warning Assign limited permissions to the technical user For this secret, use a technical user with read-only permissions. This limits the risk, as anyone with access to the Kubernetes cluster could retrieve the password from the secret and potentially modify or publish images to the registry. ::: ## Deploy to Kyma > Source: /docs/guides/deploy/to-kyma#deploy-to-kyma Let's start with a new sample project and prepare it for production using an SAP HANA database and XSUAA for authentication:
```sh cds init bookshop --java --add sample && cd bookshop cds add hana,xsuaa ```
```sh cds init bookshop --nodejs --add sample && cd bookshop cds add hana,xsuaa ```
#### User Interfaces > Source: /docs/guides/deploy/to-kyma#user-interfaces-beta- If you need a UI, you can also add SAP Build Work Zone support: ```sh cds add workzone ``` > This is currently only supported for single-tenant scenarios. #### Add CAP Helm Charts > Source: /docs/guides/deploy/to-kyma#add-cap-helm-charts CAP provides a configurable [Helm chart](https://helm.sh/) for Node.js and Java applications, which can be added like so: ```sh cds add kyma ``` > You will be asked to provide a Kyma cluster domain and your container registry name. ::: details Running `cds build` now creates a _gen_/_chart_ folder This folder will have all the necessary files required to deploy the Helm chart. Files from the _chart_ folder are copied to _gen/chart_. They support the deployment of your CAP service, database, UI content, and the creation of instances for BTP services. ::: #### Build and Deploy > Source: /docs/guides/deploy/to-kyma#build-and-deploy **First, ensure the Docker daemon** is running, for example by starting Docker Desktop. You can now quickly deploy the application like so: ```sh cds up -2 k8s [ -n ] ``` ::: details Essentially, this automates the following steps... ```zsh cds add kyma # if not already done # Installing app dependencies, e.g. > Source: /docs/guides/deploy/to-kyma#installing-app-dependencies-eg # If package-lock.json doesn't exist > Source: /docs/guides/deploy/to-kyma#if-package-lockjson-doesnt-exist npm install --prefix app/browse npm run build --prefix app/browse # If package-lock.json doesn't exist > Source: /docs/guides/deploy/to-kyma#if-package-lockjson-doesnt-exist-1 npm i app/admin-books npm run build --prefix app/admin-books # If project is multitenant > Source: /docs/guides/deploy/to-kyma#if-project-is-multitenant npm i --package-lock-only mtx/sidecar # If package-lock.json doesn't exist > Source: /docs/guides/deploy/to-kyma#if-package-lockjson-doesnt-exist-2 npm i --package-lock-only # Build tasks > Source: /docs/guides/deploy/to-kyma#build-tasks cds build --production # Buildpack commands > Source: /docs/guides/deploy/to-kyma#buildpack-commands pack build bookshop-srv:latest --path gen/srv --builder builder-jammy-base --env BP_NODE_RUN_SCRIPTS="" pack build bookshop-html5-deployer:latest --path app/html5-deployer --builder builder-jammy-base --env BP_NODE_RUN_SCRIPTS="" # Final assembly and deployment, e.g. > Source: /docs/guides/deploy/to-kyma#final-assembly-and-deployment-eg helm upgrade --install bookshop ./gen/chart --namespace bookshop --wait --wait-for-jobs --timeout=10m kubectl rollout status deployment bookshop-srv --timeout=8m kubectl rollout status deployment bookshop-approuter --timeout=8m kubectl rollout status deployment bookshop-sidecar --timeout=8m ``` ::: _This command uses checksums to detect changes in your code. If any modifications are found, it automatically triggers a rebuild. The checksums are reflected in the Docker image tags._ This process can take a few minutes to complete and logs output like this: ```log […] The release bookshop is installed in namespace [namespace]. Your services are available at: [workload] - https://bookshop-[workload]-[namespace].[configured-domain] […] ``` You can use this URL to access the approuter as the entry point of your application. For **multitenant applications**, you have to subscribe a tenant first. The application is accessible via a tenant-specific URL after subscription. ::: info SaaS Extensibility Share the above App-Router URL with SaaS consumers for logging in as extension developers using `cds login` or other [extensibility-related commands](https://cap.cloud.sap/docs/guides/extensibility/customization#prep-as-operator). ::: ## Next Up... > Source: /docs/guides/deploy/to-kyma#next-up You would then [set up your CI/CD](cicd) for automating deployments, for example after merging pull requests. {} ## Deep Dives > Source: /docs/guides/deploy/to-kyma#deep-dives ### Configure Image Repository > Source: /docs/guides/deploy/to-kyma#configure-image-repository Specify the repository where you want to push the images: ::: code-group ```yaml [containerize.yaml] ... repository: ``` ::: ### Customize Helm Chart > Source: /docs/guides/deploy/to-kyma#customize-helm-chart #### About CAP Helm Charts > Source: /docs/guides/deploy/to-kyma#about-cap-helm-charts The following files are added to a _chart_ folder by executing `cds add kyma`: ```zsh chart/ ├── values.yaml # Default configuration of the chart ├── Chart.yaml # Chart metadata └── values.schema.json # JSON Schema for values.yaml file ``` [Learn more about _values.yaml_.](https://helm.sh/docs/chart_template_guide/values_files/){.learn-more} [Learn more about _Chart.yaml_.](https://helm.sh/docs/topics/charts/){.learn-more}
In addition, a `cds build` also puts some files to the _gen/chart_ folder: ```zsh chart/ ├── templates/ │ ├── NOTES.txt # Message printed after Helm upgrade │ ├── *.tpl # Template libraries used in template resources │ ├── *.yaml # Template files for Kubernetes resources ``` [Learn how to create a Helm chart from scratch.](https://helm.sh/docs){.learn-more} #### Configure > Source: /docs/guides/deploy/to-kyma#configure You can change the configuration of CAP Helm charts by editing the _chart/values.yaml_ file. The `helm` CLI also offers you other options to overwrite settings from _chart/values.yaml_ file: + Overwrite properties using the `--set` parameter. + Overwrite properties from a YAML or JSON file using the `-f` parameter. ::: tip Multiple deployment types It is recommended to do the main configuration in the _chart/values.yaml_ file and have additional YAML files for specific deployment types (dev, test, productive) and targets. ::: #### Global Properties > Source: /docs/guides/deploy/to-kyma#global-properties ::: code-group ```yaml [values.yaml] # Secret name to access container registry, only for private registries > Source: /docs/guides/deploy/to-kyma#secret-name-to-access-container-registry-only-for-private-registries imagePullSecret: name: # Kubernetes cluster ingress domain (used for application URLs) > Source: /docs/guides/deploy/to-kyma#kubernetes-cluster-ingress-domain-used-for-application-urls domain: # Container image registry where to pull the image from > Source: /docs/guides/deploy/to-kyma#container-image-registry-where-to-pull-the-image-from image: registry: ``` ::: #### Deployment Properties > Source: /docs/guides/deploy/to-kyma#deployment-properties The following properties are available for the `srv` key: ::: code-group ```yaml [values.yaml] srv: # Service bindings bindings: # Kubernetes container resources # https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ resources: # Map of additional env variables env: MY_ENV_VAR: 1 # Kubernetes Liveness, Readiness and Startup Probes # https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ health: liveness: path: readiness: path: startupTimeout: # Container image image: ``` ::: > You can explore more configuration options in the subchart's directory _gen/chart/charts/web-application_. ### SAP BTP Services > Source: /docs/guides/deploy/to-kyma#sap-btp-services You can find a list of SAP BTP services in the [Discovery Center](https://discovery-center.cloud.sap/viewServices?provider=all®ions=all&showFilters=true). To find out if a service is supported in the Kyma and Kubernetes environment, go to the **Service Marketplace** of your Subaccount in the SAP BTP Cockpit and select Kyma or Kubernetes in the environment filter. You can find information about planned SAP BTP, Kyma Runtime features in the [product road map](https://roadmaps.sap.com/board?PRODUCT=73554900100800003012&PRODUCT=73554900100800003012). #### Built-in SAP BTP Services > Source: /docs/guides/deploy/to-kyma#built-in-sap-btp-services The Helm chart supports creating service instances for commonly used services. Services are pre-populated in _chart/values.yaml_ based on the used services in the `requires` section of the CAP configuration. You can use the following services in your configuration: ::: code-group ```yaml [values.yaml] xsuaa: parameters: xsappname: HTML5Runtime_enabled: true # for SAP Launchpad service event-mesh: … connectivity: … destination: … html5-apps-repo-host: … hana: … service-manager: … saas-registry: … ``` ::: #### Arbitrary BTP Services > Source: /docs/guides/deploy/to-kyma#arbitrary-btp-services These are the steps to create and bind to an arbitrary service, using the binding of the feature toggle service to the CAP application as an example: 1. In the _chart/Chart.yaml_ file, add an entry to the `dependencies` array. ```yaml dependencies: ... - name: service-instance alias: feature-flags version: 0.1.0 ``` 2. Add service configuration and binding in _chart/values.yaml_: ```yaml feature-flags: serviceOfferingName: feature-flags servicePlanName: lite ... srv: bindings: feature-flags: serviceInstanceName: feature-flags ``` > The `alias` property in `dependencies` must match the property added in the root of _chart/values.yaml_ and the value of `serviceInstanceName` in the binding. ::: details Additional requirements for the SAP Connectivity service... To access the SAP Connectivity service, add the following modules in your Kyma Cluster: - connectivity-proxy - transparent-proxy - istio You can do so using the `kubectl` CLI: ```sh kubectl edit kyma default -n kyma-system ``` Then, add the three modules: ::: code-group ```yaml [editor] spec: modules: - name: connectivity-proxy - name: transparent-proxy - name: istio ``` ::: Finally, you should see a success message as follows: ```sh kyma.operator.kyma-project.io/default edited ``` [Learn more about adding modules from the Kyma Dashboard.](https://help.sap.com/docs/btp/sap-business-technology-platform/enable-and-disable-kyma-module?version=Cloud#loio1b548e9ad4744b978b8b595288b0cb5c){.learn-more} #### Configuration Options for Services > Source: /docs/guides/deploy/to-kyma#configuration-options-for-services _Services have the following configuration options:_ ::: code-group ```yaml [values.yaml] ### Required ### > Source: /docs/guides/deploy/to-kyma#required- serviceOfferingName: my-service servicePlanName: my-plan ### Optional ### > Source: /docs/guides/deploy/to-kyma#optional- # Use instead of generated nname > Source: /docs/guides/deploy/to-kyma#use-instead-of-generated-nname fullNameOverride: # Name for service instance in SAP BTP > Source: /docs/guides/deploy/to-kyma#name-for-service-instance-in-sap-btp externalName: # List of tags describing service, > Source: /docs/guides/deploy/to-kyma#list-of-tags-describing-service # copied to ServiceBinding secret in a 'tags' key > Source: /docs/guides/deploy/to-kyma#copied-to-servicebinding-secret-in-a-tags-key customTags: - foo - bar # Some services support additional configuration, > Source: /docs/guides/deploy/to-kyma#some-services-support-additional-configuration # as found in the respective service offering > Source: /docs/guides/deploy/to-kyma#as-found-in-the-respective-service-offering parameters: key: val jsonParameters: {} # List of secrets from which parameters are populated > Source: /docs/guides/deploy/to-kyma#list-of-secrets-from-which-parameters-are-populated parametersFrom: - secretKeyRef: name: my-secret key: secret-parameter ``` ::: > You can explore more configuration options in the subchart's directory _gen/chart/charts/service-instance_. #### Configuration Options for Service Bindings > Source: /docs/guides/deploy/to-kyma#configuration-options-for-service-bindings ::: code-group ``` yaml [values.yaml] : # Exactly one of these must be specified serviceInstanceName: my-service # within Helm chart serviceInstanceFullname: my-service-full-name # using absolute name # Additional parameters parameters: key: val ``` ::: #### Configuration Options for Container Images > Source: /docs/guides/deploy/to-kyma#configuration-options-for-container-images ::: code-group ``` yaml [values.yaml] repository: my-repo.docker.io # container repo name tag: latest # optional container image version tag ``` ::: #### HTML5 Applications > Source: /docs/guides/deploy/to-kyma#html5-applications ::: code-group ``` yaml [values.yaml] html5-apps-deployer: image: bindings: resources: env: # Name of your business service (unique per subaccount) SAP_CLOUD_SERVICE: ``` ::: [Container image]: #configuration-options-for-container-images [HTML5 application deployer]: https://help.sap.com/docs/BTP/65de2977205c403bbc107264b8eccf4b/9b178ab3388c4647b0c52f2c85641844.html [Kubernetes Container resources]: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ #### Backend Destinations > Source: /docs/guides/deploy/to-kyma#backend-destinations Backend destinations maybe required for HTML5 applications or for App Router deployment. They can be configured using `backendDestinations`. If you want to add an external destination, you can do so by providing the `external` property like this: ::: code-group ``` yaml [values.yaml] ... srv: # Key is the target service, e.g. 'srv' backendDestinations: srv-api: service: srv ui5: # [!code ++] external: true # [!code ++] name: ui5 # [!code ++] Type: HTTP # [!code ++] proxyType: Internet # [!code ++] url: https://ui5.sap.com # [!code ++] Authentication: NoAuthentication # [!code ++] ``` ::: > Our Helm chart will remove the `external` key and add the rest of the keys as-is to the environment variable. ### Modify > Source: /docs/guides/deploy/to-kyma#modify Modifying the Helm chart allows you to customize it to your needs. However, this has consequences if you want to update with the latest changes from the CAP template. You can run `cds add kyma` again to update your Helm chart. It has the following behavior for modified files: 1. Your changes of the _chart/values.yaml_ and _chart/Chart.yaml_ will not be modified. Only new or missing properties will be added by `cds add kyma`. 2. To modify any of the generated files such as templates or subcharts, copy the files from _gen/chart_ folder and place it in the same level inside the _chart_ folder. After the next `cds build` executions the generated chart will have the modified files. 3. If you want to have some custom files such as templates or subcharts, you can place them in the _chart_ folder at the same level where you want them to be in _gen/chart_ folder. They will be copied as is. ### Extend > Source: /docs/guides/deploy/to-kyma#extend Instead of modifying consider extending the CAP Helm chart. Just make sure adding new files to the Helm chart does not conflict with `cds add kyma`. ::: tip Consider Kustomize A modification-free approach to change files is to use [Kustomize](https://kustomize.io/) as a [post-processor](https://helm.sh/docs/topics/advanced/#post-rendering) for your Helm chart. This might be usable for small changes if you don't want to branch-out from the generated `cds add kyma` content. ::: ### Services from Cloud Foundry > Source: /docs/guides/deploy/to-kyma#services-from-cloud-foundry To bind service instances created on Cloud Foundry (CF) to a workload (`srv`, `hana-deployer`, `html5-deployer`, `approuter` or `sidecar`) in the Kyma environment, do the following: 1. Create a secret with credentials from the service key of that instance. 2. Use the `fromSecret` property inside the `bindings` key of the workload. For example, if you want to use an `hdi-shared` instance created on CF: 1. [Create a Kubernetes secret](https://kubernetes.io/docs/concepts/configuration/secret/#creating-a-secret) with service key credentials from CF 2. Add additional properties to the Kubernetes secret: ```yaml stringData: # <…> .metadata: | { "credentialProperties": [ { "name": "certificate", "format": "text"}, { "name": "database_id", "format": "text"}, { "name": "driver", "format": "text"}, { "name": "hdi_password", "format": "text"}, { "name": "hdi_user", "format": "text"}, { "name": "host", "format": "text"}, { "name": "password", "format": "text"}, { "name": "port", "format": "text"}, { "name": "schema", "format": "text"}, { "name": "url", "format": "text"}, { "name": "user", "format": "text"} ], "metaDataProperties": [ { "name": "plan", "format": "text" }, { "name": "label", "format": "text" }, { "name": "type", "format": "text" }, { "name": "tags", "format": "json" } ] } type: hana label: hana plan: hdi-shared tags: '[ "hana", "database", "relational" ]' ``` > Update the values of the properties accordingly. 3. Change `serviceInstanceName` to `fromSecret` for each workload with that service instance in `bindings` in _chart/values.yaml_: ```yaml [values.yaml] … srv: bindings: db: serviceInstanceName: ## [!code --] fromSecret: ## [!code ++] hana-deployer: bindings: hana: serviceInstanceName: ## [!code --] fromSecret: ## [!code ++] ``` 4. Delete `hana` in _chart/values.yaml_: ```yaml … hana: ## [!code --] serviceOfferingName: hana ## [!code --] servicePlanName: hdi-shared ## [!code --] … ``` 5. Make the following changes to _chart/Chart.yaml_: ```yaml … dependencies: … - name: service-instance ## [!code --] alias: hana ## [!code --] version: ">0.0.0" ## [!code --] … ``` ### Cloud Native Buildpacks > Source: /docs/guides/deploy/to-kyma#cloud-native-buildpacks Cloud Native Buildpacks provide advantages like embracing [best practices](https://buildpacks.io/features/) and secure standards such as: + Resulting images use an unprivileged user + Builds are [reproducible](https://buildpacks.io/docs/features/reproducibility/) + [Software Bill of Materials](https://buildpacks.io/docs/features/bill-of-materials/) (SBoM) baked into the image + Auto-detection of base images Additionally Cloud Native Buildpacks can be easily plugged together to fulfill more complex requirements. For example the [ca-certificates](https://github.com/paketo-buildpacks/ca-certificates) enables adding additional certificates to the system trust-store at build and runtime. When using Cloud Native Buildpacks you can continuously benefit from best practices coming from the community without any changes required. [Learn more about Cloud Native Buildpacks Concepts.](https://buildpacks.io/docs/for-platform-operators/concepts/){ .learn-more}
### CAP Operator > Source: /docs/guides/deploy/to-kyma#cap-operator The [CAP Operator](https://sap.github.io/cap-operator/) brings enterprise-grade lifecycle automation to CAP applications. It is a Kubernetes-native Operator that declaratively deploys and manages application versions, automates tenant operations, and manages domains and networking consistently across Kyma and Gardener-managed clusters. # Deploy using CI/CD Pipelines > Source: /docs/guides/deploy/cicd A comprehensive guide to implementing continuous integration and continuous deployment (CI/CD) for CAP projects using best practices, tools, and services. ## GitHub Actions > Source: /docs/guides/deploy/cicd#github-actions GitHub offers continuous integration using [GitHub Actions](https://docs.github.com/en/actions/automating-builds-and-tests/about-continuous-integration). In our [samples](https://github.com/capire/samples), we use simple workflows to [test, deploy and release new versions](https://github.com/capire/samples/tree/main/.github/workflows). Add a default set of workflows to your project like so: ```sh cds add github-actions ``` > You can also use `cds add gha` as a shortcut. ### Deploy to Staging > Source: /docs/guides/deploy/cicd#deploy-to-staging The created workflows do a _Staging_ deployment for pushes on the `main` branch, usually after merging pull requests. If no defaults are maintained in your GitHub org, a set of variables and secrets has to be provided. Open the repository and navigate here to maintain them: `Settings` → `Secrets and variables` → `Actions` For a minimal deployment setup, these variables and secrets are required: #### Cloud Foundry > Source: /docs/guides/deploy/cicd#cloud-foundry | **Type** | **Name** | **Note** | **Example** | |------------|----------------|------------|-------------------| | Variable | `CF_API` | API URL | `https://api.cf.example.com` | | | `CF_USERNAME` | Username | `user@example.com` | | | `CF_ORG` | Org Name | `my-org` | | | `CF_SPACE` | Space Name | `my-space` | | Secret | `CF_PASSWORD` | Password | `********` | #### Kyma > Source: /docs/guides/deploy/cicd#kyma | **Type** | **Name** | **Note** | **Example** | |------------|----------------|-----------|-------------------| | Secret | `KUBE_CONFIG` | Base64-encoded Kubernetes config | see below | ::: details Example of a decoded `KUBE_CONFIG` Your `KUBE_CONFIG` will have to look similar to this. Make sure to replace `token` by an authorization token created for your technical user used for deployment. ```yaml{6-7,11} apiVersion: v1 kind: Config clusters: - name: default-cluster cluster: certificate-authority-data: ... server: https://api..stage.kyma.ondemand.com users: - name: ci-user user: token: ... contexts: - name: ci-context context: cluster: default-cluster namespace: ci user: ci-user current-context: ci-context ``` [Learn more about configuring Kubernetes](./to-kyma#configure-kubernetes){.learn-more} ::: #### BTP Prerequisites > Source: /docs/guides/deploy/cicd#btp-prerequisites Also make sure sufficient service entitlements are assigned to your subaccount depending on your expected usage. ::: tip Set up a sandbox subaccount as an org-wide default Organization variables and secrets allow you to provide defaults for new projects without prior setup. Once required for your use case, you can easily **overwrite org-wide** variables and secrets by **repository-local** ones. ::: #### You're set! > Source: /docs/guides/deploy/cicd#youre-set You can now simply push any CAP project that was set up using `cds add github-actions` to your org. When merging PRs or pushing to your `main` branch, the deployment workflow will start and after some time a new entry will show up in the _Deployments_ section on your repository front page: ![](./assets/github-deployment.png){} ### Create a GitHub Release > Source: /docs/guides/deploy/cicd#create-a-github-release #### Prerequisites > Source: /docs/guides/deploy/cicd#prerequisites For the actual release we want to override org-wide sandbox variables to deploy to a different subaccount/organization and database. Go to **Settings** → **Environments** → **New environment** → enter "Production". Now override org-wide variables (for example `CF_ORG` and `CF_SPACE` in Cloud Foundry) to use a dedicated subaccount you created for the release deployment. #### Publish the release > Source: /docs/guides/deploy/cicd#publish-the-release On your repository front page go to `Releases` → `Draft a new release` → `Select tag`. Now enter a tag name, for example, `v1.0.0` and select `Create new tag: v1.0.0 on publish`. You can optionally add a release title and release notes. Hit **Publish release** once you're ready. The release will show up in your _Releases_ page and a deployment to your production environment is started. Once finished, a _Production_ entry shows up next to _Staging_: ![](./assets/github-release.png){} ## SAP Continuous Integration and Delivery > Source: /docs/guides/deploy/cicd#sap-continuous-integration-and-delivery [SAP Continuous Integration and Delivery](https://help.sap.com/viewer/SAP-Cloud-Platform-Continuous-Integration-and-Delivery) lets you configure and run predefined continuous integration and delivery pipelines. It connects with your Git SCM repository and in its user interface, you can easily monitor the status of your builds and detect errors as soon as possible, which helps you prevent integration problems before completing your development. SAP Continuous Integration and Delivery has a ready-to-use pipeline for CAP, that is applicable to Node.js, Java and multitarget application (MTA) based projects. It does not require you to host your own Jenkins instance and it provides an easy, UI-guided way to configure your pipelines. Try the tutorial [Get Started with SAP Continuous Integration and Delivery](https://developers.sap.com/tutorials/cicd-start-cap.html) to configure a CI/CD pipeline that builds, tests, and deploys your code changes. ## CI/CD Pipelines with SAP Piper > Source: /docs/guides/deploy/cicd#cicd-pipelines-with-sap-piper For more flexibility you can set up continuous delivery in your software development project, applicable to both SAP Business Technology Platform (BTP) and SAP on-premise platforms. SAP implements tooling for continuous delivery in project [Piper](https://www.project-piper.io/). Try the tutorial [Create Automated System Tests for SAP Cloud Application Programming Model Projects](https://developers.sap.com/tutorials/cicd-wdi5-cap.html) to create system tests against a CAP-based sample application and automate your tests through a CI/CD pipeline. [See a comparison with SAP Continuous Integration and Delivery Service.](https://www.project-piper.io/){.learn-more} # Deploy Multitenant SaaS Applications > Source: /docs/guides/multitenancy/ Introducing the fundamental concepts of multitenancy, underpinning SaaS solutions in CAP. It describes how to run and test apps in multitenancy mode with minimized setup and overhead. ## Introduction & Overview > Source: /docs/guides/multitenancy/#introduction--overview CAP has built-in support for multitenancy with [the `@sap/cds-mtxs` package](https://www.npmjs.com/package/@sap/cds-mtxs). Essentially, multitenancy is the ability to serve multiple tenants through single clusters of microservice instances, while strictly isolating the tenants' data. Tenants are clients using SaaS solutions. In contrast to single-tenant mode, applications wait for tenants to subscribe before serving any end-user requests. [Learn more about SaaS applications.](#about-saas-applications){.learn-more} ## Enable Multitenancy > Source: /docs/guides/multitenancy/#enable-multitenancy Simply enable multitenancy for your CAP application using `cds add`, followed by an `npm install` for Node.js projects, or `mvn install` for Java projects, to install added package dependencies, like this: ::: code-group ```sh [Node.js] cds add multitenancy npm install ``` ```sh [Java] cds add multitenancy mvn install ``` ::: ::: details See what this adds to your **Node.js** project… In case of **CAP Node.js** projects, the `cds add multitenancy` command... 1. Adds package dependency `@sap/cds-mtxs` to your project: ```jsonc { "dependencies": { "@sap/cds-mtxs": "^4" }, } ``` 2. Adds this configuration to your _package.json_ to enable multitenancy with sidecar: ```jsonc { "cds": { "profile": "with-mtx-sidecar", "requires": { "[production]": { "multitenancy": true }, "[with-mtx]": { "multitenancy": true } } } } ``` 3. Adds a sidecar subproject at `mtx/sidecar` with this _package.json_: ```json { "name": "bookshop-mtx", "dependencies": { "@cap-js/hana": "^3", "@sap/cds": "^10", "@sap/cds-mtxs": "^4", "@sap/xssec": "^4", }, "devDependencies": { "@cap-js/sqlite": "^3" }, "engines": { "node": ">=24" }, "scripts": { "start": "cds-serve" }, "cds": { "profile": "mtx-sidecar" } } ``` 4. If necessary, modifies deployment descriptors such as `mta.yaml` for Cloud Foundry and Helm charts for Kyma. ::: ::: details See what this adds to your **Java** project… In case of **CAP Java** projects, the `cds add multitenancy` command... 1. Adds the following to _.cdsrc.json_ in your app: ```jsonc { "profile": "with-mtx-sidecar", "requires": { "[production]": { "multitenancy": true }, "[with-mtx]": { "multitenancy": true } } } ``` 2. Adds the following to your _srv/pom.xml_ in your app: ```xml com.sap.cds cds-feature-mt runtime org.xerial sqlite-jdbc runtime ``` 3. Adds the following to your _srv/src/main/resources/application.yaml_: ```yml --- spring: config.activate.on-profile: with-mtx cds: multi-tenancy: sidecar.url: http://localhost:4005/ # in production mode overwrite with the URL from mta.yaml ``` 4. Adds a sidecar subproject at `mtx/sidecar` with this _package.json_: ```json { "name": "bookshop-mtx", "dependencies": { "@cap-js/hana": "^3", "@sap/cds": "^10", "@sap/cds-mtxs": "^4", "@sap/xssec": "^4" }, "devDependencies": { "@cap-js/sqlite": "^3" }, "engines": { "node": ">=24" }, "scripts": { "start": "cds-serve", "build": "cds build ../.. --for mtx-sidecar --production && npm ci --prefix gen" }, "cds": { "profiles": [ "mtx-sidecar", "java" ] } } ``` ::: ::: details Profile-based configuration presets The profiles `with-mtx-sidecar` and `mtx-sidecar` activate pre-defined configuration presets, which are defined as follows: ```js { "[with-mtx-sidecar]": { // [!code focus] requires: { db: { '[development]': { kind: 'sqlite', credentials: { url: 'db.sqlite' }, schema_evolution: 'auto', }, '[production]': { kind: 'hana', 'deploy-format': 'hdbtable', 'vcap': { 'label': 'service-manager' } }, }, "[java]": { "cds.xt.ModelProviderService": { kind: 'rest', model:[] }, "cds.xt.DeploymentService": { kind: 'rest', model:[] }, }, "cds.xt.SaasProvisioningService": false, "cds.xt.DeploymentService": false, "cds.xt.ExtensibilityService": false, } }, "[mtx-sidecar]": { // [!code focus] requires: { db: { "[development]": { kind: 'sqlite', credentials: { url: "../../db.sqlite" }, schema_evolution: 'auto', }, "[production]": { kind: 'hana', 'deploy-format': 'hdbtable', 'vcap': { 'label': 'service-manager' } }, }, "cds.xt.ModelProviderService": { "[development]": { root: "../.." }, // sidecar is expected to reside in ./mtx/sidecar "[production]": { root: "_main" }, "[prod]": { root: "_main" } // for simulating production in local tests }, "cds.xt.SaasProvisioningService": true, "cds.xt.DeploymentService": true, "cds.xt.ExtensibilityService": true, }, "[development]": { server: { port: 4005 } } }, … } ``` ::: tip Inspect configuration You can always inspect the _effective_ configuration with `cds env`. ::: ## Test-Drive Locally > Source: /docs/guides/multitenancy/#test-drive-locally Before deploying to the cloud, you can test-drive common SaaS operations with your app locally, including SaaS startup, subscribing tenants, and upgrading tenants. ::: details Additional configuration required for **CAP Java** projects… In case of **CAP Java** projects you need additional dependencies in the _pom.xml_ of the `srv` directory. To support mock users in the local test scenario add `cds-starter-cloudfoundry`: ```xml com.sap.cds cds-starter-cloudfoundry ``` Then you add additional mock users to the spring-boot profile: ::: code-group ```yaml [application.yaml] --- spring: config.activate.on-profile: with-mtx #... cds: multi-tenancy: mtxs.enabled: true security.mock.users: # [!code focus] alice: # [!code focus] tenant: t1 roles: [ admin ] bob: # [!code focus] tenant: t1 roles: [ cds.ExtensionDeveloper ] erin: # [!code focus] tenant: t2 roles: [ admin, cds.ExtensionDeveloper ] ``` ::: ### 1. Start MTX Sidecar > Source: /docs/guides/multitenancy/#1-start-mtx-sidecar In a first terminal, start the MTX sidecar process: ```sh cds watch mtx/sidecar ``` ::: details Inspecting the log output... In the trace output, we see several MTX services being served; most interesting for multitenancy: the _ModelProviderService_ and the _DeploymentService_. ```log [cds] - connect using bindings from: { registry: '~/.cds-services.json' } [cds] - connect to db > sqlite { url: '../../db.sqlite' } [cds] - serving cds.xt.ModelProviderService { path: '/-/cds/model-provider' } // [!code focus] [cds] - serving cds.xt.DeploymentService { path: '/-/cds/deployment' } // [!code focus] [cds] - serving cds.xt.SaasProvisioningService { path: '/-/cds/saas-provisioning' } [cds] - serving cds.xt.ExtensibilityService { path: '/-/cds/extensibility' } [cds] - serving cds.xt.JobsService { path: '/-/cds/jobs' } ``` In addition, we can see a [`t0` tenant]./mtxs#about-technical-tenant-t0) being deployed, which is used by the MTX services for book-keeping tasks. ```log [cds] - loaded model from 1 file(s): ../../db/t0.cds [mtx|t0] - (re-)deploying SQLite database for tenant: t0 // [!code focus] /> successfully deployed to db-t0.sqlite // [!code focus] ``` With that, the server waits for tenant subscriptions, listening on port 4005 by default in development mode. ```log [cds] - server listening on { url: 'http://localhost:4005' } // [!code focus] [cds] - launched at 3/5/2023, 1:49:33 PM, version: 7.0.0, in: 1.320s [cds] - [ terminate with ^C ] ``` [If you get an error on server start, read the troubleshooting information.](../../get-started/get-help#why-do-i-get-an-error-on-server-start){.learn-more} ::: ### 2. Launch the app server > Source: /docs/guides/multitenancy/#2-launch-the-app-server In a second terminal, start the main CAP application server: ::: code-group ```sh [Node.js] cds watch --with-mtx ``` ```sh [Java] mvn cds:watch -Dspring-boot.run.profiles=with-mtx ``` ::: ::: details Launched with shared database... The server starts as usual, but automatically uses a persistent database shared with the MTX sidecar instead of an in-memory one, as we can see in the trace output: ::: code-group ```log [Node.js] [cds] - loaded model from 6 file(s): db/schema.cds srv/admin-service.cds srv/cat-service.cds srv/user-service.cds ../../../cds-mtxs/srv/bootstrap.cds ../../../cds/common.cds [cds] - connect using bindings from: { registry: '~/.cds-services.json' } [cds] - connect to db > sqlite { url: 'db.sqlite' } // [!code focus] [cds] - serving AdminService { path: '/odata/v4/admin', impl: 'srv/admin-service.js' } [cds] - serving CatalogService { path: '/odata/v4/catalog', impl: 'srv/cat-service.js' } [cds] - serving UserService { path: '/user', impl: 'srv/user-service.js' } [cds] - server listening on { url: 'http://localhost:4004' } [cds] - launched at 3/5/2023, 2:21:53 PM, version: 6.7.0, in: 748.979ms [cds] - [ terminate with ^C ] ``` ```log [Java] 2023-03-31 14:19:23.987 INFO 68528 --- [ restartedMain] c.s.c.bookshop.Application : The following 1 profile is active: "with-mtx" ... 2023-03-31 14:19:23.987 INFO 68528 --- [ restartedMain] c.s.c.services.impl.ServiceCatalogImpl : Registered service ExtensibilityService$Default 2023-03-31 14:19:23.999 INFO 68528 --- [ restartedMain] c.s.c.services.impl.ServiceCatalogImpl : Registered service CatalogService 2023-03-31 14:19:24.016 INFO 68528 --- [ restartedMain] c.s.c.f.s.c.runtime.CdsRuntimeConfig : Registered DataSource 'ds-mtx-sqlite'// [!code focus] 2023-03-31 14:19:24.017 INFO 68528 --- [ restartedMain] c.s.c.f.s.c.runtime.CdsRuntimeConfig : Registered TransactionManager 'tx-mtx-sqlite'// [!code focus] 2023-03-31 14:19:24.554 INFO 68528 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2023-03-31 14:19:24.561 INFO 68528 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2023-03-31 14:19:24.561 INFO 68528 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.71] ``` ::: ### 3. Subscribe Tenants > Source: /docs/guides/multitenancy/#3-subscribe-tenants In the third terminal, subscribe and unsubscribe tenants as follows: ::: code-group ```sh [cds subscribe] cds subscribe t1 --to http://localhost:4005 cds subscribe t2 --to http://localhost:4005 ``` ```sh [cds unsubscribe] cds unsubscribe t1 --from http://localhost:4005 cds unsubscribe t2 --from http://localhost:4005 ``` ::: ::: details Behind the scenes... The `cds subscribe` command sends HTTP requests to the MTX sidecar's `SaasProvisioningService` as follows: ```http [HTTP] POST http://localhost:4005/-/cds/saas-provisioning/subscribe HTTP/1.1 Content-Type: application/json Authorization: Basic yves: { "tenant": "t1" } ``` A programmatic call from a CAP Node.js client would look like this: ```js [JavaScript] const ds = await cds.connect.to ('cds.xt.SaasProvisioningService') await ds.subscribe('t1') ``` Upon receiving a subscription request, the sidecar creates a new persistent tenant database per tenant, hence keeping tenant data isolated: ```log [cds] - POST /-/cds/deployment/subscribe [mtx] - (re-)deploying SQLite database for tenant: t1 // [!code focus] > init from db/init.js // [!code focus] > init from db/data/sap.capire.bookshop-Authors.csv // [!code focus] > init from db/data/sap.capire.bookshop-Books.csv // [!code focus] > init from db/data/sap.capire.bookshop-Books_texts.csv // [!code focus] > init from db/data/sap.capire.bookshop-Genres.csv // [!code focus] /> successfully deployed to ./../../db-t1.sqlite // [!code focus] [mtx] - successfully subscribed tenant t1 ``` ::: ### 4. Test via the app's UI > Source: /docs/guides/multitenancy/#4-test-via-the-apps-ui For example, in case of [_@capire/bookshop_](https://github.com/capire/bookshop) sample, you can now test your app with different users/tenants as follows... Open the _Manage Books_ app at and log in with `alice`. Select **Wuthering Heights** to open the details, edit here the title and save your changes. You've changed data in one tenant. To see requests served in tenant isolation, that is, from different databases, check that it's not visible in the other one. Open a private/incognito browser window and log in as `erin` to see that the title still is _Wuthering Heights_. In the following example, _Wuthering Heights (only in t1)_ was changed by _alice_. _erin_ doesn't see it, though. ![A screenshot of the bookshop application showing the effect of tenant isolation logged in as _alice_, as described in the previous sentence.](assets/book-changed-t1.png){} ::: details Use private/incognito browser windows to test with different tenants... Do this to force new logins with different users, assigned to different tenants: 1. Open a new _private_ / _incognito_ browser window. 2. Open in it → log in as `alice`. 3. Repeat that with `erin`, another pre-defined user, assigned to tenant `t2`. ::: ::: details Note tenants displayed in trace output... We can see tenant labels in server logs for incoming requests: ```log [cds] - server listening on { url: 'http://localhost:4004' } [cds] - launched at 3/5/2023, 4:28:05 PM, version: 6.7.0, in: 736.445ms [cds] - [ terminate with ^C ] ... [odata|t1] - POST /adminBooks { '$count': 'true', '$select': '... } // [!code focus] [odata|t2] - POST /adminBooks { '$count': 'true', '$select': '... } // [!code focus] ... ``` ::: ::: details Pre-defined users in `mocked-auth` How users are assigned to tenants and how tenants are determined at runtime largely depends on your identity providers and authentication strategies. The `mocked` authentication strategy, used by default with `cds watch`, has a few [pre-defined users](../../node.js/authentication#mock-users) configured. You can inspect these by running `cds env requires.auth`: ```js [bookshop] cds env requires.auth { kind: 'basic-auth', strategy: 'mock', users: { alice: { tenant: 't1', roles: [ 'admin' ] }, bob: { tenant: 't1', roles: [ 'cds.ExtensionDeveloper' ] }, carol: { tenant: 't1', roles: [ 'admin', 'cds.ExtensionDeveloper' ] }, // [!code focus] dave: { tenant: 't1', roles: [ 'admin' ], features: [] }, erin: { tenant: 't2', roles: [ 'admin', 'cds.ExtensionDeveloper' ] }, // [!code focus] fred: { tenant: 't2', features: ... }, me: { tenant: 't1', features: ... }, yves: { roles: [ 'internal-user' ] } '*': true //> all other logins are allowed as well }, tenants: { t1: { features: … }, t2: { features: '*' } } } ``` You can also add or override users or tenants by adding something like this to your _package.json_: ```jsonc "cds":{ "requires": { "auth": { "users": { "u2": { "tenant": "t2" }, // [!code focus] "u3": { "tenant": "t3" } // [!code focus] } } } } ``` ::: ### 5. Upgrade Your Tenant > Source: /docs/guides/multitenancy/#5-upgrade-your-tenant When deploying new versions of your app, you also need to upgrade your tenants' databases. For example, open `db/data/sap.capire.bookshop-Books.csv` and add one or more entries in there. Then upgrade tenant `t1` as follows: ::: code-group ```sh [CLI] cds upgrade t1 --at http://localhost:4005 -u yves: ``` ```http POST http://localhost:4005/-/cds/deployment/upgrade HTTP/1.1 Content-Type: application/json Authorization: Basic yves: { "tenant": "t1" } ``` ```js [JavaScript] const ds = await cds.connect.to('cds.xt.DeploymentService') await ds.upgrade('t1') ``` ::: > After that, open or refresh again as _alice_ and _erin_ → the added entries are visible for _alice_, but still missing for _erin_, as `t2` has not yet been upgraded. ## Deploy to Cloud > Source: /docs/guides/multitenancy/#deploy-to-cloud In order to get your multitenant application deployed, follow this excerpt from the [deployment to CF](../deploy/to-cf) and [deployment to Kyma](../deploy/to-kyma) guides. 1. Prepare your application for production, **once**: ::: code-group ```sh [Cloud Foundry] cds add hana,xsuaa # add required production services cds add portal # as an option to serve UIs, if any cds add mta # to enable MTA based deployments ``` ```sh [Kyma] cds add hana,xsuaa # add required production services cds add portal # as an option to serve UIs, if any cds add kyma # to enable Kyma/Helm based deployments ``` ::: 2. Deploy the application: ```sh cds up ``` :::tip For manual setups, ensure the metadata container (`t0`) is unique If you’re not running [`cds-mtx upgrade *`](#update-database-schema) as a Cloud Foundry hook (as set up by `cds add multitenancy`) and instead use a custom setup, deploy the MTX sidecar with a single instance for the initial rollout. This avoids conflicts when `t0` is created. ::: ### Subscribe via BTP Cockpit > Source: /docs/guides/multitenancy/#subscribe-via-btp-cockpit **Create a BTP subaccount** to subscribe to your deployed application. This subaccount has to be in the same region as the provider subaccount, for example, `us10`. See the [list of all available regions](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/f344a57233d34199b2123b9620d0bb41.html). {.learn-more} ![Global Account view to create a subaccount.](assets/create-subaccount.png){.mute-dark} In your **subscriber account** go to _Instances and Subscription_ and select _Create_. ![The screenshot is explained in the accompanying text.](assets/sub-account.png){.mute-dark} Select _bookshop_ and use the only available plan _default_. ![The screenshot is explained in the accompanying text.](assets/subscribe-bookshop.png){.mute-dark} [Learn more about subscribing to a SaaS application using the SAP BTP cockpit.](https://help.sap.com/docs/btp/sap-business-technology-platform/subscribe-to-multitenant-applications-using-cockpit?version=Cloud#procedure){.learn-more} [Learn more about subscribing to a SaaS application using the `btp` CLI.](https://help.sap.com/docs/btp/btp-cli-command-reference/btp-subscribe-accounts-subaccount?locale=en-US){.learn-more} You can now access your subscribed application via _Go to Application_. ![The screenshot is explained in the accompanying text.](assets/go-to-app.png){.mute-dark} As you can see, your route doesn't exist yet. You need to create and map it first. > If you're deploying to Kyma, your application will load and you won't get the below error. You can skip the step of exposing the route. ```log 404 Not Found: Requested route ('...') does not exist. ``` > Leave the window open. You need the information to create the route. #### Cloud Foundry > Source: /docs/guides/multitenancy/#cloud-foundry Use the following command to create and map a route to your application: ```sh cf map-route ‹app› ‹paasDomain› --hostname ‹subscriberSubdomain›-‹saasAppName› ``` In our example, let's assume our `saas-registry` is configured in the _mta.yaml_ like this: ```yaml - name: bookshop-registry type: org.cloudfoundry.managed-service parameters: service: saas-registry service-plan: application config: appName: bookshop-${org}-${space} # [!code focus] ``` Let's also assume we've deployed to our app to Cloud Foundry org `myOrg` and space `mySpace`. This would be the full command to create a route for the subaccount with subdomain `subscriber1`: ```sh cf map-route bookshop cfapps.us10.hana.ondemand.com --hostname subscriber1-myOrg-mySpace-bookshop ``` ::: details Learn how to do this in the BTP cockpit instead… Switch to your **provider account** and go to your space → Routes. Click on _New Route_. ![The screenshot is explained in the accompanying text.](assets/cockpit-routes.png){.mute-dark} Here, you need to enter a _Domain_ and _Host Name_. ![The screenshot is explained in the accompanying text.](assets/cockpit-routes-new.png){.mute-dark} Let's use this route as example: __ - The **Domain** here is _cfapps.us10.hana.ondemand.com_ - The **Host Name** here is _subscriber1-bookshop_ Hit _Save_ to create the route. You can now see the route is created but not mapped to an application yet. ![The screenshot is explained in the accompanying text.](assets/cockpit-routes-new-overview.png){.mute-dark} Click on _Map Route_, choose your App Router module and hit _Save_. ![The screenshot is explained in the accompanying text.](assets/cockpit-routes-new-map.png){.mute-dark} You should now see the route mapped to your application. ![Overview in your dev space with the newly mapped route.](assets/cockpit-routes-new-mapped-overview.png){.mute-dark} ::: ### Update Database Schema > Source: /docs/guides/multitenancy/#update-database-schema There are several ways to update the database schema of a multitenant application. * For **CAP Java** applications, schema updates should be done as described in the respective [Java Guide](../../java/multitenancy#database-update). * For **CAP Node.js** applications, you can use either of the following as shown in the examples below: - the `cds-mtx upgrade` command from a terminal - the [MTX Sidecar API](mtxs#upgrade-tenants--jobs) - via a [CloudFoundry hook](https://help.sap.com/docs/btp/sap-business-technology-platform/module-hooks) - via a [CloudFoundry task](https://tutorials.cloudfoundry.org/cf4devs/advanced-concepts/tasks/) - via a [Kubernetes job](https://kubernetes.io/docs/concepts/workloads/controllers/job/) ::: code-group ```sh [cds-mtx upgrade] cd mtx/sidecar cds-mtx upgrade t1 # single tenants cds-mtx upgrade \* # all tenants ``` ```http [MTX Sidecar API] POST /-/cds/saas-provisioning/upgrade HTTP/1.1 Content-Type: application/json { "tenants": ["t1"] } ``` ```yaml [CF hook] # mta.yaml > Source: /docs/guides/multitenancy/#mtayaml hooks: - name: upgrade-all type: task phases: - blue-green.application.before-start.idle - deploy.application.before-start parameters: name: upgrade memory: 512M disk-quota: 768M command: cds-mtx upgrade '*' ``` ```sh [CF task] cf run-task ‹app› --name "upgrade-all" --command "cds-mtx upgrade '*'" ``` ```yaml [Kubernetes job] # values.yaml > Source: /docs/guides/multitenancy/#valuesyaml mtx-upgrade: bindings: saas-registry: # when using XSUAA serviceInstanceName: saas-registry subscription-manager: # when using IAS serviceInstanceName: subscription-manager service-manager: serviceInstanceName: service-manager image: repository: bookshop-sidecar resources: limits: ephemeral-storage: 1G memory: 1G requests: ephemeral-storage: 1G cpu: 1000m memory: 1G command: ["launcher"] args: - "cds-mtx" - "upgrade" - '*' ``` ::: ::: info Managing large upgrade workloads Very large projects might need to increase resources or limit parallelism of tenant upgrades. [The best practice algorithm is laid out in our _Get Help_ guide.](../../get-started/get-help#why-is-my-mtx-sidecar-is-killed-with-exit-status-137){.learn-more}{} ::: ### Test-Drive in Hybrid Setup > Source: /docs/guides/multitenancy/#test-drive-in-hybrid-setup You can run the app locally while binding it to remote service instances created by a Cloud Foundry deployment. To do so, use `cds bind` to bind your SaaS app and the MTX sidecar to its required cloud services, like that: ```sh cds bind -a bookshop-srv ``` For testing the sidecar, make sure to run the command there as well: ```sh cd mtx/sidecar cds bind -a bookshop-mtx ``` To generate the SAP HANA HDI files for deployment, go to your project root and run the build: ```sh cds build --production ``` ::: warning Run `cds build` after model changes Each time you update your model or any SAP HANA source file, you need repeat the build. ::: > Make sure to stop any running CAP servers left over from local testing. By passing `--profile hybrid` you can now run the app with cloud bindings and interact with it as you would while [testing your app locally](#test-drive-locally). Run this in your project root: ```sh cds watch mtx/sidecar --profile hybrid ``` Then, in another terminal, start the main application: ::: code-group ```sh [Node.js] cds watch --profile hybrid ``` ```sh [Java] cd srv mvn cds:watch -Dspring-boot.run.profiles=hybrid ``` ::: Learn more about [Hybrid Testing](../../tools/cds-bind).{.learn-more} ::: tip Manage multiple deployments Use a dedicated profile for each deployment landscape if you are using several, such as `dev`, `test`, `prod`. For example, after logging in to your `dev` space: ```sh cds bind -2 bookshop-db --profile dev cds watch --profile dev ``` ::: ###### sap-hana-tenant-management-service-v2 > Source: /docs/guides/multitenancy/#sap-hana-tenant-management-service-v2 ### SAP HANA TMS v2 > Source: /docs/guides/multitenancy/#sap-hana-tms-v2 The SAP HANA Tenant Management Service (TMS) v2 service provides direct support for managing SAP HANA tenants. > [!important] Be aware of the current limitations: > - **Not suitable for existing applications** as there is **no migration from Service Manager** available yet. This will be provided as HANA tool later.
> There **won't be support for both Service Manager and TMS v2** together in one application. [For more information, see the SAP HANA documentation](https://help.sap.com/docs/hana-cloud/sap-hana-cloud-multitenancy/introducing-sap-hana-cloud-multitenancy){.learn-more} [Find the TMS v2 API on the SAP Business Accelerator Hub](https://api.sap.com/api/TenantAPI/overview){.learn-more} #### Configure MTXS for Tenant Management Service > Source: /docs/guides/multitenancy/#configure-mtxs-for-tenant-management-service This documentation uses the bookshop sample to showcase necessary configuration. If you start with a multitenant application that's configured to use to SAP HANA, you need to change the configuration of the database in your _mta.yaml_ file: ```yaml - name: bookshop-db type: org.cloudfoundry.managed-service parameters: service: hana-cloud service-plan: hana-multitenancy ``` > [!danger] Only use the `hana-multitenancy` plan > As the tenant containers are filtered by the SAP HANA Cloud service instance, applications will potentially access data of other applications when using a different plan. For SAP HANA TMS v2, you also need to specify the database ID of the database that you plan to use for your tenant containers. You can specify this using the [`cds.xt.DeploymentService` configuration](./mtxs#deployment-config). With `cds.xt.DeploymentService` you only configure the default database ID. If you want to specify different database IDs for a tenant HDI container, you need to [add the database ID to the payload of the individual subscription using a handler](./mtxs#example-handler-for-saasprovisioningservice). To keep the application configuration agnostic, we recommend adding the cds/requires/cds.xt.DeploymentService/hdi/create/database_id configuration as an environment variable to the MTX service in _mta.yaml_: ```yaml{6-17} - name: bookshop-mtx type: nodejs path: gen/mtx/sidecar ... properties: CDS_CONFIG: | { "requires": { "cds.xt.DeploymentService": { "hdi": { "create": { "database_id": "4baa4d82-a474-4281-90af-67261c893590" } } } } } ``` To further separate deployment configuration, you can also use a separate [Deployment Extension Descriptor](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-developer-guide-for-cloud-foundry-multitarget-applications-sap-business-app-studio/mta-deployment-extension-descriptor) on Cloud Foundry. You can also pass the configuration for each individual subscription as a payload extension in the request to the [SaasProvisioningService](./mtxs#subscription). #### Handle SAP HANA Tenants > Source: /docs/guides/multitenancy/#handle-sap-hana-tenants By default, MTXS creates a separate SAP HANA tenant for each subscriber tenant (BTP tenant). In some scenarios, you might also want to use the same SAP HANA tenant for several applications or microservices. The SAP HANA tenant ID must be unique across all SAP HANA database instances within a region. If you intend to deploy your application multiple times in a region, ensure this uniqueness across deployments. ##### Mandatory: specify a unique prefix for the SAP HANA tenant name > Source: /docs/guides/multitenancy/#mandatory-specify-a-unique-prefix-for-the-sap-hana-tenant-name Specify a cds/requires/cds.xt.DeploymentService/hdi/create/hana_tenant_prefix value that is unique for each **deployed application instance**: ```jsonc "cds.xt.DeploymentService": { "hdi": { "create": { ... "hana_tenant_prefix": "" } } } ``` The resulting SAP HANA tenant name is constructed from `prefix`+`subscriber tenant`. The SAP HANA tenant ID is generated as a hashed UUID from the SAP HANA tenant name, and the original name is added as a label. ![Shows the relations of a HANA Tenant](./assets/hana_tenants.drawio.svg) To help ensure that the generated SAP HANA tenant UUID is unique within a region, you could build the `hana_tenant_prefix` in your deployment descriptor using various variables, for example, like `prefix-${org}-${space}` in Cloud Foundry. :::warning Prefix is mandatory The cds/requires/cds.xt.DeploymentService/hdi/create/hana_tenant_prefix configuration is mandatory to ensure that the internal tenant [`t0`](./mtxs#about-technical-tenant-t0) is created with its own SAP HANA tenant. ::: :::warning Length restriction The prefix and subscriber tenant name are each limited to 63 characters maximum. ::: :::warning Do not use `hana_tenant_prefix` in queries The `hana_tenant_prefix` is only used to generate the SAP HANA tenant UUID to avoid duplicates when creating an SAP HANA tenant. Although it is added as `prefix` label to the SAP HANA tenant, it is not reliable and must not be used for any query. ::: ##### Mandatory for CAP Java Applications > Source: /docs/guides/multitenancy/#mandatory-for-cap-java-applications For **CAP Java** applications you need to set the same prefix in the cds.multitenancy.hanaMtService.hanaTenantPrefix property. We recommend doing this using the environment variable in the _mta.yaml_: ```yaml - name: srv type: java path: srv ... properties: CDS_MULTITENANCY_HANAMTSERVICE_HANATENANTPREFIX: "some-prefix" ``` ##### Assign Many Tenant Containers to a Common SAP HANA Tenant > Source: /docs/guides/multitenancy/#assign-many-tenant-containers-to-a-common-sap-hana-tenant To group the tenant containers of many applications or microservices in a common HANA tenant, you need to make sure that the same SAP HANA tenant ID is used. ![One HANA tenant for many applications or microservices](./assets/hana_tenants_for_many.drawio.svg) **Option 1: Configure the same `hana_tenant_prefix`** : You can configure the same [`hana_tenant_prefix`](#mandatory-specify-a-unique-prefix-for-the-sap-hana-tenant-name) many applications or microservices. With that, the SAP HANA tenant ID is generated in the same way for each subscriber tenant. : If you choose this option, the following preconditions need to be met - The subscriber tenant ID (BTP tenant ID) needs to be the same for all applications or microservices. - All applications or microservices need to use the [same database](#configure-mtxs-for-tenant-management-service) for the same subscriber tenant. **Option 2: Pass the SAP HANA Tenant ID with a Subscription** : **... in CAP Node.js** If you want to control the ID of the SAP HANA tenant ID on your own, you can pass it as subscription payload as parameters, for example, using a handler for the [`SaasRegistryService`](./mtxs#put-tenant): ```jsonc { "subscribedTenantId": "t1", "subscribedSubdomain": "subdomain1", "eventType": "CREATE" ... "_": { "hdi": { "create": { "hana_tenant_id": "5b3c0699-1c65-4ec1-9a8e-b7cfc3cc15bc" } } } } ``` The `hana_tenant_id` must be a valid UUID and must be unique per subscriber tenant. Specifying `hana_tenant_id` overrides the prefix settings mentioned earlier, except for [the internal tenant t0](./mtxs#about-technical-tenant-t0). Also ensure that the ID is unique within a region. : **... in CAP Java** To specify the ID of the SAP HANA tenant in **CAP Java** applications you can register a custom handler for the `before` phase of the `SUBSCRIBE` event that sets the `hana_tenant_id` within the provisioning parameters: ```java @Before public void beforeSubscription(SubscribeEventContext context) { context.getOptions().put("provisioningParameters", Collections.singletonMap("hana_tenant_id", "")); } ``` This will affect every new [tenant subscription](../../java/multitenancy.md#subscribe-tenant) and will set the specified SAP HANA tenant ID.
##### Delete SAP HANA tenants > Source: /docs/guides/multitenancy/#delete-sap-hana-tenants When you unsubscribe and the tenant container is deleted, the corresponding SAP HANA tenant isn't deleted as it could potentially still be in use for other applications. #### Limitations > Source: /docs/guides/multitenancy/#limitations There are still some limitations with the current client implementation. - **Database ID is Mandatory** As mentioned, you need to specify a database ID that's to be used, either for all tenants or per subscription request, see [Deployment configuration](./mtxs#deployment-config). ## SaaS Dependencies > Source: /docs/guides/multitenancy/#saas-dependencies Some of the xsuaa-based services your application consumes need to be registered as _reuse services_ to work in multitenant environments. This holds true for the usage of both the SaaS Registry service and the Subscription Manager Service (SMS). CAP Java as well as `@sap/cds-mtxs`, each offer an easy way to integrate these dependencies. They support some services out of the box and also provide a simple API for applications. Most notably, you need such dependencies for the following SAP BTP services: [Audit Log](https://discovery-center.cloud.sap/serviceCatalog/audit-log-service), [Event Mesh](https://discovery-center.cloud.sap/serviceCatalog/event-mesh), [Destination](https://discovery-center.cloud.sap/serviceCatalog/destination-service), [HTML5 Application Repository](https://discovery-center.cloud.sap/serviceCatalog/html5-application-repository-service), and [Cloud Portal](https://discovery-center.cloud.sap/serviceCatalog/cloud-portal-service). For CAP Java, all these services are supported natively and SaaS dependencies are automatically created if such a service instance is bound to the CAP Java application, that is, the `srv` module. :::tip Explicitly activate the Destination service SaaS dependency for Destination service needs to be activated explicitly in the `application.yaml` due to security reasons. SaaS dependencies for some of the other services can be **de**activated by setting the corresponding property to `false` in the `application.yaml`. Refer to the `cds.multiTenancy.dependencies` section in the [CDS properties](../../java/developing-applications/properties#cds-properties). ::: For CAP Node.js, all these services are supported natively and can be activated individually by providing configuration in `cds.requires`. In the most common case, you simply activate service dependencies like so: ::: code-group ```json [mtx/sidecar/package.json] "cds": { "requires": { "audit-log": true, "connectivity": true, "destinations": true, "html5-repo": true, "portal": true } } ``` ::: ::: details Defaults provided by `@sap/cds-mtxs`... The Boolean values in the _mtx/sidecar/package.json_ activate the default configuration in `@sap/cds-mtxs`: ```json "cds": { "requires": { "connectivity": { // Uses credentials.xsappname "vcap": { "label": "connectivity" }, "subscriptionDependency": "xsappname" }, "portal": { "vcap": { "label": "portal" }, // Uses credentials.uaa.xsappname "subscriptionDependency": { "uaa": "xsappname" } }, ... } } ``` ::: ### Additional Services > Source: /docs/guides/multitenancy/#additional-services In **CAP Java**, if your application uses a service that isn't supported out of the box, you can define dependencies by providing a custom handler. [Learn more about defining dependent services](../../java/multitenancy#define-dependent-services){.learn-more} In **CAP Node.js**, you can use a custom `subscriptionDependency` entry in your application's or CAP plugin's _package.json_: ```json [package.json] "cds": { "requires": { "my-service": { "subscriptionDependency": "xsappname" } } } ``` > The `subscriptionDependency` specifies the property name of the credentials value with the desired `xsappname`, starting from `cds.requires['my-service'].credentials`. Usually it's just `"xsappname"`, but JavaScript objects interpreted as a key path are also allowed, such as `{ "uaa": "xsappname" }` in the defaults example for `portal`. Alternatively, overriding the [`dependencies`](./mtxs#get-dependencies) handler gives you full flexibility for any custom implementation.
## Adding Custom Handlers > Source: /docs/guides/multitenancy/#adding-custom-handlers [MTX services](mtxs.md) are implemented as standard CAP services, so you can add custom handlers to all respective lifecycle events just as you would for any application service. To do so simply add a `server.js` file in the _mtx/sidecar/_ folder, with content like this: ::: code-group ```js [mtx/sidecar/server.js] cds.on('served', () => { const { 'cds.xt.DeploymentService': ds } = cds.services ds.before('subscribe', async (req) => { // HDI container credentials are not yet available here const { tenant } = req.data }) ds.before('upgrade', async (req) => { // HDI container credentials are not yet available here const { tenant } = req.data }) ds.after('deploy', async (result, req) => { const { container } = req.data.options const { tenant } = req.data ... }) ds.after('unsubscribe', async (result, req) => { const { container } = req.data.options const { tenant } = req.data }) }) ``` ::: [Learn more about that in the _MTX Services Reference_ documentation](./mtxs){.learn-more} In case of **CAP Java** projects, you can alternatively add custom handlers to the main app as described in the [Java documentation](../../java/multitenancy#custom-logic): ```java @After private void subscribeToService(SubscribeEventContext context) { String tenant = context.getTenant(); Map options = context.getOptions(); } @On private void upgradeService(UpgradeEventContext context) { List tenants = context.getTenants(); Map options = context.getOptions(); } @Before private void unsubscribeFromService(UnsubscribeEventContext context) { String tenant = context.getTenant(); Map options = context.getOptions(); } ``` [Learn more about that in the _Java Multitenancy Guide_ documentation](../../java/multitenancy#custom-logic){.learn-more} ## Configuring the Java Service > Source: /docs/guides/multitenancy/#configuring-the-java-service In case of CAP Java projects, `cds add multitenancy` adds additional configuration similar to this: ::: code-group ```yaml [mta.yaml (Cloud Foundry)] modules: - name: bookshop-srv type: java path: srv parameters: ... provides: - name: srv-api # required by consumers of CAP services (e.g. approuter) properties: srv-url: ${default-url} requires: - name: app-api properties: CDS_MULTITENANCY_APPUI_URL: ~{url} CDS_MULTITENANCY_APPUI_TENANTSEPARATOR: "-" - name: bookshop-auth - name: bookshop-db - name: mtx-api properties: CDS_MULTITENANCY_SIDECAR_URL: ~{mtx-url} - name: bookshop-registry ``` ```yaml [values.yaml (Kyma)] ... srv: bindings: ... image: repository: bookshop-srv env: SPRING_PROFILES_ACTIVE: cloud CDS_MULTITENANCY_APPUI_TENANTSEPARATOR: "-" CDS_MULTITENANCY_APPUI_URL: https://{{ .Release.Name }}-srv-{{ .Release.Namespace }}.{{ .Values.global.domain }} CDS_MULTITENANCY_SIDECAR_URL: https://{{ .Release.Name }}-sidecar-{{ .Release.Namespace }}.{{ .Values.global.domain }} ... ``` ::: In which the following environment variables are set: | `CDS_MULTITENANCY_...` | Description | |---------------------|-------------| | `SIDECAR_URL` | Sets the application property cds.multitenancy.sidecar.url. This URL is required by the CAP Java runtime to connect to the MTX Sidecar application and is derived from the property `mtx-url` of the mtx-sidecar module. | | `APPUI_URL` | Sets the entry point URL that is shown in the SAP BTP Cockpit. | | `APPUI_TENANTSEPARATOR` | The separator in generated tenant-specific URL. | The tenant application requests are separated by the tenant-specific app URL: ```http https:// ``` ::: tip Use MTA extensions for landscape-specific configuration You can define the environment variable `CDS_MULTITENANCY_APPUI_TENANTSEPARATOR` in an MTA extension descriptor: ::: code-group ```yaml [mt.mtaext] _schema-version: "3.1" extends: my-app ID: my-app.id modules: - name: srv properties: CDS_MULTITENANCY_APPUI_TENANTSEPARATOR: "-" - name: app properties: TENANT_HOST_PATTERN: ^(.*)-${default-uri} ``` [Learn more about _Defining MTA Extension Descriptors_](https://help.sap.com/docs/btp/sap-business-technology-platform/defining-mta-extension-descriptors?q=The%20MTA%20Deployment%20Extension%20Descriptor){.learn-more} ::: #### Option: Provisioning Only > Source: /docs/guides/multitenancy/#option-provisioning-only Under certain conditions it makes a lot of sense to use the MTX Sidecar only for tenant provisioning. This configuration can be used in particular when the application doesn't offer (tenant-specific) model extensions and feature toggles. In such cases, business requests can be served by the Java runtime without interaction with the sidecar, for example to fetch an extension model. Use the following MTX Sidecar configuration to achieve this: ::: code-group ```json [.cdsrc.json] { "requires": { "multitenancy": true, "extensibility": false, // [!code focus] "toggles": false // [!code focus] }, "build": { ... } } ``` ::: In this case, the application can use its static local model without requesting the MTX sidecar for the model. This results in a significant performance gain because CSN and EDMX metadata are loaded from the JAR instead of the MTX Sidecar. To make the Java application aware of this setup as well, set the following properties: ::: code-group ```yaml [application.yaml] cds: model: provider: extensibility: false # [!code focus] toggles: false # [!code focus] ``` ::: ::: tip Enable only the features that you need You can also selectively use these properties to enable only extensibility or feature toggles, thus decreasing the dimensions when looking up dynamic models. :::




# Appendix > Source: /docs/guides/multitenancy/#appendix ## About SaaS Applications > Source: /docs/guides/multitenancy/#about-saas-applications Software-as-a-Service (SaaS) solutions are deployed once by a SaaS provider, and then used by multiple SaaS customers subscribing to the software. SaaS applications need to register with the [_SAP BTP SaaS Provisioning service_](https://discovery-center.cloud.sap/serviceCatalog/saas-provisioning-service) to handle `subscribe` and `unsubscribe` events. In contrast to [single-tenant deployments](../deploy/to-cf), databases or other _tenant-specific_ resources aren't created and bootstrapped upon deployment, but upon subscription per tenant. CAP includes the **MTX services**, which provide out-of-the-box handlers for `subscribe`/`unsubscribe` events, for example to manage SAP HANA database containers. If everything is set up, the following graphic shows what's happening when a user subscribes to a SaaS application: ![The graphic is explained in the following text.](assets/saas-overview.drawio.svg){} 1. The SaaS Provisioning Service sends a `subscribe` event to the CAP application. 2. The CAP application delegates the request to the MTX services. 3. The MTX services use Service Manager to create the database tenant. 4. The CAP Application connects to this tenant at runtime using Service Manager. ## About Sidecar Setups > Source: /docs/guides/multitenancy/#about-sidecar-setups The SaaS operations `subscribe` and `upgrade` tend to be resource-intensive. Therefore, it's recommended to offload these tasks onto a separate microservice, which you can scale independently of your main app servers. Java-based projects even require such a sidecar, as the MTX services are implemented in Node.js. In these MTX sidecar setups, a subproject is added in _./mtx/sidecar_, which serves the MTX Services as depicted in the illustration below. ![The main app serves the CAP services and the database. The sidecar serves the Deployment service and the Model Provider service. The Deployment service receives upgrade and subscribe request and sends deploy requests to the database of the main app. The Deployment service and the CAP services get the model from the Model Provider service to keep all layers in sync.](./assets/mtx-sidecar.drawio.svg) The main task for the MTX sidecar is to serve `subscribe` and `upgrade` requests. The CAP services runtime requests models from the sidecar only when you apply tenant-specific extensions. For Node.js projects, you have the option to run the MTX services embedded in the main app, instead of in a sidecar. # MTX Services Reference > Source: /docs/guides/multitenancy/mtxs API reference for multitenancy and extensibility. ## Introduction & Overview > Source: /docs/guides/multitenancy/mtxs#introduction--overview The `@sap/cds-mtxs` package provides a set of CAP services which implement _**multitenancy**_, _[features toggles](../extensibility/feature-toggles)_ and _[extensibility](../extensibility/)_ (_'MTX'_ stands for these three functionalities). These services work in concert as depicted in the following diagram: ![The graphic depicting the MTX infrastructure as described in the following guide.](./assets/mtx-overview.drawio.svg) MTX services are implemented in Node.js and can run in the same Node.js server as your application services or in separate micro services called _sidecars_. All services can be consumed via REST APIs. As the services are defined and implemented as standard CAP services, with definitions in CDS and implementations based on the CAP Node.js framework, application projects can hook into all events to add custom logic using CAP Node.js. ## Getting Started… > Source: /docs/guides/multitenancy/mtxs#getting-started ### Add `@sap/cds-mtxs` Package Dependency > Source: /docs/guides/multitenancy/mtxs#add-sapcds-mtxs-package-dependency ```sh npm add @sap/cds-mtxs ``` ### Enable MTX Functionality > Source: /docs/guides/multitenancy/mtxs#enable-mtx-functionality Add one or more of the following convenience configuration flags, for example, to your `package.json` in a Node.js-based project: ```json "cds": { "requires": { "multitenancy": true, "extensibility": true, "toggles": true } } ``` [Java-based projects require a sidecar setup.](#sidecars){.learn-more} ### Test-Drive Locally > Source: /docs/guides/multitenancy/mtxs#test-drive-locally After enabling MTX features, you can test MTX functionality with local development setups and in-memory databases as usual: ```sh cds watch ``` This shows the MTX services being served in addition to your app services: ```log{6-8,11-15} [cds] - loaded model from 6 file(s): db/schema.cds srv/admin-service.cds srv/cat-service.cds ../../db/extensions.cds ../../srv/deployment-service.cds ../../srv/bootstrap.cds [cds] - connect to db > sqlite { url: ':memory:' } [cds] - serving cds.xt.SaasProvisioningService { path: '/-/cds/saas-provisioning' } [cds] - serving cds.xt.DeploymentService { path: '/-/cds/deployment' } [cds] - serving cds.xt.ModelProviderService { path: '/-/cds/model-provider' } [cds] - serving cds.xt.ExtensibilityService { path: '/-/cds/extensibility' } [cds] - serving cds.xt.JobsService { path: '/-/cds/jobs' } [cds] - serving AdminService { path: '/admin' } [cds] - serving CatalogService { path: '/browse', impl: 'srv/cat-service.js' } [cds] - server listening on { url: 'http://localhost:4004' } [cds] - launched at 5/6/2023, 9:31:11 AM, in: 863.803ms ``` ## Grow As You Go > Source: /docs/guides/multitenancy/mtxs#grow-as-you-go Follow CAP principles of _'Grow as you go...'_ to minimize complexity of setups, stay in [inner loops](https://www.getambassador.io/docs/telepresence/latest/concepts/devloop) with fast turnarounds, and hence minimize costs and accelerate development. ### Enable MTX Only if Required > Source: /docs/guides/multitenancy/mtxs#enable-mtx-only-if-required During development you rarely need to run your servers with MTX functionality enabled. Only do so when you really need it. For example, in certain tests or by using configuration profiles. This configuration would have development not use MTX by default. You could still run with MTX enabled on demand and have it always active in production: ```jsonc "cds": { "requires": { "[local-multitenancy]": { "multitenancy": true, "extensibility": true, "toggles": true }, "[production]": { "multitenancy": true, "extensibility": true, "toggles": true } } } ``` During development you could occasionally run with MTX: ```sh cds watch --profile local-multitenancy ``` ### Testing With Minimal Setup > Source: /docs/guides/multitenancy/mtxs#testing-with-minimal-setup When designing test suites that run frequently in CI/CD pipelines, you can shorten runtimes and reduce costs. First run a set of functional tests which use MTX in minimized setups – that is, with local servers and in-memory databases as introduced in the [_Multitenancy_ guide](../multitenancy/index#test-drive-locally). Only in the second and third phases, you would then run the more advanced hybrid tests. These hybrid tests could include testing tenant subscriptions with SAP HANA, or integration tests with the full set of required cloud services. ## Sidecar Setups > Source: /docs/guides/multitenancy/mtxs#sidecar-setups In the minimal setup introduced in the _[Getting Started...](#getting-started)_ chapter, we had the MTX services being served embedded with our main app, that is, in the same server as our application services. While this is possible for Node.js and even recommended to reduce complexity during development, quite frequently, we'd want to run them in a separate micro service. Reasons for that include: - **For Java-based projects** — As these services are implemented in Node.js we need to run them separately and consume them remotely for Java-based apps. - **To scale independently** — As some operations, especially `upgrade`, are very resource-intensive, we want to scale these services separate from our main application. As MTX services are built and consumed as CAP services, we benefit from CAP's agnostic design and can easily move them to separate services. ### Create Sidecar as a Node.js Subproject > Source: /docs/guides/multitenancy/mtxs#create-sidecar-as-a-nodejs-subproject An MTX sidecar is a standard, yet minimal Node.js CAP project. By default it's added to a subfolder `mtx/sidecar` within your main project, containing just a _package.json_ file. ::: code-group ```json [mtx/sidecar/package.json] { "name": "bookshop-mtx", "version": "0.0.0", "dependencies": { "@sap/cds": "^10", "@cap-js/hana": "^3", "@sap/cds-mtxs": "^4", "@sap/xssec": "^4", }, "devDependencies": { "@cap-js/sqlite": "^3" }, "scripts": { "start": "cds-serve" }, "cds": { "profile": "mtx-sidecar" } } ``` ::: The only configuration necessary for the project is the `mtx-sidecar` profile. ::: details Let's have a look at what this profile provides... #### Required MTX Services > Source: /docs/guides/multitenancy/mtxs#required-mtx-services ```jsonc ... "cds": { "requires": { "cds.xt.ModelProviderService": "in-sidecar", "cds.xt.DeploymentService": true, "cds.xt.SaasProvisioningService": true, "cds.xt.ExtensibilityService": true ... } } ``` Here we enable all MTX services in a standard configuration. Of course, you can choose to only serve some of which, according to your needs, using [individual configuration](#conf-individual). #### Using Shared Database > Source: /docs/guides/multitenancy/mtxs#using-shared-database ```jsonc ... "[development]": { "db": { "kind": "sqlite", "credentials": { "url": "../../db.sqlite" }} } ... ``` With multitenancy the _[DeploymentService](#deploymentservice)_ needs to deploy the very database instances which are subsequently used by the main application. This setting ensures that for local development with SQLite. #### Additional `[development]` Settings > Source: /docs/guides/multitenancy/mtxs#additional-development-settings ```jsonc ... "[development]": { "requires": { "auth": "mocked" }, "server": { "port": 4005 } } ... ``` These additional settings for profile `[development]` are to support local tests with default values for the server port (different from the default port `4004` of the main app), and to allow mock authentication in the sidecar (secured by default in production). ::: ### Testing Sidecar Setups > Source: /docs/guides/multitenancy/mtxs#testing-sidecar-setups With the above setup in place, we can test-drive the sidecar mode locally. To do so, we'll simply start the sidecar and main app in separate shells. 1. Run sidecar in first shell: ```sh cds watch mtx/sidecar ``` ::: details You see the sidecar starting on port 4005... ```log cd mtx/sidecar cds serve all --with-mocks --in-memory? live reload enabled for browsers ___________________________ [cds] - loaded model from 3 file(s): ../cds-mtxs/srv/model-provider.cds ../cds-mtxs/srv/deployment-service.cds ../cds-mtxs/db/t0.cds [cds] - connect using bindings from: { registry: '~/.cds-services.json' } [cds] - connect to db > sqlite { url: '../../db.sqlite' } [cds] - using authentication: { kind: 'mocked' } [cds] - serving cds.xt.ModelProviderService { path: '/-/cds/model-provider' } [cds] - serving cds.xt.DeploymentService { path: '/-/cds/deployment' } [cds] - loaded model from 1 file(s): ../cds-mtxs/db/t0.cds [mtx] - (re-)deploying SQLite database for tenant: t0 /> successfully deployed to db-t0.sqlite [cds] - server listening on { url: 'http://localhost:4005' } // [!code highlight] [cds] - launched at 5/6/2023, 1:08:33 AM, version: 7.3.0, in: 772.25ms [cds] - [ terminate with ^C ] ``` ::: 2. Run the main app as before in a second shell: ```sh cds watch ``` #### _ModelProviderService_ serving models from main app > Source: /docs/guides/multitenancy/mtxs#modelproviderservice-serving-models-from-main-app When we use our application, we can see `model-provider/getCsn` requests in the sidecar's trace log. In response to those requests, the sidecar reads and returns the main app's models, that is, the models from two levels up the folder hierarchy as is the default with the `mtx-sidecar` profile. #### Note: Service Bindings by `cds watch` > Source: /docs/guides/multitenancy/mtxs#note-service-bindings-by-cds-watch Required service bindings are done automatically by `cds watch`'s built-in runtime service registry. This is how it works: 1. Each server started using `cds watch` registers all served services in `~/cds-services.json`. 2. Every subsequently started server binds automatically all `required` remote services, to equally named services already registered in `~/cds-services.json`. In our case: The main app's `ModelProviderService` automatically receives the service binding credentials, for example `url`, to talk to the one served by the sidecar. ### Build Sidecar for Production > Source: /docs/guides/multitenancy/mtxs#build-sidecar-for-production When deploying a sidecar for production, it doesn't have access to the main app's models two levels up the deployed folder hierarchy. Instead we have to prepare deployment by running `cds build` in the project's root: ```sh cds build ``` One of the build tasks that are executed is the `mtx-sidecar` build task. It generates log output similar to the following: ```log [cds] - the following build tasks will be executed {"for":"mtx-sidecar", "src":"mtx/sidecar", "options":... } [cds] - done > wrote output to: gen/mtx/sidecar/_main/fts/isbn/csn.json gen/mtx/sidecar/_main/fts/reviews/csn.json gen/mtx/sidecar/_main/resources.tgz gen/mtx/sidecar/_main/srv/_i18n/i18n.json gen/mtx/sidecar/_main/srv/csn.json gen/mtx/sidecar/package.json gen/mtx/sidecar/srv/_i18n/i18n.json gen/mtx/sidecar/srv/csn.json [cds] - build completed in 687 ms ``` The outcome of that build task is a compiled and deployable version of the sidecar in the _gen/mtx/sidecar_ staging areas: ```zsh{6-17} bookshop/ ├─ _i18n/ ├─ app/ ├─ db/ ├─ fts/ ├─ gen/mtx/sidecar/ │ ├─ _main/ │ │ ├── fts/ │ │ │ ├── isbn/ │ │ │ │ └── csn.json │ │ │ └── reviews/ │ │ │ └── csn.json │ │ ├── srv/ │ │ │ ├── _i18n │ │ │ └── csn.json │ │ └── resources.tgz │ └─ package.json ├─ mtx/sidecar/ ├─ ... ``` In essence, the `mtx-sidecar` build task does the following: 1. It runs a standard Node.js build for the sidecar. 2. It pre-compiles the main app's models, including all features into respective _csn.json_ files, packaged into the `_main` subfolder. 3. It collects all additional sources required for subsequent deployments to `resources.tgz`. For example, these include _.csv_ and _i18n_ files. ### Test-Drive Production Locally > Source: /docs/guides/multitenancy/mtxs#test-drive-production-locally We can also test-drive the production-ready variant of the sidecar locally before actual deployment, again using two separate shells. 1. **First, start sidecar** from `gen/mtx/sidecar` in `prod` simulation mode: ```sh cds watch gen/mtx/sidecar --profile development,prod ``` 2. **Second, start main** app as usual: ```sh cds watch ``` #### _ModelProviderService_ serving models from main app > Source: /docs/guides/multitenancy/mtxs#modelproviderservice-serving-models-from-main-app-1 When we now use our application again, and inspect the sidecar's trace logs, we see that the sidecar reads and returns the main app's precompiled models from `_main` now: ```log [cds] – POST /-/cds/model-provider/getCsn [cds] – model loaded from 3 file(s): gen/mtx/sidecar/_main/srv/csn.json gen/mtx/sidecar/_main/fts/isbn/csn.json gen/mtx/sidecar/_main/fts/reviews/csn.json ``` ## Configuration > Source: /docs/guides/multitenancy/mtxs#configuration ### Shortcuts `cds.requires.multitenancy / extensibility / toggles` > Source: /docs/guides/multitenancy/mtxs#shortcuts-cdsrequiresmultitenancy--extensibility--toggles The easiest way to enable multitenancy, extensibility, and feature toggles is as follows: ```json "cds": { "requires": { "multitenancy": true, "extensibility": true, "toggles": true } } ``` On the one hand, these settings are interpreted by the CAP runtime to support features such as tenant-specific database connection pooling when `multitenancy` is enabled. On the other hand, these flags are checked during server bootstrapping to ensure the required combinations of services are served by default. The following tables shows which services are enabled by one of the shortcuts: | | `multitenancy` | `extensibility` | `toggles` | | ----------------------------------------------------- | :------------: | :-------------: | :-------: | | _[SaasProvisioningService](#saasprovisioningservice)_ | yes | no | no | | _[DeploymentService](#deploymentservice)_ | yes | no | no | | _[ExtensibilityService](#extensibilityservice)_ | no | yes | no | | _[ModelProviderService](#modelproviderservice)_ | yes | yes | yes | ### Configuring Individual Services > Source: /docs/guides/multitenancy/mtxs#configuring-individual-services In addition or alternatively to the convenient shortcuts above you can configure each service individually, as shown in the following examples: ```jsonc "cds": { "requires": { "cds.xt.DeploymentService": true } } ``` The names of the service-individual configuration options are: - `cds/requires/` ##### Allowed Values > Source: /docs/guides/multitenancy/mtxs#allowed-values - `false` — deactivates the service selectively - `true` — activates the service with defaults for embedded usage - `` — uses [preset](#presets), for example, with defaults for sidecar usage - `{ ...options }` — add/override individual configuration options ##### Common Config Options > Source: /docs/guides/multitenancy/mtxs#common-config-options - `model` — specifies/overrides the service model to be used - `impl` — specifies/overrides the service implementation to be used - `kind` — the kind of service/consumption, for example, `rest` for remote usage > These options are supported by all services. #### Combined with Convenience Flags > Source: /docs/guides/multitenancy/mtxs#combined-with-convenience-flags ```json "cds": { "requires": { "multitenancy": true, "cds.xt.SaasProvisioningService": false, "cds.xt.DeploymentService": false, "cds.xt.ModelProviderService": { "kind": "rest" } } } ``` This tells the CAP runtime to enable multitenancy, but neither serve the _DeploymentService_, nor the _SaasProvisioningService_, and to use a remote _ModelProviderService_ via REST protocol. #### Individual Configurations Only > Source: /docs/guides/multitenancy/mtxs#individual-configurations-only We can also use only the individual service configurations: ```json "cds": { "requires": { "cds.xt.DeploymentService": true, "cds.xt.ModelProviderService": { "root": "../.." } } } ``` In this case, the server will **not** run in multitenancy mode. Also, extensibility and feature toggles are not supported. Yet, the _DeploymentService_ and the _ModelProviderService_ are served selectively. For example, this kind of configuration can be used in [sidecars](#sidecars). ### Using Configuration Presets > Source: /docs/guides/multitenancy/mtxs#using-configuration-presets #### Profile-based configuration > Source: /docs/guides/multitenancy/mtxs#profile-based-configuration The simplest and for most projects sufficient configuration is the profile-based one, where just these two entries are necessary: ::: code-group ```json [package.json] "cds": { "profile": "with-mtx-sidecar" } ``` ::: ::: code-group ```json [mtx/sidecar/package.json] "cds": { "profile": "mtx-sidecar" } ``` ::: #### Preset-based configuration > Source: /docs/guides/multitenancy/mtxs#preset-based-configuration Some MTX services come with pre-defined configuration presets, which can easily be used by referring to the preset suffixes. For example, to simplify and standardize sidecar configuration, _[ModelProviderService](#modelproviderservice)_ supports the `in-sidecar` preset which can be used like that: ```json "cds": { "requires": { "cds.xt.ModelProviderService": "in-sidecar" } } ``` These presets are actually configured in `cds.env` defaults like that: ```js cds: { requires: { // Configuration Presets (in cds.env.requires.kinds) kinds: { "cds.xt.ModelProviderService-in-sidecar": { "[development]": { root: "../.." }, "[production]": { root: "_main" }, }, "cds.xt.ModelProviderService": { model: "@sap/cds/srv/model-provider" }, // ... } } } ``` [Learn more about `cds.env`](../../node.js/cds-env){.learn-more} ### Inspecting Effective Configuration > Source: /docs/guides/multitenancy/mtxs#inspecting-effective-configuration You can always inspect the effective configuration by executing this in the _mtx/sidecar_ folder: ```sh cds env get requires ``` This will give you an output like this: ```js { auth: { strategy: 'dummy', kind: 'dummy' }, 'cds.xt.ModelProviderService': { root:'../..', model:'@sap/cds/srv/model-provider', kind:'in-sidecar' } } ``` Add CLI option `--profile` to inspect configuration in different profiles: ```sh cds env get requires --profile development cds env get requires --profile production ``` ## Customization > Source: /docs/guides/multitenancy/mtxs#customization All services are defined and implemented as standard CAP services, with service definitions in CDS, and implementations based on the CAP Node.js framework. Thus, you can easily do both, adapt service definitions, as well as hook into all events to add custom logic using CAP Node.js. ### Customizing Service Definitions > Source: /docs/guides/multitenancy/mtxs#customizing-service-definitions For example, you could override the endpoints to serve a service: ```cds using { cds.xt.ModelProviderService } from '@sap/cds-mtxs'; annotate ModelProviderService with @path: '/mtx/mps'; ``` For sidecar scenarios, define the annotations in the Node.js sidecar application and not as part of the main application. ### Adding Custom Lifecycle Event Handlers > Source: /docs/guides/multitenancy/mtxs#adding-custom-lifecycle-event-handlers Register handlers in `server.js` files: ::: code-group ```js [mtx/sidecar/server.js] const cds = require('@sap/cds') cds.on('served', ()=>{ const { 'cds.xt.ModelProviderService': mps } = cds.services const { 'cds.xt.DeploymentService': ds } = cds.services ds.before ('upgrade', (req) => { ... }) ds.after ('subscribe', (_,req) => { ... }) mps.after ('getCsn', (csn) => { ... }) }) ``` ::: ::: tip Custom hooks for CLI usage For CLI usage via `cds subscribe|upgrade|unsubscribe` you can create a `mtx/sidecar/cli.js` file, which works analogously to a `server.js`. ::: #### Example handler for SaasProvisioningService > Source: /docs/guides/multitenancy/mtxs#example-handler-for-saasprovisioningservice A common usecase is the specification of an individual database ID per subscription. ```js cds.on('served', async () => { const { 'cds.xt.SaasProvisioningService': provisioning, } = cds.services await provisioning.prepend(() => { provisioning.on('UPDATE', 'tenant', async (req, next) => { req.data = cds.utils.merge(req?.data, { _: { hdi: { create: { database_id: '', } } } }) return next() }) }) }) ``` ## Consumption > Source: /docs/guides/multitenancy/mtxs#consumption ### Via Programmatic APIs > Source: /docs/guides/multitenancy/mtxs#via-programmatic-apis Consume MTX services using standard [Service APIs](../../node.js/core-services). For example, in `cds repl`: ```js await cds.test() var { 'cds.xt.ModelProviderService': mps } = cds.services var { 'cds.xt.DeploymentService': ds } = cds.services var db = await ds.subscribe ('t1') var csn = await mps.getCsn('t1') cds.context = { tenant:'t1' } await db.run('SELECT type, name from sqlite_master') ``` ### Via REST APIs > Source: /docs/guides/multitenancy/mtxs#via-rest-apis Common usage of the MTX services is through REST APIs. Here's an example: 1. Start the server ```sh cds watch ``` 2. Subscribe a tenant ```http POST /-/cds/deployment/subscribe HTTP/1.1 Content-Type: application/json { "tenant": "t1" } ``` 3. Get CSN from `ModelProviderService` ```http POST /-/cds/model-provider/getCsn HTTP/1.1 Content-Type: application/json { "tenant": "t1", "toggles": ["*"] } ``` ## ModelProviderService > Source: /docs/guides/multitenancy/mtxs#modelproviderservice The _ModelProviderService_ serves model variants, which may include tenant-specific extensions and/or feature-toggled aspects. | | | | ----------------------- | ---------------------------------- | | Service Definition | `@sap/cds-mtxs/srv/model-provider` | | Service Definition Name | `cds.xt.ModelProviderService` | | Default HTTP Endpoint | `/-/cds/model-provider` | ### Configuration > Source: /docs/guides/multitenancy/mtxs#configuration-1 ```json "cds.xt.ModelProviderService": { "root": "../../custom/path" } ``` - [Common Config Options](#common-config-options) - `root` — a directory name, absolute or relative to the _package.json_'s location, specifying the location to search for models and resources to be served by the model provider services. Default is undefined, for embedded usage of model provider. In case of a sidecar, it refers to the main app's model; usually `"../.."` during development, and `"_main"` in production. ##### Supported Presets > Source: /docs/guides/multitenancy/mtxs#supported-presets - `in-sidecar` — provides defaults for usage in sidecars - `from-sidecar` — shortcut for `{ "kind": "rest" }` ### `getCsn` _(tenant, toggles) → CSN_ > Source: /docs/guides/multitenancy/mtxs#getcsn-tenant-toggles--csn Returns the application's effective CSN document for the given tenant + feature toggles vector. CAP runtimes call that method to obtain the effective models to serve. | Arguments | Description | | --------- | ----------------------------------------------------------- | | `tenant` | A string identifying the tenant | | `toggles` | An array listing toggled features; `['*']` for all features | #### Example Usage > Source: /docs/guides/multitenancy/mtxs#example-usage ```http POST /-/cds/model-provider/getCsn HTTP/1.1 Content-Type: application/json { "tenant": "t1", "toggles": ["*"] } ``` The response is a CSN in JSON representation. [Learn more about **CSN**](http://localhost:5173/docs/cds/csn) {.learn-more} ### `getEdmx` _(tenant, toggles, service, locale) → EDMX_ > Source: /docs/guides/multitenancy/mtxs#getedmx-tenant-toggles-service-locale--edmx Returns the EDMX document for a given service in context of the given tenant and feature toggles vector. CAP runtimes call this to get the EDMX document they return in response to OData `$metadata` requests. | Arguments | Description | | --------- | ----------------------------------------------------------- | | `tenant` | A string identifying the tenant | | `toggles` | An array listing toggled features; `['*']` for all features | | `service` | Fully-qualified name of a service definition | | `locale` | Requested locale, that is, as from `accept-language` header | #### Example Usage > Source: /docs/guides/multitenancy/mtxs#example-usage-1 ```http POST /-/cds/model-provider/getEdmx HTTP/1.1 Content-Type: application/json { "tenant": "t1", "toggles": ["*"], "service": "CatalogService", "locale": "en" } ``` ### `getResources` _() → TAR_ > Source: /docs/guides/multitenancy/mtxs#getresources---tar Returns a _.tar_ archive containing CSV files, I18n files, as well as native database artifacts, required for deployment to databases. `DeploymentService` calls that whenever it receives a `subscribe` or `upgrade` event. ### `getExtensions` _(tenant) → CSN_ {get-extensions} > Source: /docs/guides/multitenancy/mtxs#getextensions-tenant--csn-get-extensions Returns a _parsed_ CSN document containing all the extensions stored in `cds.xt.Extensions` for the given tenant. | Arguments | Description | | --------- | ------------------------------- | | `tenant` | A string identifying the tenant | ### `isExtended` _(tenant) → true|false_ > Source: /docs/guides/multitenancy/mtxs#isextended-tenant--truefalse Returns `true` if the given `tenant` has extensions applied. | Arguments | Description | | --------- | ------------------------------- | | `tenant` | A string identifying the tenant | ## ExtensibilityService > Source: /docs/guides/multitenancy/mtxs#extensibilityservice The _ExtensibilityService_ allows to add and activate tenant-specific extensions at runtime. | | | | ----------------------- | ----------------------------------------- | | Service Definition | `@sap/cds-mtxs/srv/extensibility-service` | | Service Definition Name | `cds.xt.ExtensibilityService` | | Default HTTP Endpoint | `/-/cds/extensibility` | [See the extensibility guide for more context](../extensibility/customization){.learn-more} ### Configuration > Source: /docs/guides/multitenancy/mtxs#configuration-2 ```jsonc "cds.xt.ExtensibilityService": { // fields must start with x_ or xx_ "element-prefix": ["x_", "xx_"], // namespaces starting with com.sap or sap. can't be extended "namespace-blocklist": ["com.sap.", "sap."], "extension-allowlist": [ { // at most 2 new fields in entities from the my.bookshop namespace "for": ["my.bookshop"], "kind": "entity", "new-fields": 2, // allow extensions for field "description" only "fields": ["description"] }, { // at most 2 new entities in CatalogService "for": ["CatalogService"], "new-entities": 2, // allow @readonly annotations in CatalogService "annotations": ["@readonly"] } ] } ``` - [Common Config Options](#common-config-options) - `element-prefix` — restrict field names to prefix - `namespace-blocklist` — restrict namespaces to be extended - `extension-allowlist` — allow certain entities to be extended > Without `extension-allowlist` configured, extensions are forbidden. Using `"for": ["*"]` applies the rules to all possible values. See the [list of possible `kind` values](../../cds/csn#def-properties).{.learn-more} - `new-fields` specifies the maximum number of fields that can be added. - `fields` lists the fields that are allowed to be extended. If the list is omitted, all fields can be extended. - `new-entities` specifies the maximum number of entities that can be added to a service. [Check Extension Restrictions for more details.](#extension-restrictions){.learn-more} ### GET `Extensions/` _→ [{ ID, csn, timestamp }]_ > Source: /docs/guides/multitenancy/mtxs#get-extensionsid---id-csn-timestamp- Returns a list of all tenant-specific extensions.
#### Request Format > Source: /docs/guides/multitenancy/mtxs#request-format | **Parameters** | Description | | - | - | | `ID` | String uniquely identifying the extension | > Omitting `ID` will return all extensions.
#### Response Format > Source: /docs/guides/multitenancy/mtxs#response-format | **Body** | Description | | - | - | | `ID` | String uniquely identifying the extension | | `csn` | Compiled extension CSN | | `timestamp` | Timestamp of activation date |
#### Example Request > Source: /docs/guides/multitenancy/mtxs#example-request ##### Get a specific extension > Source: /docs/guides/multitenancy/mtxs#get-a-specific-extension ::: code-group ```http [Request] GET /-/cds/extensibility/Extensions/isbn-extension HTTP/1.1 Content-Type: application/json ``` ```json [Response] { "ID": "isbn-extension", "csn": "{\"extensions\":[{\"extend\":\"my.bookshop.Books\",\"elements\":{\"Z_ISBN\":{\"type\":\"cds.String\"}}}],\"definitions\":{}}", "timestamp": "2023-01-01T01:01:01.111Z" } ``` ::: ##### Get all extensions > Source: /docs/guides/multitenancy/mtxs#get-all-extensions ::: code-group ```http [Request] GET /-/cds/extensibility/Extensions HTTP/1.1 Content-Type: application/json ``` ```json [Response] [ { "ID": "isbn-extension", "csn": "{\"extensions\":[{\"extend\":\"my.bookshop.Books\",\"elements\":{\"Z_ISBN\":{\"type\":\"cds.String\"}}}],\"definitions\":{}}", "timestamp": "2023-01-01T01:01:01.111Z" }, { "ID": "rental-extension", "csn": "{\"extensions\":[{\"extend\":\"my.bookshop.Books\",\"elements\":{\"Z_rentalPrice\":{\"type\":\"cds.Integer\"}}}],\"definitions\":{}}", "timestamp": "2023-01-01T01:02:01.111Z" } ] ``` ::: ### PUT `Extensions/` (\[csn\]) _→ \[{ ID, csn, timestamp }\]_ > Source: /docs/guides/multitenancy/mtxs#put-extensionsid-csn---id-csn-timestamp- Creates a new tenant-specific extension. #### HTTP Request Options > Source: /docs/guides/multitenancy/mtxs#http-request-options | Request Header | Example Value | Description | | ---------------- | -------------------------------------------------------|--------------| | `prefer` | `respond-async` | Trigger asynchronous extension activation |
#### Request Format > Source: /docs/guides/multitenancy/mtxs#request-format-1 | **Parameters** | Description | | - | - | | `ID` | String uniquely identifying the extension | | **Body** | `csn` | Array of extension CDL or CSN to apply | | `i18n` | Texts and translations |
#### Response Format > Source: /docs/guides/multitenancy/mtxs#response-format-1 | **Body** | Description | | - | - | | `ID` | String uniquely identifying the extension | | `csn` | Compiled extension CSN | | `i18n` | Texts and translations | | `timestamp` | Timestamp of activation date |
#### Example Request > Source: /docs/guides/multitenancy/mtxs#example-request-1 ::: code-group ```http [Request] PUT /-/cds/extensibility/Extensions/isbn-extension HTTP/1.1 Content-Type: application/json { "csn": ["using my.bookshop.Books from '_base/db/data-model'; extend my.bookshop.Books with { Z_ISBN: String };"], "i18n": [{ "name": "i18n.properties", "content": "Books_stock=Stock" }, { "name": "i18n_de.properties", "content": "Books_stock=Bestand" }] } ``` ```json [Response] { "ID": "isbn-extension", "csn": "{\"extensions\":[{\"extend\":\"my.bookshop.Books\",\"elements\":{\"Z_ISBN\":{\"type\":\"cds.String\"}}}],\"definitions\":{}}", "i18n": "{\"\":{\"Books_stock\":\"Stock\"},\"de\":{\"Books_stock\":\"Bestand\"}}", "timestamp": "2023-09-07T22:31:28.246Z" } ``` ::: The request can also be triggered asynchronously by setting the `Prefer: respond-async` header. You can use the URL returned in the `Location` response header to poll the job status. In addition, you can poll the status for individual tenants using its individual task ID: ```http GET /-/cds/jobs/pollTask(ID='') HTTP/1.1 ``` The response is similar to the following: ```js { "status": "FINISHED", "op": "activateExtension" } ``` The job and task status can take on the values `QUEUED`, `RUNNING`, `FINISHED` and `FAILED`. > By convention, custom (tenant-specific) fields are usually prefixed with `Z_`. The i18n data can also be passed in JSON format: ```json "i18n": [{ "name": "i18n.json", "content": "{\"\":{\"Books_stock\":\"Stock\"},\"de\":{\"Books_stock\":\"Bestand\"}}" }] ``` You also get this JSON in the response body of PUT or [GET](#get-extensions) requests. In this example, the text with key "Books_stock" from the base model is replaced. ### DELETE `Extensions/` > Source: /docs/guides/multitenancy/mtxs#delete-extensionsid Deletes a tenant-specific extension. #### HTTP Request Options > Source: /docs/guides/multitenancy/mtxs#http-request-options-1 | Request Header | Example Value | Description | | ---------------- | -------------------------------------------------------|--------------| | `prefer` | `respond-async` | Trigger asynchronous extension activation |
#### Request Format > Source: /docs/guides/multitenancy/mtxs#request-format-2 | **Parameters** | Description | | - | - | | `ID` | String uniquely identifying the extension |
#### Example Usage > Source: /docs/guides/multitenancy/mtxs#example-usage-2 ```http [Request] DELETE /-/cds/extensibility/Extensions/isbn-extension HTTP/1.1 Content-Type: application/json ``` The request can also be triggered asynchronously by setting the `Prefer: respond-async` header. You can use the URL returned in the `Location` response header to poll the job status. In addition, you can poll the status for individual tenants using its individual task ID: ```http GET /-/cds/jobs/pollTask(ID='') HTTP/1.1 ``` The response is similar to the following: ```js { "status": "FINISHED", "op": "activateExtension" } ``` The job and task status can take on the values `QUEUED`, `RUNNING`, `FINISHED` and `FAILED`. ### Extension Restrictions > Source: /docs/guides/multitenancy/mtxs#extension-restrictions You can restrict what parts of the application model can be extended by the SaaS customer. This section lists the restrictions that you can define via the `extension-allowlist`. > If an `extension-allowlist` is defined, only extensions in that list are allowed. Using `"for": ["*"]` allows to apply rules to all entities and services. :::info Most checks happen at design time `cds push` automatically builds the extension project, checking most restrictions locally. Some checks can only be performed at runtime, for example extension limit violations across multiple projects. ::: #### Restrict Service Extensions > Source: /docs/guides/multitenancy/mtxs#restrict-service-extensions By adding services to the `extension-allowlist`, services are enabled for extensions by Saas customers. In addition, you can restrict the number of bound entities by setting a limit for "new-entites". ```jsonc "cds.xt.ExtensibilityService": { "extension-allowlist": [ { // at most 2 new entities in CatalogService "for": ["CatalogService"], "new-entities": 2 } ] ``` #### Restrict Entities and Fields > Source: /docs/guides/multitenancy/mtxs#restrict-entities-and-fields Entities can be extended with additional fields and also modifications of existing fields. Both kinds of extensions can be restricted. - `new-fields` specifies the maximum number of fields that can be added. - `fields` lists existing fields that are allowed to be extended. If the list is omitted, all fields can be extended. ```jsonc "cds.xt.ExtensibilityService": { "extension-allowlist": [ { // at most 2 new fields in entities from the my.bookshop namespace "for": ["my.bookshop"], "new-fields": 2, // allow extensions for field "description" only "fields": ["description"] }, { // at most 1 new fields in my.bookshop.Authors "for": ["my.bookshop.Authors"], "new-fields": 1 } ] } ``` This restriction allows two new fields for all entities in namespace `my.bookshop` but only one new field in entity `my.bookshop.Authors`. The `field` restriction allows ```cds extend my.bookshop.Books:description with (length: 2000); ``` but not, for example ```cds extend my.bookshop.Books:title with (length: 200); ``` #### Restrict / Enable Annotations > Source: /docs/guides/multitenancy/mtxs#restrict--enable-annotations The following annotations are blocked by default because they affect the persistence or security. ```txt @restrict @requires @readonly @mandatory @assert.* @cds.persistence.* @sql.append @sql.prepend @path @impl @cds.autoexpose @cds.api.ignore @odata.etag @cds.query.limit @cds.localized @cds.valid.* @cds.search ``` You can, at your own risk, add exceptions for annotations. ```jsonc "cds.xt.ExtensibilityService": { "extension-allowlist": [ { "for": ["my.bookshop.Books"], "annotations": ["@mandatory", "@cds.api.ignore"] }, { "for": ["my.bookshop.Authors:placeOfBirth"], "annotations": ["@mandatory"] } ] } ``` > Exception: `@cds.persistence.journal` cannot be applied as an extension to base entities. #### Restrict Unbound Entities > Source: /docs/guides/multitenancy/mtxs#restrict-unbound-entities You can also restrict unbound entities via their namespace. For example ```jsonc "cds.xt.ExtensibilityService": { "extension-allowlist": [ { // at most 1 new entities for namepace my.new "for": ["my.new"], "new-entities": 1 } ] ``` only allows one unbound entity with namespace `my.new`. As a special case, you can also block any unbound entities: ```jsonc "cds.xt.ExtensibilityService": { "extension-allowlist": [ { // no new entities for all namespaces "for": ["*"], "new-entities": 0 } ] ``` ## DeploymentService > Source: /docs/guides/multitenancy/mtxs#deploymentservice The _DeploymentService_ handles `subscribe`, `unsubscribe`, and `upgrade` events for single tenants and single apps or micro services. Actual implementation is provided through internal plugins, for example, for SAP HANA and SQLite. | | | | ----------------------- | -------------------------------------- | | Service Definition | `@sap/cds-mtxs/srv/deployment-service` | | Service Definition Name | `cds.xt.DeploymentService` | | Default HTTP Endpoint | `/-/cds/deployment` | ### Configuration > Source: /docs/guides/multitenancy/mtxs#configuration-3 ```jsonc "cds.xt.DeploymentService": { "hdi": { "deploy": { ... }, "create": { "database_id": "", ... }, "bind": { ... } } } ``` - [Common Config Options](#common-config-options) - `hdi` — bundles HDI-specific settings - `deploy` — [HDI deployment parameters](https://www.npmjs.com/package/@sap/hdi-deploy#supported-features) - `create` — tenant creation parameters (≈ [`cf create-service`](https://help.sap.com/docs/BTP/65de2977205c403bbc107264b8eccf4b/a36df26b36484129b482ae20c3eb8004.html)) - `database_id` — SAP HANA Cloud instance ID - `bind` — binding parameters (≈ [`cf bind-service`](https://help.sap.com/docs/BTP/65de2977205c403bbc107264b8eccf4b/c7b09b79d3bb4d348a720ba27fe9a2d5.html)) ##### Supported Presets > Source: /docs/guides/multitenancy/mtxs#supported-presets-1 - `in-sidecar` — provides defaults for usage in sidecars - `from-sidecar` — shortcut for `{ "kind": "rest" }` ### `subscribe` _(tenant)_ > Source: /docs/guides/multitenancy/mtxs#subscribe-tenant Received when a new tenant subscribes. The implementations create and initialize required resources, that is, creating and initializing tenant-specific HDI containers in case of SAP HANA, or tenant-specific databases in case of SQLite. ::: tip `subscribe` can be triggered multiple times In SAP BTP scenarios, the SaaS registry uses the same endpoint for both initial subscription and later updates. MTX forwards the original SaaS registry payload to `DeploymentService` as the `metadata` parameter. Custom handlers for `subscribe` must therefore be **idempotent** and able to handle multiple calls for the same tenant. ::: ### `upgrade` _(tenant)_ > Source: /docs/guides/multitenancy/mtxs#upgrade-tenant Used to upgrade a subscribed tenant. Implementations read the latest models and content from the latest deployed version of the application and re-deploy that to the tenant's database. ##### Drop-Creating Databases for SQLite > Source: /docs/guides/multitenancy/mtxs#drop-creating-databases-for-sqlite In case of SQLite, especially in case of in-memory databases, an upgrade will simply drop and create a new tenant-specific database. Which means all data is lost. ##### Schema Evolution for SAP HANA > Source: /docs/guides/multitenancy/mtxs#schema-evolution-for-sap-hana In case of SAP HANA, the delta to the former database layout will be determined, and corresponding CREATE TABLE, DROP-CREATE VIEW, and ALTER TABLE statements will eventually be executed without any data loss. ### `unsubscribe` _(tenant)_ > Source: /docs/guides/multitenancy/mtxs#unsubscribe-tenant Received when a tenant is deleted. The implementations free required resources, that is, dispose tenant-specific HDI containers in case of SAP HANA, or tenant-specific databases in case of SQLite. ## SaasProvisioningService > Source: /docs/guides/multitenancy/mtxs#saasprovisioningservice The _SaasProvisioningService_ is a façade for the _DeploymentService_ to adapt to the API expected by [SAP BTP's SaaS Provisioning service](https://discovery-center.cloud.sap/serviceCatalog/saas-provisioning-service), hence providing out-of-the-box integration. | | | | ----------------------- | ------------------------------------------------ | | Service Definition | `@sap/cds-mtxs/srv/cf/saas-provisioning-service` | | Service Definition Name | `cds.xt.SaasProvisioningService` | | Default HTTP Endpoint | `/-/cds/saas-provisioning` | ### Configuration > Source: /docs/guides/multitenancy/mtxs#configuration-4 ```jsonc "cds.xt.SaasProvisioningService": { "jobs": { "queueSize": 5, // default: 100 "workerSize": 5, // default: 4 "clusterSize": 5, // default: 1 } } ``` - [Common Config Options](#common-config-options) - `jobs` — settings of the built-in job orchestrator - `workerSize` — max number of parallel asynchronous jobs per database - `clusterSize` — max number of database clusters, running `workerSize` jobs each - `queueSize` — max number of jobs waiting to run in the job queue :::warning clusterSize configuration is not available with HANA TMS v2 When using [HANA TMS v2](../multitenancy/index.md#sap-hana-tms-v2), the cds/requires/cds.xt.SaasProvisioningService/jobs/clusterSize is automatically set to `1`. HANA TMS v2 currently does not provide a performant way to determine all database IDs, so clustering the upgrade by database does not work properly. ::: #### HTTP Request Options > Source: /docs/guides/multitenancy/mtxs#http-request-options-2 | Request Header | Example Value | Description | | ---------------- | -------------------------------------------------------|--------------| | `prefer` | `respond-async` | Trigger subscription, upgrade or unsubscription request asynchronously. | | `status_callback` | `/saas-manager/v1/subscription-callback/123456/result` | Callback path for SAP BTP SaaS Provisioning service. Set automatically if asynchronous subscription is configured for `saas-registry` service. | ::: tip No `prefer: respond-async` needed with callback Requests are implicitly asynchronous when `status_callback` is set. ::: ##### Passing tenant-specific deployment parameters > Source: /docs/guides/multitenancy/mtxs#passing-tenant-specific-deployment-parameters Using the `"_"` section of the payload, you can pass deployment parameters for an individual tenant. The syntax is identical with the [static deployment configuration of `cds.xt.DeploymentService`](#deployment-config). In most cases, the requests are received from a third party, so the deployment parameters need to be added in [a handler implementation](#adding-custom-lifecycle-event-handlers) for `cds.xt.SaasProvisioningService`. ##### Example Usage > Source: /docs/guides/multitenancy/mtxs#example-usage-3
##### Subscription > Source: /docs/guides/multitenancy/mtxs#subscription A subscription for a tenant `t1` with a specific database ID: ```http PUT /-/cds/saas-provisioning/tenant/t1 HTTP/1.1 Content-Type: application/json { "subscribedTenantId": "t1", "eventType": "CREATE", "_": { "hdi": { "create": { "database_id": "" } } } } ``` ##### Upgrade > Source: /docs/guides/multitenancy/mtxs#upgrade With `@sap/hdi-deploy` [parameters](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-developer-guide-for-cloud-foundry-multitarget-applications-sap-business-app-studio/deployment-options-in-hdi) `trace` and `version`: ```http POST /-/cds/saas-provisioning/upgrade HTTP/1.1 Content-Type: application/json { "tenants": ["t1"], "options": { "_": { "hdi": { "deploy": { "trace": true, "version": true } } } } } ``` ### GET `tenant/` > Source: /docs/guides/multitenancy/mtxs#get-tenanttenant Returns tenant-specific metadata if `` is set, and a list of all tenants' metadata if omitted. | Parameters | Description | | ---------------- | ----------------------------------------------------------- | | `tenant` | A string identifying the tenant. | #### Example Usage > Source: /docs/guides/multitenancy/mtxs#example-usage-4 ##### Get Metadata for a Specific Tenant > Source: /docs/guides/multitenancy/mtxs#get-metadata-for-a-specific-tenant ::: code-group ```http [Request] GET /-/cds/saas-provisioning/tenant/t1 HTTP/1.1 Content-Type: application/json ``` ```json [Response] { "subscribedTenantId": "tenant-1", "eventType": "CREATE", "subscribedSubdomain": "subdomain-1", "subscriptionAppName": "app-1", "subscribedSubaccountId": "subaccount-1", "createdAt": "2023-11-10T14:36:22.639Z", "modifiedAt": "2023-13-10T15:16:22.802Z" } ``` ::: ##### Get Metadata for All Tenants > Source: /docs/guides/multitenancy/mtxs#get-metadata-for-all-tenants ::: code-group ```http [Request] GET /-/cds/saas-provisioning/tenant HTTP/1.1 Content-Type: application/json ``` ```json [Response] [ { "subscribedTenantId": "tenant-1", "eventType": "CREATE", "subscribedSubdomain": "subdomain-1", "subscriptionAppName": "app-1", "subscribedSubaccountId": "subaccount-1", "createdAt": "2023-11-10T14:36:22.639Z", "modifiedAt": "2023-13-10T15:16:22.802Z" }, { "subscribedTenantId": "tenant-2", "eventType": "CREATE", "subscribedSubdomain": "subdomain-2", "subscriptionAppName": "app-2", "subscribedSubaccountId": "subaccount-2", "createdAt": "2023-11-11T14:36:22.639Z", "modifiedAt": "2023-11-12T12:14:45.452Z" } ] ``` ::: ### PUT `tenant/` (...) > Source: /docs/guides/multitenancy/mtxs#put-tenanttenant- Creates tenant resources required for onboarding. Learn about query parameters, arguments, and their description in the following table: | Parameters | | | ---------------- | ------------------------------------------------------------------------ | | `tenant` | A string identifying the tenant | | Arguments | | `subscribedTenantId` | A string identifying the tenant | | `subscribedSubdomain` | A string identifying the tenant-specific subdomain | | `eventType` | The `saas-registry` event (`CREATE` or `UPDATE`) | #### Example Usage > Source: /docs/guides/multitenancy/mtxs#example-usage-5 ::: code-group ```http [Request] PUT /-/cds/saas-provisioning/tenant/t1 HTTP/1.1 Content-Type: application/json { "subscribedTenantId": "t1", "subscribedSubdomain": "subdomain1", "eventType": "CREATE", "_": { "hdi": { ... } } } ``` ```txt [Response] https://my.app.url ``` ::: ### DELETE `tenant/` > Source: /docs/guides/multitenancy/mtxs#delete-tenanttenant Deletes all tenant resources. ### GET `dependencies` _→ [{ xsappname }]_ > Source: /docs/guides/multitenancy/mtxs#get-dependencies---xsappname- Returns configured SAP BTP SaaS Provisioning service dependencies. [Learn how to configure SaaS dependencies](./#saas-dependencies){.learn-more} ### `upgrade` _[tenants] → Jobs_ > Source: /docs/guides/multitenancy/mtxs#upgrade-tenants--jobs Use the `upgrade` endpoint to upgrade tenant base models. | Arguments | Description | | --------- | ----------------------------------------------------------- | | `tenants` | A list of tenants, or `[*]` for all tenants | | `options` | Additional options, including HDI deployment options, see [DeploymentService](#deployment-config), prefixed with `_` | #### Example Usage > Source: /docs/guides/multitenancy/mtxs#example-usage-6 ##### Asynchronously Upgrade a List of Tenants > Source: /docs/guides/multitenancy/mtxs#asynchronously-upgrade-a-list-of-tenants ::: code-group ```http [Request] POST /-/cds/saas-provisioning/upgrade HTTP/1.1 Content-Type: application/json Prefer: respond-async { "tenants": ["t1", "t2"], "options": { "_": { "hdi": { "deploy": { "trace": true, "version": true } } } } } ``` ```json [Response] { "ID": "", "createdAt": "", "op": "upgrade", "tenants": { "t1": { "ID": "" } } } ``` ::: ##### Asynchronously Upgrade All Tenants > Source: /docs/guides/multitenancy/mtxs#asynchronously-upgrade-all-tenants ::: code-group ```http [Request] POST /-/cds/saas-provisioning/upgrade HTTP/1.1 Content-Type: application/json Prefer: respond-async { "tenants": ["*"] } ``` ```json [Response] { "ID": "", "createdAt": "", "op": "upgrade", "tenants": { "t1": { "ID": "" } } } ``` ::: We recommended to execute the upgrades asynchronously by setting the `Prefer: respond-async` header. You can use the URL returned in the `Location` response header to poll the job status. In addition, you can poll the status for individual tenants using its individual task ID: ```http GET /-/cds/jobs/pollTask(ID='') HTTP/1.1 ``` The response is similar to the following: ```js { "status": "FINISHED", "op": "upgrade" } ``` The job and task status can take on the values `QUEUED`, `RUNNING`, `FINISHED` and `FAILED`. ## About technical Tenant `t0` > Source: /docs/guides/multitenancy/mtxs#about-technical-tenant-t0 `t0` is a technical tenant used by `@sap/cds-mtxs`. It is a dedicated database container that stores operational metadata. The application's domain model is **not** deployed to `t0`. #### What `t0` stores > Source: /docs/guides/multitenancy/mtxs#what-t0-stores | Entity | Purpose | |--------|---------| | `cds.xt.Tenants` | Registry of all subscribed tenants with their metadata, schema info, and version | | `cds.xt.Jobs` | Async job state for operations like `subscribe`, `upgrade`, `extend` | | `cds.xt.Tasks` | Individual tasks within a job (one per tenant per operation) | #### Lifecycle of `t0` > Source: /docs/guides/multitenancy/mtxs#lifecycle-of-t0 The `t0` tenant is automatically created or updated at startup of the MTX sidecar. ##### Schema evolution > Source: /docs/guides/multitenancy/mtxs#schema-evolution Each startup checks if `t0` needs redeployment. If the schema is up-to-date, no action is taken. ##### Special constraints for `t0` > Source: /docs/guides/multitenancy/mtxs#special-constraints-for-t0 - Never uses `hana_tenant_id` from subscription parameters for [SAP HANA TMS v2](./index#sap-hana-tms-v2) - Never applies `dataEncryption` - Never applies the application's `cdsc` compiler options #### Configuring a different Tenant Name for `t0` > Source: /docs/guides/multitenancy/mtxs#configuring-a-different-tenant-name-for-t0 The default tenant name is `'t0'`. It can be customized via configuration cds/requires/multitenancy/t0 or environment variable `CDS_REQUIRES_MULTITENANCY_T0=my-custom-t0`. This is useful for scenarios where the `'t0'` name must vary per deployment (e.g., when multiple apps share the same Service Manager instance and need distinct `t0` containers). #### Default `database_id` and `lazyT0` > Source: /docs/guides/multitenancy/mtxs#default-databaseid-and-lazyt0 By default, `t0` is created on server startup without an explicit `database_id`. This means it uses the Service Manager's default (primary) HANA database associated with the service instance or the database that is configured for the cds/requires/cds.xt.DeploymentService/hdi/create/database_id [Learn more about DeploymentService configuration.](./mtxs.md#deployment-config){.learn-more} ##### `lazyT0` configuration > Source: /docs/guides/multitenancy/mtxs#lazyt0-configuration The creation of the `t0` tenant can be deferred using configuration cds/requires/cds.xt.DeploymentService/lazyT0. With that, the `t0` tenant is only created together with the first subscription. Before the first tenant is subscribed, `t0` is created with the same onboarding parameters (including `database_id`) as the subscribing tenant. This can be useful if the `database_id` is not known when deploying the MTX sidecar. ## Old MTX Reference > Source: /docs/guides/multitenancy/mtxs#old-mtx-reference [See Reference docs for former 'old' MTX Services.](old-mtx-apis){.learn-more} # Microservices with CAP > Source: /docs/guides/deploy/microservices A comprehensive guide on deploying your CAP application as microservices. ## Create a Solution Monorepo > Source: /docs/guides/deploy/microservices#create-a-solution-monorepo Assumed we want to create a composite application consisting of two or more micro services, each living in a separate GitHub repository, for example: - https://github.com/capire/bookstore - https://github.com/capire/reviews - https://github.com/capire/orders With some additional repositories, used as dependencies in the same manner, like: - https://github.com/capire/common - https://github.com/capire/bookshop - https://github.com/capire/data-viewer This guide describes a way to manage development and deployment via *[monorepos](https://en.wikipedia.org/wiki/Monorepo)* using *[NPM workspaces](https://docs.npmjs.com/cli/using-npm/workspaces)* and *[git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules)* techniques. 1. Create a new monorepo root directory using *NPM workspaces*: ```sh mkdir capire cd capire echo "{\"name\":\"@capire/samples\",\"workspaces\":[\"*\"]}" > package.json ``` 2. Add the previously mentioned projects as `git` submodules: ```sh git init git submodule add https://github.com/capire/bookstore git submodule add https://github.com/capire/reviews git submodule add https://github.com/capire/orders git submodule add https://github.com/capire/common git submodule add https://github.com/capire/bookshop git submodule add https://github.com/capire/data-viewer git submodule update --init ``` Add a _.gitignore_ file with the following content: ```txt node_modules gen ``` > The outcome of this looks and behaves exactly as the monorepo layout in *[cap/samples](https://github.com/capire/samples)*, so we can exercise the subsequent steps in there... 3. Test-drive locally: ```sh npm install ``` ```sh cds w bookshop ``` ```sh cds w bookstore ``` Each microservice can be started independently. If you start each microservice, one after the other in a different terminal, the connection is already established. [Learn more about Automatic Bindings by `cds watch`](../integration/reuse-and-compose#bindings-via-cds-watch){.learn-more} ::: details The project structure The project structure used here is as follows: ```txt / ├─ bookstore/ ├─ orders/ ├─ reviews/ ├─ ... └─ package.json ``` The individual services (`bookstore`, `reviews`, `orders`) can be one of the following: * folders, committed directly to the root project * git submodules Links between the projects are established using NPM dependencies. Since the root project defines workspaces, these dependencies are also found locally without the need for publishing or linking. When one of the projects is cloned in isolation, it's still possible to fetch dependencies to other modules via the NPM registry. ::: ## Using a Shared Database > Source: /docs/guides/deploy/microservices#using-a-shared-database You can deploy your model to a single database and then share it across applications, if you have one of the following scenarios: - multiple CAP applications relying on the same domain model - a monolithic CAP application that you want to split up **on the service level only, while still sharing the underlying database layer** In the following steps, we create an additional project to easily collect the relevant models from these projects, and act as a vehicle to deploy these models to SAP HANA in a controlled way. ::: details Why a shared database? A shared database is beneficial if the following are important for you: - **Query Performance:** Complex queries are executed much faster, for example `$expand` to an entity on another microservice, compared to calls across services with own data persistencies. - **Independent Scalability** of application runtimes, compared to a monolithic application. These are the (not so beneficial) side effects you when using a shared persistence: - Accessing data directly (without an API) means any changes in the data model affect all applications directly. - Every change in one of the services requires either one of the following: - Redeployment of all microservices involved. - Logic to decide which microservices need redeployment to avoid inconsistencies. - Violates the 12 factors concept. ::: ### Add a Project For Shared Database > Source: /docs/guides/deploy/microservices#add-a-project-for-shared-database 1. Add another `cds` project to collect the models from these projects: ```sh cds init shared-db --nodejs --add hana ``` ```sh npm add --workspace shared-db @capire/bookstore npm add --workspace shared-db @capire/reviews npm add --workspace shared-db @capire/orders ``` > Note how *NPM workspaces* allows us to use the package names of the projects, and nicely creates symlinks in *node_modules* accordingly. 2. Add a `shared-db/db/schema.cds` file as a mashup to actually collect the models: ::: code-group ```cds [shared-db/db/schema.cds] using from '@capire/bookstore'; using from '@capire/reviews'; using from '@capire/orders'; ``` ::: > Note: the `using` directives refer to `index.cds` files existing in the target packages. Your projects may have different entry points. ::: details Try it out With that we're basically done with the setup of the collector project. In sum, it's just another CAP project with some cds models in it, which we can handle as usual. We can test whether it all works as expected, for example, we can test-compile and test-deploy it to sqlite and hana, build it, and deploy it to the cloud as usual: ```sh cd shared-db ``` ```sh cds compile db -2 sql ``` ```sh cds compile db -2 hana ``` ```sh cds deploy -2 sqlite ``` ```sh cds build --for hana ``` ```sh cd .. ``` > Note: As we can see in the output for `cds deploy` and `cds build`, it also correctly collects and adds all initial data from enclosed `.csv` files. ::: ::: details Other project structures The project structure used here is as follows: ```txt / ├─ bookstore/ ├─ reviews/ ├─ orders/ └─ shared-db/ └─ db/ └─ schema.cds # references schemas of bookstore, reviews, orders └─ package.json # npm dependencies to bookstore, reviews, orders ├─ ... └─ package.json ``` The `shared-db` module is simply another CAP project, with only database content. The dependencies are installed via NPM, so it's still possible to install via an NPM registry if used outside of the monorepo setup. The database model could also be collected on root level instead of creating a separate `shared-db` module. When collecting on root level, the `cds build --ws` option can be used to collect the models of all NPM workspaces. ::: ## All-in-one Deployment > Source: /docs/guides/deploy/microservices#all-in-one-deployment This section is about how to deploy all 3+1 projects at once with a common _mta.yaml_. ![component diagram with synchronous and event communication for orders](./assets/microservices/bookstore.excalidraw.svg) [@capire/samples](https://github.com/capire/samples#readme) already has an all-in-one deployment implemented. Similar steps are necessary to convert projects with multiple CAP applications into a shared database deployment. ### Deployment Descriptor > Source: /docs/guides/deploy/microservices#deployment-descriptor Add initial multitarget application configuration for deployment to Cloud Foundry: ```shell cds add mta ``` [Learn more about **how to deploy to Cloud Foundry**.](../deploy/to-cf){.learn-more} ### Database > Source: /docs/guides/deploy/microservices#database Add initial database configuration using the command: ```shell cds add hana ``` Delete the generated _db_ folder as we don't need it on the root level: ```shell rm -r db ``` Update the `db-deployer` path to use our `shared-db` project [created previously](#using-a-shared-database): ::: code-group ```yaml [mta.yaml] - name: samples-db-deployer path: gen/db # [!code --] path: shared-db/gen/db # [!code ++] ``` ::: Add build command for generation of the database artifacts: ::: code-group ```yaml [mta.yaml] build-parameters: before-all: - builder: custom commands: - npm ci - npx cds build --production # [!code --] - npx cds build ./shared-db --for hana --production # [!code ++] ``` ::: ::: info `cds build --ws` If the CDS models of every NPM workspace contained in the monorepo should be considered, then instead of creating this `shared-db` folder, you can also use: ```shell cds build --for hana --production --ws ``` The `--ws` aggregates all models in the NPM workspaces. In this walkthrough, we only include a subset of the CDS models in the deployment. ::: ::: details Configure each app for cloud readiness The preceding steps only added configuration to the workspace root. Additionally add database configuration to each module that we want to deploy - bookstore, orders, and reviews: ```shell npm i @cap-js/hana --workspace bookstore npm i @cap-js/hana --workspace orders npm i @cap-js/hana --workspace reviews ``` ::: ### Applications > Source: /docs/guides/deploy/microservices#applications Replace the MTA module for `samples-srv` with versions for each CAP service and adjust `name`, `path`, and `provides[0].name` to match the module name. Also change the `npm-ci` builder to the `npm` builder. ::: code-group ```yaml [mta.yaml] modules: - name: bookstore-srv # [!code focus] type: nodejs path: bookstore/gen/srv # [!code focus] parameters: instances: 1 buildpack: nodejs_buildpack build-parameters: builder: npm # [!code focus] provides: # [!code focus] - name: bookstore-api # [!code focus] properties: srv-url: ${default-url} requires: - name: samples-db - name: samples-auth - name: samples-messaging - name: samples-destination - name: orders-srv # [!code focus] type: nodejs path: orders/gen/srv # [!code focus] parameters: instances: 1 buildpack: nodejs_buildpack build-parameters: builder: npm # [!code focus] provides: # [!code focus] - name: orders-api # [!code focus] properties: srv-url: ${default-url} requires: - name: samples-db - name: samples-auth - name: samples-messaging - name: samples-destination - name: reviews-srv # [!code focus] type: nodejs path: reviews/gen/srv # [!code focus] parameters: instances: 1 buildpack: nodejs_buildpack build-parameters: builder: npm # [!code focus] provides: # [!code focus] - name: reviews-api # [!code focus] properties: srv-url: ${default-url} requires: - name: samples-db - name: samples-auth - name: samples-messaging - name: samples-destination ... ``` ::: Add build commands for each module to be deployed: ::: code-group ```yaml [mta.yaml] build-parameters: before-all: - builder: custom commands: - npm ci - npx cds build ./shared-db --for hana --production - npx cds build ./orders --for nodejs --production # [!code ++] - npx cds build ./reviews --for nodejs --production # [!code ++] - npx cds build ./bookstore --for nodejs --production # [!code ++] ``` ::: ### Authentication > Source: /docs/guides/deploy/microservices#authentication Add [security configuration](../security/authentication) using the command: ```shell cds add xsuaa --for production ``` Add the admin role ::: code-group ```json [xs-security.json] { "scopes": [ { // [!code ++] "name": "$XSAPPNAME.admin", // [!code ++] "description": "admin" // [!code ++] } // [!code ++] ], "role-templates": [ { // [!code ++] "name": "admin", // [!code ++] "scope-references": [ // [!code ++] "$XSAPPNAME.admin" // [!code ++] ], // [!code ++] "description": "cap samples multi-service shared-db" // [!code ++] } // [!code ++] ] } ``` ::: ::: details Configure each app for cloud readiness Add NPM dependency `@sap/xssec`: ```shell npm i @sap/xssec --workspace bookstore npm i @sap/xssec --workspace orders npm i @sap/xssec --workspace reviews ``` ::: ### Messaging > Source: /docs/guides/deploy/microservices#messaging The messaging service is used to organize asynchronous communication between the CAP services. ```shell cds add enterprise-messaging ``` Relax the publish filters for the message topics ::: code-group ```json [event-mesh.json] { ... "rules": { "topicRules": { "publishFilter": [ "${namespace}/*" // [!code --] "*" // [!code ++] ], "subscribeFilter": [ "*" ] }, "queueRules": { "publishFilter": [ "${namespace}/*" ], "subscribeFilter": [ "${namespace}/*" ] } } } ``` ::: Parameterize the properties `emname` and `namespace`: ::: code-group ```json [event-mesh.json] { "emname": "samples-emname", // [!code --] "version": "1.1.0", "namespace": "default/samples/1", // [!code --] ... } ``` ::: ::: code-group ```yaml [mta.yaml] resources: - name: samples-messaging type: org.cloudfoundry.managed-service parameters: service: enterprise-messaging service-plan: default path: ./event-mesh.json config: # [!code ++] emname: bookstore-${org}-${space} # [!code ++] namespace: cap/samples/${space} # [!code ++] ``` ::: ::: details Configure each app for cloud readiness Enable messaging for the modules that use it: ::: code-group ```json [bookstore/package.json] { "cds": { "requires": { "messaging": true // [!code ++] } } } ``` ```json [orders/package.json] { "cds": { "requires": { "messaging": true // [!code ++] } } } ``` ::: ### Destinations > Source: /docs/guides/deploy/microservices#destinations Add [destination configuration](https://cap.cloud.sap/docs/guides/services/consuming-services#using-destinations) for connectivity between the apps: ```shell cds add destination ``` Add destinations that point to the API endpoints of the orders and reviews applications: ::: code-group ```yaml [mta.yaml] modules: ... - name: destination-content type: com.sap.application.content requires: - name: orders-api - name: reviews-api - name: bookstore-api - name: samples-auth parameters: service-key: name: xsuaa_service-key - name: samples-destination parameters: content-target: true build-parameters: no-source: true parameters: content: instance: existing_destinations_policy: update destinations: - Name: orders-dest URL: ~{orders-api/srv-url} Authentication: OAuth2ClientCredentials TokenServiceInstanceName: samples-auth TokenServiceKeyName: xsuaa_service-key - Name: reviews-dest URL: ~{reviews-api/srv-url} Authentication: OAuth2ClientCredentials TokenServiceInstanceName: samples-auth TokenServiceKeyName: xsuaa_service-key ... ``` ::: Use the destinations in the bookstore application: ::: code-group ```yaml [mta.yaml] modules: - name: bookstore-srv ... properties: # [!code ++] cds_requires_ReviewsService_credentials: {"destination": "reviews-dest","path": "/reviews"} # [!code ++] cds_requires_OrdersService_credentials: {"destination": "orders-dest","path": "/odata/v4/orders"} # [!code ++] ``` ::: ::: details Configure each app for cloud readiness Add `@sap-cloud-sdk/http-client` and `@sap-cloud-sdk/resilience` for each module utilizing the destinations: ```shell npm i @sap-cloud-sdk/http-client --workspace bookstore npm i @sap-cloud-sdk/resilience --workspace bookstore ``` ::: ### App Router > Source: /docs/guides/deploy/microservices#app-router Add _App Router_ configuration using the command: ```shell cds add approuter ``` The App Router serves the UIs and acts as a proxy for requests toward the different apps. Since the App Router folder is only necessary for deployment, we move it into a `.deploy` folder. ```shell mkdir .deploy mv app/router .deploy/app-router ``` ::: code-group ```yaml [mta.yaml] modules: ... - name: samples type: approuter.nodejs path: app/router # [!code --] path: .deploy/app-router # [!code ++] ... ``` ::: #### Static Content > Source: /docs/guides/deploy/microservices#static-content The App Router can serve static content. Since our UIs are located in different NPM workspaces, we create symbolic links to them as an easy way to deploy them as part of the App Router. ```shell mkdir .deploy/app-router/resources cd .deploy/app-router/resources ln -s ../../../bookshop/app/vue bookshop ln -s ../../../orders/app/orders orders ln -s ../../../reviews/app/vue reviews cd ../../.. ``` ::: warning Simplified Setup This is a simplified setup which deploys the static content as part of the App Router. See [Deploy to Cloud Foundry](./to-cf#add-ui) for a productive UI setup. ::: #### Configuration > Source: /docs/guides/deploy/microservices#configuration Add destinations for each app url: ::: code-group ```yaml [mta.yaml] modules: ... - name: samples type: approuter.nodejs .... requires: - name: service-api # [!code --] group: destinations # [!code --] properties: # [!code --] name: service-api # [!code --] url: ~{srv-url} # [!code --] forwardAuthToken: true # [!code --] - name: orders-api # [!code ++] group: destinations # [!code ++] properties: # [!code ++] name: orders-api # [!code ++] url: ~{srv-url} # [!code ++] forwardAuthToken: true # [!code ++] - name: reviews-api # [!code ++] group: destinations # [!code ++] properties: # [!code ++] name: reviews-api # [!code ++] url: ~{srv-url} # [!code ++] forwardAuthToken: true # [!code ++] - name: bookstore-api # [!code ++] group: destinations # [!code ++] properties: # [!code ++] name: bookstore-api # [!code ++] url: ~{srv-url} # [!code ++] forwardAuthToken: true # [!code ++] ``` ::: The _xs-app.json_ file describes how to forward incoming request to the API endpoint / OData services and is located in the _.deploy/app-router_ folder. Each exposed CAP Service endpoint needs to be directed to the corresponding application which is providing this CAP service. ::: code-group ```json [.deploy/app-router/xs-app.json] { "routes": [ { // [!code --] "source": "^/(.*)$", // [!code --] "target": "$1", // [!code --] "destination": "srv-api", // [!code --] "csrfProtection": true // [!code --] } // [!code --] { // [!code ++] "source": "^/admin/(.*)$", // [!code ++] "target": "/admin/$1", // [!code ++] "destination": "bookstore-api", // [!code ++] "csrfProtection": true // [!code ++] }, // [!code ++] { // [!code ++] "source": "^/browse/(.*)$", // [!code ++] "target": "/browse/$1", // [!code ++] "destination": "bookstore-api", // [!code ++] "csrfProtection": true // [!code ++] }, // [!code ++] { // [!code ++] "source": "^/user/(.*)$", // [!code ++] "target": "/user/$1", // [!code ++] "destination": "bookstore-api", // [!code ++] "csrfProtection": true // [!code ++] }, // [!code ++] { // [!code ++] "source": "^/odata/v4/orders/(.*)$", // [!code ++] "target": "/odata/v4/orders/$1", // [!code ++] "destination": "orders-api", // [!code ++] "csrfProtection": true // [!code ++] }, // [!code ++] { // [!code ++] "source": "^/reviews/(.*)$", // [!code ++] "target": "/reviews/$1", // [!code ++] "destination": "reviews-api", // [!code ++] "csrfProtection": true // [!code ++] } // [!code ++] ] } ``` ::: Add routes for static content: ::: code-group ```json [.deploy/app-router/xs-app.json] { "routes": [ ... { // [!code ++] "source": "^/app/(.*)$", // [!code ++] "target": "$1", // [!code ++] "localDir": "resources", // [!code ++] "cacheControl": "no-cache, no-store, must-revalidate" // [!code ++] } // [!code ++] ] } ``` ::: The `/app/*` route exposes our UIs, so bookstore is available as `/app/bookstore`, orders as `/app/orders` and reviews as `/app/reviews`. Due to the `/app` prefix, make sure that static resources are accessed via relative paths inside the UIs. Add the `bookshop/index.html` as initial page when visiting the app: ::: code-group ```json [.deploy/app-router/xs-app.json] { "welcomeFile": "app/bookshop/index.html", // [!code ++] "routes": { ... } } ``` ::: Additionally, the welcomeFile is important for deployed Vue UIs as they obtain CSRF-Tokens via this url. ### Deploy > Source: /docs/guides/deploy/microservices#deploy Before deploying you need to log in to Cloud Foundry: `cf login --sso` Start the deployment and build process: ```sh cds up ``` [Learn more about `cds up`.](./to-cf#build-and-deploy){.learn-more} Once the app is deployed, you can get the url of the App Router via ```shell cf apps # [!code focus] name requested state processes routes bookstore-srv started web:1/1 my-capire-bookstore-srv.cfapps.us10-001.hana.ondemand.com orders-srv started web:1/1 my-capire-orders-srv.cfapps.us10-001.hana.ondemand.com reviews-srv started web:1/1 my-capire-reviews-srv.cfapps.us10-001.hana.ondemand.com samples started web:1/1 my-capire-samples.cfapps.us10-001.hana.ondemand.com # [!code focus] samples-db-deployer stopped web:0/1 ``` You can then navigate to this url and the corresponding apps ```text / -> bookshop /app/bookshop -> bookshop /app/orders -> orders /app/reviews -> reviews ``` ## Deployment as Separate MTA > Source: /docs/guides/deploy/microservices#deployment-as-separate-mta This is an alternative to the all-in-one deployment. Assume the applications each already have their own _mta.yaml_. For example by running `cds add mta` in the _reviews_, _orders_ and _bookstore_ folder. ### Database > Source: /docs/guides/deploy/microservices#database-1 We can add the [previously created](#using-a-shared-database) `shared-db` project as its own MTA deployment: ::: code-group ```sh [shared-db/] cds add mta ``` ::: This adds everything necessary for a full CAP application. Since we only want the database and database deployment, remove everything else like the srv module and destination and messaging resources: ::: details Diff ```yaml _schema-version: 3.3.0 ID: shared-db version: 1.0.0 description: "A simple CAP project." parameters: enable-parallel-deployments: true build-parameters: before-all: - builder: custom commands: - npm ci - npx cds build --production # [!code --] - npx cds build --production --for hana # [!code ++] modules: - name: shared-db-srv # [!code --] type: nodejs # [!code --] path: gen/srv # [!code --] parameters: # [!code --] instances: 1 # [!code --] buildpack: nodejs_buildpack # [!code --] build-parameters: # [!code --] builder: npm-ci # [!code --] provides: # [!code --] - name: srv-api # [!code --] properties: # [!code --] srv-url: ${default-url} # [!code --] requires: # [!code --] - name: shared-db-destination # [!code --] - name: shared-db-messaging # [!code --] - name: shared-db-db # [!code --] - name: shared-db-db-deployer type: hdb path: gen/db parameters: buildpack: nodejs_buildpack requires: - name: shared-db-db resources: - name: shared-db-destination # [!code --] type: org.cloudfoundry.managed-service # [!code --] parameters: # [!code --] service: destination # [!code --] service-plan: lite # [!code --] - name: shared-db-messaging # [!code --] type: org.cloudfoundry.managed-service # [!code --] parameters: # [!code --] service: enterprise-messaging # [!code --] service-plan: default # [!code --] path: ./event-mesh.json # [!code --] - name: shared-db-db type: com.sap.xs.hdi-container parameters: service: hana service-plan: hdi-shared ``` ::: #### Binding to shared database > Source: /docs/guides/deploy/microservices#binding-to-shared-database The only thing left to care about is to ensure all 3+1 projects are bound and connected to the same database at deployment, subscription, and runtime. Configure the _mta.yaml_ of the other apps to bind to the existing shared database, for example, in the reviews module: ```yaml [reviews/mta.yaml] ... modules: ... - name: reviews-db-deployer # [!code --] type: hdb # [!code --] path: gen/db # [!code --] parameters: # [!code --] buildpack: nodejs_buildpack # [!code --] requires: # [!code --] - name: reviews-db # [!code --] resources: ... - name: reviews-db type: com.sap.xs.hdi-container # [!code --] type: org.cloudfoundry.existing-service # [!code ++] parameters: service: hana # [!code --] service-plan: hdi-shared # [!code --] service-name: shared-db-db # [!code ++] ``` #### Subsequent updates > Source: /docs/guides/deploy/microservices#subsequent-updates Whenever one of the projects has changes affecting the database, the database artifacts need to be deployed prior to the application deployment. With a single _mta.yaml_, this is handled in the scope of the MTA deployment. When using multiple deployment units, ensure to first deploy the `shared-db` project before deploying the others. ## Late-Cut Microservices > Source: /docs/guides/deploy/microservices#late-cut-microservices Microservices have been attributed with a multitude of benefits like - granular scalability, - deployment agility, - distributed development, and so on. While these benefits exist, they are accompanied by complexity and performance losses. True microservices each constitute their own deployment unit with their own database. The benefits attributed to microservices can be broken down into multiple aspects. | Aspect | Benefits | Drawbacks | | ---------- | -------- | --------- | | App Instances | Scalability, Resilience | Requires Statelessness | | Modules | Distributed Development, Structure | | | Applications | Independent Scalability, Fault Tolerance | Communication Overhead | | Deployment Units | Faster Deploy Times, Independent Deployments | Configuration Complexity | | Databases | Depends | Data Consistency, Fragmentation | ### Flexibility in Deployments > Source: /docs/guides/deploy/microservices#flexibility-in-deployments Instead of just choosing between a monolith and microservices, these aspects can be combined into an architecture that fits the specific product. Since each cut not only has benefits, but also drawbacks, it's important to choose which benefits actually help the overall product and which drawbacks can be accepted. ![Multiple deployment units - one contains the UIs, one contains shared service instances, one contains a shared database, two each contain an app connected to the shared database, one contains a database and an app, which is also connected to the shared database](./assets/microservices/complex.excalidraw.svg) ### A Late Cut > Source: /docs/guides/deploy/microservices#a-late-cut When developing a product, it may initially not be apparent where the boundaries are. Keeping this in mind, an app can be developed as a modular application with use case specific CAP services. It can first be deployed as a [monolith / modulith](#monolith-or-microservice). Once the boundaries are clear, it can then be split into multiple applications. Generally, the semantic separation and structure can be enforced using modules. The deployment configuration is then an independent step on top. In this way, the same application can be deployed as a monolith, as microservices with a shared database, as true microservices, or a combination of these, just via configuration change. ![Modules which can be arranged in different deploy configurations, for example, as a monolith (bookshop, reviews, orders), as two apps (bookshop, orders in one, reviews in the other), and so on.](./assets/microservices/late-cut.excalidraw.svg) ### Best Practices > Source: /docs/guides/deploy/microservices#best-practices * Prefer a late cut * Stay flexible in where to cut * Prefer staying loosely coupled → for example, ReviewsService → reviewed events → UPDATE average ratings * Leverage database-level integration selectively → Prefer referring to (public) service entities, not (private) database entities ## Appendix > Source: /docs/guides/deploy/microservices#appendix ### Monolith or Microservice > Source: /docs/guides/deploy/microservices#monolith-or-microservice A monolith is a single deployment unit with a single application. This is very convenient, because every part of the app is accessible in memory. ![A diagram showing a monolithic application architecture. Three modules labeled bookshop, reviews, and orders are grouped together inside a single large container, representing one deployment unit. The modules are visually separated within the container but are part of the same application. The environment is clean and technical, focusing on modular structure within a unified deployment. The tone is neutral and informative. Text in the image includes bookshop, reviews, and orders.](./assets/microservices/monolith.excalidraw.svg) A modulith, even though the app is separated into multiple CAP services inside multiple modules, can still be deployed as a single monolithic application. This combines the benefit of a clear structure and distributed development while keeping a simple deployment. ![A single application visualized as a large container holding three labeled modules: bookshop, reviews, and orders. The modules are grouped together within the container, indicating they are part of the same deployment unit. The environment is clean and technical, focusing on modular structure within a unified application. The tone is neutral and informative. Text in the image includes bookshop, reviews, and orders.](./assets/microservices/modulith.excalidraw.svg) True microservices each consist of their own deployment unit with their own application and their own database. Meaning that they're truly independent of each other. And it works well if they are actually independent. ![Diagram showing a simplified microservices architecture with three separate deployment units. Each unit contains one application labeled App and one database labeled DU. The units are visually separated, emphasizing their independence. The environment is clean and technical, focusing on the modular structure of microservices. The tone is neutral and informative. No additional text is present in the image.](./assets/microservices/true-microservices.excalidraw.svg) What was mentioned earlier is a simplified view. In an actual microservice deployment, there are typically shared service instances and wiring needs to be provided so that apps can talk to each other, directly or via events. If the microservices are not cut well, the communication overhead leads to high performance losses and often the need for data replication or caching. ![Diagram showing a detailed microservices architecture with three separate deployment units. Each unit contains one application labeled App and one database labeled DU. The units are visually separated, emphasizing their independence. Between the applications, there are two types of communication: a solid line labeled Events connecting the rightmost and center units, and a solid line connecting the center and left units. The environment is clean and technical, focusing on the modular structure and event-driven communication between microservices. The tone is neutral and informative. Text in the image includes DU, App, and Events.](./assets/microservices/true-microservices-full.excalidraw.svg) ### Application Instances > Source: /docs/guides/deploy/microservices#application-instances Having only a single virtual machine or container, the application can only be scaled vertically by increasing the CPU and memory resources. This typically has an upper limit and requires a restart when scaling. To improve scalability, we can start multiple instances of the same application. Benefits: - Near unlimited scaling - No downtimes when scaling - Better resilience against failures in single app instances Requirement: - The app needs to be stateless, state needs to be persisted Multiple app instances can be used for both monoliths and microservices. ![A single app with multiple application instances](./assets/microservices/app-instances.excalidraw.svg) ### Modules > Source: /docs/guides/deploy/microservices#modules When many developers work on an app, a distribution of work is necessary. Nowadays this distribution is often reached by each team working on one or multiple microservices. Also, microservices are potentially cut by which team is developing them. Instead, developers can work on single modules, which are later deployed and run as a single app... or as multiple apps. But this choice is then independent of who is developing the module. Benefits: - Distributed Development - Clear Structure ![Modules: bookshop containing AdminService and CatalogService, reviews containing ReviewsService, orders containing OrdersService, common](./assets/microservices/modules.excalidraw.svg) ### Multiple Applications > Source: /docs/guides/deploy/microservices#multiple-applications As described above, [application instances](#application-instances) already have near unlimited scaling, even for a monolith. So why would you want multiple apps? Benefits: - Resource Separation - Independent Scaling - Fault Tolerance Drawbacks: - Latency for synchronous calls between dependent apps ![Three applications connected to the same database, two of the applications communicate](./assets/microservices/multiple-apps.excalidraw.svg) #### Resource Separation > Source: /docs/guides/deploy/microservices#resource-separation One part of an application may do highly critical background processing, while another handles incoming requests. The incoming requests take CPU cycles and consume memory, which should rather be used for the background processing. To make sure that there are always enough resources for specific tasks, they can be split into their own app. #### Independent Scaling > Source: /docs/guides/deploy/microservices#independent-scaling Similar to resource separation, different parts of the app may have different requirements and profiles for scaling. For some parts, a 100% CPU utilization over an extended period is accepted for efficiency, while request handling apps need spare resources to handle user requests with low latency. #### Fault Tolerance > Source: /docs/guides/deploy/microservices#fault-tolerance While app instances already provide some resilience, there are failure classes (for example, bugs) which affect each app instance. Separating functionality into different apps means that when one app experiences issues, the functionality of the other apps is still available. In the bookstore example, while reviews may be down, orders may still be possible. This benefit is null for apps with synchronous dependencies on each other. If A depends on synchronous calls to B, then if B is down, A is down as well. ### Multiple Deployment Units > Source: /docs/guides/deploy/microservices#multiple-deployment-units With multiple apps, you can still deploy them together as one unit, for example as part of a multitarget application archive. Once an application grows bigger, this takes a significant amount of time. Deployments can then be split up either by type (for example, deploying UIs separately) or horizontally (for example, deploying each app via its own deployment unit). Benefits: - Faster individual deploy times - Independent deployments Drawbacks: - Coordination between deployment units for updates with dependencies - Configuration wiring to connect systems across deployment units ![Diagram illustrating multiple deployment units in a microservices architecture. The image shows several distinct containers, each representing a deployment unit. One unit contains UIs, another contains shared service instances, a third contains a shared database, and two separate units each contain an app connected to the shared database. There is also a unit that contains both a database and an app, which is also connected to the shared database. The containers are visually separated, emphasizing modularity and independent deployment. The environment is technical and organized, focusing on the structure and relationships between deployment units. Text in the image includes DU, App, and UIs. The tone is neutral and informative, highlighting architectural flexibility and separation of concerns in microservices deployment.](./assets/microservices/multiple-deployment-units.excalidraw.svg) With a single deployment unit, when a fix for one part needs to be deployed, the risk of redeploying the rest of the application needs to be considered. For example, there may already been changes to other parts of the app in the same code line. A restart / rolling restart may also lead to higher resource consumption due to startup activities and thus slightly degrade the performance during this time. Being able to deploy apps or other resources independently reduces the risk when a single part of the system needs to be updated. The update decision needs less coordination and can be made by the team responsible for this part of the system. Coordination is still necessary when deploying changes that affect the whole system, for example when a feature needs implementations in multiple apps. ### Multiple Databases > Source: /docs/guides/deploy/microservices#multiple-databases Here we need to differentiate between two scenarios: - Using multiple types of databases - Using multiple databases of the same type A polyglot persistence can be used when the app has different requirements for the types of data it needs to store. For example, there may be a large number of large files that can be stored in a document store, while corresponding administrative data is stored in a relational database. Benefits: - Use suitable technology for different use cases In contrast, using multiple databases of the same type may be suggested for - Scalability - Resource Separation - Tenant Isolation - Semantic Separation Scalability and resource separation need multiple database instances. Tenant isolation and semantic separation could also be achieved through multiple schemas or containers inside the same database instance. Drawbacks: - Data consistency across databases - Collecting data across databases ![A single application connected to multiple databases](./assets/microservices/multiple-dbs.excalidraw.svg) #### Data Federation > Source: /docs/guides/deploy/microservices#data-federation When data is distributed across multiple databases, strategies may be necessary to combine data from multiple sources. - Fetching on demand - Caching - HANA synonyms - Data Replication # Health Checks > Source: /docs/guides/deploy/health-checks On both Cloud Foundry and Kubernetes, it is possible to provide two separate endpoints for liveness checks ("are you alive?") and readiness checks ("are you ready for more requests?"). A failure on the former leads to a restart, whereas a failure on the latter temporarily takes the app instance out of the request dispatching rotation until a subsequent readiness probe is successful. [Learn more about health checks on Cloud Foundry.](https://docs.cloudfoundry.org/devguide/deploy-apps/healthchecks.html) {.learn-more} [Learn more about health checks on Kubernetes.](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes) {.learn-more} For **CAP Node.js**, the runtime provides an out-of-the-box endpoint for liveness and readiness checks at `/health`. Requests that reach this public endpoint are answered with the status code 200 and the body `{ status: 'UP' }`. [Learn more about availability checks in Node.js.](../../node.js/best-practices#availability-checks) {.learn-more} For **CAP Java**, `cds add mta` and `cds add kyma` add the necessary dependencies and configuration for the publicly available probe endpoints `/actuator/health/liveness` and `/actuator/health/readiness`. [Learn more about availability checks in Java.](../../java/operating-applications/observability#availability) {.learn-more} [Learn more about Spring Boot health checks.](../../java/operating-applications/observability#spring-health-checks){.learn-more} For deployment to Kyma/ Kubernetes, `@sap/cds-dk` adds the necessary configurations to Helm charts. ::: warning Limited support for readiness checks on CF Although supported by the Cloud Foundry core, readiness checks are not yet supported by the Cloud Foundtry CLI as well as the Cloud MTA Build Tool (MBT). ::: ::: tip Adjust deployment descriptors for custom health checks Adjust the values created by `cds add` in case you add different Spring Boot health check endpoints or use a fully custom _server.js_. ::: # Customizing `cds build` > Source: /docs/guides/deploy/build ## Automatic Build Tasks > Source: /docs/guides/deploy/build#automatic-build-tasks `cds build` runs _build tasks_ on your project to prepare it for deployment. Build tasks compile _source files_ (typically CDS sources) and create required artifacts, for example, EDMX files or SAP HANA design-time artifacts. For a full production build, this command should be enough for most projects: ```sh cds build --production ``` Build tasks are derived from the CDS configuration and project context. By default, CDS models are resolved from these sources: - _db/_, _srv/_, _app/_ — default root folders of a CAP project - _fts/_ and its subfolders when using [feature toggles](../extensibility/feature-toggles#enable-feature-toggles) - CDS model folders and files defined by [required services](../../node.js/cds-env#services) - Built-in examples: [Event Queues](../../node.js/event-queues#configuration) or [MTX-related services](../multitenancy/mtxs#mtx-services-reference) - Explicit `src` folder configured in the build task Feature toggle folders and required built-in service models will also be added if user-defined models have been configured as a [`model` option](#build-task-properties) in your build tasks. [Learn more about `cds.resolve`](../../node.js/cds-compile#cds-resolve){.learn-more} ## Extending `cds build` > Source: /docs/guides/deploy/build#extending-cds-build Provide additional service integrations by writing a `cds build` plugin: ```js // cds-plugin.js const cds = require('@sap/cds') cds.build?.register?.('my-plugin', class extends cds.build.Plugin { async build() { /* ... */ } } ) ``` [Learn more about `cds build` plugins](../../tools/apis/cds-build){.learn-more}{} ## Custom Build Tasks > Source: /docs/guides/deploy/build#custom-build-tasks If custom build tasks are configured, those properties have precedence. For example, you want to configure the _src_ folder and add the default models. To achieve this, **do not define the _model_ option in your build task**: ::: code-group ```jsonc [package.json] { "cds": { "build": { "target": "gen", "tasks": [ { "for": "nodejs", "src": "srv" } ] } }} ``` ::: This way, the model paths will still be dynamically determined, but the _src_ folder is taken from the build task configuration. You still benefit from the automatic determination of models – for example when adding a new external services or when CAP is changing any built-in service defaults. To control which tasks `cds build` executes, you can add them as part of your [project configuration](../../node.js/cds-env#project-settings) in _package.json_ or _.cdsrc.json_, as outlined in the following chapter. ## Build Task Types > Source: /docs/guides/deploy/build#build-task-types The `for` property defines the executed build task type creating its part of the deployment layout. Currently supported types are: | Type | Description | |------------------|-----------------------------------------------------------------------------| | `hana` | SAP HANA Development Infrastructure (HDI) artifacts

[Learn more about **configuring SAP HANA**](../databases/hana#configure-hana){.learn-more} | | `nodejs` | Node.js applications | | `java` | Java applications | | `mtx-sidecar` | [MTX](../multitenancy/mtxs)-enabled projects _with_ sidecar architecture.

[Learn more about **Multitenant Saas Application Deployment**](./to-cf){.learn-more} | | `mtx` | MTX-enabled projects _without_ sidecar architecture (Node.js only). Required services are served by the Node.js application itself. | | `mtx-extension` | MTX extension project (_extension.tgz_), which is required for extension activation using `cds push`. Extension point restrictions defined by the SaaS app provider are validated by default. If any restriction is violated the build aborts and the errors are logged.

The build task is created by default for projects that have `"cds": { "extends": "\" }` configured in their _package.json_.

[Learn more about **Extending and Customizing SaaS Solutions**](../extensibility/customization){.learn-more} | Additional types may be supported by build plugin contributions. ## Build Task Properties > Source: /docs/guides/deploy/build#build-task-properties Build tasks can be customized using the following properties: | Property | Description | |-----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `src` | Source folder of module to be built. | | `dest` | Optional destination of the module's build destination, relative to the enclosing project. The _src_ folder is used by default. | | `options` | `model`: _string_ or _array of string_

The given list of folders or individual _.cds_ file names is resolved based on the current working directory or project folder passed to `cds build`.

CDS built-in models (prefix _@sap/cds*_) are added by default to the user-defined list of models. | **Note:** Alternatively you can execute build tasks and pass the described arguments to the command line. See also `cds build --help` for further details. ## Build Target Folder > Source: /docs/guides/deploy/build#build-target-folder To change the default target folder, use the cds.build.target=path/to/my/folder. It is resolved based on the root folder of your project. #### Node.js > Source: /docs/guides/deploy/build#nodejs Node.js projects use the folder _./gen_ below the project root as build target folder by default.
Relevant source files from _db_ or _srv_ folders are copied into this folder, which makes it self-contained and ready for deployment. The default folder names can be changed with the cds.folders.db, cds.folders.srv, cds.folders.app configuration. Or you can go for individual build task configuration for full flexibility. Project files like _.cdsrc.json_ or _.npmrc_ located in the _root_ folder or in the _srv_ folder of your project are copied into the application's deployment folder (default _gen/srv_). Files located in the _srv_ folder have precedence over the corresponding files located in the project root directory. As a consequence these files are used when deployed to production. Make sure that the folders do not contain one of these files by mistake. Consider using profiles `development` or `production` in order to distinguish environments. CDS configuration that should be kept locally can be defined in a file _.cdsrc-private.json_. The contents of the _node_modules_ folder is _not_ copied into the deployment folder. For security reasons the files _default-env.json_ and _.env_ are also not copied into the deployment folder. You can verify the CDS configuration settings that become effective in `production` deployments. Executing `cds env --profile production` in the deployment folder _gen/srv_ will log the CDS configuration used in production environment. [Learn more about `cds env get`](../../node.js/cds-env#cli){.learn-more} **Note:** `cds build` provides `options` you can use to switch the copy behavior of specific files on or off on build task level: ::: code-group ```json [package.json] { "build": { "tasks": [ { "for": "nodejs", "options": { "contentCdsrcJson": false, "contentNpmrc": false } }, { "for": "hana", "options": { "contentNpmrc": false } } ] } } ``` ::: #### npm Workspace Support > Source: /docs/guides/deploy/build#npm-workspace-support-beta- Use CLI option `--ws-pack` to enable tarball based deployment of [npm workspace](https://docs.npmjs.com/cli/using-npm/workspaces) dependencies. Workspaces are typically used to manage multiple local packages within a singular top-level root package. Such a setup is often referred to as a [monorepo](https://earthly.dev/blog/npm-workspaces-monorepo/). As an effect, your workspace dependencies can be deployed to SAP BTP without them being published to an npm registry before. Behind the scenes, `cds build --ws-pack` creates a tarball in folder _gen/srv_ for each workspace dependency of your project that has a `*` version identifier. Dependencies in _gen/package.json_ will be adapted to point to the correct tarball file URL: ::: code-group ```jsonc [package.json] { "dependencies": { "some-package": "^1", // regular package "some-workspace": "*" // workspace dependency, marked as such via "*" } } ``` ::: Packaging of the tarball content is based on the rules of the [`npm pack`](https://docs.npmjs.com/cli/commands/npm-pack) command: - Files and folders defined in _.gitignore_ will not be added - If an optional `files` field is defined in the workspace's _package.json_, only those files will be added. #### Java > Source: /docs/guides/deploy/build#java Java projects use the project's root folder _./_ as build target folder by default.
This causes `cds build` to create the build output below the individual source folders. For example, _db/src/gen_ contains the build output for the _db/_ folder. No source files are copied to _db/src/gen_ because they're assumed to be deployed from their original location, the _db/_ folder itself. # Core Data Services (CDS) > Source: /docs/cds/ Language Reference Documentation { .subtitle} CDS is the backbone of the SAP Cloud Application Programming Model (CAP). It provides the means to declaratively capture service definitions and data models, queries, and expressions. The CDS toolkit allows to parse from a variety of source languages into a uniform format and to compile it into various target languages. !["The graphic is explained in the accompanying text."](./assets/csn.drawio.svg) At runtime, CDS models are plain JavaScript objects complying to the _[Core Schema Notation (CSN)](./csn)_, an open specification derived from [JSON Schema](https://json-schema.org/). You can easily create or interpret these models, which foster extensions by 3rd-party contributions. Models are processed dynamically at runtime and can also be created dynamically. > We use the terms _CDS_ or _CDS models_ as synonym to your models written in CDL. [See the Nature of Models for more details](models){.learn-more}
# Conceptual Definition Language (CDL) > Source: /docs/cds/cdl The *Conceptual Definition Language (CDL)* is a human-readable language for defining CDS models. Sources are commonly provided in files with`.cds` extensions and get compiled into [CSN representations](csn). Following sections provide a reference of all language constructs in CDL, which also serves as a reference of all corresponding CDS concepts and features. ## Language Preliminaries > Source: /docs/cds/cdl#language-preliminaries - [Keywords & Identifiers](#keywords--identifiers) - [Built-in Types](#built-in-types) - [Literals](#literals) - [Model Imports](#model-imports) - [Namespaces](#namespaces) - [Comments](#comments) ### Keywords & Identifiers > Source: /docs/cds/cdl#keywords--identifiers *Keywords* in CDL are used to prelude statements, such as imports and namespace directives as well as entity and type declarations. *Identifiers* are used to refer to definitions. ```cds namespace capire.bookshop; using { managed, cuid } from '@sap/cds/common'; aspect primary : managed, cuid {} entity Books : primary { title : String; author : Association to Authors; } entity Authors : primary { name : String; } ``` Keywords are *case-insensitive*, but are most commonly used in lowercase notation. Identifiers are *case-significant*, that is, `Foo` and `foo` would identify different things. Identifiers have to comply to `/^[$A-Za-z_]\w*$/` or be enclosed in `![`...`]` like that: ```cds type ![Delimited Identifier] : String; ``` ::: warning Avoid using delimited identifiers Delimited identifiers in general, but in particular non-ASCII characters, should be avoided as much as possible, for reasons of interoperability. ::: ### Built-in Types > Source: /docs/cds/cdl#built-in-types ANSI SQL types, when deployed to a relational database (concrete mappings to specific databases may differ): | CDS Type | Remarks | ANSI SQL | |------------------------|------------------------------------------------------------------------|----------------| | `UUID` | [RFC 4122](https://tools.ietf.org/html/rfc4122)-compliant UUIDs | _NVARCHAR(36)_ | | `Boolean` | Values: `true`, `false`, `null`, `0`, `1` | _BOOLEAN_ | | `Integer` | Same as `Int32` by default | _INTEGER_ | | `Int16` | Signed 16-bit integer, range *[ -215 ... +215 )* | _SMALLINT_ | | `Int32` | Signed 32-bit integer, range *[ -231 ... +231 )* | _INTEGER_ | | `Int64` | Signed 64-bit integer, range *[ -263 ... +263 )* | _BIGINT_ | | `UInt8` | Unsigned 8-bit integer, range *[ 0 ... 255 ]* | _TINYINT_ | | `Decimal`(`p`,`s`) | Decimal with precision `p` and scale `s` | _DECIMAL_ | | `Double` | Floating point with binary mantissa | _DOUBLE_ | | `Date` | for example, `2022-12-31` | _DATE_ | | `Time` | for example, `23:59:59` | _TIME_ | | `DateTime` | _sec_ precision | _TIMESTAMP_ | | `Timestamp` | _µs_ precision, with up to 7 fractional digits | _TIMESTAMP_ | | `String` (`length`) | Default *length*: 255; on HANA: 5000 | _NVARCHAR_ | | `Binary` (`length`) | Default *length*: 255; on HANA: 5000 | _VARBINARY_ | | `Vector` (`dimension`) | for Vector Embeddings [-> see notes below](#vector-embeddings) | ( _DB-specific_ ) | | `LargeBinary` | Unlimited binary data, usually streamed at runtime | _BLOB_ | | `LargeString` | Unlimited textual data, usually streamed at runtime | _NCLOB_ | | `Map` | Mapped to *NCLOB* for HANA. | *JSON* type | > [!info] Default String Lengths > Lengths can be omitted, in which case default lengths are used. While this is usual in initial phases of a project, productive apps should always use explicitly defined length. The respective default lengths are configurable through the config options > cds.cdsc.defaultStringLength = 255 and
> cds.cdsc.defaultBinaryLength = 255 . ###### Vector Embeddings > Source: /docs/cds/cdl#vector-embeddings > [!info] Vector Embeddings > The `Vector` type is used for vector embeddings, which are a way to represent data (like text, images, etc.) as high-dimensional vectors. Requires SAP HANA Cloud QRC 1/2024, or later, [`@sap/cds` v9.9+](/releases/2026/apr26), and [CAP Java v4.9+](/releases/2026/apr26) to use with H2 or SQLite. > [!tip] Use Attachments instead of LargeBinary > Consider using _Attachments_, as provided through [the CAP Attachments plugins](../plugins/index#attachments), instead of `LargeBinary` types for user-generated content like documents, images, etc. See also: [Additional Reuse Types and Aspects by `@sap/cds/common`](common) {.learn-more} [Mapping to OData EDM types](../guides/protocols/odata#type-mapping) {.learn-more} [HANA-native Data Types](../guides/databases/hana-native#hana-types){.learn-more} ### Literals > Source: /docs/cds/cdl#literals The following literals can be used in CDL (mostly as in JavaScript, Java, and SQL): ```cds true , false , null // as in all common languages 11 , 2.4 , 1e3 , 1.23e-11 // for numbers 'A string''s literal' // for strings `A string\n paragraph` // for strings with escape sequences { foo:'boo', bar:'car' } // for records [ 1, 'two', {three:4} ] // for arrays ``` [Learn more about literals and their representation in CSN.](./csn#literals) {.learn-more} #### Date & Time Literals > Source: /docs/cds/cdl#date--time-literals In addition, type-keyword-prefixed strings can be used for date & time literals: ```cds date'2016-11-24' time'16:11:32' timestamp'2016-11-24T12:34:56.789Z' ``` #### Multiline String Literals > Source: /docs/cds/cdl#multiline-string-literals Use string literals enclosed in **single or triple backticks** for multiline strings: ```cds @escaped: `OK Emoji: \u{1f197}` @multiline: ``` This is a CDS multiline string. - The indentation is stripped. - \u{0055}nicode escape sequences are possible, just like common escapes from JavaScript such as \r \t \n and more! ``` @data: ```xml
The tag is ignored by the core-compiler but may be used for syntax highlighting, similar to markdown.
``` entity DocumentedEntity { // ... } ``` ::: tip These annotations are illustrative only and are not defined nor have any meaning beyond this example. ::: Within those strings, escape sequences from JavaScript, such as `\t` or `\u0020`, are supported. Line endings are normalized. If you don't want a line ending at that position, end a line with a backslash (`\`). For string literals inside triple backticks, indentation is stripped and tagging is possible. ### Model Imports > Source: /docs/cds/cdl#model-imports #### The `using` Directive > Source: /docs/cds/cdl#the-using-directive Using directives allow to import definitions from other CDS models. As shown in line 3 below, you optionally can specify local aliases to be used subsequently. You can import single definitions as well as several ones with a common namespace prefix. ::: code-group using foo.bar.scoped.Bar from './contexts'; using foo.bar.scoped.nested from './contexts'; using foo.bar.scoped.nested as animal from './contexts'; entity Car : Bar {} //> : foo.bar.scoped.Bar entity Moo : nested.Zoo {} //> : foo.bar.scoped.nested.Zoo entity Zoo : animal.Zoo {} //> : foo.bar.scoped.nested.Zoo ``` ::: Multiple named imports through ES6-like deconstructors: ```cds using { Foo as Moo, sub.Bar } from './base-model'; entity Boo : Moo { /*...*/ } entity Car : Bar { /*...*/ } ``` > Also in the deconstructor variant of `using` shown in the previous example, specify fully qualified names. > [!important] Names do not restrict the import scope > All definitions of the model provided after `from` are imported, no matter which names are specified before `from`. > The purpose of these names is only to make global names accessible locally. #### Model Resolution > Source: /docs/cds/cdl#model-resolution Imports in `cds` work very much like [`require` in Node.js](https://nodejs.org/api/modules.html#requireid) and `import`s in [ES6](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import). In fact, we reuse **[Node's module loading mechanisms](https://nodejs.org/api/modules.html#modules_all_together)**. Hence, the same rules apply: - Relative path resolution
Names starting with `./` or `../` are resolved relative to the current model. - Resolving absolute references
Names starting with `/` are resolved absolute to the file system. - Resolving module references
Names starting with neither `.` nor `/` such as `@sap/cds/common` are fetched for in `node_modules` folders: - Files having _.cds_, _.csn_, or _.json_ as suffixes, appended in order - Folders, from either the file set in `cds.main` in the folder's _package.json_ or `index.` file. ::: tip To allow for loading from precompiled _.json_ files it's recommended to **omit _.cds_ suffixes** in import statements, as shown in the provided examples. ::: ### Namespaces > Source: /docs/cds/cdl#namespaces #### The `namespace` Directive > Source: /docs/cds/cdl#the-namespace-directive To prefix the names of all subsequent definitions, place a `namespace` directive at the top of a model. This is comparable to other languages, like Java. ::: code-group ```cds[namespace.cds] namespace foo.bar; entity Foo {} //> foo.bar.Foo entity Bar : Foo {} //> foo.bar.Bar ``` ::: A namespace is not an object of its own. There is no corresponding definition in CSN. #### The `context` Directive > Source: /docs/cds/cdl#the-context-directive Use `contexts` for nested namespace sections. ::: code-group ```cds[contexts.cds] namespace foo.bar; entity Foo {} //> foo.bar.Foo context scoped { entity Bar : Foo {} //> foo.bar.scoped.Bar context nested { entity Zoo {} //> foo.bar.scoped.nested.Zoo } } ``` ::: #### Scoped Definitions > Source: /docs/cds/cdl#scoped-definitions You can define types and entities with other definitions' names as prefixes: ```cds [prefixes.cds] namespace foo.bar; entity Foo {} //> foo.bar.Foo entity Foo.Bar {} //> foo.bar.Foo.Bar type Foo.Bar.Car {} //> foo.bar.Foo.Bar.Car ``` #### Fully Qualified Names > Source: /docs/cds/cdl#fully-qualified-names A model ultimately is a collection of definitions with unique, fully qualified names. For example, the model in `contexts.cds` would compile to the following [CSN](./csn): ::: code-group ```json [contexts.json] {"definitions":{ "foo.bar.Foo": { "kind": "entity" }, "foo.bar.scoped": { "kind": "context" }, "foo.bar.scoped.Bar": { "kind": "entity", "includes": [ "foo.bar.Foo" ] }, "foo.bar.scoped.nested": { "kind": "context" }, "foo.bar.scoped.nested.Zoo": { "kind": "entity" } }} ``` ::: ### Comments > Source: /docs/cds/cdl#comments CDL supports line-end, block comments, and *doc* comments as in Java and JavaScript: ```cds // line-end comment /* block comment */ /** doc comment */ ``` #### Doc Comments > Source: /docs/cds/cdl#doc-comments A multi-line comment of the form `/** … */` at an [annotation position](#annotation-targets) is considered a *doc comment*: ```cds /** * I am the description for "Employee" */ entity Employees { key ID : Integer; /** * I am the description for "name" */ name : String; } ``` The text of a doc comment is stored in CSN in the property `doc`. Doc comments are not propagated. For example, a doc comment defined for an entity isn't automatically copied to projections of this entity. When generating OData EDM(X), doc comments are translated to the annotation `@Core.Description`. In CAP Node.js, doc comments need to be switched on when calling the compiler: ::: code-group ```sh [CLI] cds compile foo.cds --docs ``` ```js [JavaScript] cds.compile(..., { docs: true }) ``` ::: ::: tip Doc comments are automatically enabled in CAP Java. In CAP Java, doc comments are automatically enabled by the [CDS Maven Plugin](../java/developing-applications/building#cds-maven-plugin). In generated interfaces they are converted to corresponding Javadoc comments. ::: When generating output for deployment to SAP HANA, the first paragraph of a doc comment is translated to the HANA `COMMENT` feature for tables, table columns, and for views (but not for view columns): ```sql CREATE TABLE Employees ( ID INTEGER, name NVARCHAR(...) COMMENT 'I am the description for "name"' ) COMMENT 'I am the description for "Employee"' ``` ## Entities & Type Definitions > Source: /docs/cds/cdl#entities--type-definitions - [Entity Definitions](#entity-definitions) - [Type Definitions](#type-definitions) - [Structured Types](#structured-types) - [Arrayed Types](#arrayed-types) - [Virtual Elements](#virtual-elements) - [Calculated elements](#calculated-elements) - [Default Values](#default-values) - [Type References](#type-references) - [Constraints](#constraints) - [Enums](#enums) ### Entity Definitions > Source: /docs/cds/cdl#entity-definitions {#entities} Entities are structured types with named and typed elements, representing sets of (persisted) data that can be read and manipulated using usual CRUD operations. They usually contain one or more designated primary key elements: ```cds define entity Employees { key ID : Integer; name : String; jobTitle : String; } ``` > The `define` keyword is optional, that means `define entity Foo` is equal to `entity Foo`. ### Type Definitions > Source: /docs/cds/cdl#type-definitions {#types} You can declare custom types to reuse later on, for example, for elements in entity definitions. Custom-defined types can be simple, that is derived from one of the predefined types, structured types or [Associations](#associations). ```cds define type User : String(111); define type Amount { value : Decimal(10,3); currency : Currency; } define type Currency : Association to Currencies; ``` > The `define` keyword is optional, that means `define type Foo` is equal to `type Foo`. [Learn more about **Definitions of Named Aspects**.](#aspects){.learn-more} ### Structured Types > Source: /docs/cds/cdl#structured-types You can declare and use custom struct types as follows: ```cds type Amount { value : Decimal(10,3); currency : Currency; } entity Books { price : Amount; } ``` Elements can also be specified with anonymous inline struct types. For example, the following is equivalent to the definition of `Books` above: ```cds entity Books { price : { value : Decimal(10,3); currency : Currency; }; } ``` You can declare structured types based on other definitions using the `projection on` syntax. You can use nested projections or aliases as known from entity projections. Only the effective signature of the projection is relevant. ```cds type CustomerData : projection on Customer { name.firstName, // select from structures name.lastName, address as customerAddress, // aliases } ``` ### Arrayed Types > Source: /docs/cds/cdl#arrayed-types Prefix a type specification with `array of` or `many` to signify array types. ```cds entity Foo { emails: many String; } entity Bar { emails: many { kind:String; address:String; }; } entity Car { emails: many EmailAddress; } entity Car { emails: EmailAddresses; } type EmailAddresses : many { kind:String; address:String; } type EmailAddress : { kind:String; address:String; } ``` > Keywords `many` and `array of` are mere syntax variants with identical semantics and implementations. When deployed to SQL databases, such fields are mapped to [LargeString](./types) columns and the data is stored denormalized as JSON array. With OData V4, arrayed types are rendered as `Collection` in the EDM(X). ::: warning Filter expressions, [instance-based authorization](../guides/security/authorization#instance-based-auth) and [search](../guides/services/served-ootb#searching-data) are not supported on arrayed elements. ::: #### Null Values > Source: /docs/cds/cdl#null-values For arrayed types the `null` and `not null` constraints apply to the _members_ of the collections. The default is `not null` indicating that the collections can't hold `null` values. ::: warning An empty collection is represented by an empty JSON array. A `null` value is invalid for an element with arrayed type. ::: In the following example the collection `emails` may hold members that are `null`. It may also hold a member where the element `kind` is `null`. The collection `emails` itself must not be `null`! ```cds entity Bar { emails : many { kind : String null; address : String not null; } null; // -> collection emails may hold null values, overwriting default } ``` ### Virtual Elements > Source: /docs/cds/cdl#virtual-elements An element definition can be prefixed with modifier keyword `virtual`. This keyword indicates that this element isn't added to persistent artifacts, that is, tables or views in SQL databases. Virtual elements are part of OData metadata. By default, virtual elements are annotated with `@Core.Computed: true`, not writable for the client and will be [silently ignored](../guides/services/constraints#readonly). This means also, that they are not accessible in custom event handlers. If you want to make virtual elements writable for the client, you explicitly need to annotate these elements with `@Core.Computed: false`. Still those elements are not persisted and therefore, for example, not sortable or filterable. Further, during read requests, you need to provide values for all virtual elements. You can do this by using post-processing in an `after` handler. ```cds entity Employees { [...] virtual something : String(11); } ``` ### Calculated Elements > Source: /docs/cds/cdl#calculated-elements Elements of entities and aspects can be specified with a calculation expression, in which you can refer to other elements of the same entity/aspect. This can be either a value expression or an expression that resolves to an association. Calculated elements with a value expression are read-only, no value must be provided for them in a WRITE operation. When reading such a calculated element, the result of the expression is returned. They come in two variants: "on-read" and "on-write". The difference between them is the point in time when the expression is evaluated. #### On-read > Source: /docs/cds/cdl#on-read ```cds entity Employees { firstName : String; lastName : String; name : String = firstName || ' ' || lastName; name_upper = upper(name); addresses : Association to many Addresses; city = addresses[kind='home'].city; } ``` For a calculated element with "on-read" semantics, the calculation expression is evaluated when reading an entry from the entity. Using such a calculated element in a query or view definition is equivalent to writing the expression directly into the query, both with respect to semantics and to performance. In CAP, it is implemented by replacing each occurrence of a calculated element in a query by the respective expression. Entity using calculated elements: ```cds entity EmployeeView as select from Employees { name, city }; ``` Equivalent entity: ```cds entity EmployeeView as select from Employees { firstName || ' ' || lastName as name : String, addresses[kind='home'].city as city }; ``` Calculated elements "on-read" are a pure convenience feature. Instead of having to write the same expression several times in queries, you can define a calculated element **once** and then simply refer to it. In the _definition_ of a calculated element "on-read", you can use almost all expressions that are allowed in queries. Some restrictions apply: * Subqueries are not allowed. * Nested projections (inline/expand) are not allowed. * A calculated element can't be key. Like for views, the expressions are sent unchanged to the database, so you need to ensure that they work on your respective database system(s). A calculated element can be *used* in every location where an expression can occur. A calculated element can't be used in the following cases: * in the ON condition of an unmanaged association * as the foreign key of a managed association * in a query together with nested projections (inline/expand) ::: warning For the Node.js runtime, only the new database services under the _@cap-js_ scope support this feature. ::: #### On-write > Source: /docs/cds/cdl#on-write Calculated elements "on-write" (also referred to as "stored" calculated elements) are defined by adding the keyword `stored`. A type specification is mandatory. ```cds entity Employees { firstName : String; lastName : String; name : String = (firstName || ' ' || lastName) stored; } ``` For a calculated element "on-write", the expression is already evaluated when an entry is written into the database. The resulting value is then stored/persisted like a regular field, and when reading from the entity, it behaves like a regular field as well. Using a stored calculated element can improve performance, in particular when it's used for sorting or filtering. This is paid for by higher memory consumption. While calculated elements "on-read" are handled entirely by CAP, the "on-write" variant is implemented by using the corresponding feature for database tables. The previous entity definition results in the following table definition: ```sql -- SAP HANA syntax -- CREATE TABLE Employees ( firstName NVARCHAR, lastName NVARCHAR, name NVARCHAR GENERATED ALWAYS AS (firstName || ' ' || lastName) ); ``` For the definition of calculated elements on-write, all the on-read variant's restrictions apply and referencing localized elements isn't allowed. In addition, there are restrictions that depend on the particular database. Currently all databases supported by CAP have a common restriction: The calculation expression may only refer to fields of the same table row. Therefore, such an expression must not contain subqueries, aggregate functions, or paths with associations. No restrictions apply for reading a calculated element on-write. #### Association-like calculated elements > Source: /docs/cds/cdl#association-like-calculated-elements A calculated element can also define a filtered association/composition using infix filters: ```cds entity Employees { addresses : Association to many Addresses; homeAddress = addresses [1: kind='home']; } ``` For such a calculated element, no explicit type can be specified. Only a single association or composition can occur in the expression, and a filter must be specified. The effect essentially is like [publishing an association with an infix filter](#publish-associations-with-filter). ### Default Values > Source: /docs/cds/cdl#default-values As in SQL you can specify default values to fill in upon INSERTs if no value is specified for a given element. ```cds entity Foo { bar : String default 'bar'; boo : Integer default 1; } ``` Default values can also be specified in custom type definitions: ```cds type CreatedAt : Timestamp default $now; type Complex { real : Decimal default 0.0; imag : Decimal default 0.0; } ``` If the element has an enum type, you can use the enum symbol instead of a literal value: ```cds type Status : String enum {open; closed;} entity Orders { status : Status default #open; } ``` ### Type References > Source: /docs/cds/cdl#type-references If you want to base an element's type on another element of the same structure, you can use the `type of` operator. ```cds entity Authors { firstname : String(100); lastname : type of firstname; // has type "String(100)" } ``` For referencing elements of other artifacts, you can use the element access through `:`. Element references with `:` don't require `type of` in front of them. ```cds entity Employees { firstname: Author:firstname; lastname: Author:lastname; } ``` ### Constraints > Source: /docs/cds/cdl#constraints Element definitions can be augmented with constraint `not null` as known from SQL. ```cds entity Employees { name : String(111) not null; } ``` ### Enums > Source: /docs/cds/cdl#enums You can specify enumeration values for a type as a semicolon-delimited list of symbols. For string types, declaration of actual values is optional; if omitted, the actual values are the string counterparts of the symbols. ```cds type Gender : String enum { male; female; non_binary = 'non-binary'; } entity Orders { status : Integer enum { submitted = 1; fulfilled = 2; shipped = 3; canceled = -1; }; } ``` To enforce your _enum_ values during runtime, use the [`@assert.range` annotation](../guides/services/constraints#assertrange). For localization of enum values, model them as [code list](./common#adding-own-code-lists).
## Views & Projections > Source: /docs/cds/cdl#views--projections {#views} Use `as select from` or `as projection on` to derive new entities from existing ones by projections, very much like views in SQL. When mapped to relational databases, such entities are in fact translated to SQL views but they're frequently also used to declare projections without any SQL views involved. The entity signature is inferred from the projection. - [The `as select from` Variant](#as-select-from) - [The `as projection on` Variant](#as-projection-on) - [Views with Inferred Signatures](#views-with-inferred-signatures)
- [Views with Parameters](#views-with-parameters) ### The `as select from` Variant > Source: /docs/cds/cdl#the-as-select-from-variant Use the `as select from` variant to use all possible features an underlying relational database would support using any valid [CQL](./cql) query including all query clauses. ```cds entity Foo1 as select from Bar; //> implicit {*} entity Foo2 as select from Employees { * }; entity Foo3 as select from Employees LEFT JOIN Bar on Employees.ID=Bar.ID { foo, bar as car, sum(boo) as moo } where exists ( SELECT 1 as anyXY from SomeOtherEntity as soe where soe.x = y ) group by foo, bar order by moo asc; ``` ### The `as projection on` Variant > Source: /docs/cds/cdl#the-as-projection-on-variant Use the `as projection on` variant instead of `as select from` to indicate that you don't use the full power of SQL in your query. For example, having a restricted query in an entity allows us to serve such an entity from external OData services. ```cds entity Foo as projection on Bar {...} ``` Currently, the restrictions of `as projection on` compared to `as select from` are: - no explicit, manual `JOINs` - no explicit, manual `UNIONs` - no sub selects in from clauses Over time, we can add additional checks depending on specific outbound protocols. ### Views with Inferred Signatures > Source: /docs/cds/cdl#views-with-inferred-signatures By default views inherit all properties and annotations from their primary underlying base entity. Their [`elements`](./csn#structured-types) signature is **inferred** from the projection on base elements. Each element inherits all properties from the respective base element, except the `key` property. The `key` property is only inherited if all of the following applies: - No explicit `key` is set in the query. - All key elements of the primary base entity and all key elements of explicitly joined entities are selected. For example, the following definition: ```cds entity SomeView as select from Employees { ID, name, job.title as jobTitle }; ``` Might result in this inferred signature: ```cds entity SomeView { key ID: Integer; name: String; jobTitle: String; }; ``` Note: CAP does **not** enforce uniqueness for key elements of a view or projection. Use a CDL cast to set an element's type, if one of the following conditions apply: + You don't want to use the inferred type. + The query column is an expression (no inferred type is computed). ```cds entity SomeView as select from Employees { ID : Integer64, name : LargeString, 'SAP SE' as company : String }; ``` ::: tip By using a cast, annotations and other properties are inherited from the provided type and not the base element, see [Annotation Propagation](#annotation-propagation) :::
### Virtual elements in views > Source: /docs/cds/cdl#virtual-elements-in-views Virtual elements can be defined in views or projections like this: ```cds entity SomeView as select from Employee { // ..., virtual virt1 : String(22), virtual virt2 // virtual element without type } ``` These virtual elements have no relation to the query source `Employee` but are new fields in the view. Virtual elements in views or projections are handled as described in the section on [virtual elements in entities](#virtual-elements).
### Views with Parameters > Source: /docs/cds/cdl#views-with-parameters You can equip views with parameters that are passed in whenever that view is queried. Default values can be specified. Refer to these parameters in the view's query using the prefix `:`. ```cds entity SomeView ( foo: Integer, bar: Boolean ) as SELECT * from Employees where ID=:foo; ``` When selecting from a view with parameters, the parameters are passed by name. In the following example, `UsingView` also has a parameter `bar` that is passed down to `SomeView`. ```cds entity UsingView ( bar: Boolean ) as SELECT * from SomeView(foo: 17, bar: :bar); ``` For Node.js, there's no programmatic API yet. You need to provide a [CQN snippet](cqn#select). In CAP Java, run a select statement against the view with named [parameter values](../java/working-with-cql/query-execution#querying-views): ::: code-group ```js [Node] SELECT.from({ ref: [{ id: 'UsingView', args: { bar: { val: true }}} ]} ) ``` ```Java [Java] var params = Map.of("bar", true); Result result = service.run(Select.from("UsingView"), params); ``` ::: [Learn more about how to expose views with parameters in **Services - Exposed Entities**.](#exposed-entities){ .learn-more} [Learn more about views with parameters for existing HANA artifacts in **Native SAP HANA Artifacts**.](../guides/databases/hana-native){ .learn-more} ### Runtime Views > Source: /docs/cds/cdl#runtime-views To add or update CDS views without redeploying the database schema, annotate them with [@cds.persistence.skip](../guides/databases/cdl-to-ddl#cdspersistenceskip). This advises the CDS compiler to skip generating database views for these CDS views. Instead, CAP resolves them *at runtime* on each request. Runtime views must be simple [projections](#as-projection-on), not using *aggregations*, *join*, *union* or *subqueries* in the *from* clause, but may have a *where* condition if they are only used to read. In CAP Java, runtime views are enabled by default. Node.js does not support it yet. [Learn more about runtime views in CAP Java.](../java/working-with-cql/query-execution#runtimeviews) {.learn-more} By default, runtime views are translated into _Common Table Expressions_ (CTEs) and sent with the query to the database. For example, given the following CDS model and query: ```cds entity Books { key ID : UUID; title : String; stock : Integer; author : Association to one Authors; } @cds.persistence.skip entity BooksWithLowStock as projection on Books { ID, title, author.name as author } where stock < 10; // makes the view read only ``` ```sql SELECT from BooksWithLowStock where author = 'Kafka' ``` The runtime translates the view definition into a _Common Table Expression_ (CTE) and sends it with the query to the database. ```sql WITH BOOKSWITHLOWSTOCK_CTE AS ( SELECT B.ID, B.TITLE, A.NAME AS "AUTHOR" FROM BOOKS B LEFT OUTER JOIN AUTHOR A ON B.AUTHOR_ID = A.ID WHERE B.STOCK < 10 ) SELECT ID, TITLE, AUTHOR AS "author" FROM BOOKSWITHLOWSTOCK_CTE WHERE A.NAME = ? ``` ## Associations > Source: /docs/cds/cdl#associations Associations capture relationships between entities. They are like forward-declared joins added to a table definition in SQL. - [Unmanaged Associations](#unmanaged-associations) - [Managed Associations](#managed-associations) - [To-many Associations](#to-many-associations) - [Many-to-many Associations](#many-to-many-associations) - [Compositions](#compositions) - [Managed Compositions](#managed-compositions) ### Unmanaged Associations > Source: /docs/cds/cdl#unmanaged-associations Unmanaged associations specify arbitrary join conditions in their `on` clause, which refer to available foreign key elements. The association's name (`address` in the following example) is used as the alias for the to-be-joined target entity. ```cds entity Employees { address : Association to Addresses on address.ID = address_ID; address_ID : Integer; //> foreign key } ``` ```cds entity Addresses { key ID : Integer; } ``` ### Managed (To-One) Associations > Source: /docs/cds/cdl#managed-to-one-associations ###### managed-associations > Source: /docs/cds/cdl#managed-associations For to-one associations, CDS can automatically resolve and add requisite foreign key elements from the target's primary keys and implicitly add respective join conditions. ```cds entity Employees { address : Association to Addresses; } ``` This example is equivalent to the [unmanaged example above](#unmanaged-associations), with the foreign key element `address_ID` being added automatically upon activation to a SQL database. The names of the automatically added foreign key elements cannot be changed. > Note: For adding foreign key constraints on database level, see [Database Constraints.](../guides/databases/cdl-to-ddl#database-constraints). If the target has a single primary key, a default value can be provided. This default applies to the generated foreign key element `address_ID`: ```cds entity Employees { address : Association to Addresses default 17; } ``` ### To-many Associations > Source: /docs/cds/cdl#to-many-associations For to-many associations specify an `on` condition following the canonical expression pattern `. = $self` as in this example: ```cds entity Employees { key ID : Integer; addresses : Association to many Addresses on addresses.owner = $self; } ``` ```cds entity Addresses { owner : Association to Employees; //> the backlink } ``` > The backlink can be any managed to-one association on the _many_ side pointing back to the _one_ side. ### Many-to-many Associations > Source: /docs/cds/cdl#many-to-many-associations For many-to-many association, follow the common practice of resolving logical many-to-many relationships into two one-to-many associations using a link entity to connect both. For example: ```cds entity Employees { [...] addresses : Association to many Emp2Addr on addresses.emp = $self; } entity Emp2Addr { key emp : Association to Employees; key adr : Association to Addresses; } ``` [Learn more about **Managed Compositions for Many-to-many Relationships**.](#for-many-to-many-relationships){.learn-more} [Watch a short video by DJ Adams to see an example of how a link entity can be used.](https://www.youtube.com/shorts/yGg3YD1weIA){.learn-more}
### Compositions > Source: /docs/cds/cdl#compositions Compositions constitute document structures through _contained-in_ relationships. They frequently show up in to-many header-child scenarios. ```cds entity Orders { key ID: Integer; //... Items : Composition of many Orders.Items on Items.parent = $self; } entity Orders.Items { key pos : Integer; key parent : Association to Orders; product : Association to Products; quantity : Integer; } ``` :::info Contained-in relationship Essentially, Compositions are the same as _[associations](#associations)_, just with the additional information that this association represents a _contained-in_ relationship; so the same syntax and rules apply in their base form. ::: ::: warning Limitations of Compositions of one Using compositions of one for entities is discouraged. There is often no added value of using them as the information can be placed in the root entity. Compositions of one have limitations as follow: - Very limited Draft support. Fiori elements does not support compositions of one unless you take care of their creation in a custom handler. - No extensive support for modifications over paths if compositions of one are involved. You must fill in foreign keys manually in a custom handler. See the [Keep it Simple, Stupid](../guides/domain/index#keep-it-simple-stupid) best practice, especially the [Prefer Flat Models](../guides/domain/index#prefer-flat-models) section. ::: ### Managed Compositions of Aspects > Source: /docs/cds/cdl#managed-compositions-of-aspects Use managed compositions variant to nicely reflect document structures in your domain models, without the need for separate entities, reverse associations, and unmanaged `on` conditions. #### With Inline Targets > Source: /docs/cds/cdl#with-inline-targets ```cds entity Orders { key ID: Integer; //... Items : Composition of many { key pos : Integer; product : Association to Products; quantity : Integer; } }; ``` Managed Compositions are mostly syntactical sugar: Behind the scenes, they are unfolded to the [unmanaged equivalent as shown above](#compositions) by automatically adding a new entity, the name of which being constructed as a [scoped name](#scoped-names) from the name of parent entity, followed by the name of the composition element, that is `Orders.Items` in the previous example. You can safely use this name at other places, for example to define an association to the generated child entity: ```cds entity Orders { // … specialItem : Association to Orders.Items; }; ``` #### With Named Targets > Source: /docs/cds/cdl#with-named-targets Instead of anonymous target aspects you can also specify named aspects, which are unfolded the same way as anonymous inner types, as shown in the previous example: ```cds entity Orders { key ID: Integer; //... Items : Composition of many OrderItems; } aspect OrderItems { key pos : Integer; product : Association to Products; quantity : Integer; } ``` #### Default Target Cardinality > Source: /docs/cds/cdl#default-target-cardinality If not otherwise specified, a managed composition of an aspect has the default target cardinality *to-one* for the backlink. #### For Many-to-many Relationships > Source: /docs/cds/cdl#for-many-to-many-relationships Managed Compositions are handy for [many-to-many relationships](#many-to-many-associations), where a link table usually is private to one side. ```cds entity Teams { [...] members : Composition of many { key user: Association to Users; } } entity Users { [...] teams: Association to many Teams.members on teams.user = $self; } ``` And here's an example of an attributed many-to-many relationship: ```cds entity Teams { [...] members : Composition of many { key user : Association to Users; role : String enum { Lead; Member; Collaborator; } } } entity Users { ... } ``` To navigate between _Teams_ and _Users_, you have to follow two associations: `members.user` or `teams.up_`. In OData, to get all users of all teams, use a query like the following: ```cds GET /Teams?$expand=members($expand=user) ``` ### Publish Associations in Projections > Source: /docs/cds/cdl#publish-associations-in-projections As associations are first class citizens, you can put them into the select list of a view or projection ("publish") like regular elements. A `select *` includes all associations. If you need to rename an association, you can provide an alias. ```cds entity P_Employees as projection on Employees { ID, addresses } ``` The effective signature of the projection contains an association `addresses` with the same properties as association `addresses` of entity `Employees`. #### Publish Associations with Infix Filter > Source: /docs/cds/cdl#publish-associations-with-infix-filter When publishing an unmanaged association in a view or projection, you can add a filter condition. The ON condition of the resulting association is the ON condition of the original association plus the filter condition, combined with `and`. ```cds entity P_Authors as projection on Authors { *, books[stock > 0] as availableBooks }; ``` In this example, in addition to `books` projection `P_Authors` has a new association `availableBooks` that points only to those books where `stock > 0`. If the filter condition effectively reduces the cardinality of the association (or composition) to one, you should make this explicit in the filter by adding a `1:` before the condition: ```cds entity P_Employees as projection on Employees { *, addresses[1: kind='home'] as homeAddress // homeAddress is to-one } ``` ::: warning `:1` doesn't itself reduce the cardinality The `:1` syntax itself has no effect on the cardinality. It is only an information by the developer that the specified condition reduces the cardinality of the association or composition to one. ::: Filters usually are provided only for to-many associations, which usually are unmanaged. Thus publishing with a filter is almost exclusively used for unmanaged associations. Nevertheless you can also publish a managed association with a filter. This will automatically turn the resulting association into an unmanaged one. You must ensure that all foreign key elements needed for the ON condition are explicitly published. ```cds entity P_Books as projection on Books { author.ID as authorID, // needed for ON condition of deadAuthor author[dateOfDeath is not null] as deadAuthor // -> unmanaged association }; ``` Publishing a _composition_ with a filter is similar, with an important difference: in a deep Update, Insert, or Delete statement the respective operation does not cascade to the target entities. Thus the type of the resulting element is set to `cds.Association`. [Learn more about `cds.Association`.](csn#associations){.learn-more} In [SAP Fiori Draft](../guides/uis/fiori#draft-support), it behaves like an "enclosed" association, that means, it points to the target draft entity. In the following example, `singleItem` has type `cds.Association`. In draft mode, navigating along `singleItems` doesn't leave the draft tree. ```cds @odata.draft.enabled entity P_orders as projection on Orders { *, Items[quantity = 1] as singleItems } ``` ## Annotations > Source: /docs/cds/cdl#annotations This section describes how to add Annotations to model definitions written in CDL, focused on the common syntax options, and fundamental concepts. Find additional information in the [OData Annotations](../guides/protocols/odata#annotations) guide. - [Annotation Syntax](#annotation-syntax) - [Annotation Targets](#annotation-targets) - [Annotation Values](#annotation-values) - [Expressions as Annotation Values](#expressions-as-annotation-values) - [Records as Syntax Shortcuts](#records-as-syntax-shortcuts) - [Annotation Propagation](#annotation-propagation) - [The `annotate` Directive](#annotate) - [Extend Array Annotations](#extend-array-annotations) ### Annotation Syntax > Source: /docs/cds/cdl#annotation-syntax Annotations in CDL are prefixed with an `@` character and can be placed before a definition, after the defined name or at the end of simple definitions. ```cds @before entity Foo @inner { @before simpleElement @inner : String @after; @before structElement @inner { /* elements */ } } ``` Multiple annotations can be placed in each spot separated by whitespaces or enclosed in `@(...)` and separated by comma - like the following are equivalent: ```cds entity Foo @( my.annotation: foo, another.one: 4711 ) { /* elements */ } ``` ```cds @my.annotation:foo @another.one: 4711 entity Foo { /* elements */ } ``` For annotations at the `@inner` position, only the syntax `@(...)` is available. #### Using `annotate` Directives > Source: /docs/cds/cdl#using-annotate-directives Instead of interspersing annotations with definitions, you can also use the `annotate` directive to add annotations to existing definitions. ```cds annotate Foo with @( my.annotation: foo, another.one: 4711 ); ``` [Learn more about the `annotate` directive in the _Aspects_ chapter below.](#annotate){.learn-more} ### Annotation Targets > Source: /docs/cds/cdl#annotation-targets You can basically annotate any named thing in a CDS model, such as: Contexts and services: ```cds @before context foo.bar @inner { ... } @before service Sue @inner { ... } ``` Definitions and elements with simple or struct types: ```cds @before type Foo @inner : String @after; @before entity Foo @inner { @before key ID @inner : String @after; @before title @inner : String @after; @before struct @inner { ...elements... }; } ``` Enums: ```cds … status : String @inner enum { open @after; closed @after; cancelled @after; accepted @after; rejected @after; } ``` Columns in a view definition's query: ```cds … as select from Foo { @before expr as alias @inner : String, … } ``` Parameters in view definitions: ```cds … with parameters ( @before param @(inner) : String @after ) … ``` Actions/functions including their parameters and result: ```cds @before action doSomething @inner ( @before param @(inner) : String @after ) returns @before resultType; ``` Or in case of a structured result: ```cds action doSomething() returns @before { @before resultElem @inner : String @after; }; ``` ### Annotation Values > Source: /docs/cds/cdl#annotation-values Values can be literals, references, or expressions. Expressions are explained in more detail in the next section. If no value is given, the default value is `true` as for `@aFlag` in the following example: ```cds @aFlag //= true, if no value is given @aBoolean: false @aString: 'foo' @anInteger: 11 @aDecimal: 11.1 @aSymbol: #foo @aReference: foo.bar @anArray: [ /* can contain any kind of value */ ] @anExpression: ( foo.bar * 17 ) // expression, see next section ``` As described in the [CSN spec](./csn#literals), the previously mentioned annotations would compile to CSN as follows: ```jsonc { "@aFlag": true, "@aBoolean": false, "@aString": "foo", "@anInteger": 11, "@aDecimal": 11.1, "@aSymbol": {"#":"foo"}, "@aReference": {"=":"foo.bar"}, "@anArray": [ /* … */ ], "@anExpression": { /* see next section */ } } ``` ::: tip In contrast to references in [expressions](#expressions-as-annotation-values), plain references aren't checked, resolved, or rewritten by CDS parsers or linkers. They're interpreted and evaluated only on consumption-specific modules. For example, for SAP Fiori models, it's the _4odata_ and _2edm(x)_ processors. ::: ### Records as Syntax Shortcuts > Source: /docs/cds/cdl#records-as-syntax-shortcuts Annotations in CDS are flat lists of key-value pairs assigned to a target. The record syntax - that is, `{key:, ...}` - is a shortcut notation that applies a common prefix to nested annotations. For example, the following are equivalent: ```cds @Common.foo.bar @Common.foo.car: 'wheels' ``` ```cds @Common: { foo.bar, foo.car: 'wheels' } ``` ```cds @Common.foo: { bar } @Common.foo.car: 'wheels' ``` ```cds @Common.foo: { bar, car: 'wheels' } ``` and they would show up as follows in a parsed model (→ see [CSN](./csn)): ```json { "@Common.foo.bar": true, "@Common.foo.car": "wheels" } ``` ### Annotation Propagation > Source: /docs/cds/cdl#annotation-propagation Annotations are inherited from types and base types to derived types, entities, and elements as well as from elements of underlying entities in case of views. For example, given this view definition: ```cds using Books from './bookshop-model'; entity BooksList as select from Books { ID, genre : Genre, title, author.name as author }; ``` * `BooksList` would inherit annotations from `Books` * `BooksList:ID` would inherit from `Books:ID` * `BooksList:author` would inherit from `Books:author.name` * `BooksList.genre` would inherit from type `Genre` The rules are: 1. Entity-level properties and annotations are inherited from the **primary** underlying source entity — here `Books`. 2. Each element that can **unambiguously** be traced back to a single source element, inherits that element's properties. 3. An explicit **cast** in the select clause cuts off the inheritance, for example, as for `genre` in our previous example. ### Expressions as Annotation Values > Source: /docs/cds/cdl#expressions-as-annotation-values In order to use an expression as an annotation value, it must be enclosed in parentheses: ```cds @anExpression: ( foo.bar * 11 ) ``` Syntactically, the same expressions are supported as in a select item or in the where clause of a query, except subqueries. The expression can of course also be a single reference or a simple value: ```cds @aRefExpr: ( foo.bar ) @aValueExpr: ( 11 ) ``` Some advantages of using expressions as "first class" annotation values are: * syntax and references are checked by the compiler * code completion * [automatic path rewriting in propagated annotations](#propagation) * [automatic translation of expressions in OData annotations](#odata-annotations) ::: info Limitations Elements that are not available to the compiler, for example the OData draft decoration, can't be used in annotation expressions. ::: #### Name resolution > Source: /docs/cds/cdl#name-resolution Each path in the expression is checked: * For an annotation assigned to an entity, the first path step is resolved as element of the entity. * For an annotation assigned to an entity element, the first path step is resolved as the annotated element or its siblings. * If the annotation is assigned to a subelement of a structured element, the top level elements of the entity can be accessed via `$self`. * A parameter `par` can be accessed via `:par`, just like parameters of a parametrized entity in queries. * For an annotation assigned to a bound action or function, elements of the respective entity can be accessed via `$self`. * The draft-specific elements `IsActiveEntity`, `HasActiveEntity`, and `HasDraftEntity` can be referred to with respective magic variables `$draft.IsActiveEntity`, `$draft.HasActiveEntity`, and `$draft.HasDraftEntity`. During draft augmentation, `$draft.<...>` is rewritten to `$self.<...>` for all draft enabled entities (root and sub nodes, but not for named types or entity parameters). * If a path can't be resolved successfully, compilation fails with an error. In contrast to `@aReference: foo.bar`, a single reference written as expression `@aRefExpr: ( foo.bar )` is checked by the compiler. ```cds @MyAnno: (a) // reference to element entity Foo (par: Integer) { key ID : Integer; @MyAnno: (:par) // reference to entity parameter a : Integer; @MyAnno: (a) // reference to sibling element b : Integer; s { @MyAnno: (y) // reference to sibling element x : Integer; @MyAnno: ($self.a) // reference to top level element y : Integer; } } actions { @MyAnno: ($self.a) action A () } ``` #### CSN Representation > Source: /docs/cds/cdl#csn-representation In CSN, the expression is represented as a record with one of the properties `xpr`, `ref`, `val`, `func`, etc., that contains the tokenized representation of the expression (like if the expression was written in a query). ```json { "@anExpression": { "xpr": [ {"ref": ["foo", "bar"]}, "*", {"value": 11} ] }, "@aRefExpr": { "ref": ["foo", "bar"] }, "@aValueExpr": { "val": 11 } } ``` Note the different CSN representations for a [plain value](#annotation-values) `"@anInteger": 11` and a value written as expression `@aValueExpr: ( 11 )`, respectively. For expressions that are simple references, the record currently contains an additional property `=` with the string representation of the expression. Do not rely on this property, but use the tokenized representation. Property `=` may vanish in a future release. #### Propagation > Source: /docs/cds/cdl#propagation [Annotations are propagated](#annotation-propagation) in views/projections, via includes, and along type references. If the annotation value is an expression, it is sometimes necessary to adapt references inside the expression during propagation, for example, when a referenced element is renamed in a projection. The compiler automatically takes care of the necessary rewriting. Example: ```cds entity E { @Common.Text: (text) code : Integer; text : String; } entity P as projection on E { code, text as descr } ``` When propagated to element `code` of projection `P`, the annotation is automatically rewritten to `@Common.Text: (descr)`. ::: details Resulting CSN ```jsonc { "definitions": { "E": { // ... "elements": { // ... "code": { // original annotation "@Common.Text": { "ref": ["text"] }, "type": "cds.Integer" }, "text": {"type": "cds.String"} } }, "P": { // ... "elements": { // ... "code": { // propagated annotation, reference adapted "@Common.Text": { "ref": ["descr"] }, "type": "cds.Integer" }, "descr": {"type": "cds.String"} } } } } ``` ::: ::: info There may be situations where automatic rewriting doesn't work, resulting in a compiler error, with message ID `anno-missing-rewrite`. In these cases you can overwrite the annotation with the correct expression in the new location. ::: #### CDS Annotations > Source: /docs/cds/cdl#cds-annotations Using an expression as annotation value only makes sense if the evaluator of the annotation is prepared to deal with the new CSN representation. Currently, the CAP runtimes support expressions * in the `where` property of annotation [`@restrict`](../guides/security/authorization#restrict-annotation) * in annotation [`@assert`](../guides/services/constraints#assert-constraint) Example: ```cds entity Orders @(restrict: [ { grant: 'READ', to: 'Auditor', where: (AuditBy = $user.id) } ]) {/*...*/} ``` More annotations are going to follow in upcoming releases. Of course, you can use this feature also in your custom annotations, where you control the code that evaluates the annotations. #### OData Annotations > Source: /docs/cds/cdl#odata-annotations The OData backend of the CAP CDS compiler supports expression-valued annotations. See [Expressions in OData Annotations](../guides/protocols/odata#expression-annotations). ### Extend Array Annotations > Source: /docs/cds/cdl#extend-array-annotations Usually, the annotation value provided in an `annotate` directive overwrites an already existing annotation value. If the existing value is an array, the *ellipsis* syntax allows to insert new values **before** or **after** the existing entries, instead of overwriting the complete array. The ellipsis represents the already existing array entries. Of course, this works with any kind of array entries. This is a sample of an existing array: ```cds @anArray: [3, 4] entity Foo { /* elements */ } ``` This shows how to extend the array: ```cds annotate Foo with @anArray: [1, 2, ...]; //> prepend new values: [1, 2, 3, 4] annotate Foo with @anArray: [..., 5, 6]; //> append new values: [3, 4, 5, 6] annotate Foo with @anArray: [1, 2, ..., 5, 6]; //> prepend and append ``` It's also possible to insert new entries at **arbitrary positions**. For this, use `... up to` with a *comparator* value that identifies the insertion point. ```cds [... up to , newEntry, ...] ``` `... up to` represents the existing entries of the array from the current position up to and including the first entry that matches the comparator. New entries are then inserted behind the matched entry. If there's no match, new entries are appended at the end of the existing array. This is a sample of an existing array: ```cds @anArray: [1, 2, 3, 4, 5, 6] entity Bar { /* elements */ } ``` This shows how to insert values after `2` and `4`: ```cds annotate Bar with @anArray: [ ... up to 2, // existing entries 1, 2 2.1, 2.2, // insert new entries 2.1, 2.2 ... up to 4, // existing entries 3, 4 4.1, 4.2, // insert new entries 4.1, 4.2 ... // remaining existing entries 5, 6 ]; ``` The resulting array is: ```js [1, 2, 2.1, 2.2, 3, 4, 4.1, 4.2, 5, 6] ``` If your array entries are objects, you have to provide a *comparator object*. It matches an existing entry, if all attributes provided in the comparator match the corresponding attributes in an existing entry. The comparator object doesn't have to contain all attributes that the existing array entries have, simply choose those attributes that sufficiently characterize the array entry after which you want to insert. Only simple values are allowed for the comparator attributes. Example: Insert a new entry after `BeginDate`. ```cds @UI.LineItem: [ { $Type: 'UI.DataFieldForAction', Action: 'TravelService.acceptTravel', Label: '{i18n>AcceptTravel}' }, { Value: TravelID, Label: 'ID' }, { Value: BeginDate, Label: 'Begin' }, { Value: EndDate, Label: 'End' } ] entity TravelService.Travel { /* elements */ } ``` For this, you provide a comparator object with the attribute `Value`: ```cds annotate TravelService.Travel with @UI.LineItem: [ ... up to { Value: BeginDate }, // ... up to with comparator object { Value: BeginWeekday, Label: 'Day of week' }, // new entry ... // remaining array entries ]; ``` ::: tip Only direct annotations can be extended using `...`. It's not supported to extend propagated annotations, for example, from aspects or types. :::
## Aspects > Source: /docs/cds/cdl#aspects CDS's aspects allow to flexibly extend definitions by new elements as well as overriding properties and annotations. They're based on a mixin approach as known from Aspect-oriented Programming methods. - [The `extend` Directive](#extend) - [The `annotate` Directive](#annotate) - [Named Aspects](#named-aspects) - [Shortcut Syntax `:`](#includes) - [Extending Views / Projections](#extend-view) - See also: [Aspect-oriented Modelling](aspects) ### The `extend` Directive > Source: /docs/cds/cdl#the-extend-directive {#extend} Use `extend` to add extension fields or to add/override metadata to existing definitions, for example, annotations, as follows: ```cds extend Foo with @title:'Foo'; extend Bar with @title:'Bar' { newField : String; extend nestedStructField { newField : String; extend existingField @title:'Nested Field'; } } ``` ::: details Note the nested `extend` for existing fields Make sure that you prepend the `extend` keyword to nested elements if you want to modify them. Without that a new field with that name would be added. If you only want to add annotations to an existing field, you can use [the **annotate** directive.](#annotate) instead. ::: You can also directly extend a single element: ```cds extend Foo:nestedStructField with { newField : String; } ``` With `extend` you can enlarge the *length* of a String or *precision* and *scale* of a Decimal: ```cds extend User with (length:120); extend Books:price.value with (precision:12,scale:3); ``` The extended type or element directly must have the respective property. For multiple conflicting `extend` statements, the last `extend` wins, that means in three files `a.cds <- b.cds <- c.cds`, where `<-` means `using from`, the `extend` from `c.cds` is applied, as it is the last in the dependency chain. ### The `annotate` Directive > Source: /docs/cds/cdl#the-annotate-directive {#annotate} The `annotate` directive allows to annotate already existing definitions that may have been [imported](#model-imports) from other files or projects. ```cds annotate Foo with @title:'Foo'; annotate Bar with @title:'Bar' { nestedStructField { existingField @title:'Nested Field'; } } ``` ::: details `annotate` is a shortcut for `extend` ... The `annotate` directive is essentially a shortcut variant of the [`extend` directive](#extend), with the default mode being switched to `extend`ing existing fields instead of adding new ones. For example, the following is equivalent to the previous example: ```cds extend Foo with @title:'Foo'; extend Bar with @title:'Bar' { extend nestedStructField { extend existingField @title:'Nested Field'; } } ``` ::: You can also directly annotate a single element: ```cds annotate Foo:existingField @title: 'Simple Field'; annotate Foo:nestedStructField.existingField @title:'Nested Field'; ``` ### Named Aspects > Source: /docs/cds/cdl#named-aspects You can use `extend` with predefined aspects, to apply the same extensions to multiple targets: ```cds @annotation aspect NamedAspect { created { at: Timestamp; _by: User; } } actions { action A() returns String; } ``` ```cds extend Foo with NamedAspect; extend Bar with NamedAspect; ``` By extending an entity with an aspect, you add all the aspect's fields, actions, and annotations to the entity. Use keyword `aspect` as shown in the example to declare definitions that are only meant to be used in such extensions, not as types for elements. To reuse annotations, without adding elements, use an empty aspect and extend your target with it You can even extend projections with such aspects. ```cds @annotation aspect ReuseAnnotations {}; entity Proj as projection on Bar; ``` ```cds extend Proj with ReuseAnnotations; ``` ### Includes -- `:` as Shortcut Syntax > Source: /docs/cds/cdl#includes-----as-shortcut-syntax You can use an inheritance-like syntax option to extend a definition with one or more [named aspects](#named-aspects) as follows: ```cds define entity Foo : SomeAspect, AnotherAspect { key ID : Integer; name : String; [...] } ``` This is syntactical sugar and equivalent to using a sequence of [extends](#extend) as follows: ```cds define entity Foo {} extend Foo with SomeAspect; extend Foo with AnotherAspect; extend Foo with { key ID : Integer; name : String; [...] } ``` You can apply this to any definition of an entity or a structured type. ### Extending Views and Projections > Source: /docs/cds/cdl#extending-views-and-projections Use the `extend with columns` variant to extend the select list of a projection or view entity and do the following: * Include more elements existing in the underlying entity. * Add new calculated fields. * Add new unmanaged associations. ```cds extend SomeView with columns { foo as moo @woo, 1 + 1 as two, bar : Association to Bar on bar.ID = moo } ``` Enhancing nested structs isn't supported. Furthermore, the table alias of the view's data source is not accessible in such an extend. You can use the common [`annotate` directive](#annotate) to just add/override annotations of a view's elements.
## Services > Source: /docs/cds/cdl#services - [Service Definitions](#service-definitions) - [Exposed Entities](#exposed-entities) - [(Auto-) Redirected Associations](#auto-redirect) - [Auto-exposed Targets](#auto-expose) - [Custom Actions/Functions](#actions) - [Custom-defined Events](#events) - [Extending Services](#extend-service) ### Service Definitions > Source: /docs/cds/cdl#service-definitions CDS allows to define service interfaces as collections of exposed entities enclosed in a `service` block, which essentially is and acts the same as [`context`](#context): ```cds service SomeService { entity SomeExposedEntity { ... }; entity AnotherExposedEntity { ... }; } ``` The endpoint of the exposed service is constructed by its name, following some conventions (the string `service` is dropped and kebab-case is enforced). If you want to overwrite the path, you can add the `@path` annotation as follows: ```cds @path: 'myCustomServicePath' service SomeService { ... } ``` [Watch a short video by DJ Adams on how the `@path` annotations works.](https://www.youtube.com/shorts/Q_PipD_7yBs){.learn-more} ### Exposed Entities > Source: /docs/cds/cdl#exposed-entities The entities exposed by a service are most frequently projections on entities from underlying data models. Standard view definitions, using [`as select from`](#views) or [`as projection on`](#as-projection-on), can be used for exposing entities. ```cds service CatalogService { entity Products as projection on data.Products { *, created.at as since } excluding { created }; } service MyOrders { //> $user only implemented for SAP HANA entity Orders as select from data.Orders { * } where buyer=$user.id; entity Products as projection on CatalogService.Products; } ``` ::: tip You can optionally add annotations such as `@readonly` or `@insertonly` to exposed entities, which will be enforced by the CAP runtimes in Java and Node.js. ::: Entities can be also exposed as views with parameters: ```cds service MyOrders { entity OrderWithParameter( foo: Integer, bar: Boolean ) as select from data.Orders where id=:foo; } ``` A parametrized view like modeled in the section on [`view with parameter`](#views-with-parameters) can be exposed as follows: ```cds service SomeService { entity ViewInService( p1: Integer, p2: Boolean ) as select from data.SomeView(foo: :p1, bar: :p2) {*}; } ``` Then the OData request for views with parameters should look like this: ```cds GET: /OrderWithParameter(foo=5)/Set or GET: /OrderWithParameter(5)/Set GET: /ViewInService(p1=5, p2=true)/Set ``` To expose an entity, it's not necessary to be lexically enclosed in the service definition. An entity's affiliation to a service is established using its fully qualified name, so you can also use one of the following options: - Add a namespace. - Use the service name as prefix. In the following example, all entities belong to/are exposed by the same service: ::: code-group ```cds [myservice.cds] service foo.MyService { entity A { /*...*/ }; } entity foo.MyService.B { /*...*/ }; ``` ::: ::: code-group ```cds [another.cds] namespace foo.MyService; entity C { /*...*/ }; ``` ::: ### (Auto-) Redirected Associations > Source: /docs/cds/cdl#auto--redirected-associations {#auto-redirect} When exposing related entities, associations are automatically redirected. This ensures that clients can navigate between projected entities as expected. For example: ```cds service AdminService { entity Books as projection on my.Books; entity Authors as projection on my.Authors; //> AdminService.Authors.books refers to AdminService.Books } ``` #### Resolving Ambiguities > Source: /docs/cds/cdl#resolving-ambiguities Auto-redirection fails if a target can't be resolved unambiguously, that is, when there is more than one projection with the same minimal 'distance' to the source. For example, compiling the following model with two projections on `my.Books` would produce this error: ::: danger Add “@cds.redirection.target” to either “AdminService.Books” or “AdminService.ListOfBooks” to select the entity as redirection target for “bookshop.Books” in this service; can't auto-redirect “AdminService.Authors:books” otherwise (in entity:“AdminService.Books”) ::: ```cds using bookshop as my from '../db/schema'; service AdminService { entity ListOfBooks as projection on my.Books; entity Books as projection on my.Books; entity Authors as projection on my.Authors; //> which one should AdminService.Authors.books refer to? } ``` #### Using `redirected to` with Projected Associations > Source: /docs/cds/cdl#using-redirected-to-with-projected-associations You can use `redirected to` to resolve the ambiguity as follows: ```cds service AdminService { entity ListOfBooks as projection on my.Books; entity Books as projection on my.Books; entity Authors as projection on my.Authors { *, // [!code focus] books : redirected to Books //> resolved ambiguity // [!code focus] }; } ``` #### Using `@cds.redirection.target` Annotations > Source: /docs/cds/cdl#using-cdsredirectiontarget-annotations Alternatively, you can use the boolean annotation `@cds.redirection.target` with value `true` to make an entity a preferred redirection target, or with value `false` to exclude an entity as target for auto-redirection. ```cds service AdminService { @cds.redirection.target: true // [!code focus] entity ListOfBooks as projection on my.Books; // [!code focus] entity Books as projection on my.Books; entity Authors as projection on my.Authors; } ``` ### Auto-Exposed Entities > Source: /docs/cds/cdl#auto-exposed-entities {#auto-expose} Annotate entities with `@cds.autoexpose` to automatically expose them in services containing entities with associations referring to them. For example, given the following entity definitions: ```cds // schema.cds namespace schema; entity Bar @cds.autoexpose { key id: Integer; } using { sap.common.CodeList } from '@sap/cds/common'; entity Car : CodeList { key code: Integer; } //> inherits @cds.autoexpose from sap.common.CodeList ``` ... a service definition like this: ```cds using { schema as my } from './schema.cds'; service Zoo { entity Foo { //... bar : Association to my.Bar; car : Association to my.Car; } } ``` ... would result in the service being automatically extended like this: ```cds extend service Zoo with { // auto-exposed entities: @readonly entity Foo_bar as projection on Bar; @readonly entity Foo_car as projection on Car; } ``` You can still expose such entities explicitly, for example, to make them read-write: ```cds service MyOrders { entity Orders { /*...*/ } entity Bar as projection on my.Bar; } ``` [Learn more about **CodeLists in `@sap/cds/common`**.](./common#code-lists){.learn-more} ### Custom Actions and Functions > Source: /docs/cds/cdl#custom-actions-and-functions Within service definitions, you can additionally specify `actions` and `functions`. Use a comma-separated list of named and typed inbound parameters (optional) and a response type (optional for actions), which can be either a: - [Predefined Type](#types) - [Reference to a custom-defined type](#types) - [Inline definition of an anonymous structured type](#structured-types) ```cds service MyOrders { entity Orders { /*...*/ }; // unbound actions / functions type cancelOrderRet { acknowledge: String enum { succeeded; failed; }; message: String; } action cancelOrder ( orderID:Integer, reason:String ) returns cancelOrderRet; function countOrders() returns Integer; function getOpenOrders() returns array of Order; } ``` ::: tip The notion of actions and functions in CDS adopts that of [OData](https://docs.oasis-open.org/odata/odata/v4.0/os/part1-protocol/odata-v4.0-os-part1-protocol.html#_Toc372793737); actions and functions on service-level are _unbound_ ones. ::: #### Bound Actions and Functions > Source: /docs/cds/cdl#bound-actions-and-functions Actions and functions can also be bound to individual entities of a service, enclosed in an additional `actions` block as the last clause in an entity/view definition. ```cds service CatalogService { entity Products as projection on data.Products { ... } actions { // bound actions/functions action addRating (stars: Integer); function getViewsCount() returns Integer; } } ``` Bound actions and functions have a binding parameter that is usually implicit. It can also be modeled explicitly: the first parameter of a bound action or function is treated as binding parameter, if it's typed with `$self` or `many $self`. Use the keyword [`many`](#arrayed-types) to indicate that the action or function is bound to a collection of instances rather than to a single one. Also use the binding parameter to control its name. ```cds service CatalogService { entity Products as projection on data.Products { ... } actions { // bound actions/functions with explicit binding parameter action A1 (prod: $self, stars: Integer); action A2 (in: many $self); // bound to collection of Products } } ``` Explicitly modelled binding parameters are ignored for OData V2. #### Returning Media Data Streams > Source: /docs/cds/cdl#returning-media-data-streams Actions and functions can also be modeled to return streamed media data such as images and CSV files. To achieve this, the return type of the actions or functions must refer to a [predefined type](#types), annotated with [media data annotations](../guides/services/media-data#annotating-media-elements), that is defined in the same service. The minimum set of annotations required is `@Core.MediaType`. ```cds service CatalogService { @Core.MediaType: 'image/png' @Core.ContentDisposition.Filename: 'image.png' @Core.ContentDisposition.Type: 'attachment' type png : LargeBinary; entity Products as projection on data.Products { ... } actions { function image() returns png; } } ``` ### Custom-Defined Events > Source: /docs/cds/cdl#custom-defined-events Similar to [Actions and Functions](../cds/cdl#actions) you can declare `events`, which a service emits via messaging channels. Essentially, an event declaration looks very much like a type definition, specifying the event's name and the type structure of the event messages' payload. ```cds service MyOrders { ... event OrderCanceled { orderID: Integer; reason: String; } } ``` An event can also be defined as projection on an entity, structured type, or another event. Only the effective signature of the projection is relevant. ```cds service MyOrders { ... event OrderCanceledNarrow : projection on OrderCanceled { orderID } } ``` ### Extending Services > Source: /docs/cds/cdl#extending-services You can [extend](#extend) services with additional entities and actions much as you would add new entities to a context: ```cds extend service CatalogService with { entity Foo {}; function getRatings() returns Integer; } ``` Similarly, you can [extend](#extend) entities with additional actions as you would add new elements: ```cds extend entity CatalogService.Products with actions { function getRatings() returns Integer; } ``` [JSON Schema]: https://json-schema.org [OpenAPI]: https://www.openapis.org # Core Schema Notation (CSN) > Source: /docs/cds/csn CSN (pronounced as "_Season_") is a notation for compact representations of CDS models — tailored to serve as an optimized format to share and interpret models with minimal footprint and dependencies. It's similar to [JSON Schema] but goes beyond JSON's abilities, in order to capture full-blown _Entity-Relationship Models_ and [Extensions](#aspects). This makes CSN models a perfect source to generate target models, such as [OData/EDM](../guides/protocols/odata) or [OpenAPI] interfaces, as well as persistence models for SQL or NoSQL databases. ## Anatomy > Source: /docs/cds/csn#anatomy A CSN model in **JSON**: ```json { "requires": [ "@sap/cds/common", "./db/schema" ], "definitions": { "some.type": { "type": "cds.String", "length": 11 }, "another.type": { "type": "some.type" }, "structured.type": { "elements": { "foo": { "type": "cds.Integer" }, "bar": { "type": "cds.String" } }} }, "extensions": [ { "extend":"Foo", "elements":{ "bar": { "type": "cds.String" } }} ] } ``` The same model in **YAML**: ```yaml requires: - @sap/cds/common - ./db/schema definitions: some.type: {type: cds.String, length: 11} another.type: {type: some.type } structured.type: elements: foo: {type: cds.Integer} bar: {type: cds.String} extensions: - extend: Foo elements: bar: {type: cds.String} ``` The same model as a **plain JavaScript** object: ```js ({ requires:[ '@sap/cds/common', './db/schema' ], definitions: { 'some.type': { type:"cds.String", length:11 }, 'another.type': { type:"some.type" }, 'structured.type': { elements: { 'foo': { type:"cds.Integer" }, 'bar': { type:"cds.String" } }} }, extensions: [ { extend:'Foo', elements:{ 'bar': { type:"cds.String" } } ], }) ``` For the remainder of this spec, you see examples in plain JavaScript representation with the following **conventions**: ```js ({property:...}) // a CSN-specified property name ({'name':...}) // a definition's declared name "value" // a string value, including referred names 11, true // number and boolean literal values ``` #### Properties > Source: /docs/cds/csn#properties * [`requires`](#imports) – an array listing [imported models](#imports) * [`definitions`](#definitions) – a dictionary of named [definitions](#definitions) * [`extensions`](#aspects) – an array of unnamed [aspects](#aspects) * [`i18n`](#i18n) – a dictionary of dictionaries of [text translations](#i18n) > [!TIP] All properties are optional > For example, one model could contain a few definitions, while another one only contains some extensions. > [!NOTE] References are case-sensitive > All references in properties like `type` or `target` use exactly the same notation regarding casing as their targets' names. To avoid problems when translating models to case-insensitive environments like SQL databases, avoid case-significant names and references. For example, avoid two different definitions in the same scope whose names only differ in casing, such as `foo` and `Foo`. ## Literals > Source: /docs/cds/csn#literals There are several places where literals can show up in models, such as in SQL expressions, calculated fields, or annotations. Standard literals are represented as in JSON: | Kind | Example | |-----------------------|--------------------------| | Globals | `true`, `false`, `null` | | Numbers1 | `11` or `2.4` | | Strings | `"foo"` | | Dates2 | `"2016-11-24"` | | Times2 | `"16:11Z"` | | DateTimes2 | `"2016-11-24T16:11Z"` | | Records | `{"foo":, ...}` | | Arrays | `[, ...]` | In addition, CSN specifies these special forms for references, expressions, and `enum` symbols: | Kind | Example | |--------------------------|-----------------------| | Unparsed Expressions | `{"=":"foo.bar < 9"}` | | Enum symbols3 | `{"#":"asc"}` | #### Remarks > Source: /docs/cds/csn#remarks >1 This is as in JSON and shares the same issues when decimals are mapped to doubles with potential rounding errors. The same applies to Integer64. Use strings to avoid that, if applicable. > >2 Also, as in JSON, dates, and times are represented just as strings as specified in [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601); consumers are assumed to know the types and handle the values correctly. > >3 As enum symbols are equal to their values, it frequently suffices to just provide them as strings. Similar to time and dates in CSN and JSON, the consumers are assumed to know the types and handle the values correctly. The `{"#":...}` syntax option is to serve cases where you have to distinguish the kind only based on the provided value, for example, in untyped annotations. ## Definitions > Source: /docs/cds/csn#definitions Each entry in the `definitions` dictionary is essentially a type definition. The name is the absolute, fully qualified name of the definition, and the value is a record with the definition details. #### Example > Source: /docs/cds/csn#example ```js ({definitions:{ 'Name': {type:"cds.String"}, 'Currency': {type:"cds.String", length:3}, 'USD': {type:"Currency"}, 'Amount': {elements:{ 'value': {type:"cds.Decimal", precision:11, scale:3}, 'currency': {type:"Currency"}, }}, 'SortOrder':{enum:{ 'asc':{}, 'desc':{} }} }}) ``` The __name__ of a definition is its key in the enclosing dictionary, like in `definitions` for top-level entries or in `elements` for structured types and entities. Names **must**: * Be nonempty strings. * Neither start, nor end with `.` or `::`. * Not contain substrings `..` or `:::`. * Not contain the substring `::` more than once. #### Properties > Source: /docs/cds/csn#properties-1 * `kind` – one of `context`, `service`, `entity`, `type`, `action`, `function`, or `annotation` * `type` – an optional base type that this definition is derived from * [`elements`][elements] – optional dictionary of [_elements_][elements] in case of structured types Property `kind` is always omitted for [elements] and can be omitted for top-level [type definitions]. These examples are semantically equivalent: ```js Foo1 = { type:"cds.String" } Foo2 = { type:"cds.String", kind:"type" } ``` ## Type Definitions > Source: /docs/cds/csn#type-definitions [type definitions]: #type-definitions Custom-defined types are entries in [`definitions`](#definitions) with an optional property `kind`=`"type"` and the following properties. | Property | Used for | |------------|-----------------------------------------------------------------------| | `type` | [Scalar Types](#scalar-types), [Structured Types](#structured-types), and [Associations](#associations) | | `elements` | [Structured Types](#structured-types) | | `items` | [Arrayed Types](#arrayed-types) | | `enum` | [Enumeration Types](#enumeration-types) | #### Example > Source: /docs/cds/csn#example-1 ```js ({definitions: { 'scalar.type': {type:"cds.String", length:3 }, 'struct.type': {elements:{'foo': {type:"cds.Integer"}}}, 'arrayed.type': {items:{type:"cds.Integer"}}, 'enum.type': {enum:{ 'asc':{}, 'desc':{} }} }}) ``` #### Properties > Source: /docs/cds/csn#properties-2 * `kind` – omitted or _`"type"`_ * `type` – the base type, this definition is derived from * [`elements`][elements] – optional element definitions for [_structured types_][struct]. * [`items`][arrays] – optional definition of item types for [_arrayed types_][arrays]. * [`enum`][enum] – an optional dictionary of enum members for [_enumeration types_][enum]. * `value` – a constant [literal value](#literals) or calculation expression * `default` – a default [value or expression](#literals) * `localized` _= true_ if this type was declared like _foo : localized String_ * `...` – other type-specific properties, for example, a String's `length` ### Scalar Types > Source: /docs/cds/csn#scalar-types Scalar types always have property `type` specified, plus optional type-specific parameter properties. ```js ({definitions:{ 'scalar.type': {type:"cds.String", length:3 }, }}) ``` See the [CDL reference docs](types) for an overview of CDS' built-in types. While in [CDS sources](cdl) you can refer to these types without prefix, they always have to be specified with their **fully qualified names in CSN**, for example: ```js ({definitions: { 'Foo': { type:"cds.Integer" }, 'Bar': { type:"cds.Decimal", precision:11, scale:3 }, }}) ``` ### Structured Types > Source: /docs/cds/csn#structured-types [struct]: #structured-types [elements]: #structured-types [Structured Types]: #structured-types Structured types are signified by the presence of an `elements` property. The value of `elements` is a dictionary of `elements`. The name is the local name of the element and the values in turn are [Type Definitions](#type-definitions). The optional property `includes` contains a list of fully qualified entity-, aspect-, or type-names. Elements, actions, and annotations from those definitions are then copied into the structured type. ```js ({definitions:{ 'structured.type': {elements:{ 'foo': {type:"cds.Integer"}, 'bar': {type:"cds.String"} }} }}) ``` ### Arrayed Types > Source: /docs/cds/csn#arrayed-types [arrays]: #arrayed-types Arrayed types are signified by the presence of a property `items`. The value of which is in turn a [type definition](#type-definitions) that specifies the arrayed items' type. ```js ({definitions:{ 'arrayed.type': {items:{type:"cds.Integer"}} }}) ``` ### Enumeration Types > Source: /docs/cds/csn#enumeration-types [enum]: #enumeration-types The `enum` property is a dictionary of enum member elements with the name being the enum symbol and the value being a [CQN literal value expression](cxn#literal-values). The literal expression optionally specifies a constant `val` as a [literal](#literals) plus optional annotations. An enumeration type can specify an explicit `type` (for example, _Decimal_) but can also omit it and refer from given enumeration values, or _String_ as default. ```js ({definitions:{ 'Gender': {enum:{ 'male':{}, 'female':{}, 'non_binary': { val: 'non-binary' } }}, 'Status': {enum:{ 'submitted': {val:1}, 'fulfilled': {val:2} }}, 'Rating': {type:"cds.Decimal", enum:{ 'low': {val:0}, 'medium': {val:50}, 'high': {val:100} }} }}) ``` ## Entity Definitions > Source: /docs/cds/csn#entity-definitions [entities]: #entity-definitions [entity]: #entity-definitions Entities are [structured types](#structured-types) with **_kind_** =`'entity'`. In addition, one or more elements usually have property `key` set to true, to flag the entity's primary key. #### Example > Source: /docs/cds/csn#example-2 ```js ({definitions:{ 'Products': {kind:"entity", elements:{ 'ID': {type:"cds.Integer", key:true}, 'title': {type:"cds.String", notNull:true}, 'price': {type:"Amount", virtual:true}, }} }}) ``` #### Properties > Source: /docs/cds/csn#properties-3 * `kind` – is always _`"entity"`_ * `elements` – as in [Structured Types], optionally equipped with one or more of these boolean properties: * `key` – signifies that the element is (part of) the primary key * `virtual` – has this element ignored in generic persistence mapping * `notNull` – the _not null_ constraint as in SQL * `includes` – as in [Structured Types] ### View Definitions > Source: /docs/cds/csn#view-definitions [views]: #view-definitions [view]: #view-definitions Views are entities defined as projections on underlying entities. In CSN, views are signified by the presence of property `query`, which captures the projection as a [CQN](cqn) expression. #### Example > Source: /docs/cds/csn#example-3 ```js ({definitions:{ 'Foo': { kind:"entity", query: { SELECT:{ from: {ref:['Bar']}, columns: [ {ref:['title']}, {ref:['price']} ] } }} }}) ``` #### Properties > Source: /docs/cds/csn#properties-4 * `kind` – mandatory; always _`"entity"`_ * `query` – the parsed query in [CQN](cqn) format * `elements` – optional [elements signature](#views-with-declared-signatures), omitted and inferred * `params` – optional [parameters](#views-with-parameters) ### Views with Declared Signatures > Source: /docs/cds/csn#views-with-declared-signatures Views with declared signatures have the additional property `elements` filled in as in [entities](cdl#entities): ```js ({definitions:{ 'with.declared.signature': {kind:"entity", elements: { 'title': {type:"cds.String"}, 'price': {type:"Amount"} }, query: { SELECT:{...} }, } }}) ``` ### Views with Parameters > Source: /docs/cds/csn#views-with-parameters Views with parameters have an additional property `params` – an optional dictionary of parameter [type definitions](#type-definitions): ```js ({definitions:{ 'with.params': {kind:"entity", params: { 'ID': { type: 'cds.Integer' } }, query: { SELECT:{...} }, } }}) ``` ### Projections > Source: /docs/cds/csn#projections Use the `projection` property for views if you don't need the full power of SQL. See `as projection on` in [CDL](./cdl#as-projection-on) for restrictions. ```js ({ definitions: { 'Foo': { kind: "entity", projection: { from: { ref: ['Bar'] }, columns: [ '*' ] } } }}) ``` #### Properties > Source: /docs/cds/csn#properties-5 * `kind` – mandatory; always _`"entity"`_ * `projection` – the parsed query; equivalent to `query.SELECT`, see [CQN](cqn) * `elements` – optional [elements signature](#views-with-declared-signatures), omitted and inferred ## Associations > Source: /docs/cds/csn#associations Associations are like [scalar type definitions](#scalar-types) with `type` being `cds.Association` or `cds.Composition` plus additional properties specifying the association's `target` and optional information like `on` conditions or foreign `keys`. ### Basic to-one Associations > Source: /docs/cds/csn#basic-to-one-associations The basic form of associations are *to-one* associations to a designated target: ```js ({definitions:{ 'Books': { kind:"entity", elements:{ 'author': { type:"cds.Association", target:"Authors" }, }}, //> an association type-def 'Currency': { type:"cds.Association", target:"Currencies" }, }}) ``` ### With Specified `cardinality` > Source: /docs/cds/csn#with-specified-cardinality Add property `cardinality` to explicitly specify a *to-one* or *to-many* relationship: ```js ({definitions:{ 'Authors': { kind:"entity", elements:{ 'books': { type:"cds.Association", target:"Books", cardinality:{max:"*"} }, }}, }}) ``` Property `cardinality` is an object `{src?,min?,max}` with... * `src` set to `1` give a hint to database optimizers, that a source entity always exists * `min` specifying the target's minimum cardinality – default: `0` * `max` specifying the target's maximum cardinality – default: `1` In summary, the default cardinality is _[0..1]_, which means *to-one*. ### With Specified `on` Condition > Source: /docs/cds/csn#with-specified-on-condition So-called *unmanaged* associations have an explicitly specified `on` condition: ```js ({definitions:{ 'Authors': { kind:"entity", elements:{ 'books': { type:"cds.Association", target:"Books", cardinality{max:"*"}, on: [{ref:['books', 'author']}, '=', {ref:['$self']}] }, }} }}) ``` ### With Specified `keys` > Source: /docs/cds/csn#with-specified-keys Managed to-one associations automatically use the target's designated primary `key` elements. You can overrule this by explicitly specifying alternative target properties to be used in the `keys` property: ```js ({definitions:{ 'Books': {kind:"entity", elements:{ 'genre': {type:"cds.Association", target:"Genres", keys:[ {ref:["category"], as:"cat"}, {ref:["name"]}, ]}, }}, }}) ``` Property `keys` has the format and mechanisms of [CQN projections](cqn#select). ## Annotations > Source: /docs/cds/csn#annotations Annotations are represented as properties, prefixed with `@`. This format applies to type/entity-level annotations as well as to element-level ones. #### Example > Source: /docs/cds/csn#example-4 ```js ({definitions:{ 'Employees': {kind:"entity", '@title':"Mitarbeiter", '@readonly':true, elements:{ 'firstname': {type:"cds.String", '@title':"Vorname"}, 'surname': {type:"cds.String", '@title':"Nachname"}, } }, }}) ``` Annotations are used to add custom information to definitions, the prefixed `@` acts as a protection against conflicts with built-in/standard properties. They're flat lists of key-value pairs, with keys being fully qualified property names and values being represented as introduced in the section [Literals and Expressions](#literals). ## Aspects > Source: /docs/cds/csn#aspects In parsed-only models, the top-level property `extensions` holds an array of unapplied extensions or annotations (→ see also [Aspects in CDL](cdl#aspects)). The entries are of this form: ```js ext = { extend|annotate: , : , … } ``` with: - `extend` or `annotate` referring to the definition to be extended or annotated - `` being the property that should be extended, for example, `elements` if an entity should be extended with further elements ### Extend with \ > Source: /docs/cds/csn#extend-with-named-aspect The most basic form allows to express an extension of a named definition with another named definition (→ see [Named Aspects](cdl#named-aspects)): ```js csn = { extensions:[ { extend:"TargetDefinition", includes:["NamedAspect"]} ]} ``` ### Extend with \ > Source: /docs/cds/csn#extend-with- The form `{ extend:, : , … }` allows to add elements to an existing [struct] definition as well as to add or override annotations of the target definition: ```js csn = { extensions:[ // extend Foo with @foo { ..., bar: String; } { extend: "Foo", '@foo': true, elements: { // adds a new element 'bar' bar: { type: "cds.String", '@bar': true }, } }, ]} ``` ### annotate with \ > Source: /docs/cds/csn#annotate-with- The form `{ annotate:, : , … }` allows to add or override annotations of the target definition as well as those of nested elements: ```js csn = {extensions:[ // annotate Foo with @foo; { annotate:"Foo", '@foo':true }, // annotate Foo with @foo { boo @boo } { annotate:"Foo", '@foo':true, elements: { // annotates existing element 'boo' boo: {'@boo':true }, }}, ]} ``` ## Services > Source: /docs/cds/csn#services Services are definitions with _kind =`'service'`_: ```js ({definitions:{ 'MyOrders': {kind:"service"} }}) ``` ### Actions / Functions > Source: /docs/cds/csn#actions--functions Entity definitions (for _bound_ actions/functions) can have an additional property `actions`. The keys of these `actions` are the (local) names of actions/functions. _Unbound_ actions/functions of a service are represented as top level definitions. Example: ```js ({definitions:{ 'OrderService': {kind:"service"}, 'OrderService.Orders': {kind:"entity", elements:{...}, actions:{ 'validate': {kind:"function", returns: {type: "cds.Boolean"} } }}, 'OrderService.cancelOrder': {kind:"action", params:{ 'orderID': {type:"cds.Integer"}, 'reason': {type:"cds.String"}, }, returns: {elements:{ 'ack': {enum:{ 'succeeded':{}, 'failed':{} }}, 'msg': {type:"cds.String"}, }} } }} }}) ``` #### Properties > Source: /docs/cds/csn#properties-6 * `kind` – either `"action"` or `"function"` as in _OData_ * `params` – a dictionary with the values being [Type Definitions](#type-definitions) * `returns` – a [Type Definition](#type-definitions) describing the response > Note: The definition of the response can be a reference to a declared type or the inline definition of a new (structured) type. ## Imports > Source: /docs/cds/csn#imports The `requires` property lists other models to import definitions from. It is the CSN equivalent of the CDL [`using` directive](./cdl#using). #### Example > Source: /docs/cds/csn#example-5 ```js ({ requires: [ '@sap/cds/common', './db/schema' ], // [...] }) ``` As in Node.js the filenames are either absolute module names or relative filenames, starting with `./` or `../`. ## i18n > Source: /docs/cds/csn#i18n A CSN may optionally contain a top-level `i18n` property, which can contain translated texts. The expected structure is as follows: ```js ({ i18n: { 'language-key': { 'text-key': "some string" } } }) ``` This data must be written and handled by the application, there's no out-of-the-box support for this by CAP. # Query Language (CQL) > Source: /docs/cds/cql CDS Query Language (CQL) is based on standard SQL, which it enhances by... ## Postfix Projections > Source: /docs/cds/cql#postfix-projections {#postfix-projections} CQL allows to put projections, that means, the `SELECT` clause, behind the `FROM` clause enclosed in curly braces. For example, the following are equivalent: ```sql SELECT name, address.street from Authors ``` ```sql SELECT from Authors { name, address.street } ``` ### Nested Expands > Source: /docs/cds/cql#nested-expands-beta- {#nested-expands} Postfix projections can be appended to any column referring to a struct element or an association and hence be nested. This allows **expand** results along associations and hence read deeply structured documents: ```sql SELECT from Authors { name, address { street, town { name, country }} }; ``` This actually executes three correlated queries to authors, addresses, and towns and returns a structured result set like that: ```js results = [ { name: 'Victor Hugo', address: { street: '6 Place des Vosges', town: { name: 'Paris', country: 'France' } } }, { name: 'Emily Brontë', … }, … ] ``` > This is rather a feature tailored to NoSQL databases and has no equivalent in standard SQL as it requires structured result sets. Some SQL vendors allow things like that with non-scalar subqueries in SELECT clauses. ::: warning Nested Expands following _to-many_ associations are not supported. ::: #### Alias > Source: /docs/cds/cql#alias As the name of the struct element or association preceding the postfix projection appears in the result set, an alias can be provided for it: ```sql SELECT from Authors { name, address as residence { street, town as city { name, country }} }; ``` The result set now is: ```js results = [ { name: 'Victor Hugo', residence: { street: '6 Place des Vosges', city: { name: 'Paris', country: 'France' } } }, … ] ``` #### Expressions > Source: /docs/cds/cql#expressions Nested Expands can contain expressions. In addition, it's possible to define new structures that aren't present in the data source. In this case an alias is mandatory and is placed *behind* the `{…}`: ```sql SELECT from Books { title, author { name, dateOfDeath - dateOfBirth as age }, { stock as number, stock * price as value } as stock }; ``` The result set contains two structured elements: ```js results = [ { title: 'Wuthering Heights', author: { name: 'Emily Brontë', age: 30 }, stock: { number: 12, value: 133.32 } }, … ] ``` ### Nested Inlines > Source: /docs/cds/cql#nested-inlines-beta- Put a **`"."`** before the opening brace to **inline** the target elements and avoid writing lengthy lists of paths to read several elements from the same target. For example: ```sql SELECT from Authors { name, address.{ street, town.{ name, country }} }; ``` … is equivalent to: ```sql SELECT from Authors { name, address.street, address.town.name, address.town.country }; ``` Nested Inlines can contain expressions: ```sql SELECT from Books { title, author.{ name, dateOfDeath - dateOfBirth as author_age, address.town.{ concat(name, '/', country) as author_town } } }; ``` The previous example is equivalent to the following: ```sql SELECT from Books { title, author.name, author.dateOfDeath - author.dateOfBirth as author_age, concat(author.address.town.name, '/', author.address.town.country) as author_town }; ``` ## Smart `*` Selector > Source: /docs/cds/cql#smart--selector Within postfix projections, the `*` operator queries are handled slightly different than in plain SQL select clauses. #### Example: > Source: /docs/cds/cql#example ```sql SELECT from Books { *, author.name as author } ``` Queries like in our example, would result in duplicate element effects for `author` in SQL. In CQL, explicitly defined columns following an `*` replace equally named columns that have been inferred before. ### Excluding Clause > Source: /docs/cds/cql#excluding-clause Use the `excluding` clause in combination with `SELECT *` to select all elements except for the ones listed in the exclude list. ```sql SELECT from Books { * } excluding { author } ``` The effect is about **late materialization** of signatures and staying open to late extensions. For example, assume the following definitions: ```cds entity Foo { foo : String; bar : String; car : String; } entity Bar as select from Foo excluding { bar }; entity Boo as select from Foo { foo, car }; ``` A `SELECT * from Bar` would result into the same as a query of `Boo`: ```sql SELECT * from Bar --> { foo, car } SELECT * from Boo --> { foo, car } ``` Now, assume a consumer of that package extends the definitions as follows: ```cds extend Foo with { boo : String; } ``` With that, queries on `Bar` and `Boo` would return different results: ```sql SELECT * from Bar --> { foo, car, boo } SELECT * from Boo --> { foo, car } ``` ### In Nested Expands > Source: /docs/cds/cql#in-nested-expands-beta- If the `*` selector is used following an association, it selects all elements of the association target. For example, the following queries are equivalent: ```sql SELECT from Books { title, author { * } } ``` ```sql SELECT from Books { title, author { ID, name, dateOfBirth, … } } ``` A `*` selector following a struct element selects all elements of the structure and thus is equivalent to selecting the struct element itself. The following queries are all equivalent: ```sql SELECT from Authors { name, struc { * } } SELECT from Authors { name, struc { elem1, elem2 } } SELECT from Authors { name, struc } ``` The `excluding` clause can also be used for Nested Expands: ```sql SELECT from Books { title, author { * } excluding { dateOfDeath, placeOfDeath } } ``` ### In Nested Inlines > Source: /docs/cds/cql#in-nested-inlines-beta- The expansion of `*` in Nested Inlines is analogous. The following queries are equivalent: ```sql SELECT from Books { title, author.{ * } } SELECT from Books { title, author.{ ID, name, dateOfBirth, … } } ``` The `excluding` clause can also be used for Nested Inlines: ```sql SELECT from Books { title, author.{ * } excluding { dateOfDeath, placeOfDeath } } ``` ## Path Expressions > Source: /docs/cds/cql#path-expressions Use path expressions to navigate along associations and/or struct elements in any of the SQL clauses as follows: In `from` clauses: ```sql SELECT from Authors[name='Emily Brontë'].books; SELECT from Books:authors.towns; ``` In `select` clauses: ```sql SELECT title, author.name from Books; SELECT *, author.address.town.name from Books; ``` In `where` clauses: ```sql SELECT from Books where author.name='Emily Brontë' ``` The same is valid for `group by`, `having`, and `order by`. ### Path Expressions in `from` Clauses > Source: /docs/cds/cql#path-expressions-in-from-clauses Path expressions in from clauses allow to fetch only those entries from a target entity, which are associated to a parent entity. They unfold to _SEMI JOINS_ in plain SQL queries. For example, the previous mentioned queries would unfold to the following plain SQL counterparts: ```sql SELECT * from Books WHERE EXISTS ( SELECT 1 from Authors WHERE Authors.ID = Books.author_ID AND Authors.name='Emily Brontë' ); ``` ```sql SELECT * from Towns WHERE EXISTS ( SELECT 1 from Authors WHERE Authors.town_ID = Towns.ID AND EXISTS ( SELECT 1 from Books WHERE Books.author_ID = Authors.ID ) ); ``` ### Path Expressions in All Other Clauses > Source: /docs/cds/cql#path-expressions-in-all-other-clauses Path expressions in all other clauses are very much like standard SQL's column expressions with table aliases as single prefixes. CQL essentially extends the standard behavior to paths with multiple prefixes, each resolving to a table alias from a corresponding `LEFT OUTER JOIN`. For example, the path expressions in the previous mentioned queries would unfold to the following plain SQL queries: ```sql -- plain SQL SELECT Books.title, author.name from Books LEFT JOIN Authors author ON author.ID = Books.author_ID; ``` ```sql -- plain SQL SELECT Books.*, author_address_town.name from Books LEFT JOIN Authors author ON author.ID = Books.author_ID LEFT JOIN Addresses author_address ON author_address.ID = author.address_ID LEFT JOIN Towns author_address_town ON author_address_town.ID = author_address.town_ID; ``` ```sql -- plain SQL SELECT Books.* from Books LEFT JOIN Authors author ON author.ID = Books.author_ID WHERE author.name='Emily Brontë' ``` ::: tip All column references get qualified → in contrast to plain SQL joins there's no risk of ambiguous or conflicting column names. ::: ### With Infix Filters > Source: /docs/cds/cql#with-infix-filters Append infix filters to associations in path expressions to narrow the resulting joins. For example: ```sql SELECT books[genre='Mystery'].title from Authors WHERE name='Agatha Christie' ``` ... unfolds to: ```sql SELECT books.title from Authors LEFT JOIN Books books ON ( books.author_ID = Authors.ID ) AND ( books.genre = 'Mystery' ) //--> from Infix Filter WHERE Authors.name='Agatha Christie'; ``` If an infix filter effectively reduces the cardinality of a *to-many* association to *one*, make this explicit with: ```sql SELECT name, books[1: favorite=true].title from Authors ``` ::: warning `:1` has no effect on result set The `:1` syntax in the filter has no effect on the result. It is only an information by the developer that the specified condition reduces the result to a single entry. ::: ### Exists Predicate > Source: /docs/cds/cql#exists-predicate Use a filtered path expression to test if any element of the associated collection matches the given filter: ```sql SELECT FROM Authors {name} WHERE EXISTS books[year = 2000] ``` ...unfolds to: ```sql SELECT name FROM Authors WHERE EXISTS ( SELECT 1 FROM Books WHERE Books.author_id = Authors.id AND Books.year = 2000 ) ``` Exists predicates can be nested: ```sql SELECT FROM Authors { name } WHERE EXISTS books[year = 2000 and EXISTS pages[wordcount > 1000]] ``` A path with several associations is rewritten as nested exists predicates. The previous query is equivalent to the following query. ```sql SELECT FROM Authors { name } WHERE EXISTS books[year = 2000].pages[wordcount > 1000] ``` ::: warning Paths *inside* the filter are not yet supported. ::: ## Casts in CDL > Source: /docs/cds/cql#casts-in-cdl There are two different constructs commonly called casts. SQL casts and CDL casts. The former produces SQL casts when rendered into SQL, whereas the latter does not: ```sql SELECT cast (foo+1 as Decimal) as bar from Foo; -- standard SQL SELECT from Foo { foo+1 as bar : Decimal }; -- CDL-style ``` [Learn more about CDL type definitions](./cdl#types){.learn-more} Use SQL casts when you actually want a cast in SQL. CDL casts are useful for expressions such as `foo+1` as the compiler does not deduce types. For the OData backend, by specifying a type, the compiler will also assign the correct EDM type in the generated EDM(X) files. ::: tip You don't need a CDL cast if you already use a SQL cast. The compiler will extract the type from the SQL cast. ::: ## Use enums > Source: /docs/cds/cql#use-enums In queries, you can use enum symbols instead of the respective literals in places where the corresponding type can be deduced: ```cds type Status : String enum { open; closed; in_progress; }; entity OpenOrder as projection on Order { case status when #open then 0 when #in_progress then 1 end as status_int : Integer, (status = #in_progress ? 'is in progress' : 'is open') as status_txt : String, } where status = #open or status = #in_progress; ``` ## Association Definitions > Source: /docs/cds/cql#association-definitions ### Query-Local Mixins > Source: /docs/cds/cql#query-local-mixins Use the `mixin...into` clause to logically add unmanaged associations to the source of the query, which you can use and propagate in the query's projection. This is only supported in postfix notation. ```sql SELECT from Books mixin { localized : Association to LocalizedBooks on localized.ID = ID; } into { ID, localized.title }; ``` ### In the select list > Source: /docs/cds/cql#in-the-select-list Define an unmanaged association directly in the select list of the query to add the association to the view's signature. This association cannot be used in the query itself. In contrast to mixins, these association definitions are also possible in projections. ```cds entity BookReviews as select from Reviews { ..., subject as bookID, book : Association to Books on book.ID = bookID }; ``` In the ON condition you can, besides target elements, only reference elements of the select list. Elements of the query's data sources are not accessible. This syntax can also be used to add new unmanaged associations to a projection or view via `extend`: ```cds extend BookReviews with columns { subject as bookID, book : Association to Books on book.ID = bookID }; ``` # Query Notation (CQN) > Source: /docs/cds/cqn ## Introduction > Source: /docs/cds/cqn#introduction CQN is a canonical plain object representation of CDS queries. Such query objects can be obtained by parsing [CQL](./cql), by using the [query builder APIs](../node.js/cds-ql), or by simply constructing respective objects directly in your code. For example, the following three snippets all construct the same query object: ```js // Parsing CQL tagged template strings let query = cds.ql `SELECT from Foo` ``` ```js // Query building let query = SELECT.from (ref`Foo`) ``` ```js // Constructing plain CQN objects let query = {SELECT:{from:[{ref:['Foo']}]}} ``` Such queries can be [executed with `cds.run`](../node.js/core-services#srv-run-query): ```js let results = await cds.run (query) ``` Following is a detailed specification of the CQN as [TypeScript declarations](https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html), including all query types and their properties, as well as the fundamental expression types. Find the [full CQN type definitions in the appendix below](#full-cqndts-file). ## SELECT > Source: /docs/cds/cqn#select Following is the TypeScript declaration of `SELECT` query objects: ```tsx class SELECT { SELECT: { distinct? : true count? : true one? : true from : source columns? : column[] where? : xo[] having? : xo[] groupBy? : expr[] orderBy? : order[] limit? : { rows: val, offset: val } }} ``` > Using: > [`source`](#source), > [`column`](#column), > [`xo`](#xo), > [`expr`](#expr), > [`order`](#order), > [`val`](#val) CQL SELECT queries enhance SQL's SELECT statements with these noteworthy additions: - The `from` clause supports [`{ref}`](#ref) paths with *[infix filters](#infix)*. - The `columns` clause supports deeply *[nested projections](#expand)*. - The `count` property requests the total count, similar to OData's `$count`. - The `one` property causes a single row object to be read instead of an array. Also `SELECT` statements with `from` as the only mandatory property are allowed, which is equivalent to SQL's `SELECT * from ...`. ### `.from` > Source: /docs/cds/cqn#from ###### source > Source: /docs/cds/cqn#source Property `from` specifies the source of the query, which can be a table, a view, or a subquery. It is specified with type `source` as follows: ```tsx class SELECT { SELECT: { //... from : source // [!code focus] }} ``` ```tsx type source = ref &as | SELECT | { join : 'inner' | 'left' | 'right' args : [ source, source ] on? : expr } ``` > Using: > [`ref`](#ref), > [`as`](#as), > [`expr`](#expr) > > Used in: > [`SELECT`](#select) ### `.columns` > Source: /docs/cds/cqn#columns ###### column > Source: /docs/cds/cqn#column ###### as > Source: /docs/cds/cqn#as ###### cast > Source: /docs/cds/cqn#cast ###### infix > Source: /docs/cds/cqn#infix ###### expand > Source: /docs/cds/cqn#expand Property `columns` specifies the columns to be selected, projected, or aggregated, and is specified as an array of `column`s: ```tsx class SELECT { SELECT: { //... columns : column[] // [!code focus] }} ``` ```tsx type column = '*' | expr &as &cast | ref &as &( { expand?: column[] } | { inline?: column[] } ) &infix ``` ```tsx interface as { as?: name } interface cast { cast?: {type:name} } interface infix { orderBy? : order[] where? : expr limit? : { rows: val, offset: val } } ``` > Using: > [`expr`](#expr), > [`name`](#name), > [`ref`](#ref), > > Used in: > [`SELECT`](#select) ### `.where` > Source: /docs/cds/cqn#where ### `.having` > Source: /docs/cds/cqn#having ### `.search` > Source: /docs/cds/cqn#search Properties `where`, and `having`, specify the filter predicates to be applied to the rows selected, or grouped, respectively. Property `search` is of same kind and is used for full-text search. ```tsx class SELECT { SELECT: { where : xo[] // [!code focus] having : xo[] // [!code focus] search : xo[] // [!code focus] }} ``` ### `.orderBy` > Source: /docs/cds/cqn#orderby ###### order > Source: /docs/cds/cqn#order ```tsx class SELECT { SELECT: { //... orderBy : order[] // [!code focus] }} ``` ```tsx type order = expr & { sort : 'asc' | 'desc' nulls : 'first' | 'last' } ``` > Using: > [`expr`](#expr) > > Used in: > [`SELECT`](#select) > ## INSERT > Source: /docs/cds/cqn#insert ## UPSERT > Source: /docs/cds/cqn#upsert CQN representations for `INSERT` and `UPSERT` are essentially identical: ```tsx class INSERT { INSERT: UPSERT['UPSERT'] } class UPSERT { UPSERT: { into : ref entries? : data[] columns? : string[] values? : scalar[] rows? : scalar[][] from? : SELECT }} ``` ```tsx interface data { [elm:string]: scalar | data | data[] } ``` > Using: > [`ref`](#ref), > [`expr`](#expr) > [`scalar`](#scalar), > [`SELECT`](#select) > > See also: > [`UPDATE.data`](#data), Data to be inserted can be specified in one of the following ways: * Using [`entries`](#entries) as an array of records with name-value pairs. * Using [`values`](#values) as in SQL's _values_ clauses. * Using [`rows`](#rows) as an array of one or more `values`. The latter two options require a `columns` property to specify names of columns to be filled with the values in the same order. ### `.entries` > Source: /docs/cds/cqn#entries Allows input data to be specified as records with name-value pairs, including _deep_ inserts. ```js let q = {INSERT:{ into: { ref: ['Books'] }, entries: [ { ID:201, title:'Wuthering Heights' }, { ID:271, title:'Catweazle' } ]}} ``` ```js let q = {INSERT:{ into: { ref: ['Authors'] }, entries: [ { ID:150, name:'Edgar Allan Poe', books: [ { ID:251, title:'The Raven' }, { ID:252, title:'Eleonora' } ]} ]}} ``` [See definition in `INSERT` summary](#insert) {.learn-more} ### `.values` > Source: /docs/cds/cqn#values {#scalar} Allows input data to be specified as an single array of values, as in SQL. ```js let q = {INSERT:{ into: { ref: ['Books'] }, columns: [ 'ID', 'title', 'author_id', 'stock' ], values: [ 201, 'Wuthering Heights', 101, 12 ] }} ``` [See definition in `INSERT` summary](#insert) {.learn-more} ### `.rows` > Source: /docs/cds/cqn#rows Allows input data for multiple rows to be specified as arrays of values. ```js let q = {INSERT:{ into: { ref: ['Books'] }, columns: [ 'ID', 'title', 'author_id', 'stock' ], rows: [ [ 201, 'Wuthering Heights', 101, 12 ], [ 252, 'Eleonora', 150, 234 ] ] }} ``` [See definition in `INSERT` summary](#insert) {.learn-more} ## UPDATE > Source: /docs/cds/cqn#update ```tsx class UPDATE { UPDATE: { entity : ref where? : expr data : data with : changes }} ``` > Using: > [`ref`](#ref), > [`expr`](#expr), > [`data`](#data), > [`changes`](#changes) ### `.data` > Source: /docs/cds/cqn#data Data to be updated can be specified in property `data` as records with name-value pairs, same as in [`INSERT.entries`](#entries). ```tsx interface data { [element:name]: scalar | data | data[] } ``` > Using: > [`name`](#name), > [`scalar`](#scalar) ### `.with` > Source: /docs/cds/cqn#with ###### changes > Source: /docs/cds/cqn#changes Property `with` specifies the changes to be applied to the data, very similar to property [`data`](#data) with the difference to also allow [expressions](#expressions) as values. ```tsx interface changes { [element:name]: scalar | expr | changes | changes[] } ``` > Using: > [`name`](#name), > [`expr`](#expr), > [`scalar`](#scalar) ## DELETE > Source: /docs/cds/cqn#delete ```js class DELETE { DELETE: { from : ref where? : expr }} ``` > Using: > [`ref`](#ref), > [`expr`](#expr) ## Expressions > Source: /docs/cds/cqn#expressions ###### expr > Source: /docs/cds/cqn#expr ###### ref > Source: /docs/cds/cqn#ref ###### val > Source: /docs/cds/cqn#val ###### xpr > Source: /docs/cds/cqn#xpr ###### list > Source: /docs/cds/cqn#list ###### func > Source: /docs/cds/cqn#func ###### param > Source: /docs/cds/cqn#param ###### xo > Source: /docs/cds/cqn#xo ###### name > Source: /docs/cds/cqn#name ###### scalar > Source: /docs/cds/cqn#scalar Expressions can be entity or element references, query parameters, literal values, lists of all the former, function calls, sub selects, or compound expressions. ```tsx type expr = ref | val | xpr | list | func | param | SELECT ``` ```tsx type ref = { ref: ( name | { id:name &infix })[] } type val = { val: scalar } type xpr = { xpr: xo[] } type list = { list: expr[] } type func = { func: string, args: expr[] } type param = { ref: [ '?' | number | string ], param: true } ``` ```tsx type xo = expr | keyword | operator type operator = '=' | '==' | '!=' | '<' | '<=' | '>' | '>=' type keyword = 'in' | 'like' | 'and' | 'or' | 'not' type scalar = number | string | boolean | null type name = string ``` >[!note] > CQN by intent does not _understand_ expressions and therefore > keywords and operators are just represented as plain strings in flat > `xo` sequences. This allows us to translate to and from any other query languages, > including support for native SQL features.
## Full `cqn.d.ts` File > Source: /docs/cds/cqn#full-cqndts-file ::: code-group ```tsx [cqn.d.ts] /** * `INSERT` and `UPSERT` queries are represented by the same internal * structures. The `UPSERT` keyword is used to indicate that the * statement should be updated if the targeted data exists. * The `into` property specifies the target entity. * * The data to be inserted or updated can be specified in different ways: * * - in the `entries` property as deeply nested records. * - in the `columns` and `values` properties as in SQL. * - in the `columns` and `rows` properties, with `rows` being array of `values`. * - in the `from` property with a `SELECT` query to provide the data to be inserted. * * The latter is the equivalent of SQL's `INSERT INTO ... SELECT ...` statements. */ export class INSERT { INSERT: UPSERT['UPSERT'] } export class UPSERT { UPSERT: { into : ref entries? : data[] columns? : string[] values? : scalar[] rows? : scalar[][] from? : SELECT }} /** * `UPDATE` queries are used to capture modifications to existing data. * They support a `where` clause to specify the rows to be updated, * and a `with` clause to specify the new values. Alternatively, the * `data` property can be used to specify updates with plain data only. */ export class UPDATE { UPDATE: { entity : ref where? : expr data : data with : changes }} /** * `DELETE` queries are used to remove data from a target datasource. * They support a `where` clause to specify the rows to be deleted. */ export class DELETE { DELETE: { from : ref where? : expr }} /** * `SELECT` queries are used to retrieve data from a target datasource, * and very much resemble SQL's `SELECT` statements, with these noteworthy * additions: * * - The `from` clause supports `{ref}` paths with infix filters. * - The `columns` clause supports deeply nested projections. * - The `count` property requests the total count, similar to OData's `$count`. * - The `one` property indicates that only a single record object shall be * returned instead of an array. * * Also, CDS, and hence CQN, supports minimalistic `SELECT` statements with a `from` * as the only mandatory property, which is equivalent to SQL's `SELECT * from ...`. */ export class SELECT { SELECT: { distinct? : true count? : true one? : true from : source columns? : column[] where? : xo[] having? : xo[] groupBy? : expr[] orderBy? : order[] limit? : { rows: val, offset: val } }} type source = OneOf< ref &as | SELECT | { join : 'inner' | 'left' | 'right' args : [ source, source ] on? : expr }> type column = OneOf< '*' | expr &as &cast | ref &as & OneOf<( { expand?: column[] } | { inline?: column[] } )> &infix > type order = expr & { sort : 'asc' | 'desc' nulls : 'first' | 'last' } interface changes { [elm:string]: OneOf< scalar | expr | changes | changes[] >} interface data { [elm:string]: OneOf< scalar | data | data[] >} interface as { as?: name } interface cast { cast?: {type:name} } interface infix { orderBy? : order[] where? : expr limit? : { rows: val, offset: val } } /** * Expressions can be entity or element references, query parameters, * literal values, lists of all the former, function calls, sub selects, * or compound expressions. */ export type expr = OneOf< ref | val | xpr | list | func | param | SELECT > export type ref = { ref: OneOf< name | { id:name &infix } >[] } export type val = { val: scalar } export type xpr = { xpr: xo[] } export type list = { list: expr[] } export type func = { func: string, args: expr[] } export type param = { ref: [ '?' | number | string ], param: true } /** * This is used in `{xpr}` objects as well as in `SELECT.where` clauses to * represent compound expressions as flat `xo` sequences. * Note that CQN by intent does not _understand_ expressions and therefore * keywords and operators are just represented as plain strings. * This allows us to translate to and from any other query languages, * including support for native SQL features. */ type xo = OneOf< expr | keyword | operator > type operator = '=' | '==' | '!=' | '<' | '<=' | '>' | '>=' type keyword = 'in' | 'like' | 'and' | 'or' | 'not' type scalar = number | string | boolean | null type name = string // --------------------------------------------------------------------------- // maybe coming later... declare class CREATE { CREATE: {} } declare class DROP { DROP: {} } // --------------------------------------------------------------------------- // internal helpers... type OneOf = Partial<(U extends any ? (k:U) => void : never) extends (k: infer I) => void ? I : never> ``` ::: # CDS Expression Language (CXL) > Source: /docs/cds/cxl ## Preliminaries > Source: /docs/cds/cxl#preliminaries The CDS Expression Language (`CXL`) is a language to express calculations, conditions, and other expressions in the context of CDS models and queries. **`CXL` is based on the SQL expression language**, so many syntax elements from SQL are also available in `CXL`. `CXL` can be used in various places: - In [CQL queries](./cql#path-expressions) created at runtime via respective language bindings, such as the [`cds.ql`](../node.js/cds-ql) template tag API in JavaScript, or the fluent API variants. - In [CDL views and projections](./cdl#views), as well as in on-conditions of [unmanaged associations](./cdl#associations), in [calculated elements](./cdl#calculated-elements), and in [annotations](./cdl.md#expressions-as-annotation-values) ::: tip Expressions in CAP are materialized in the context of queries No matter where `CXL` is used, it always manifests in queries. For example, [a calculated element](./cdl#calculated-elements) defined in an entity will be resolved to the respective calculation in the generated query when the entity is queried. ::: ### Live Code > Source: /docs/cds/cxl#live-code The language syntax is described using [syntax diagrams](https://en.wikipedia.org/wiki/Syntax_diagram). Most of the accompanying samples are runnable directly in the browser. Press the play button to see the result and the corresponding sql: ```cds live SELECT from Books { title } ``` You can also edit the query, making this your personal playground. :::info Application Context The cds model initialized on this page is a slightly modified version of the [capire/bookshop](https://github.com/capire/bookshop). All samples run on a single browser-local `cds` instance, you can access it via the dev tools or run statements in the following code block: ```js live await INSERT.into('Books').entries( { ID: 2, author_ID: 150, title: 'Eldorado' } ) ``` ::: ### Trying it with `cds repl` > Source: /docs/cds/cxl#trying-it-with-cds-repl To try the samples by yourself, create a simple CAP app: ```sh cds init bookshop --nodejs --add sample && cd bookshop ``` We encourage you to play around with the snippets. Just create the sample app as described above and start a repl session within the newly created app by running: ```sh cds repl --run . ``` Simply use `cds.ql` to run CXL as part of a CQL query: ```js > await cds.ql`SELECT from Books { title }` // [!code focus] [ { title: 'Wuthering Heights' }, { title: 'Jane Eyre' }, { title: 'The Raven' }, { title: 'Eleonora' }, { title: 'Catweazle' } ] ``` There's also a CQL mode: ```js > .ql // [!code focus] cql> select from Books { title } // [!code focus] [ { title: 'Wuthering Heights' }, { title: 'Jane Eyre' }, { title: 'The Raven' }, { title: 'Eleonora' }, { title: 'Catweazle' } ] ``` ## Expressions (`expr`) > Source: /docs/cds/cxl#expressions-expr ###### expr > Source: /docs/cds/cxl#expr An expression can hold various elements, such as references, literals, function calls, operators, and more. A few examples, in the context of a select list: ```cds select from Books { 42 as answer, // literal title, // element reference price * quantity as totalPrice, // binary operator substring(title, 1, 3) as shortTitle, // function call author.name as authorName, // path expression chapters[number < 3] as earlyChapters, // ref with infix filter exists chapters as hasChapters, // exists count(chapters) as chapterCount, // aggregate function } ``` This syntax diagram describes the possible expressions: ![](assets/cxl/expr.drawio.svg?raw) > Using: > [Path Expressions](#ref), > [Operators](#xpr), > [Literals](#val), > [Functions](#func), > > Used in: > [Calculated Elements](#in-calculated-elements), > [Annotations](#in-annotations), > [Queries](#in-queries), ::: tip An expression can be used in various places, in the following sections we will give a brief overview of _some_ use cases. ::: ### In Calculated Elements > Source: /docs/cds/cxl#in-calculated-elements Expressions can be used to define calculated elements. Typically, this is done on the select list of a query. CAP also allows to define calculated elements directly in the model: ```cds extend Books with { total = price * stock; } ``` In this example, we define a calculated element `total` in the `Books` entity that calculates the total value of all books in stock by multiplying the `price` with the `stock`. ```cds live SELECT title, total from Books ``` [Learn more about calculated elements](./cdl.md#calculated-elements){ .learn-more } ### In Annotations Annotations can [contain expressions](./cdl.md#expressions-as-annotation-values) as their value. The meaning and effect of the expression depend on the specific annotation being used. For example, the [`@assert` annotation](../guides/services/constraints.md#assert-constraint) lets us declaratively define input validation constraints. In this example, we want to make sure that no Books with negative stocks are created: ```cds annotate AdminService.Books:stock with @assert: (case when stock < 0 then 'Enter a positive number' end); ``` Upon insert, the expression is evaluated against the updated data: :::code-group ```js [cds repl] > const { Books } = AdminService.entities > const insert = INSERT.into(Books).entries({ // [!code focus] ID: 277, author_ID: 101, title: 'Lord of the Rings', stock: -2, // [!code focus] }) > await AdminService.run(insert) Uncaught: { status: 400, // [!code focus] code: 'ASSERT', // [!code focus] target: 'stock', // [!code focus] numericSeverity: 4, '@Common.numericSeverity': 4, message: 'Enter a positive number' // [!code focus] } ``` ```sql [sql log] BEGIN -- sql statement for the insert: INSERT INTO sap_capire_bookshop_Books (createdAt,createdBy,modifiedAt,modifiedBy,ID,author_ID,title,descr,genre_ID,stock,price,currency_code) SELECT (CASE WHEN json_type(value,'$."createdAt"') IS NULL THEN ISO(session_context('$now')) ELSE ISO(value->>'$."createdAt"') END),(CASE WHEN json_type(value,'$."createdBy"') IS NULL THEN session_context('$user.id') ELSE value->>'$."createdBy"' END),(CASE WHEN json_type(value,'$."modifiedAt"') IS NULL THEN ISO(session_context('$now')) ELSE ISO(value->>'$."modifiedAt"') END),(CASE WHEN json_type(value,'$."modifiedBy"') IS NULL THEN session_context('$user.id') ELSE value->>'$."modifiedBy"' END),value->>'$."ID"',value->>'$."author_ID"',value->>'$."title"',value->>'$."descr"',value->>'$."genre_ID"',value->>'$."stock"',value->>'$."price"',value->>'$."currency_code"' FROM json_each(?) [ [ [ { ID: 277, author_ID: 101, title: 'Lord of the Rings', stock: -2 } ] ] ] -- assert expressions are evaluated: SELECT json_insert('{}','$."ID"',ID,'$."@assert:stock"',"@assert:stock") as _json_ FROM ( SELECT Books.ID, case when Books.stock < ? then ? end as "@assert:stock" FROM AdminService_Books as Books WHERE (Books.ID) in ((?)) ) [ 0, 'Enter a positive number', 277 ] -- result of evaluation contains violated constraints, -- which leads to a rollback: ROLLBACK ``` ::: ::: tip What-not-how! The `@assert` annotation lets you capture the intent via an expression, without having to deal with the technical details. This conforms to the core principle [what-not-how](../guides/domain/index#capture-intent-—-what-not-how) of CAP. ::: ### In Queries Expressions can be used in various parts of a query, for example,, on the select list, in the where clause, in order by clauses, and more: ```cds live SELECT from Books { title, stock, price, price * stock as total } where price > 10 ``` Compared to the previous example, we now use the expression directly in the query to calculate the total value of all books in stock. ## Path Expressions (`ref`) > Source: /docs/cds/cxl#path-expressions-ref ###### ref > Source: /docs/cds/cxl#ref A `ref` (short for reference) is used to refer to an element within the model. It can be used to navigate along path segments. Such a navigation is often referred to as a **path expression**. ![](assets/cxl/ref.drawio.svg?raw) > Using: > [Infix Filters](#infix-filters) > > Used in: > [Expressions](#expr) Examples: ```zsh element struct.element assoc.element assoc[filter].element assoc[filter].struct.assoc.element ``` ::: info Leaf elements Leaf elements as opposed to associations and structured elements represent scalar values, such as strings, numbers, dates, as well as the array and map types. They typically manifest as columns in database tables. ::: ### Simple Element Reference > Source: /docs/cds/cxl#simple-element-reference The simplest form of a `ref` references a single element: ```cds live SELECT from Books { title } ``` In this example, we select the `title` element from the `Books` entity. ### Path Navigation > Source: /docs/cds/cxl#path-navigation A path expression navigates to elements of an association's target: ```cds live SELECT from Books { title, author.name as author } ``` In this example, we select all books together with the name of their author. The association `author` defined in the `Books` entity relates a book to its author. ::: warning Flattening of to-many associations When navigating along a to-many association to a leaf element, the result is flattened: :::code-group ```js [CQL] > await cds.ql `SELECT from Authors { books.title as title, name as author }` // [!code focus] [ { title: 'Wuthering Heights', author: 'Emily Brontë' }, { title: 'Jane Eyre', author: 'Charlotte Brontë' }, { title: 'Eleonora', author: 'Edgar Allen Poe' }, // [!code focus] { title: 'The Raven', author: 'Edgar Allen Poe' }, // [!code focus] { title: 'Catweazle', author: 'Richard Carpenter' } ] ``` ```sql [SQL] SELECT books.title as title, name as author FROM sap_capire_bookshop_Authors as Authors LEFT JOIN sap_capire_bookshop_Books as books ON books.author_ID = Authors.ID ``` In this example, we select the book titles together with each author. Since books is a to-many association, we get a _joined_ result that repeats every author (name) for every associated book. ::: Use expand to read to-many associations as structured result: ::: code-group ```js [CQL] > await cds.ql`SELECT from Authors { name as author, books { title } }` // [!code focus] [ { author: 'Emily Brontë', books: [ { title: 'Wuthering Heights' } ] }, { author: 'Charlotte Brontë', books: [ { title: 'Jane Eyre' } ] }, { // [!code focus] author: 'Edgar Allen Poe', // [!code focus] books: [ { title: 'The Raven' }, { title: 'Eleonora' } ] // [!code focus] }, // [!code focus] { author: 'Richard Carpenter', books: [ { title: 'Catweazle' } ] } ] ``` ```sql [SQL] SELECT Authors.name as author, ( SELECT jsonb_group_array( jsonb_insert('{}', '$."title"', title, '$."genre"', genre->'$') ) as _json_ FROM ( SELECT books.title, ( SELECT json_insert('{}', '$."name"', name) as _json_ FROM ( SELECT genre.name FROM sap_capire_bookshop_Genres as genre WHERE books.genre_ID = genre.ID LIMIT ? ) ) as genre FROM sap_capire_bookshop_Books as books WHERE Authors.ID = books.author_ID )) as books FROM sap_capire_bookshop_Authors as Authors ``` ::: ::: warning Annotation expressions expect single-valued results When writing annotation expressions, it's often important to ensure that the result yields a single value for each entry in the annotated entity. To achieve this, use the [exists](#in-exists-predicates) predicate. ::: ### In `exists` Predicates > Source: /docs/cds/cxl#in-exists-predicates Path expressions can also be used after the `exists` keyword to check whether the set referenced by the path is empty. This is especially useful for to-many relations. For example, to select all authors that have written **at least** one book: ```cds live SELECT from Authors { name } where exists books ``` [Learn more about the `exists` predicate.](./cql.md#exists-predicate){.learn-more} The `exists` predicate can be further enhanced by [combining it with infix filters](#exists-infix-filter). This allows you to specify conditions on subsets of associated entities, enabling more precise and expressive queries. ## Infix Filters > Source: /docs/cds/cxl#infix-filters An infix in linguistics refers to a letter or group of letters that are added in the middle of a word to make a new word. If we apply this terminology to path expressions, an infix filter condition is an expression that is applied to a path segment of a path expression. This allows you to filter the target of an association based on certain criteria. ![](assets/cxl/infix-filter.drawio.svg?raw) > Using: > [Expressions](#expr) > > Used in: > [Path Expressions](#ref) ### Applied to `exists` Predicate > Source: /docs/cds/cxl#applied-to-exists-predicate In this example, we want to select all authors with books that have a certain stock amount. To achieve this, we can apply an infix filter to the path segment `books` in the exists predicate: ```cds live SELECT from Authors { name } where exists books[stock > 100] ``` Exist predicates with infix filters can also be nested. Here we select all authors that have written at least one book in the `Fantasy` genre: ```cds live SELECT from Authors { name } where exists books[exists genre[name = 'Fantasy']] ``` ### Applied to `from` Clause > Source: /docs/cds/cxl#applied-to-from-clause Infix filters can also be applied to [path expressions in the `from` clause](./cql#path-expressions-in-from-clauses). For example, we want to get the author names of books with a price greater than 19.99. Intuitively, we can formulate a query using a condition in the `where` clause: ```cds live SELECT from Books { author.name as name } where price > 19.99 ``` But we can also move this condition to an infix filter: ```cds live SELECT from Books[price > 19.99] { author.name as name } ``` Now we can further use path navigation to navigate from the filtered books to their authors: ```cds live SELECT from Books[price > 19.99]:author { name } ``` ::: info Note that the generated SQL is equivalent to querying authors with an [exists predicate](#exists-infix-filter): ```cds live SELECT from Authors { name } where exists books[price > 19.99] ``` ::: ### In Calculated Elements > Source: /docs/cds/cxl#in-calculated-elements-1 You can also use the infix filter notation to derive another more specific association from an existing one. In the `Authors` entity in the `Books.cds` file add a new element `cheapBooks`: ```cds entity Authors { books : Association to many Books on books.author = $self; cheapBooks = books[price < 19.99]; // based on `books` association } ``` Now we can use `cheapBooks` just like any other association. For example, to select the set of authors which have no cheap books: ```cds live SELECT from Authors { name } where not exists cheapBooks ``` [Learn more about association-like calculated elements.](./cdl.md#association-like-calculated-elements){ .learn-more } We can also use `cheapBooks` in nested expands to get all cheap books of each author: ```cds live SELECT from Authors { name, cheapBooks { title, price } } ``` ### Between Path Segments > Source: /docs/cds/cxl#between-path-segments Assuming you have the [calculated element](#in-calculated-elements) `age` in place on the Authors entity: ```cds extend Authors with { age = years_between(dateOfBirth, coalesce(dateOfDeath, date( $now ))); } ``` In this case we want to select all books but the author is only included in the result if their age is below 40: ```cds live SELECT from Books { title, author[age < 40].name as author } ``` The path expression `author[ age < 40 ].name` navigates along the `author` association of the `Books` entity only if the author's age is below 40. ## Operators (`xpr`) > Source: /docs/cds/cxl#operators-xpr ###### xpr > Source: /docs/cds/cxl#xpr As depicted in below excerpt of the syntax diagram for `expr`, CXL supports all the standard SQL operators as well as a few additional ones, such as the `?` operator to check for the existence of a path. ![](assets/cxl/operators.drawio.svg?raw) > Using: > [Expressions](#expr) > > Used in: > [Expressions](#expr) Following table gives an overview of the guaranteed supported operators in CXL: | Operator | Description | Example | | -------- | ----------- | ------- | | `\|\|` | String concatenation. | `'Hello ' \|\| world` | | `*`, `/`, `%` | Multiplication, division, and modulo. | `price * quantity` | | `+`, `-` | Addition and subtraction. | `price + tax` | | `<`, `>`, `<=`, `>=` | Comparison. | `price < 100` | | `=`, `==`, `!=`, `<>` | Equality. | `price == 100` | | `is null`, `is not null` | Null checks (postfix). | `price is null` | | `like`, `not like` | Pattern matching. | `name like 'A%'` | | `between`-`and` | Range checking. | `x between 1 and 10` | | `case`-`when`-`then` | Case checking. | `case when 1 then 2 end` | | `exists`, `not exists` | Existence checking (prefix). | `name like 'A%'` | | `and`, `or` | Logical operators. | `x>1 or y<2` | > [!tip] Bivalent `==` and `!=` Operators > In addition to standard SQL's `=` and `<>` operators, CXL also supports `==` and `!=` as bivalent variants as opposed to the trivalent semantics of `=` and `<>` when it comes to null handling. Learn more about this in the [_Bivalent `==` and `!=` Operators_](../guides/databases/cap-level-dbs#bivalent--and--operators) section of the databases documentation. > [!tip] Ternary `?:` Operator > In addition to the standard SQL `case when then` expression, CXL also supports the ternary `?:` operator as a more concise syntax for simple case expressions. Learn more about this in the [_Ternary `?:` Operator_](../guides/databases/cap-level-dbs#ternary--operator) section of the databases documentation. ## Functions (`func`) > Source: /docs/cds/cxl#functions-func ###### func > Source: /docs/cds/cxl#func ![](assets/cxl/function.drawio.svg?raw) > Using: > [Expressions](#expr) > > Used in: > [Expressions](#expr) CAP supports a set of [portable functions](../guides/databases/cap-level-dbs#portable-functions) that can be used in all expressions. The CAP compiler, and the CAP runtimes, automatically translate these functions to database-specific native equivalents, allowing you to use the same functions across different databases, which greatly enhances portability. ## Literals (`val`) > Source: /docs/cds/cxl#literals-val ###### val > Source: /docs/cds/cxl#val Literal values represent constant data embedded directly in an expression. They are independent of model elements and evaluate to the same value. :::code-group ```js [cds repl] > cds.parse.expr ` 42 ` { val: 42 } > cds.parse.expr ` 'Hello World' ` { val: 'Hello World' } > cds.parse.expr ` null ` { val: null } > cds.parse.expr ` true ` { val: true } > cds.parse.expr ` false ` { val: false } > cds.parse.expr ` Date'2026-01-01' ` { val: '2026-01-01', literal: 'date' } > cds.parse.expr ` Time'08:42:15.000' ` { val: '08:42:15.000', literal: 'time' } > cds.parse.expr ` TimeStamp'2026-01-14T10:30:00Z' ` { val: '2026-01-14T10:30:00Z', literal: 'timestamp' } ``` ::: > Using: > [Expressions](#expr) [Learn more about literals.](./csn.md#literals){ .learn-more } # Expression Notation (CXN) > Source: /docs/cds/cxn Expressions in CDS definitions and queries can be one of: ```js expr = // one of... val | // [literal values]: #literal-values ref | // references or functions xpr | // operator expressions func | // function calls list | // lists/tupels param | // binding parameters sym | // enum symbol SELECT // subqueries ``` ## Literal Values > Source: /docs/cds/cxn#literal-values Literal values are represented as `{val:...}` with property `val` holding the actual literal value as specified in JSON. ```js val = {val:literal} literal = string | number | true | false | null ``` Examples: ```js cds.parse.expr(`'a string'`) == {val:'a string'} cds.parse.expr(`11`) == {val:11} cds.parse.expr(`true`) == {val:true} cds.parse.expr(`null`) == {val:null} cds.parse.expr(`date'2023-04-15'`) == {val: '2023-04-15', literal: 'date'} cds.parse.expr(`time'13:05:23Z'`) == {val: '13:05:23Z', literal: 'time'} cds.parse.expr(`timestamp'2023-04-15T13:05:23Z'`) == {val: '2023-04-15T13:05:23Z', literal: 'timestamp'} ``` ## References > Source: /docs/cds/cxn#references A reference is represented as `{ ref: … }` with property `ref`. This property holds an array of reference segments as plain identifier strings. Only in case of infix filters and/or arguments, the property holds an object `{ id: 'identifier', … }` and all properties except `id` are optional, as shown in the following snippet: ```js ref = {ref:[..._segment]} _segment = string | { id: string, args: _named, where: _xpr, groupBy: [ ...expr ], having: _xpr, orderBy: [ ...ordering_term ], limit: { rows: expr, offset: expr } } _named = { ... :expr } ``` Examples: ```js let cqn4 = cds.parse.expr cqn4(`![keyword]`) == {ref:['keyword']} cqn4(`foo.bar`) == {ref:['foo','bar']} cqn4(`foo[9].bar`) == {ref:[{ id:'foo', where:[{val:9}] }, 'bar' ]} cqn4(`foo(p:x).bar`) == {ref:[{ id:'foo', args:{p:{ref:['x']}} }, 'bar' ]} cqn4(`foo[where a=1 group by b having b>2 order by c limit 7].bar`) == {ref:[{ id:'foo', where:[{ref:['a']}, '=', {val:9}], groupBy: [{ref: ['b']}], having: [{ref: ['b']}, '>', {val:2}], orderBy: [{ref: ['c']}], limit: {rows: {val: 7}} }, 'bar' ]} ``` ## Function Calls > Source: /docs/cds/cxn#function-calls Function calls are represented as follows: ```js func = { func:string, args: _positional | _named, xpr:_xpr } _positional = [ ...expr ] _named = { ... :expr } ``` The optional attribute `xpr` is used for the `over` clause of SQL window functions. Examples: ```js let cqn4 = cds.parse.expr cqn4(`foo(p=>x)`) == {func:'foo', args:{p:{ref:['x']}}} cqn4(`sum(x)`) == {func:'sum', args:[{ref:['x']}]} cqn4(`count(*)`) == {func:'count', args:['*']} cqn4(`rank() over (...)`) == {func:'rank', args:[], xpr:['over', {xpr:[...]}]} ``` Method style function calls and instantiation syntax for spatial functions are represented as `{xpr:...}` with `.` and `new` as operators: ```js cqn4(`shape.ST_Area()`) == {xpr: [{ref: ['shape']}, '.', {func: 'ST_Area', 'args': []}]} cqn4(`new ST_Point(2, 3)`) == {xpr: ['new', {func: 'ST_Point', args: [{val: 2}, {val: 3}]}]} ``` ## Lists > Source: /docs/cds/cxn#lists Lists or tupels are represented as `{list:...}`, with property `list` holding an array of the list entries. Examples: ```js cds.parse.expr(`(1, 2, 3)`) == {list: [{val: 1}, {val: 2}, {val: 3}]} cds.parse.expr(`(foo, bar)`) == {list: [{ref: ['foo']}, {ref: ['bar']}]} ``` ## Operator Expressions > Source: /docs/cds/cxn#operator-expressions Operators join one or more expressions into complex ones, represented as `{xpr:...}`. The property `xpr` holds a sequence of operators and operands. ```js xpr = {xpr:_xpr} _xpr = [...( _operand | _operator )] _operand = expr _operator = string ``` * *Operands* can be any kind of expression * *Operators* are represented as plain strings, like `'='` or `'and'` * Parentheses `( ... )` around sub-expressions are represented as nested `xpr` Examples: ```js [dev] cds repl > cds.parse.expr(`x<9`) == {xpr:[ {ref:['x']}, '<', {val:9} ]} > cds.parse.expr(`x<9 and (y=1 or z=2)`) == {xpr:[ {ref:['x']}, '<', {val:9}, 'and', {xpr:[ {ref:['y']}, '=', {val:1}, 'or', {ref:['z']}, '=', {val:2} ]} ]} > cds.parse.expr(`exists books[year = 2000]`) == {xpr:[ 'exists', {ref: [ {id:'books', where:[ {'ref':['year']}, '=', {'val': 2000} ]}]} ]} ``` CQN intentionally doesn't aim to _understand_ the individual operators and related expressions. It captures them as arbitrary sequences, in the same lexical structure and order they're written in the source. This 'ignorance' allows us to stay open to any kind of operators and keywords. For example, we can easily express native extensions of underlying database dialects. As an exception to that rule, CDS supports the ternary conditional operator on source level, but immediately converts it to the corresponding CASE expression in CXN: ```js [dev] cds repl > cds.parse.expr(`x<10 ? y : z`) == {xpr:['case', 'when', {ref:['x']}, '<', {val:10}, 'then', {ref:['y']}, 'else', {ref:['z']}, 'end']} ``` ## Binding Parameters > Source: /docs/cds/cxn#binding-parameters Binding parameters for prepared statements are represented as `{ref:..., param:true}` with values for `ref` as follows. ```js param = { ref:[ '?' | number | name ], param:true } ``` Examples: ```js [dev] cds repl > cds.parse.expr(`x=:1`) //> [{ref:['x']}, '=', {ref:[1], param:true}] > cds.parse.expr(`x=:y`) //> [{ref:['x']}, '=', {ref:['y'], param:true}] > cds.parse.expr(`x=?`) //> [{ref:['x']}, '=', {ref:['?'], param:true}] ``` ## Sub Queries > Source: /docs/cds/cxn#sub-queries [See CQN](cqn){.learn-more} # Core / Built-in Types > Source: /docs/cds/types The following table lists the built-in types in CDS, and their most common mapping to ANSI SQL types, when deployed to a relational database (concrete mappings to specific databases may differ): | CDS Type | Remarks | ANSI SQL | |------------------------|------------------------------------------------------------------------|----------------| | `UUID` | [RFC 4122](https://tools.ietf.org/html/rfc4122)-compliant UUIDs | _NVARCHAR(36)_ | | `Boolean` | Values: `true`, `false`, `null`, `0`, `1` | _BOOLEAN_ | | `Integer` | Same as `Int32` by default | _INTEGER_ | | `Int16` | Signed 16-bit integer, range *[ -215 ... +215 )* | _SMALLINT_ | | `Int32` | Signed 32-bit integer, range *[ -231 ... +231 )* | _INTEGER_ | | `Int64` | Signed 64-bit integer, range *[ -263 ... +263 )* | _BIGINT_ | | `UInt8` | Unsigned 8-bit integer, range *[ 0 ... 255 ]* | _TINYINT_ | | `Decimal`(`p`,`s`) | Decimal with precision `p` and scale `s` | _DECIMAL_ | | `Double` | Floating point with binary mantissa | _DOUBLE_ | | `Date` | for example, `2022-12-31` | _DATE_ | | `Time` | for example, `23:59:59` | _TIME_ | | `DateTime` | _sec_ precision | _TIMESTAMP_ | | `Timestamp` | _µs_ precision, with up to 7 fractional digits | _TIMESTAMP_ | | `String` (`length`) | Default *length*: 255; on HANA: 5000 | _NVARCHAR_ | | `Binary` (`length`) | Default *length*: 255; on HANA: 5000 | _VARBINARY_ | | `Vector` (`dimension`) | for Vector Embeddings [-> see notes below](#vector-embeddings) | ( _DB-specific_ ) | | `LargeBinary` | Unlimited binary data, usually streamed at runtime | _BLOB_ | | `LargeString` | Unlimited textual data, usually streamed at runtime | _NCLOB_ | | `Map` | Mapped to *NCLOB* for HANA. | *JSON* type | > [!info] Default String Lengths > Lengths can be omitted, in which case default lengths are used. While this is usual in initial phases of a project, productive apps should always use explicitly defined length. The respective default lengths are configurable through the config options > cds.cdsc.defaultStringLength = 255 and
> cds.cdsc.defaultBinaryLength = 255 . ###### Vector Embeddings > Source: /docs/cds/types#vector-embeddings > [!info] Vector Embeddings > The `Vector` type is used for vector embeddings, which are a way to represent data (like text, images, etc.) as high-dimensional vectors. Requires SAP HANA Cloud QRC 1/2024, or later, [`@sap/cds` v9.9+](/releases/2026/apr26), and [CAP Java v4.9+](/releases/2026/apr26) to use with H2 or SQLite. > [!tip] Use Attachments instead of LargeBinary > Consider using _Attachments_, as provided through [the CAP Attachments plugins](../plugins/index#attachments), instead of `LargeBinary` types for user-generated content like documents, images, etc. See also: [Additional Reuse Types and Aspects by `@sap/cds/common`](common) {.learn-more} [Mapping to OData EDM types](../guides/protocols/odata#type-mapping) {.learn-more} [HANA-native Data Types](../guides/databases/hana-native#hana-types){.learn-more} # Common Types and Aspects > Source: /docs/cds/common _@sap/cds/common_ {.subtitle}
CDS ships with a prebuilt model *`@sap/cds/common`* that provides common types and aspects for reuse. [ISO 3166]: https://en.wikipedia.org/wiki/ISO_3166 [ISO 3166-1]: https://en.wikipedia.org/wiki/ISO_3166-1 [ISO 3166-2]: https://en.wikipedia.org/wiki/ISO_3166-2 [ISO 3166-3]: https://en.wikipedia.org/wiki/ISO_3166-3 [ISO 4217]: https://en.wikipedia.org/wiki/ISO_4217 [ISO/IEC 15897]: https://en.wikipedia.org/wiki/ISO/IEC_15897 [tzdata]: https://en.wikipedia.org/wiki/Tz_database [localized data]: ../guides/uis/localized-data [temporal data]: ../guides/domain/temporal-data ## Why Use _@sap/cds/common_? > Source: /docs/cds/common#why-use-sapcdscommon It's recommended that all applications use the common types and aspects provided through _@sap/cds/common_ to benefit from these features: * **Concise** and **comprehensible** models → see also [Conceptual Modeling](../guides/domain/index) * **Foster interoperability** between all applications * **Proven best practices** captured from real applications * **Streamlined** data models with **minimal entry barriers** * **Optimized** implementations and runtime performance * **Automatic** support for [localized](../guides/uis/localized-data) code lists and [value helps](../guides/uis/fiori#simple-value-helps) * **Extensibility** using [Aspects](../guides/domain/index#aspect-oriented-modeling) * **Verticalization** through third-party extension packages For example, usage is as simple as indicated in the following sample: ```cds using { Country } from '@sap/cds/common'; entity Addresses { street : String; town : String; country : Country; //> using reuse type } ``` ### Outcome = Optimized Best Practice > Source: /docs/cds/common#outcome--optimized-best-practice The final outcomes in terms of modeling patterns, persistence structures, and implementations is essentially the same as with native means, if you would have collected design experiences from prior solutions, such as we did. ::: tip All the common reuse features of _@sap/cds/common_ are provided only through this ~100 line .cds model. Additional runtime support isn't required. _@sap/cds/common_ merely uses basic CDS modeling features as well as generic features like [localized data] and [temporal data] (which only need minimal runtime support with minimal overhead). ::: In effect, the results are **straightforward**, capturing **best practices** we learned from real business applications, with **minimal footprint**, **optimized performance**, and **maximized adaptability** and **extensibility**. ## Common Reuse Aspects > Source: /docs/cds/common#common-reuse-aspects _@sap/cds/common_ defines the following [aspects](cdl#aspects) for use in your entity definitions. They give you shortcuts, for concise and comprehensible models, interoperability and out-of-the-box runtime features connected to them. ### Aspect `cuid` > Source: /docs/cds/common#aspect-cuid Use `cuid` as a convenient shortcut, to add canonical, universally unique primary keys to your entity definitions. These examples are equivalent: ```cds entity Foo : cuid {...} ``` ```cds entity Foo { key ID : UUID; [...] } ``` > The service provider runtimes automatically fill in UUID-typed keys like these with auto-generated UUIDs. [Learn more about **canonical keys** and **UUIDs**.](../guides/domain/index#prefer-canonic-keys){ .learn-more} ### Aspect `managed` > Source: /docs/cds/common#aspect-managed Use `managed`, to add four elements to capture _created by/at_ and latest _modified by/at_ management information for records. The following examples are equivalent- ```cds entity Foo : managed {...} ``` ```cds entity Foo { createdAt : Timestamp @cds.on.insert : $now; createdBy : User @cds.on.insert : $user; modifiedAt : Timestamp @cds.on.insert : $now @cds.on.update : $now; modifiedBy : User @cds.on.insert : $user @cds.on.update : $user; [...] } ``` ::: tip `modifiedAt` and `modifiedBy` are set whenever the respective row was modified, that means, also during `CREATE` operations. ::: The annotations `@cds.on.insert/update` are handled in generic service providers so to fill in those fields automatically. [Learn more about **generic service features**.](../guides/domain/index#managed-data){ .learn-more} ### Aspect `temporal` > Source: /docs/cds/common#aspect-temporal This aspect basically adds two canonical elements, `validFrom` and `validTo` to an entity. It also adds a tag annotation that connects the CDS compiler's and runtime's built-in support for _[Temporal Data](../guides/domain/temporal-data)_. This built-in support covers handling date-effective records and time slices, including time travel. All you've to do is, add the temporal aspect to respective entities as follows: ```cds entity Contract : temporal {...} ``` [Learn more about **temporal data**.][temporal data]{ .learn-more} ## Common Reuse Types > Source: /docs/cds/common#common-reuse-types _@sap/cds/common_ provides predefined easy-to-use types for _Countries_, _Currencies_, and _Languages_. Use these types in all applications to foster interoperability. ### Type `Country` > Source: /docs/cds/common#type-country [`Country`]: #country The reuse type `Country` is defined in _@sap/cds/common_ as a simple managed [Association](cdl#associations) to the [code list](#code-lists) for [countries](#entity-countries) as follows: ```cds type Country : Association to sap.common.Countries; ``` Here's an example of how you would use that reuse type: ```cds using { Country } from '@sap/cds/common'; entity Addresses { street : String; town : String; country : Country; //> using reuse type } ``` The [code lists](#code-lists) define a key element `code`, which results in a foreign key column `country_code` in your SQL table for Addresses. For example: ```sql CREATE TABLE Addresses ( street NVARCHAR(5000), town NVARCHAR(5000), country_code NVARCHAR(3) -- foreign key ); ``` [Learn more about **managed associations**.](cdl#associations){ .learn-more} ### Type `Currency` > Source: /docs/cds/common#type-currency The type for an association to [Currencies](#entity-currencies). ```cds type Currency : Association to sap.common.Currencies; ``` [It's the same as for `Country`.](#type-country){ .learn-more} ### Type `Language` > Source: /docs/cds/common#type-language The type for an association to [Languages](#entity-languages). ```cds type Language : Association to sap.common.Languages; ``` [It's the same as for `Country`.](#type-country){ .learn-more} ### Type `Timezone` > Source: /docs/cds/common#type-timezone The type for an association to [Timezones](#entity-timezones). ```cds type Timezone : Association to sap.common.Timezones; ``` [It's the same as for `Country`.](#type-country){ .learn-more} ## Common Code Lists > Source: /docs/cds/common#common-code-lists As seen in the previous section, the reuse types `Country`, `Currency`, and `Language` are defined as associations to respective code list entities. They act as code list tables for respective elements in your domain model. > Note: You rarely have to refer to the code lists in consuming models, but always only do so transitively by using the corresponding reuse types [as shown previously](#code-types). #### Namespace: `sap.common` > Source: /docs/cds/common#namespace-sapcommon The following definitions are within namespace `sap.common`... ### Aspect `CodeList` > Source: /docs/cds/common#aspect-codelist This is the base definition for the code list entities in _@sap/cds/common_. It can also be used for your own code lists. ```cds aspect sap.common.CodeList { name : localized String(111); descr : localized String(1111); } ``` [Learn more about **localized** keyword.](../guides/uis/localized-data){ .learn-more} ### Entity `Countries` > Source: /docs/cds/common#entity-countries The code list entity for countries is meant to be used with **[ISO 3166-1] two-letter alpha codes** as primary keys. For example, `'GB'` for the United Kingdom. Nevertheless, it's defined as `String(3)` to allow you to fill in three-letter codes, if needed. ```cds entity sap.common.Countries : CodeList { key code : String(3); //> ISO 3166-1 alpha-2 codes (or alpha-3) } ``` ### Entity `Currencies` > Source: /docs/cds/common#entity-currencies The code list entity for currencies is meant to be used with **[ISO 4217] three-letter alpha codes** as primary keys, for example, `'USD'` for US Dollar. In addition, it provides an element to hold the minor unit fractions and for common currency symbols. ```cds entity sap.common.Currencies : CodeList { key code : String(3); //> ISO 4217 alpha-3 codes symbol : String(5); //> for example, $, €, £, ₪, ... minorUnit : Int16; //> for example, 0 or 2 } ``` ### Entity `Languages` > Source: /docs/cds/common#entity-languages The code list entity for countries is meant to be used with POSIX locales as defined in **[ISO/IEC 15897]** as primary keys. For example, `'en_GB'` for British English. ```cds entity sap.common.Languages : CodeList { key code : sap.common.Locale; //> for example, en_GB } ``` [Learn more on **normalized locales**.](../guides/uis/i18n#normalized-locales){ .learn-more} ### Entity `Timezones` > Source: /docs/cds/common#entity-timezones The code list entity for time zones is meant to be used with primary keys like _Area/Location_, as defined in the [IANA time zone database][tzdata]. Examples are `America/Argentina/Buenos_Aires`, `Europe/Berlin`, or `Etc/UTC`. ```cds entity sap.common.Timezones : CodeList { key code : String(100); //> for example, Europe/Berlin } ``` [Learn more about time zones in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) {.learn-more} [Learn more about time zones in Java](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/time/ZoneId.html) {.learn-more} ### SQL Persistence > Source: /docs/cds/common#sql-persistence The following table definition represents the resulting SQL persistence of the `Countries` code list, with the ones for `Currencies` and `Languages` alike: ```sql -- the basic code list table CREATE TABLE sap_common_Countries ( name NVARCHAR(255), descr NVARCHAR(1000), code NVARCHAR(3), PRIMARY KEY(code) ); ``` ### Minimalistic Design > Source: /docs/cds/common#minimalistic-design The models for code lists are intentionally minimalistic to keep the entry barriers as low as possible, focusing on the bare minimum of what all applications generally need: a unique code and localizable fields for name and full name or descriptions. **ISO alpha codes** for languages, countries, and currencies were chosen because they: 1. Are most common (most projects would choose that) 2. Are most efficient (as these codes are also frequently displayed on UIs) 3. Guarantee minimal entry barriers (bringing about 1 above) 4. Guarantee best support (for example, by readable foreign keys) Assumption is that ~80% of all apps don't need more than what is already covered in this minimalistic model. Yet, in case you need more, you can easily leverage CDS standard features to adapt and extend these base models to your needs as demonstrated in the section [Adapting to your needs](#adapting-to-your-needs). ## Aspects for Localized Data > Source: /docs/cds/common#aspects-for-localized-data Following are types and aspects mostly used behind the scenes for [localized data](../guides/uis/localized-data).
For example given this entity definition: ```cds entity Foo { key ID : UUID; name : localized String; descr : localized String; } ``` When unfolding the `localized` fields, we essentially add `.texts` entities in these steps: 1. Add a new entity `Foo.texts` which inherits from `TextsAspects`: ```cds entity Foo.texts : sap.common.TextsAspects { ... } ``` Which in turn unfolds to: ```cds entity Foo.texts { key locale : sap.common.Locale; } ``` 2. Add the primary key of the main entity `Foo`: ```cds entity Foo.texts { key locale : sap.common.Locale; key ID : UUID; // [!code focus] } ``` 3. Add the localized fields: ```cds entity Foo.texts { key locale : sap.common.Locale; key ID : UUID; name : String; // [!code focus] descr : String; // [!code focus] } ``` #### Namespace: `sap.common` > Source: /docs/cds/common#namespace-sapcommon-1 The following definitions are with namespace `sap.common`... ### Aspect `TextsAspect` > Source: /docs/cds/common#aspect-textsaspect This aspect is used when generating `.texts` entities for the unfolding of localized elements. It can be extended, which effectively extends all generated `.texts` entities. ```cds aspect sap.common.TextsAspect { key locale: sap.common.Locale; } ``` [Learn more about **Extending .texts entities**.](../guides/uis/localized-data#extending-texts-entities){ .learn-more} ### Type `Locale` > Source: /docs/cds/common#type-locale ```cds type sap.common.Locale : String(14) @title: '{i18n>LanguageCode}'; ``` The reuse type `sap.common.Locale` is used when generating `.texts` entities for the unfolding of *localized* elements. [Learn more about **localized data**.](../guides/uis/localized-data){ .learn-more} ### SQL Persistence > Source: /docs/cds/common#sql-persistence-1 In addition, the base entity these additional tables and views are generated behind the scenes to efficiently deal with translations: ```sql -- _texts table for translations CREATE TABLE Foo_texts ( ID NVARCHAR(36), locale NVARCHAR(14), name NVARCHAR(255), descr NVARCHAR(1000), PRIMARY KEY(ID, locale) ); ``` ```sql -- view to easily read localized texts with automatic fallback CREATE VIEW localized_Foo AS SELECT code, COALESCE (localized.name, name) AS name, COALESCE (localized.descr, descr) AS descr FROM Foo ( LEFT JOIN Foo_texts AS localized ON localized.code= code AND localized.locale = SESSION_CONTEXT('locale') ) ``` [Learn more about **localized data**.](../guides/uis/localized-data){ .learn-more} ## Providing Initial Data > Source: /docs/cds/common#providing-initial-data You can provide initial data for the code lists by placing CSV files in a folder called `data` next to your data models. The following is an example of a `csv` file to provide data for countries: ::: code-group ```csv [db/data/sap.common-Countries.csv] code;name;descr AU;Australia;Commonwealth of Australia CA;Canada;Canada CN;China;People's Republic of China (PRC) FR;France;French Republic DE;Germany;Federal Republic of Germany IN;India;Republic of India IL;Israel;State of Israel MM;Myanmar;Republic of the Union of Myanmar GB;United Kingdom;United Kingdom of Great Britain and Northern Ireland US;United States;United States of America (USA) EU;European Union;European Union ``` ::: [Learn more about the database aspects of **Providing Initial Data**.](../guides/databases/initial-data){ .learn-more} ### Add Translated Texts > Source: /docs/cds/common#add-translated-texts In addition, you can provide translations for the `sap.common.Countries_texts` table as follows: ::: code-group ```csv [db/data/sap.common-Countries_texts.csv] code;locale;name;descr AU;de;Australien;Commonwealth Australien CA;de;Kanada;Canada CN;de;China;Volksrepublik China FR;de;Frankreich;Republik Frankreich DE;de;Deutschland;Bundesrepublik Deutschland IN;de;Indien;Republik Indien IL;de;Israel;Staat Israel MM;de;Myanmar;Republik der Union Myanmar GB;de;Vereinigtes Königreich;Vereinigtes Königreich Großbritannien und Nordirland US;de;Vereinigte Staaten;Vereinigte Staaten von Amerika EU;de;Europäische Union;Europäische Union ``` ::: [Learn more about **Localization/i18n**.](../guides/uis/localized-data){ .learn-more} ### Using Tools like Excel > Source: /docs/cds/common#using-tools-like-excel You can use Excel or similar tools to maintain these files. For example, the following screenshot shows how we maintained the above two files in Numbers on a Mac: ![This screenshot is explained in the accompanying text.](./assets/csv-numbers.png) ### Using Prebuilt Content Package > Source: /docs/cds/common#using-prebuilt-content-package Package [@sap/cds-common-content](https://www.npmjs.com/package/@sap/cds-common-content) provides prebuilt data for the entities `Countries`, `Currencies`, `Languages`, and `Timezones`. Add it your project: ```sh npm add @sap/cds-common-content --save ``` Use it in your `cds` files: ```cds using from '@sap/cds-common-content'; ``` [Learn more about integrating reuse packages](../guides/integration/reuse-and-compose){.learn-more} ## Adapting to Your Needs > Source: /docs/cds/common#adapting-to-your-needs As stated, the predefined definitions are minimalistic by intent. Yet, as _@sap/cds/common_ is also just a CDS model, you can apply all the standard features provided by [CDS](./cdl), especially CDS' [Aspects](./cdl#aspects) to adapt, and extend these definitions to your needs. Let's look at a few examples of what could be done. You can combine these extensions in an effective model. ::: tip You can do such extensions in the models of your project. You can also collect your extensions into reuse packages and share them as common definitions with several consuming projects, similar to _@sap/cds/common_ itself. ::: [Learn more about providing reuse packages.](../guides/integration/reuse-and-compose){ .learn-more} ### Adding Detailed Fields as of [ISO 3166-1] > Source: /docs/cds/common#adding-detailed-fields-as-of-iso-3166-1 ```cds 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 independent : Boolean; status : String(111); statusRemark : String(1111); remarkPart3 : String(1111); } ``` > Value lists in SAP Fiori automatically search in the new text fields as well. ### Protecting Certain Entries > Source: /docs/cds/common#protecting-certain-entries Some application logic might have to be hard-coded against certain entries in code lists. Therefore, these entries have to be protected against changes and removal. For example, let's assume a code list for payment methods defined as follows: ```cds entity PaymentMethods : sap.common.CodeList { code : String(11); } ``` Let's further assume the entires with code `Main` and `Travel` are required by implementations and hence must not be changed or removed. Have a look at a couple of solutions.
#### Programmatic Solution > Source: /docs/cds/common#programmatic-solution A fallback, and at the same time, the most open, and most flexible approach, is to use a custom handler to assert that. For example, in Node.js: ```js srv.on ('DELETE', 'PaymentMethods', req=>{ const entry = req.query.DELETE.where[2].val if (['Main','Travel'].includes(entry)) return req.reject(403, 'these entries must not be deleted') }) ``` ### Using Different Foreign Keys > Source: /docs/cds/common#using-different-foreign-keys Let's assume you prefer to have references to the latest code list entries without adjusting foreign keys. This can be achieved by adding and using numeric ISO codes for foreign keys instead of the alpha codes. ::: code-group ```cds [your-common.2.cds] namespace your.common; using { sap.common.Countries } from '@sap/cds/common'; // Extend Countries code list with fields for numeric codes extend Countries { numcode : Integer; //> ISO 3166-1 three-digit numeric codes } // Define an own Country type using numcodes for foreign keys type Country : Association to Countries { numcode }; ``` ::: You can use your own definition of `Country` instead of the one from _@sap/cds/common_ in your models as follows: ```cds using { your.common.Country } from './your-common.2'; entity Addresses { //... country : Country; } ``` ### Mapping to SAP S/4HANA or ABAP Table Signatures > Source: /docs/cds/common#mapping-to-sap-s4hana-or-abap-table-signatures ```cds using { sap.common.Countries } from '@sap/cds/common'; entity Countries4GFN as projection on Countries { code as CountryCodeAlpha2, name as CountryShortName, // ... } entity Countries4ABAP as projection on Countries { code as LAND, // ... } ``` These views are updatable on SAP HANA and many other databases. You can also use CDS to expose them through corresponding OData services in order to ease integration with SAP S/4HANA or older ABAP backends. ## Adding Own Code Lists > Source: /docs/cds/common#adding-own-code-lists As another example of adaptations, let's add support for subdivisions, that means regions, as of [ISO 3166-2] to countries. ### Defining a New Code List Entity > Source: /docs/cds/common#defining-a-new-code-list-entity ::: code-group ```cds [your-common.4.1.cds] using sap from '@sap/cds/common'; // new code list for regions entity Regions : sap.common.CodeList { key code : String(5); // ISO 3166-2 alpha5 codes, like DE-BW country : Association to sap.common.Countries; } // bi-directionally associate Regions with Countries extend sap.common.Countries { regions : Composition of many Regions on regions.country = $self; } ``` ::: `Regions` is a new, custom-defined code list entity defined in the same way as the predefined ones in _@sap/cds/common_. In particular, it inherits all elements and annotations from the base definition [`sap.common.CodeList`](#code-lists). For example, the `@cds.autoexpose` annotation, which provides that `Regions` is auto-exposed in any OData service that has exposed entities with associations to it. The localization of the predefined elements `name` and `descr` is also inherited. [Learn in our sample how an own code list can be used to localize `enum` values.](https://github.com/SAP-samples/cap-sflight/blob/236de55b58fd0620dcd1d4f043779a7c632391b1/db/schema.cds#L60){.learn-more} ### Defining a New Reuse Type > Source: /docs/cds/common#defining-a-new-reuse-type Following the pattern for codes in _@sap/cds/common_ a bit more, you can also define a reuse type for regions as a managed association: ::: code-group ```cds [your-common.4.2.cds] using { Regions } from './your-common.4.1'; /*>skip<*/ // Define an own reuse type referring to Regions type Region : Association to Regions; ``` ::: ### Using the New Reuse Type and Code List > Source: /docs/cds/common#using-the-new-reuse-type-and-code-list This finally allows you to add respective elements, the same way you do it with predefined reuse types. These elements receive the same support from built-in generic features. For example: ```cds using { Country, Region } from './your-common.4.2'; entity Addresses { street : String; town : String; country : Country; //> pre-defined reuse type region : Region; //> your custom reuse type } ``` ## Code Lists with Validity > Source: /docs/cds/common#code-lists-with-validity Even ISO codes may change over time and you may have to react to that in your applications. For example, when Burma was renamed to Myanmar in 1989. Let's investigate strategies on how that can be updated in our code lists. ### Accommodating Changes > Source: /docs/cds/common#accommodating-changes The renaming from Burma to Myanmar in 1989, was reflected in [ISO 3166] as follows (_the alpha-4 codes as specified in [ISO 3166-3] signify entries officially deleted from [ISO 3166-1] code lists_): | Name | Alpha-2 | Alpha-3 | Alpha-4 | Numeric | |---------|---------|---------|---------|---------| | Burma | BU | BUR | BUMM | 104 | | Myanmar | MM | MMR | | 104 | By default, and with the given default definitions in _@sap/cds/common_, this would have been reflected as a new entry for Myanmar and you'd have the following choices on what to do with the existing records in your data: * **(a)** Adjust foreign keys for records so that it always reflects the current state. * **(b)** Keep foreign keys as is for cases where the old records reflect the state effective at the time they were created or valid. ### Exclude Outdated Entries from Pick Lists (Optional) > Source: /docs/cds/common#exclude-outdated-entries-from-pick-lists-optional Although outdated entries like the one for Burma have to remain in the code lists as targets for references from historic records in other entities, you would certainly want to exclude it from all pick lists used in UIs when entering new data. This is how you could achieve that: #### 1. Extend the Common Code List Entity > Source: /docs/cds/common#1-extend-the-common-code-list-entity ```cds using { sap.common.Countries } from '@sap/cds/common'; extend Countries with { validTo: Date default '9999-12-31'; } ``` #### 2. Fill Validity Boundaries in Code Lists: > Source: /docs/cds/common#2-fill-validity-boundaries-in-code-lists | code | name | validTo | |------|---------|------------| | BU | Burma | 1989-06-18 | | MM | Myanmar | 9999-12-31 | #### 3. Model Pick List Entity > Source: /docs/cds/common#3-model-pick-list-entity Add the following line to your service definition: ```cds entity CountriesPickList as projection on sap.common.Countries where validTo >= $now; ``` Basically, the entity `Countries` serves all standard requests, and the new entity `CountriesPickList` is built for the value help only. This entity is a projection that gives you only those records that are valid right now. #### 4. Include Pick List with Validity on the UI > Source: /docs/cds/common#4-include-pick-list-with-validity-on-the-ui This snippet equips UI fields for a `countries` association with a value help from the `CountriesPickList` entity. ```cds annotate YourService.EntityName with { countries @( Common: { Text: country.name , // TextArrangement: #TextOnly, ValueList: { Label: 'Country Value Help', CollectionPath: 'CountriesPickList', Parameters: [ { $Type: 'Common.ValueListParameterInOut', LocalDataProperty: country_code, ValueListProperty: 'code' }, { $Type: 'Common.ValueListParameterDisplayOnly', ValueListProperty: 'name' } ] } }, ); } ``` # Common Annotations > Source: /docs/cds/annotations Find here a reference and glossary of common annotations intrinsically supported by the CDS compiler and runtimes. [Learn more about the syntax of annotations.](./cdl#annotations){.learn-more} ## General Purpose > Source: /docs/cds/annotations#general-purpose | Annotation | Description | Alternatives | |----------------|-------------|---------------------| | `@title` | | `@Common.Label` | | `@description` | | `@Core.Description` | ## Access Control > Source: /docs/cds/annotations#access-control | Annotation | Description | |-------------|---------------------------------------------------------------------------| | `@restrict` | see [Authorization](../guides/security/authorization#restrict-annotation) | | `@requires` | see [Authorization](../guides/security/authorization#requires) | ## Input Validation > Source: /docs/cds/annotations#input-validation | Annotation | Description | |------------------|----------------------------------------------------------------------| | `@readonly ` | see [Input Validation](../guides/services/constraints#readonly) | | `@mandatory` | see [Input Validation](../guides/services/constraints#mandatory) | | `@assert.target` | see [Input Validation](../guides/services/constraints#asserttarget) | | `@assert.format` | see [Input Validation](../guides/services/constraints#assertformat) | | `@assert.range` | see [Input Validation](../guides/services/constraints#assertrange) | ## Services / APIs > Source: /docs/cds/annotations#services--apis | Annotation | Description | |----------------------|-----------------------------------------------------------------------------------------| | `@path` | see [Services](./cdl#service-definitions) | | `@impl` | see [Reuse & Compose](../guides/integration/reuse-and-compose#reuse-code) | | `@odata.etag` | see [Providing Services](../guides/services/served-ootb#etag) | | `@cds.autoexpose` | see [Providing Services](../guides/services/providing-services#auto-exposed-entities) | | `@cds.api.ignore` | see [OData](../guides/protocols/odata#omitting-elements-from-apis) | | `@cds.query.limit` | see [Providing Services](../guides/services/served-ootb#annotation-cds-query-limit) | | `@cds.localized` | see [Localized Data](../guides/uis/localized-data#read-operations) | | `@cds.valid.from/to` | see [Temporal Data](../guides/domain/temporal-data#using-annotations-cdsvalidfromto) | | `@cds.search` | see [Search Capabilities](../guides/services/served-ootb#searching-data) | ## Persistence > Source: /docs/cds/annotations#persistence | Annotation | Description | |---------------------------|------------------------------------------------------------------------------| | `@cds.persistence.exists` | see [Generating DDL Files](../guides/databases/cdl-to-ddl#cdspersistenceexists) | | `@cds.persistence.table` | see [Generating DDL Files](../guides/databases/cdl-to-ddl#cdspersistencetable) | | `@cds.persistence.skip` | see [Generating DDL Files](../guides/databases/cdl-to-ddl#cdspersistenceskip) | | `@cds.persistence.mock` | `false` excludes this entity from automatic mocking | | `@cds.on.insert` | see [Providing Services](../guides/services/providing-services) | | `@cds.on.update` | see [Providing Services](../guides/services/providing-services) | | `@sql.prepend` | see [Generating DDL Files](../guides/databases/cdl-to-ddl#sqlprepend--append) | | `@sql.append` | see [Generating DDL Files](../guides/databases/cdl-to-ddl#sqlprepend--append) | ## OData > Source: /docs/cds/annotations#odata [Learn more about **OData Annotations in CDS**.](../guides/protocols/odata#annotations){.learn-more} Shortcuts: | Annotation | Description | |---------------------|--------------------------------------------------------------| | `@ValueList.entity` | see [Domain Modeling](../guides/domain/index) | | `@odata.Type` | see [OData](../guides/protocols/odata#override-type-mapping) | | `@odata.MaxLength` | see [OData](../guides/protocols/odata#override-type-mapping) | | `@odata.Precision` | see [OData](../guides/protocols/odata#override-type-mapping) | | `@odata.Scale` | see [OData](../guides/protocols/odata#override-type-mapping) | | `@odata.singleton` | see [OData](../guides/protocols/odata#singletons) | Intrinsically supported OData Annotations: | Annotation | Description | |---------------------|-------------------------------------------------------------------| | `@Core.Computed` | see [Providing Services](../guides/services/constraints#readonly) | | `@Core.Immutable` | see [Providing Services](../guides/services/constraints#readonly) | | `@Core.MediaType` | see [Media Data](../guides/services/media-data) | | `@Core.IsMediaType` | see [Media Data](../guides/services/media-data) | | `@Core.IsUrl` | see [Media Data](../guides/services/media-data) | | `@Capabilities...` | see [Fiori](../guides/uis/fiori) | # Compiler Messages > Source: /docs/cds/compiler/messages This page lists selected error messages and explanations on how to fix them. It is not a complete list of all compiler messages. ::: warning Note on message IDs Message IDs are not finalized, yet. They can change at short notice. ::: ## anno-duplicate-unrelated-layer > Source: /docs/cds/compiler/messages#anno-duplicate-unrelated-layer An annotation is assigned multiple times through unrelated layers. A _layer_ can be seen as a group of connected sources, for example CDL files. They form a cyclic connection through their dependencies (for example, `using` in CDL). If there are no cyclic dependencies, a single CDL file is equivalent to a layer. #### Example > Source: /docs/cds/compiler/messages#example Erroneous code example using four CDS files: ```cds // (1) Base.cds: Contains the artifact that should be annotated entity FooBar { } // (2) FooAnnotate.cds: First unrelated layer to Base.cds using from './Base'; annotate FooBar with @Anno: 'Foo'; // (3) BarAnnotate.cds: Second unrelated layer to Base.cds using from './Base'; annotate FooBar with @Anno: 'Bar'; // (4) All.cds: Combine all files ❌ using from './FooAnnotate'; using from './BarAnnotate'; ``` In (4) the compiler will warn that there are duplicate annotations in unrelated layers. That is because (2) and (3) are unrelated, i.e. they do not have a connection. Due to these unrelated layers, the compiler can't decide in (4) which annotation should be applied first. Instead of passing (4) to the compiler, you can also pass (2) and (3) to it. Because there are no cyclic dependencies between the files, each file represents one layer. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix Remove one of the duplicate annotations. Chances are, that only one was intended to begin with. For the erroneous example above, remove the annotation from (3). Alternatively, add an annotation assignment to (4). This annotation has precedence and the error will vanish. For the example above, (4) will look like this: ```cds // (4) All.cds: Combine all files using from './FooAnnotate'; using from './BarAnnotate'; // This annotation has precedence. annotate FooBar with @Anno: 'Bar'; ``` You can also make (3) depend on (2) so that they are no longer in unrelated layers and the compiler can determine which annotation to apply. ```cds // (3) BarAnnotate.cds: Now depends on (2) using from './FooAnnotate'; annotate FooBar with @Anno: 'Bar'; ``` This works because there is now a defined dependency order. ## anno-missing-rewrite > Source: /docs/cds/compiler/messages#anno-missing-rewrite A propagated annotation containing expressions can't be rewritten and would end up with invalid paths. While propagating annotations containing expressions such as `@anno: (path)`, the compiler ensures that the path remains valid. If necessary, the paths have to be rewritten, e.g. when being propagated to projections that rename their source's elements. If rewriting is not possible, this error is emitted. #### Example > Source: /docs/cds/compiler/messages#example-1 Erroneous code example: ```cds type T : { @anno: (sibling) elem: String; sibling: String; }; type TString : T:elem; // ❌ there is no `sibling` ``` The annotating `@anno` would be propagated to `TString`. However, because its path refers to an element that is not reachable at `TString`, the path can't be rewritten and compilation fails. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-1 Explicitly override the annotation. Either remove it by setting its value to `null` or by using another value. ```cds // (1) direct annotation @anno: null type TString : T:elem; // (2) annotate statement type TString : T:elem; annotate TString with @(anno: null); ``` Variant (1) may not always be applicable, e.g. if annotations in a structured type would need to be overridden. In those cases, use variant (2) and assign annotations via the `annotate` statement. ## check-proper-type-of > Source: /docs/cds/compiler/messages#check-proper-type-of An element in a `type of` expression doesn't have proper type information. The message's severity is `Info` but may be raised to `Error` in the SQL, SAP HANA, and OData backends. These backends require elements to have a type. Otherwise, they aren't able to render elements (for example, to SQL columns). #### Example > Source: /docs/cds/compiler/messages#example-2 Erroneous code example: ```cds entity Foo { key id : Integer; }; view ViewFoo as select from Foo { 1+1 as calculatedField @(anno) }; entity Bar { // ❌ `e` has no proper type but has the annotation `@anno`. e : ViewFoo:calculatedField; }; ``` `ViewFoo:calculatedField` is a calculated field without an explicit type. `type of` is used in `E:e`'s type specification. You would expect the element to have a proper type. However, because the referenced element is calculated, the compiler isn't able to determine the correct type. The element still inherits `ViewFoo:calculatedField`'s annotations and other properties but won't have a proper type, which is required by some backends. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-2 Assign an explicit type to `ViewFoo:calculatedField`. ```cds view ViewFoo as select from Foo { 1+1 as calculatedField @(anno) : Integer }; ``` #### Related Messages > Source: /docs/cds/compiler/messages#related-messages - [`def-missing-type`](#def-missing-type) ## def-duplicate-autoexposed > Source: /docs/cds/compiler/messages#def-duplicate-autoexposed Two or more entities with the same name can't be auto-exposed in the same service. Auto-exposure is a compiler feature which makes it easier for developers to write services. Auto-exposure uses the name of the entity to expose it in the service. It ignores the entity's namespace and context. This may lead to name collisions. The message's severity is `Error` and is raised by the compiler. You need to adapt your model to fix the error. #### Example > Source: /docs/cds/compiler/messages#example-3 Erroneous code example: ```cds // (1) entity ns.first.Foo { key parent : Association to one ns.Base; }; // (2) entity ns.second.Foo { key parent : Association to one ns.Base; }; // (3) entity ns.Base { key id : UUID; to_first : Composition of many ns.first.Foo; to_second : Composition of many ns.second.Foo; } service ns.MyService { // (4) ❌ entity BaseView as projection on ns.Base; }; ``` Both (1) and (2) define an entity `Foo`, but in different namespaces. For example, they could be located in different files with a `namespace` statement. (3) contains compositions of both `first.Foo` and `second.Foo`. In (4), a projection on `Base` is exposed in service `MyService`. Both composition targets are auto-exposed. However, because the namespaces of (2) and (3) are ignored, a name collision happens. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-3 You need to explicitly expose one or more entities under a name that does not exist in the service, yet. For the erroneous example above, you could add these two lines to the service `ns.MyService`: ```cds entity first.Foo as projection on ns.first.Foo; // (5) entity second.Foo as projection on ns.second.Foo; // (6) ``` Here we reuse the namespaces `first` and `second`. We don't use `ns` because it's the common namespace. But you can choose any other name. The compiler will pick up both manually exposed entities and will correctly redirect all associations. _Note:_ For the example, it is sufficient to expose only one entity. If you remove (6), you will get these two projections: - `ns.MyService.first.Foo` for (5) - `ns.MyService.Foo` for (6) Where (6) is the name chosen by the compiler. #### Notes on auto-exposure > Source: /docs/cds/compiler/messages#notes-on-auto-exposure You may wonder why the compiler does not reuse the namespace when auto-exposing entities. The reason is that the resulting auto-exposed names could become _long_ names that don't seem natural nor intuitive. We chose to expose the entity name because that's what most developers want to do when they manually expose entities. #### Other Notes > Source: /docs/cds/compiler/messages#other-notes This message was called `duplicate-autoexposed` in cds-compiler v3 and earlier. ## def-missing-type > Source: /docs/cds/compiler/messages#def-missing-type A type artifact doesn't have proper type information. The message's severity is `Info` but may be raised to `Error` in the SQL, SAP HANA, and OData backends. These backends require types to have type information. Otherwise, they aren't able to render elements that use this type (for example, to SQL columns). #### Example > Source: /docs/cds/compiler/messages#example-4 Erroneous code example: ```json { "definitions": { "MainType": { "kind": "type" } } } ``` `MainType` is of kind "type" but has not further type-information. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-4 Add explicit type information to `MainType`, for example, add an `elements` property to make a structured type. ```json { "definitions": { "MainType": { "kind": "type", "elements": { "id": { "type": "cds.String" } } } } } ``` #### Related Messages > Source: /docs/cds/compiler/messages#related-messages-1 - [`check-proper-type-of`](#check-proper-type-of) ## def-upcoming-virtual-change > Source: /docs/cds/compiler/messages#def-upcoming-virtual-change The behavior of `@sap/cds-compiler` v6 will change for a selected element. #### Example > Source: /docs/cds/compiler/messages#example-5 Erroneous code example: ```cds entity Source { key ID : String; a : String; }; entity Proj as projection on Source { ID, virtual a, // ❌ behavior will change in v6 }; ``` In `@sap/cds-compiler` v5 and earlier, element `Proj:a` is a reference to element `Source:a`, which was marked virtual. In `@sap/cds-compiler` v6 and later, it will instead be a _new_ element, without any reference to `Source:a`. This may or may not affect your runtime coding, hence the warning. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-5 If the v6 behavior works for you, there is nothing you need to do. However, if you want to keep a reference to `Source:a` in CSN, for example because you use the reference at runtime, then you can keep the old behavior by either: 1. prepending a table alias to the reference 2. adding a column alias ```cds // (1) prepend a table alias entity V as projection on E { ID, virtual E.a, // ok }; ``` ```cds // (2) add an alias entity V as projection on E { ID, virtual a as a, // ok }; ``` ## extend-repeated-intralayer > Source: /docs/cds/compiler/messages#extend-repeated-intralayer The order of elements of an artifact may not be stable due to multiple extensions in the same layer (for example in the same file). A _layer_ can be seen as a group of connected sources, for example, CDL files. They form a cyclic connection through their dependencies (for example, `using` in CDL). #### Example > Source: /docs/cds/compiler/messages#example-6 Erroneous code example with multiple CDL files: ```cds // (1) Definition.cds using from './Extension.cds'; entity FooBar { }; extend FooBar { foo: Integer; }; // ❌ // (2) Extension.cds using from './Definition.cds'; extend FooBar { bar: Integer; }; // ❌ ``` Here we have a cyclic dependency between (1) and (2). Together they form one layer with multiple extensions. Again, the element order isn't stable. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-6 Move extensions for the same artifact into the same extension block: ```cds // (1) Definition.cds : No extension block using from './Extension.cds'; entity FooBar { } // (2) Extension.cds : Now contains both extensions using from './Definition.cds'; extend FooBar { foo : Integer; bar : Integer; } ``` #### Related Messages > Source: /docs/cds/compiler/messages#related-messages-2 - [`extend-unrelated-layer`](#extend-unrelated-layer) ## extend-unrelated-layer > Source: /docs/cds/compiler/messages#extend-unrelated-layer Unstable element order due to extensions for the same artifact in unrelated layers. A _layer_ can be seen as a group of connected sources, for example CDL files. They form a cyclic connection through their dependencies (for example, `using` in CDL). #### Example > Source: /docs/cds/compiler/messages#example-7 Erroneous code example using four CDS files: ```cds // (1) Base.cds: Contains the artifact that should be extended entity FooBar { } // (2) FooExtend.cds: First unrelated layer to Base.cds using from './Base'; extend FooBar { foo : Integer; } // (3) BarExtend.cds: Second unrelated layer to Base.cds using from './Base'; extend FooBar { bar : Integer; } // (4) ❌ All.cds: Combine all files using from './FooExtend'; using from './BarExtend'; ``` In (4) the compiler will warn that the element order of `FooBar` is unstable. That is because the extensions in (2) and (3) are in different layers and when used in (4) it can't be ensured which extension is applied first. Instead of passing (4) to the compiler, you can also pass (2) and (3) to it. Because there are no cyclic dependencies between the files, each file represents one layer. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-7 Move extensions for the same artifact into the same layer, that is, the same file. For the erroneous example above, remove the extension from (3) and move it to (2): ```cds // (2) FooExtend.cds using from './Base'; extend FooBar { foo : Integer; bar : Integer; } ``` #### Related Messages > Source: /docs/cds/compiler/messages#related-messages-3 - [`extend-repeated-intralayer`](#extend-repeated-intralayer) ## file-unexpected-case-mismatch > Source: /docs/cds/compiler/messages#file-unexpected-case-mismatch The filename of a `using` statement does not match the file's actual name on disk. To avoid operating-system dependent issues, the compiler checks if the name of an imported file matches the name of the file in the filesystem / on disk. For example, by default macOS uses a case-insensitive file system. Hence, a file named `model.cds` will also be loaded by `using from './Model.cds'` on such systems. However, on other filesystems that are case-sensitive, e.g. when building your application in another environment, the file will not be found. Hence, the `using` statement needs to be adapted. #### Example > Source: /docs/cds/compiler/messages#example-8 Erroneous code example: ```cds // index.cds using from './Model'; ``` using following directory tree: ``` ├── index.cds └── model.cds ``` On case-insensitive systems, the file can be loaded, but the compiler will warn about the mismatch. On case-sensitive file systems, compilation will fail, as the imported file can't be found. While in this case, compilation will fail on case-sensitive systems, it could instead end up with semantic changes, too. Given the same `index.cds`, but a different directory tree: ``` ├── index.cds ├── Model │ └── index.cds └── model.cds ``` On case-sensitive systems, `./Model/index.cds` will be loaded. On case-insensitive systems, however, `model.cds` will be loaded, as the compiler first tries to load `Model.cds`, before looking for `Model/index.cds`. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-8 Adapt the filename in your `using` statement. If you have both `model.cds` and `Model/index.cds`, but don't want to use a `.cds` suffix, use `using from './Model/'`, i.e. add a trailing slash to indicate that you want to load from the folder `Model`. ## redirected-to-ambiguous > Source: /docs/cds/compiler/messages#redirected-to-ambiguous The redirected target originates more than once from the original target through direct or indirect sources of the redirected target. The message's severity is `Error` and is raised by the compiler. The error happens due to an ill-formed redirection, which requires changes to your model. #### Example > Source: /docs/cds/compiler/messages#example-9 Erroneous code example: ```cds entity Main { key id : Integer; toTarget : Association to Target; } entity Target { key id : Integer; } view View as select from Main, Target, Target as Duplicate { // ❌ This redirection can't be resolved: Main.toTarget : redirected to View }; ``` Entity `Target` exists more than once in `View` under different table aliases. In the previous example, this happens through the *direct* sources in the select clause. Because the original target exists twice in the redirected target, the compiler isn't able to correctly resolve the redirection due to ambiguities. This can also happen through *indirect* sources. For example if entity `Main` were to include `Target`, then selecting from `Target` just once would be enough to trigger this error. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-9 You must have the original target only once in your direct and indirect sources. The previous example can be fixed by removing `Duplicate` from the select clause. ```cds view View as select from Main, Target { Main.toTarget : redirected to View }; ``` If this isn't feasible then you have to redefine the association using a mixin clause. ```cds view View as select from Main, Target mixin { toMain : Association to View on Main.id = Target.id; } into { Main.id as mainId, Target.id as targetId, toMain }; ``` #### Related Messages > Source: /docs/cds/compiler/messages#related-messages-4 - [`redirected-to-unrelated`](#redirected-to-unrelated) - [`redirected-to-complex`](#redirected-to-complex) ## redirected-to-complex > Source: /docs/cds/compiler/messages#redirected-to-complex The redirected target is a complex view, for example, contains a JOIN or UNION. The message's severity is `Info` and is raised by the compiler. It is emitted to help developers identify possible modeling issues. #### Example > Source: /docs/cds/compiler/messages#example-10 Erroneous code example: ```cds entity Main { key id : Integer; // self association for example purpose only toMain : Association to one Main; } entity Secondary { content: String; }; entity CrossJoin as SELECT from Main, Secondary; entity RedirectToComplex as projection on Main { id, toMain: redirected to CrossJoin, // ❌ }; ``` `Main:toMain` is a to-one association. Since `Main` contains a single key, which is used in the managed association, we know that following the association returns a single result. The cross join in the view `CrossJoin` results in multiple rows with the same `id`. Following the redirected view now returns multiple results, effectively making the to-one association a to-many association. Visualizing the tables with a bit of data, this issue becomes obvious: ```markdown Main Secondary | id | toMain_id | | content | |-----|-----------| |---------| | 1 | 2 | | 'Hello' | | 2 | 1 | | 'World' | CrossJoin | id | toMain_id | content | |-----|-----------|---------| | 1 | 2 | 'Hello' | | 1 | 2 | 'World' | | 2 | 1 | 'Hello' | | 2 | 1 | 'World' | ``` #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-10 First, ensure that the redirected association points to an entity that is a reasonable redirection target. That means, the redirection target shouldn't accidentally make it a to-many association. Then add an explicit ON-condition or explicit foreign keys to the redirected association. That will silence the compiler message. #### Related Messages > Source: /docs/cds/compiler/messages#related-messages-5 - [`redirected-to-ambiguous`](#redirected-to-ambiguous) - [`redirected-to-unrelated`](#redirected-to-unrelated) ## redirected-to-unrelated > Source: /docs/cds/compiler/messages#redirected-to-unrelated The redirected target doesn't originate from the original target. The message's severity is `Error` and is raised by the compiler. The error happens due to an ill-formed redirection, which requires changes to your model. #### Example > Source: /docs/cds/compiler/messages#example-11 Erroneous code example: ```cds entity Main { key id : Integer; // self association for example purpose only toMain : Association to Main; } entity Secondary { key id : Integer; } entity InvalidRedirect as projection on Main { id, // ❌ Invalid redirection toMain: redirected to Secondary, }; ``` Projection `InvalidRedirect` tries to redirect `toMain` to `Secondary`. However, that entity doesn't have any connection to the original target `Main`, that means, it doesn't originate from `Main`. While this example may be clear, your model may have multiple redirections that make the error not as obvious. Erroneous code example with multiple redirections: ```cds entity Main { key id : Integer; toMain : Association to Main; } entity FirstRedirect as projection on Main { id, toMain: redirected to FirstRedirect, } entity SecondRedirect as projection on FirstRedirect { id, // Invalid redirection toMain: redirected to Main, } ``` The intent of the example above is to redirect `toMain` to its original target in `SecondRedirect`. But because `SecondRedirect` uses `toMain` from `FirstRedirect`, the original target is `FirstRedirect`. And `Main` doesn't originate from `FirstRedirect` but only vice versa. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-11 You must redirect the association to an entity that originates from the original target. In the first example above you could redirect `SecondRedirect:toMain` to `SecondRedirect`. However, if that isn't feasible then you have to redefine the association using a mixin clause. ```cds view SecondRedirect as select from FirstRedirect mixin { toMain : Association to Main on id = $self.id; } into { FirstRedirect.id as id, toMain }; ``` #### Related Messages > Source: /docs/cds/compiler/messages#related-messages-6 - [`redirected-to-ambiguous`](#redirected-to-ambiguous) - [`redirected-to-complex`](#redirected-to-complex) ## rewrite-not-supported > Source: /docs/cds/compiler/messages#rewrite-not-supported The compiler isn't able to rewrite ON conditions for some associations. They have to be explicitly defined by the user. The message's severity is `Error`. #### Example > Source: /docs/cds/compiler/messages#example-12 Erroneous code example: ```cds entity Base { key id : Integer; primary : Association to Primary on primary.id = primary_id; primary_id : Integer; } entity Primary { key id : Integer; secondary : Association to Secondary on secondary.id = secondary_id; secondary_id : Integer; } entity Secondary { key id : Integer; text : LargeString; } entity View as select from Base { id, primary.secondary // ❌ The ON condition isn't rewritten here }; ``` In the previous example, the ON condition in `View` of `secondary` can't be automatically rewritten because the associations are unmanaged and the compiler can't determine how to properly rewrite them for `View`. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-12 You have to provide an explicit ON condition. This can be achieved by using the `redirected to` statement: ```cds entity View as select from Base { id, primary.secondary_id, primary.secondary: redirected to Secondary on secondary.id = secondary_id }; ``` In the corrected view above, the association `secondary` gets an explicit ON condition. For this to work it is necessary to add `secondary_id` to the selection list, that means, we have to explicitly use the foreign key. #### Related Messages > Source: /docs/cds/compiler/messages#related-messages-7 - [`rewrite-undefined-key`](#rewrite-undefined-key) ## rewrite-undefined-key > Source: /docs/cds/compiler/messages#rewrite-undefined-key The compiler isn't able to rewrite an association's foreign keys, because the redirected target is missing elements to match them. The message's severity is `Error`. #### Example > Source: /docs/cds/compiler/messages#example-13 Erroneous code example: ```cds entity model.Base { key ID : UUID; toTarget : Association to model.Target; // (1) } entity model.Target { key ID : UUID; // (2) field : String; } service S { entity Base as projection on model.Base; // ❌ (3) Can't redirect 'toTarget' entity Target as projection on model.Target { field, // (4) No 'ID' }; } ``` In the example, the projected association `toTarget` at (3) in entity `S.Base` can't be redirected to `S.Target`, because `S.Target` does not project element `ID` (4). `toTarget` (1) is a managed association and hence foreign keys are inferred for it. The compiler generates a foreign key `ID`, which corresponds to element `ID` of `model.Target` (2). As both entities are exposed in service `S`, the compiler tries to redirect `S.Base:toTarget` to an entity inside the same service, to create a "self-contained" service. It notices, however, that `S.Target` does not have element `ID`, and therefore can't match the foreign key to a target element and emits this error message. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-13 If you don't need to expose association `toTarget` in `S.Target`, you can exclude it in the projection via an `excluding` clause. ```cds service S { entity Base as projection on model.Base excluding { toTarget }; // ... } ``` If the association is required in the service, you need to either project element `ID` in `S.Target`, or redirect the association explicitly. The easiest fix is to select `ID` explicitly: ```cds service S { // ... entity Target as projection on model.Target { field, ID, // Explicitly select element ID }; } ``` However, if you don't want to expose `ID`, redirect association `toTarget` explicitly, matching the foreign key to another element: ```cds service S { entity Base as projection on model.Base { ID, toTarget : redirected to Target { fakeID as ID }, // (1) }; entity Target as projection on model.Target { calculateKey() as fakeID : UUID, // (2) field, }; } ``` Note that at (1), we use element `fakeID` of `S.Target` as foreign key `ID`. That changes its semantic meaning and may not be feasible in all cases! In the example, we assume at (2) that a key can be calculated. #### Related Messages > Source: /docs/cds/compiler/messages#related-messages-8 - [`rewrite-not-supported`](#rewrite-not-supported) ## syntax-expecting-unsigned-int > Source: /docs/cds/compiler/messages#syntax-expecting-unsigned-int The compiler expects a safe non-negative integer here. The last safe integer is `2^53 - 1` or `9007199254740991`. A safe integer is an integer that fulfills all of the following: - Can be exactly represented as an IEEE-754 double precision number. - The IEEE-754 representation cannot be the result of rounding any other integer to fit the IEEE-754 representation. The message's severity is `Error`. #### Example > Source: /docs/cds/compiler/messages#example-14 Erroneous code example: ```cds type LengthIsUnsafe : String(9007199254740992); // ❌ type NotAnInteger : String(42.1); // ❌ ``` In the erroneous example, the string length for the type `LengthIsUnsafe` is not a safe integer. It is too large. Likewise, the string length for the type `NotAnInteger` is a decimal. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-14 You have to provide a safe integer: ```cds type LengthIsSafe : String(9007199254740991); type AnInteger : String(42); ``` At other places, using unsafe integers (or non-integer numbers) is allowed: - Annotation values: The value is then simply a string. - Expressions: The `val` property in the CSN contains a string having a sibling `literal: 'number'`. ## type-missing-enum-value > Source: /docs/cds/compiler/messages#type-missing-enum-value An enum definition is missing explicit values for one or more of its entries. Enum definitions that aren't based on string-types do not get implicit values. They have therefore to be defined explicitly in the model. The message's severity is `Warning` and is raised by the compiler. You need to adapt your model to fix the warning. #### Example > Source: /docs/cds/compiler/messages#example-15 Erroneous code example: ```cds entity Books { // … category: Integer enum { Fiction; // ❌ Action; // ❌ // … } default #Action; }; ``` Both entries `#Fiction` and `#Action` of the enum `category` are missing an explicit value. Because the base type `Integer` is not a string, no implicit values are defined for them. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-15 Explicitly assign a value or change the type to a string if the values are not important in your model. The erroneous example above can be changed to: ```cds entity Books { // … category: Integer enum { Fiction = 1; Action = 2; // … } default #Action; }; ``` #### Background > Source: /docs/cds/compiler/messages#background Many languages support implicit values for integer-like enums. However, CAP CDS does not have this feature, because otherwise, if values are persisted, adding a new entry in-between existing ones would lead to issues during deserialization later on. Assume that CAP would assign implicit values for integer enums. If a new value were to be added between `Fiction` and `Action` in the erroneous example above, then the generated SQL statement for entity `Books` would change: Instead of default value `2`, value `3` would be persisted. Without data migration, existing action books would have changed their category. To avoid this scenario, always add explicit values to enums. ## wildcard-excluding-one > Source: /docs/cds/compiler/messages#wildcard-excluding-one You're replacing an element in your projection, that is already included by using the wildcard `*`. The message's severity is `Info`. #### Example > Source: /docs/cds/compiler/messages#example-16 Erroneous code example: ```cds entity Book { key id : String; isbn : String; content : String; }; entity IsbnBook as projection on Book { *, isbn as id, // ❌ }; ``` `IsbnBook:id` replaces `Book:id`, which was included in `IsbnBook` through the wildcard `*`. #### How to Fix > Source: /docs/cds/compiler/messages#how-to-fix-16 Add the replaced element to the list of wildcard excludes: ```cds entity IsbnBook as projection on Book { *, isbn as id } excluding { id }; ``` # Aspect-Oriented Modeling > Source: /docs/cds/aspects The technique of [*Aspects*](cdl#aspects) provides a very powerful means to organize your models in a way that keeps your core domain models concise and comprehensible by factoring out secondary concerns into separate files, defining and reusing common aspects, as well as adapting reused definitions to specific needs. **See also:** Respective section in [*Five reasons to use CAP*](https://qmacro.org/blog/posts/2024/11/07/five-reasons-to-use-cap/) , and [*Separating concerns and focusing on important stuff*](https://qmacro.org/blog/posts/2024/11/04/separating-concerns-and-focusing-on-the-important-stuff/) blog posts by DJ Adams. {.learn-more} ## Similar to Aspect-Oriented Programming > Source: /docs/cds/aspects#similar-to-aspect-oriented-programming Aspect-oriented Modeling as promoted by CDS is very similar in goals and approaches to [Aspect-oriented Programming as defined in this Wikipedia article](https://en.wikipedia.org/wiki/Aspect-oriented_programming): > *Aspect-oriented programming (AOP) is a programming paradigm that aims to increase modularity by allowing the [separation](https://en.wikipedia.org/wiki/Separation_of_concerns) of [cross-cutting concerns](https://en.wikipedia.org/wiki/Cross-cutting_concern). It does so by adding behavior to existing code (an [advice](https://en.wikipedia.org/wiki/Advice_(programming))) without modifying the code, [...].* ::: tip Extend anything from anywhere In essence [CDS Aspects](cdl#aspects) allow you to arbitrarily spread a definition across different places in the same files, or separate ones, in different projects, with different ownerships and different lifecycles. ::: ## Separation of Concerns > Source: /docs/cds/aspects#separation-of-concerns Use aspects to factor out secondary concerns into separate files as follows... ### All-in-one Models > Source: /docs/cds/aspects#all-in-one-models Instead of polluting your core domain models with a multitude of annotations, put such annotations into separate files. For example, instead of having a single-source model like that: ::: code-group ```cds [srv/cat-service.cds] service CatalogService { @UI.SelectionFields: [ ID, price, currency_code ] @UI.LineItem: [ { Value: ID, Label: '{i18n>Title}' }, { Value: author, Label : '{i18n>Author}' }, { Value: genre.name}, { Value: price}, { Value: currency.symbol}, ] @UI.HeaderInfo: { TypeName : '{i18n>Book}', TypeNamePlural : '{i18n>Books}', Description : { Value: author } } @UI.HeaderFacets: [{ $Type : 'UI.ReferenceFacet', Label : '{i18n>Description}', Target : '@UI.FieldGroup#Descr' }] @UI.Facets: [{ $Type : 'UI.ReferenceFacet', Label : '{i18n>Details}', Target : '@UI.FieldGroup#Price' }] @UI.FieldGroup #Descr : { Data: [{Value : descr}, ]} @UI:FieldGroup #Price : { Data: [ { Value: price}, { Value: currency.symbol, Label: '{i18n>Currency}' }, ]} entity Books { ... } ... } ``` ::: ### Keep Your Core Clean > Source: /docs/cds/aspects#keep-your-core-clean Rather, keep your core model concise and comprehensible: ::: code-group ```cds [srv/cat-service.cds] service CatalogService { entity Books { ... } ... } ``` ::: ### Factor Out Separate Concerns > Source: /docs/cds/aspects#factor-out-separate-concerns And factor out the UI concerns into a separate file like that: ::: code-group ```cds [app/fiori-layout.cds] using { CatalogService } from '../srv/cat-service'; // Annotations for List Pages annotate CatalogService.Books with @UI:{ SelectionFields: [ ID, price, currency_code ], LineItem: [ { Value: ID, Label: '{i18n>Title}' }, { Value: author, Label : '{i18n>Author}' }, { Value: genre.name}, { Value: price}, { Value: currency.symbol}, ] } // Annotations for Object Pages annotate CatalogService.Books with @UI:{ HeaderInfo: { TypeName : '{i18n>Book}', TypeNamePlural : '{i18n>Books}', Description : { Value: author } }, HeaderFacets: [{ $Type : 'UI.ReferenceFacet', Label : '{i18n>Description}', Target : '@UI.FieldGroup#Descr' }], Facets: [{ $Type : 'UI.ReferenceFacet', Label : '{i18n>Details}', Target : '@UI.FieldGroup#Price' }], FieldGroup #Descr : { Data: [{Value : descr}, ]}, FieldGroup #Price : { Data: [ { Value: price}, { Value: currency.symbol, Label: '{i18n>Currency}' }, ]} } ``` ::: ## Common Reuse Aspects > Source: /docs/cds/aspects#common-reuse-aspects Quite frequently, you want some common aspects to be factored out and shared by and applied to multiple entities. For example, lets assume we'd want to factor out the common aspects of a standardized primary key, managed data, change tracking, extensibility, and temporal data... ### _Max Base Class_ Approach > Source: /docs/cds/aspects#max-base-class-approach The classic way to do so, for example in class-based inheritance systems like Java, is to have a central team defining single base classes like `Object` for that, and either add all the aspects in question to that single base class, or have a base class hierarchy, like that: ```cds abstract entity BusinessObject { key ID : UUID; createdAt : DateTime; createdBy : User; modifiedAt : DateTime; modifiedBy : User; changes : Composition of many Changes; extensions : PredefinedExtensionFields; } ``` ::: details With `Changes` and `PredefinedExtensionFields` defined like that... ```cds aspect Changes { operation : String enum { CREATED; MODIFIED; DELETED }; changedAt : DateTime; changedBy : User; diff : array of { element : String; old : String; new : String; }; } ``` ```cds type PredefinedExtensionFields { s1 : String; s2 : String; s3 : String; i1 : Integer; i2 : Integer; dt1 : DateTime; ... } ``` ::: ```cds abstract entity TemporalBO : BusinessObject { validFrom : Date @cds.valid.from; validTo : Date @cds.valid.to; } ``` Consumers would then use these base classes like that: ```cds using { BusinessObject, TemporalBO } from 'your-base-classes'; entity Foo : BusinessObject {...} entity Bar : TemporalBO {...} ``` ::: warning Issues with that approach... One issue is that due to single inheritance limitations, these base classes frequently have to combine several actually independent aspects into one definition, and the consumers have to take them all. Related to that is that these base classes have to depend on each other, which ultimately means they can only be provided and owned by central teams. ::: ::: details `abstract entity` is deprecated... If you try to use `abstract entity` in CDS, you'll get a warning that it is deprecated. Reason for that we found it was used mostly for the _'Max Base Class'_ anti pattern. So we decided to deprecate it to encourage the use of [_Separate Reuse Aspects_](#separate-reuse-aspects) pattern instead. ::: ### Separate Reuse Aspects > Source: /docs/cds/aspects#separate-reuse-aspects While, as shown above, the central single-inheritance-style base class approach is also possible with CDS, we can do better using CDS Aspects, leveraging the equivalent of multiple inheritance, and hence distributed ownership instead of central one: ```cds aspect cuid { key ID : UUID; } ``` ```cds aspect managed { createdAt : DateTime; createdBy : User; modifiedAt : DateTime; modifiedBy : User; } ``` ```cds aspect tracked { changes : Composition of many Changes; } ``` ```cds aspect extensible { s1 : String; s2 : String; s3 : String; i1 : Integer; i2 : Integer; dt1 : DateTime; ... } ``` ```cds aspect temporal { validFrom : Date @cds.valid.from; validTo : Date @cds.valid.to; } ``` [Some of such common reuse aspects are already covered by `@sap/cds/common`.](common) {.learn-more} Consumers would then flexibly use these reuse aspects like so: ```cds using { cuid, managed, tracked, extensible, temporal } from 'your-reuse-aspects'; entity Foo : cuid, managed, tracked, extensible {...} entity Bar : cuid, managed, temporal {...} ``` ::: tip Advantages of that approach Not only does that approach allow clearer separation of concerns, and thus freedom of choice on which combinations of aspects to pick for consumers, it also allows distributed ownership of such reuse aspects, as they don't depend on each other. ::: ::: tip Looks Like Inheritance... The [`:`-based syntax for includes](cdl#includes) looks very much like (multiple) inheritance and in fact has very much the same effects. Yet, it is not based on inheritance but on mixins, which are more powerful and also avoid common problems like the infamous diamond shapes in classical inheritance-based approaches. ::: ## Adaptation of Reused Definitions > Source: /docs/cds/aspects#adaptation-of-reused-definitions Assumed there's a reuse package offering some common types and entities which would nicely fit your needs. For example: ::: code-group ```cds [some-reuse-package/index.cds] entity Currencies : CodeList { key code : String(3); } entity Countries : CodeList { key code : String(5); } entity Languages : CodeList { key locale : String(5); } type CodeList : { name : localized String; } ``` ::: ### Adding / Adapting Fields > Source: /docs/cds/aspects#adding--adapting-fields Now also assumed, you'd want all code lists to have an additional field for long descriptions, and you also want currency symbols, and the `locale` field for languages needs to support values with up to 15 characters. With aspects, you could simply adapt the reuse types and entities accordingly as follows: ::: code-group ```cds [db/common.cds] using { CodeList, Currencies, Languages } from 'some-reuse-package'; extend CodeList with { descr: localized String } extend Currencies with { symbol: String(2) } extend Languages:locale with (length:15); ``` ::: ### Adding Relationships > Source: /docs/cds/aspects#adding-relationships You can even add [Associations](cdl#associations) and [Compositions](cdl#compositions) to definitions you obtained from somewhere else. For example, the following would extend the common reuse type `managed` obtained from `@sap/cds/common` to not only capture latest modifications, but a history of commented changes, with all entities inheriting from that aspect, own or reused ones, receiving this enhancement automatically: ```cds [db/common.cds] using { User, managed } from '@sap/cds/common'; extend managed with { ChangeNotes : Composition of many { key timestamp : DateTime; author : User; note : String(1000); } } ``` [Learn more about `managed` and `@sap/cds/common`](common) {.learn-more} ### Adding Reuse Aspects > Source: /docs/cds/aspects#adding-reuse-aspects And as the `:` notation to *inherit* an aspect is essentially just [syntactical sugar](cdl#includes) for extending a given definition with a [*named* aspect](cdl#named-aspects), you can also adapt a reused definition to *inherit* from a common reuse aspect from 'the outside' like so: ```cds using { SomeEntity } from 'some-reuse-package'; using { managed } from '@sap/cds/common'; extend SomeEntity with managed; ``` ## Customization, Verticalization > Source: /docs/cds/aspects#customization-verticalization The same approach and techniques are used by SaaS customers when customizing a SaaS application to tailor it to their needs. ### Adding Custom Fields > Source: /docs/cds/aspects#adding-custom-fields For example, SaaS customers would quite frequently add extension fields like that: ```cds using { ShipmentOrders } from 'some-saas-application'; extend ShipmentOrders with { carrier : Association to Carriers; // new association delayedBy : Time; // new field } ``` [Learn more about Extensibility](../guides/extensibility/) {.learn-more} ### Overriding Annotations > Source: /docs/cds/aspects#overriding-annotations Sometimes they'd need to override existing annotations, such as for UI labels: ```cds using { Customers } from 'some-saas-application'; annotate Customers with @title:'Patients'; // e.g. for health care ``` ### Verticalization > Source: /docs/cds/aspects#verticalization Verticalization means to adapt a given application for different regions or industries, which can be accomplished by providing respective predefined extension packages and switch them on per customer using [feature toggles](../guides/extensibility/feature-toggles). ## Inheritance Hierarchies > Source: /docs/cds/aspects#inheritance-hierarchies Sometimes you'd be tempted to create deeply nested inheritance hierarchies as you might be used to do in Java. For example, let's assume we're tempted to model something like that: ```cds abstract entity Grantees { // equivalent to aspect key name : String; } entity Users : Grantees { group : Association to Groups; } entity Groups : Grantees { members : Composition of many Users on members.group = $self; } ``` When combining that with relational persistence, you'll always end up in trade-off decisions about which strategy to choose for mapping such class hierarchies to flat tables. As that choice heavily depends on the use cases, CDS intentionally doesn't provide any automatic mapping of such inheritance hierarchies, but you have to choose one of the [three commonly known approaches](https://wiki.c2.com/?MappingInheritanceHierarchiesToRelationalSchemataInvolvesCompromises) explicitly in your models as follows... ### Table Per Leaf Class Strategy > Source: /docs/cds/aspects#table-per-leaf-class-strategy If we'd keep the model as given above, we'd end up with two separate tables, one for each leaf entity. The problem with that approach is that we'd need expensive UNIONs to, for example, display a heterogeneous list of Users and Groups. For example: ```cds entity UsersAndGroups as ( SELECT from Users ) UNION ALL ( SELECT from Groups ); ``` ### Table Per Class Strategy > Source: /docs/cds/aspects#table-per-class-strategy If we want a separate table for each entity in our model above, including the 'superclass' entity `Grantees`, we'd have to rewrite our model to use composition over inheritance like that: ```cds entity Grantees { key name : String; } entity Users { header : Association to Grantees; group : Association to Groups; } entity Groups { header : Association to Grantees; members : Composition of many Users on members.group = $self; } ``` This would allow you to display heterogeneous lists of `Grantees` without UNIONs. A lot more JOINs would be required in real-world examples, though. ### Single Table Strategy > Source: /docs/cds/aspects#single-table-strategy The third strategy is to put everything into a single table and an additional type discriminator element (→ `kind` in the sample below). ```cds entity Users { key name : String; kind : String enum { user; group }; // discriminator group : Association to Users; members : Composition of many Users on members.group = $self; } ``` ::: tip Advantages - Simple model - No UNIONs, no excess JOINs - Bonus: deeply nested `Groups` ::: # On The Nature of Models > Source: /docs/cds/models Introduces the fundamental principles of CDS models. ## Metaphysics of Languages > Source: /docs/cds/models#metaphysics-of-languages A *model* is a *thing* that describes *something*. For example, a *data model describes the type structure (commonly also called *'schema*') of *data*. ### Languages > Source: /docs/cds/models#languages ### Representations > Source: /docs/cds/models#representations Models can come in different *representations*, which follow different *syntaxes*. For example, we use the *CDL* syntax for *human-readable* representations of CDS models, while CSN is an *object notation*, that is a special form of *syntax*, used for *machine-readable* representations of CDS models. ::: details On CSN representations... We can go one meta-level further and distinguish between different representations of CSN representations: in a Node.js process at runtime they are just native in-memory JavaScript objects, when shared they are serialized to JSON format, which can in turn be translated to YAML, and so forth. When we create CSN objects at runtime, they could be plain JavaScript code. ::: ### Reflections > Source: /docs/cds/models#reflections CDS models can be compiled to other languages, that play in the same fields, yet not covering the same information, but rather with some loss of information — we call these '*reflections*'. Examples are: - SQL DDL covers the persistence model interface only → only flat tables and views - OData EDMX covers the service interfaces only → queryable entities still exist, with implicit features - GraphQL also covers service interfaces → queryable entities still exist, but without less features - OpenAPI also covers the service interfaces, with → queryable entities got 'flattened' to paths with input and output types --- The above principles apply not only to CDS models, but also to Queries: - CQL is a syntax for human-readable representations - CQN is an object notation for machine-readable representations And for Expressions: - CXL is a syntax for human-readable representations - CXN is an object notation for machine-readable representations ... ## What is a CDS Model? > Source: /docs/cds/models#what-is-a-cds-model Models in `cds` are plain JavaScript objects conforming to the _[Core Schema Notation (CSN)](./csn)_. They can be parsed from [_.cds_ sources](./cdl), read from _.json_ or _.yaml_ files or dynamically created in code at runtime. The following ways and examples of creating models are equivalent: ### In Plain Coding at Runtime > Source: /docs/cds/models#in-plain-coding-at-runtime ```js const cds = require('@sap/cds') // define the model var model = {definitions:{ Products: {kind:'entity', elements:{ ID: {type:'Integer', key:true}, title: {type:'String', length:11, localized:true}, description: {type:'String', localized:true}, }}, Orders: {kind:'entity', elements:{ product: {type:'Association', target:'Products'}, quantity: {type:'Integer'}, }}, }} // do something with it console.log (cds.compile.to.yaml (model)) ``` ### Parsed at Runtime > Source: /docs/cds/models#parsed-at-runtime ```js const cds = require('@sap/cds') // define the model var model = cds.parse (` entity Products { key ID: Integer; title: localized String(11); description: localized String; } entity Orders { product: Association to Products; quantity: Integer; } `) // do something with it console.log (cds.compile.to.yaml (model)) ``` ### From _.cds_ Source Files > Source: /docs/cds/models#from-cds-source-files ```cds // some.cds source file entity Products { key ID: Integer; title: localized String(11); description: localized String; } entity Orders { product: Association to Products; quantity: Integer; } ``` Read/parse it, and do something with it, for example: ```js const cds = require('@sap/cds') cds.get('./some.cds') .then (cds.compile.to.yaml) .then (console.log) ``` > Which is equivalent to: `cds ./some.cds -2 yaml` using the CLI ### From _.json_ Files > Source: /docs/cds/models#from-json-files ```json {"definitions": { "Products": { "kind": "entity", "elements": { "ID": { "type": "Integer", "key": true }, "title": { "type": "String", "length": 11, "localized": true }, "description": { "type": "String", "localized": true } } }, "Orders": { "kind": "entity", "elements": { "product": { "type": "Association", "target": "Products" }, "quantity": { "type": "Integer" } } } }} ``` ```js const cds = require('@sap/cds') cds.get('./some.json') .then (cds.compile.to.yaml) .then (console.log) ```
### From Other Frontends > Source: /docs/cds/models#from-other-frontends You can add any other frontend instead of using [CDL](./cdl); it's just about generating the respective [CSN](./csn) structures, most easily as _.json_. For example, different parties already added these frontends: * ABAP CDS 2 csn * OData EDMX 2 csn * Fiori annotation.xml 2 csn * i18n properties files 2 csn * Java/JPA models 2 csn ## Processing Models > Source: /docs/cds/models#processing-models All model processing and compilation steps, which can be applied subsequently just work on the basis of plain CSN objects. There's no assumption about and no lock-in to a specific source format. # CAP Service SDK for Node.js > Source: /docs/node.js/ Reference Documentation { .subtitle} As an application developer you'd primarily use the Node.js APIs documented herein to implement **domain-specific custom logic** along these lines: 1. Define services in CDS → see [Cookbook > Providing & Consuming Services](../guides/services/) 2. Add service implementations → [`cds.Service` > Implementations](core-services#implementing-services) 3. Register custom event handlers in which → [`srv.on`/`before`/`after`](core-services#srv-on-before-after) 4. Read/write data from other services in which → [`srv.run`](core-services#srv-run-query) + [`cds.ql`](cds-ql) 5. ..., that is from your primary database → [`cds.DatabaseService`](databases) 5. ..., that is from other connected services → [`cds.RemoteService`](remote-services) 6. Emit and handle asynchronous events → [`cds.MessagingService`](messaging) All the rest is largely handled by the CAP runtime framework behind the scenes. This especially applies to bootstrapping the [`cds.server`](cds-serve) and the generic features provided through [`cds.ApplicationService`](app-services). # The *cds* Façade Object > Source: /docs/node.js/cds-facade The `cds` facade object provides access to all CAP Node.js APIs. Use it like that: ```js const cds = require('@sap/cds') let csn = cds.compile(`entity Foo {}`) ``` ::: tip Use `cds repl` to try out things For example, like this to get the compiled CSN for an entity `Foo`: ```js [dev] cds repl Welcome to cds repl v 7.3.0 > cds.compile(`entity Foo { key ID : UUID }`) { definitions: { Foo: { kind: 'entity', elements: { ID: { key: true, type: 'cds.UUID' } } } }} ``` ::: ## Refs to Submodules > Source: /docs/node.js/cds-facade#refs-to-submodules Many properties of cds are references to submodules, which are lazy-loaded on first access to minimize bootstrapping time and memory consumption. The submodules are documented in separate documents. - [cds. model](cds-facade#cds-model) {.property} - [cds. resolve()](cds-compile#cds-resolve) {.method} - [cds. load()](cds-compile#cds-load) {.method} - [cds. parse()](cds-compile#cds-parse) {.method} - [cds. compile](cds-compile) {.method} - [cds. linked()](cds-reflect) {.method} - [cds. server](cds-server) {.property} - [cds. serve()](cds-serve) {.method} - cds. services {.property} - cds. middlewares {.property} - cds. protocols {.property} - cds. auth {.property} - [cds. connect](cds-connect) {.property} - [cds. ql](cds-ql) {.property} - [cds. tx()](cds-tx) {.method} - [cds. log()](cds-log) {.method} - [cds. env](cds-env) {.property} - [cds. auth](authentication) {.property} - [cds. i18n](cds-i18n) {.property} - [cds. test](cds-test) {.property} - [cds. utils](cds-utils) {.property}
Import classes and functions through the facade object only: ##### **Good:** > Source: /docs/node.js/cds-facade#good ```ts const { Request } = require('@sap/cds') // [!code ++] ``` ##### **Bad:** > Source: /docs/node.js/cds-facade#bad Never code against paths inside `@sap/cds/`: ```ts const Request = require('@sap/cds/lib/.../Request') // [!code --] ``` ## Builtin Types & Classes > Source: /docs/node.js/cds-facade#builtin-types--classes Following properties provide access to the classes and prototypes of [linked CSNs](cds-reflect). ### cds. builtin .types > Source: /docs/node.js/cds-facade#cds-builtin-types ### cds. linked .classes > Source: /docs/node.js/cds-facade#cds-linked-classes The following top-level properties are convenience shortcuts to their counterparts in `cds.linked.classes`.
For example: ```js cds.entity === cds.linked.classes.entity ``` - [cds. Association](cds-reflect#cds-association) {.property} - [cds. Composition](cds-reflect#cds-linked-classes) {.property} - [cds. entity](cds-reflect#cds-entity) {.property} - [cds. event](cds-reflect#cds-linked-classes) {.property} - [cds. type](cds-reflect#cds-linked-classes) {.property} - [cds. array](cds-reflect#cds-linked-classes) {.property} - [cds. struct](cds-reflect#cds-struct) {.property} - [cds. service](cds-reflect#cds-struct) {.property} ## Core Classes > Source: /docs/node.js/cds-facade#core-classes ### cds. Service > Source: /docs/node.js/cds-facade#cds-service - [cds. ApplicationService](app-services) {.class} - [cds. RemoteService](remote-services) {.class} - [cds. MessagingService](messaging) {.class} - [cds. DatabaseService](databases) {.class} - [cds. SQLService](databases) {.class} ### cds. EventContext > Source: /docs/node.js/cds-facade#cds-eventcontext ### cds. Event > Source: /docs/node.js/cds-facade#cds-event ### cds. Request > Source: /docs/node.js/cds-facade#cds-request ### cds. User > Source: /docs/node.js/cds-facade#cds-user ## Properties > Source: /docs/node.js/cds-facade#properties Following are properties which are not references to submodules. ### cds. version > Source: /docs/node.js/cds-facade#cds-version Returns the version of the `@sap/cds` package from which the current instance of the `cds` facade module was loaded. For example, use that to write version specific code: ```js const [major, minor] = cds.version.split('.').map(Number) if (major < 6) // code for pre cds6 usage ``` ### cds. home > Source: /docs/node.js/cds-facade#cds-home Returns the pathname of the `@sap/cds` installation folder from which the current instance of the `cds` facade module was loaded. ```js [dev] cds repl > cds.home // [!code focus] ~/.npm/lib/node_modules/@sap/cds ``` ### cds. root > Source: /docs/node.js/cds-facade#cds-root Returns the project root that is used by all CAP runtime file access as the root directory. By default this is `process.cwd()`, but can be set to a different root folder. It's guaranteed to be an absolute folder name. ```js // Print current project's package name let package_json = path.join (cds.root,'package.json') // [!code focus] let { name, description } = require(package_json) console.log ({ name, description }) ``` ### cds. cli > Source: /docs/node.js/cds-facade#cds-cli Provides access to the parsed effective `cds` cli command and arguments. Example: If you would add log respective output in a project-local `server.js`, and start your server with `cds watch`, you'd see an output like this: ```js Trace : { command: 'serve', argv: [ 'all' ], options: { 'with-mocks': true, 'in-memory?': true } } ``` For example, [`cds-plugins`](cds-serve) can use that to plug into different parts of the framework for different commands being executed. Known values for `cds.cli.command` are `add`, `build`, `compile`, `deploy`, `import`, `init`, `serve`. `cds watch` is normalized to `serve`. ### cds. entities > Source: /docs/node.js/cds-facade#cds-entities Convenience shortcut to [`cds.model.entities`](cds-reflect#-entities). Returns an iterable dictionary of entity definitions in the model, which can be used like this: - Accessing named entities directly: ```js const { Books, Authors } = cds.entities //> `Books` and `Authors` are linked CSN definitions of entities ``` - Iterating _all_ entities in the model: ```js for (let each of cds.entities) //> `each` is a linked CSN definition of an entity ``` - Iterating entities in a given namespace: ```js for (let each of cds.entities ('sap.capire.bookshop')) //> `each` is a linked CSN definition of an entity ``` ### cds. env > Source: /docs/node.js/cds-facade#cds-env Provides access to the effective configuration of the current process, transparently from various sources, including the local _package.json_ or _.cdsrc.json_, service bindings and process environments. ```js [dev] cds repl > cds.env.requires.auth // [!code focus] { kind: 'basic-auth', strategy: 'mock', users: { alice: { tenant: 't1', roles: [ 'admin' ] }, bob: { tenant: 't1', roles: [ 'cds.ExtensionDeveloper' ] }, # ..., '*': true }, tenants: { t1: { features: [ 'isbn' ] }, t2: { features: '*' } } } ``` [Learn more about `cds.env`](cds-env){.learn-more} ### cds. requires > Source: /docs/node.js/cds-facade#cds-requires ... is an overlay and convenience shortcut to [`cds.env.requires`](#cds-env), with additional entries for services with names different from the service definition's name in cds models. For example, given this service definition: ```cds service ReviewsService {} ``` ... and this configuration: ```jsonc { "cds": { "requires": { "db": "sqlite", "reviews" : { // lookup name "service": "ReviewsService" // service definition's name } } }} ``` You can access the entries as follows: ```js [dev] cds repl > cds.env.requires.db //> the effective config for db > cds.env.requires.reviews //> the effective config for reviews > cds.env.requires.ReviewsService //> undefined ``` ```js [dev] cds repl > cds.requires.db //> the effective config for db > cds.requires.reviews //> the effective config for reviews > cds.requires.ReviewsService //> same as cds.requires.reviews ``` The additional entries are useful for code that needs to securely access the service by cds definition name. Note: as `cds.requires` is an overlay to `cds.env.requires`, it inherits all properties from there via prototype chain. In effect using operations which only look at *own* properties, like `Object.keys()` behave different than for `cds.env.requires`: ```js [dev] cds repl > Object.keys(cds.env.requires) //> [ 'db', 'reviews' ] > Object.keys(cds.requires) //> [ 'ReviewsService' ] ``` ### cds. services > Source: /docs/node.js/cds-facade#cds-services A dictionary and cache of all instances of [`cds.Service`](core-services) constructed through [`cds.serve()`](cds-serve), or connected to by [`cds.connect()`](cds-connect). It's an *iterable* object, so can be accessed in the following ways: ```js let { CatalogService, db } = cds.services let all_services = [ ... cds.services ] for (let k in cds.services) //... k is a services's name for (let s of cds.services) //... s is an instance of cds.Service ``` ### cds. context > Source: /docs/node.js/cds-facade#cds-context Provides access to common event context properties like `tenant`, `user`, `locale` as well as the current root transaction for automatically managed transactions. [Learn more about that in reference docs for `cds.tx`.](./cds-tx){.learn-more} ### cds. model > Source: /docs/node.js/cds-facade#cds-model The effective [CDS model](../cds/csn) loaded during bootstrapping, which contains all service and entity definitions, including required services. Many framework operations use that as a default where models are required. It is loaded in built-in `server.js` like so: ```js const csn = await cds.load('*') cds.model = cds.compile.for.nodejs(csn) ``` [Learn more about bootstrapping in `cds.server`.](./cds-serve){.learn-more} ### cds. app > Source: /docs/node.js/cds-facade#cds-app Bootstrapping constructs the [express.js Application object](https://expressjs.com/en/api.html#app). Several framework operations use it to add express handlers or middlewares. The built-in `server.js` file initializes it: ```js cds.app = require('express')() ``` Starting from version 9.7.0, CAP Node.js supports version 5 of [`express`](https://expressjs.com/) in addition to version 4. With `express^5` support, `express` became a standard dependency (instead of an [_optional peer dependency_](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#peerdependencies)) with an open range for both major versions 4 and 5 (that is, `^4 || ^5`). If you don't require a specific version (for example, due to custom middleware), you can remove your own `express` dependency and automatically receive the latest version of `express` that is compatible with all your (transitive) dependencies. :::tip Verify installed version of `express` With CLI command `npm ls express`, you can verify the installed version(s) of `express`. ```bash xtravels % npm ls express @capire/xtravels@1.0.0 └─┬ @sap/cds@9.7.0 └── express@5.2.1 ``` ::: For more information, refer to the [`express`](https://expressjs.com/) [_Moving to Express 5_](https://expressjs.com/en/guide/migrating-5) migration guide and the [LTS Timeline](https://expressjs.com/2025/03/31/v5-1-latest-release.html). [Learn more about bootstrapping in `cds.server`.](./cds-serve){.learn-more} ### cds. db > Source: /docs/node.js/cds-facade#cds-db A shortcut to [`cds.services.db`](#cds-services), the primary database connected to during bootstrapping. Many framework operations use that to address and interact with the primary database. In particular that applies to the global [`cds.ql`](cds-ql) statement objects. For example: ```js let books = await SELECT.from(Books) // is a shortcut for: let books = await cds.db.run ( SELECT.from(Books) ) ``` It is initialized in built-in `server.js` like so: ```js cds.db = await cds.connect.to('db') ``` [Learn more about bootstrapping in `cds.server`.](./cds-serve){.learn-more} ## Methods > Source: /docs/node.js/cds-facade#methods ### cds. error() > Source: /docs/node.js/cds-facade#cds-error ```ts function cds.error ( status? : number message : string | object, details? : object caller? : function ) ``` This is a helper to construct new errors in various ways: ```js let e = new cds.error ('message') let e = new cds.error ('message', { code, ... }) let e = new cds.error ({ message, code, ... }) ``` If called without `new` the error is thrown immediately allowing code like that: ```js let e = foo || cds.error (`Expected 'foo' to be truthy, but got: ${foo}`) ``` You can also use `cds.error` with tagged template strings: ```js let e = foo || cds.error `Expected 'foo' to be truthy, but got: ${foo}` ``` > In contrast to basic template strings, passed in objects are added using Node's `util.format()` instead of `toString()`. Method `cds.error.expected` allows to conveniently construct error messages as above: ```js let e = foo || cds.error.expected `${{foo}} to be truthy` ``` Optional argument `caller` can be a calling function to truncate the error stack. Default is `cds.error` itself, so it will never show up in the stacks. ### cds. exit() > Source: /docs/node.js/cds-facade#cds-exit Provides a graceful shutdown for running servers, by first emitting `cds.emit('shutdown')` with handlers allowed to be `async` functions. If not running in a server, it calls `process.exit()` ```js cds.on('shutdown', async()=> fs.promises.rm('some-file.json')) cds.on('shutdown', ()=> console.log('shutdown')) cds.exit() //> will rune above handlers before stopping the server ``` ## Lifecycle Events > Source: /docs/node.js/cds-facade#lifecycle-events The `cds` facade object is an [EventEmitter](https://nodejs.org/api/events.html#asynchronous-vs-synchronous), which frameworks emits events to, during the server bootstrapping process, or when we compile models. You can register event handlers using `cds.on()` like so: ```js twoslash // @noErrors const cds = require('@sap/cds') cds.on('bootstrap', ...) cds.on('served', ...) cds.on('listening', ...) ``` - [Learn more about Lifecycle Events emitted by `cds.compile`](cds-compile#lifecycle-events) {.learn-more} - [Learn more about Lifecycle Events emitted by `cds.server`](cds-server#lifecycle-events) {.learn-more} > [!warning] > As we're using Node's standard [EventEmitter](https://nodejs.org/api/events.html#asynchronous-vs-synchronous), > event handlers execute **synchronously** in the order they are registered, with `served` and `shutdown` > events as the only exceptions. # Parsing and Compiling Models > Source: /docs/node.js/cds-compile ## cds. compile (...) > Source: /docs/node.js/cds-compile#cds-compile- ```tsx function cds.compile ( model : '*', 'file:' | filenames[] | // source files CDL string | { CDL strings } // sources in memory , options : CSN_flavor | { flavor? : CSN_flavor, min? : boolean, docs? : boolean, locations? : boolean, messages? : [] } ) type CSN_flavor = 'parsed' | 'inferred' ``` This is the central function to compile models from files or in-memory sources to [CSN](../cds/csn). It supports different variants based on the type of the first argument `model` as outlined below. Depending on the variants, the method returns a Promise or a sync value. ### Compiling `.cds` files (async) > Source: /docs/node.js/cds-compile#compiling-cds-files-async If the first argument is either a string starting with `"file:"`, or an _array_ of filenames, these files are read and compiled to a single CSN asynchronously: ```js let csn = await cds.compile (['db','srv','app']) let csn = await cds.compile ('*') let csn = await cds.compile ('file:db') ``` > The given filenames are resolved to effective absolute filenames using [`cds.resolve`](#cds-resolve). > [!TIP] Use cds compile as CLI equivalent > The [`cds compile` CLI](../tools/cds-cli#cds-compile) is available as entry point to the functions described here. For example, `cds compile --to hana` maps to `cds.compile.to.hana` etc. ### Single in-memory sources > Source: /docs/node.js/cds-compile#single-in-memory-sources If a single string, not starting with `file:` is passed as first argument, it is interpreted as a CDL source string and compiled to CSN synchronously: ```js let csn = cds.compile (` using {cuid} from '@sap/cds/common'; entity Foo : cuid { foo:String } entity Bar as projection on Foo; extend Foo with { bar:String } `) ``` > Note: `using from` clauses are not resolved in this usage. ### Multiple in-memory sources > Source: /docs/node.js/cds-compile#multiple-in-memory-sources Finally, you can pass an object with multiple named CDL or CSN sources, which allows to also resolve `using from` clauses: ```js let csn = cds.compile ({ 'db/schema.cds': ` using {cuid} from '@sap/cds/common'; entity Foo : cuid { foo:String } `, 'srv/services.cds': ` using {Foo} from '../db/schema'; entity Bar as projection on Foo; extend Foo with { bar:String } `, '@sap/cds/common.csn': ` {"definitions":{ "cuid": { "kind": "aspect", "elements": { "ID": { "key":true, "type": "cds.UUID" } }} }} `, }) ``` ### Additional Options > Source: /docs/node.js/cds-compile#additional-options You can pass additional options like so: ```js let csn = await cds.compile('*',{ min:true, docs:true }) ``` | Option | Description | | ----------- | ------------------------------------------------------------ | | `flavor` | By default the returned CSN is in `'inferred'` flavor, which is an effective model, with all aspects, includes, extensions and redirects applied and all views and projections inferred. Specify `'parsed'` to only have single models parsed. | | `min` | Specify `true` to have [`cds.minify()`](#cds-minify) applied after compiling the models. | | `docs` | Specify `true` to have the all `/** ... */` doc comments captured in the CSN. | | `locations` | Specify `true` to have the all `$location` properties preserved in serialized CSN. | | `messages` | Pass an empty array to get all compiler messages collected in there. | ## cds. compile .to ... > Source: /docs/node.js/cds-compile#cds-compile-to- Following are a collection of model processors which take a CSN as input and compile it to a target output. They can be used in two API flavors: ```js let sql = cds.compile(csn).to.sql ({dialect:'sqlite'}) //> fluent let sql = cds.compile.to.sql (csn,{dialect:'sqlite'}) //> direct ``` ### .json() > Source: /docs/node.js/cds-compile#json ```tsx function cds.compile.to.json ( options: { indents : integer }) ``` Renders the given model to a formatted JSON string. Option `indents` is the indent as passed to `JSON.stringify`. ### .yaml() > Source: /docs/node.js/cds-compile#yaml Renders the given model to a formatted JSON or YAML string. ### .edm() > Source: /docs/node.js/cds-compile#edm ### .edmx() > Source: /docs/node.js/cds-compile#edmx Compiles and returns an OData v4 [EDM](https://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part3-csdl.html), respectively [EDMX](https://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part3-csdl.html) model object for the passed in model, which is expected to contain at least one service definition. Accepted `options` are the same [as documented for `cds.compile`](#additional-options), with one addition: If the model contains more than one service definition, use `{service:...}` option parameter to: * Either choose exactly one, for example, `{service:'Catalog'}` * Choose to return EDM objects for all, that means, `{service:'all'}` In case of the latter, a generator is returned that yields `[ edm, {file, suffix} ]` for each service. For example, use it as follows: ```js // for one service let edm = cds.compile.to.edm (csn, {service:'Catalog'}) console.log (edm) ``` ```js // for all services let all = cds.compile.to.edm (csn, {service:'all'}) for (let [edm,{file,suffix}] of all) console.log (file,suffix,edm) ``` ### .hdbtable() > Source: /docs/node.js/cds-compile#hdbtable Use [`cds.compile.to.hana`](#hana) instead. ### .hana() > Source: /docs/node.js/cds-compile#hana-since-version800-packagesapcds- Generates `hdbtable/hdbview` output. Returns a generator function that produces `[ content, {file} ]` for each artifact. The variable `content` contains the SQL DDL statements for the `.hdb*` artifacts, and `file` is the filename. For example, use it as follows: ```js const all = cds.compile.to.hana(csn); for (const [content, { file }] of all) { console.log(file, content); } ``` Additional data for `.hdbmigrationtable` files is calculated if a `beforeImage` parameter is passed in. This is only relevant for build tools to determine the actual migration table changes. ### .sql() > Source: /docs/node.js/cds-compile#sql Generates SQL DDL statements for the given model. The default returns an array with the generated statements. Accepted `options` are: - `dialect`: _'plain' \| 'sqlite' \| 'postgres' \| 'h2'_ → chooses the dialect to generate - `names`: _'plain' \| 'quoted'_ → allows to generate DDL using quoted names - `as`: _'str'_ → returns a string with concatenated DDL statements. Examples: ```js let ddls1 = cds.compile(csn).to.sql() let ddls2 = cds.compile(csn).to.sql({dialect:'plain'}) let script = cds.compile(csn).to.sql({as:'str'}) ``` ### .cdl() > Source: /docs/node.js/cds-compile#cdl Reconstructs [CDL](../cds/cdl.md) source code for the given csn model. ### .asyncapi() > Source: /docs/node.js/cds-compile#asyncapi Convert the CSN file into an AsyncAPI document: ```js const doc = cds.compile.to.asyncapi(csn_file) ``` ## cds. load (files) > Source: /docs/node.js/cds-compile#cds-load-files Loads and parses a model from one or more files into a single effective model. It's essentially a [shortcut to `cds.compile ([...])`](#cds-compile-). In addition emits event `cds 'loaded'`. Declaration: ```tsx function cds.load ( files : filename || filenames[] options : {...} //> as in cds.compile ) ``` Usage examples: ```js // load a model from a single source const csn = await cds.load('my-model') ``` ```js // load a a model from several sources const csn = await cds.load(['db','srv']) ``` > The given filenames are resolved using [`cds.resolve()`](#cds-resolve). > > Note: It's recommended to omit file suffixes to leverage automatic loading from precompiled _[CSN](../cds/csn)_ files instead of _[CDL](../cds/cdl.md)_ sources. ## cds. parse() > Source: /docs/node.js/cds-compile#cds-parse This is an API facade for a set of functions to parse whole [CDL](../cds/cdl) models, individual [CQL](../cds/cql) queries, or CQL expressions. The three main methods are offered as classic functions, as well as [tagged template string functions](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Template_literals). ### cds. parse. cdl() > Source: /docs/node.js/cds-compile#cds-parse-cdl Parses a source string in _[CDL](../cds/cdl)_ syntax and returns it as a parsed model according to the [_CSN spec_](../cds/csn). Supports tagged template strings as well as plain string arguments. It's essentially a [shortcut to `cds.compile (..., {flavor:'parsed'})`](#cds-compile-). Examples: ```js let csn = cds.parse.cdl (`entity Foo{}`) let csn = cds.parse.cdl `entity Foo{}` let csn = cds.parse `entity Foo{}` //> shortcut to the above ``` ### cds. parse. cql() > Source: /docs/node.js/cds-compile#cds-parse-cql Parses a source string in _[CQL](../cds/cql)_ syntax and returns it as a parsed query according to the [_CQN spec_](../cds/cqn). Supports tagged template strings as well as plain string arguments. Examples: ```js let cqn = cds.parse.cql (`SELECT * from Foo`) let cqn = cds.parse.cql `SELECT * from Foo` ``` ### cds. parse. expr() > Source: /docs/node.js/cds-compile#cds-parse-expr Parses a source string in CQL expression syntax and returns it as a parsed expression according to the [_CQN Expressions spec_](../cds/cxn#operators). Supports tagged template strings as well as plain string arguments. Examples: ```js [dev] cds repl > let cxn = cds.parse.expr (`foo.bar > 9`) > let cxn = cds.parse.expr `foo.bar > 9` //> both return: {xpr:[ {ref:['foo', 'bar']}, '>', {val:9} ] } ``` ### cds. parse. xpr() > Source: /docs/node.js/cds-compile#cds-parse-xpr Convenience shortcut to `cds.parse.expr(x).xpr` Example: ```js [dev] cds repl > let xpr = cds.parse.xpr (`foo.bar > 9`) // [!code focus] [ {ref:['foo', 'bar']}, '>', {val:9} ] ``` ### cds. parse. ref() > Source: /docs/node.js/cds-compile#cds-parse-ref Convenience shortcut to `cds.parse.expr(x).ref` Example: ```js [dev] cds repl > let ref = cds.parse.ref (`foo.bar`) // [!code focus] ['foo', 'bar'] ``` ## cds. minify() > Source: /docs/node.js/cds-compile#cds-minify Minifies a given CSN model by removing all unused1 types and aspects, as well all entities tagged with `@cds.persistence.skip:'if-unused'`. Use it like that: ```js let csn = await cds.load('*').then(cds.minify) ``` Using `cds.minify()` is particularly relevant, when reuse models are in the game. For example, this applies to [`@sap/cds/common`](../cds/common). In there, all code list entities like *Countries*, *Currencies* and *Languages* are tagged with `@cds.persistence.skip:'if-unused'`. For example, run the CLI wrapper `cds minify` in *cap/samples/bookshop*: ```sh [bookshop] cds minify "*" --dry ``` ... which generates this output, informing which definitions got retained and skipped:
 Keep:

  • AdminService AdminService.Books 
  •• sap.capire.bookshop.Books 
  ••• User 
  ••• sap.capire.bookshop.Authors 
  •••• managed 
  ... more

 Skip:

   - Language 
   - Country 
   - Timezone 
   - sap.common 
   - sap.common.Countries 
   - sap.common.Timezones 
   - cuid 
   - temporal 
   - sap.common.Countries.texts 
   - sap.common.Timezones.texts 
1 Unused in that context means, not reachable from roots services and — non-skipped — entities in the model. ## cds. resolve() > Source: /docs/node.js/cds-compile#cds-resolve Resolves the given source paths by fetching matching model source files, that is _.cds_ or _.csn_ files, including models for required services. In detail, it works as follows: 1. If `paths` is `'*'`: `paths` = [ ...`cds.env.roots`, ...`cds.requires..model` ] 2. If `paths` is a single string: `paths` = [ `paths` ] 3. For `` in `paths`: ... - if _\.csn|cds_ exists → use it - if _\/index.csn|cds_ exists → use it - if _\_ is a folder → use all _.csn|cds_ found in there [Learn more about `cds.env`](cds-env){.learn-more} In effect, it resolves and returns an array with the absolute filenames of the root cds model files to be used to invoke the compiler. If no files are found, `undefined` is returned. Examples: ```js [dev] cds repl > cds.env.folders // = folders db, srv, app by default > cds.env.roots // + schema and services in cwd > cds.resolve('*',false) // + models in cds.env.requires > cds.resolve('*') // > the resolved existing files > cds.resolve(['db']) // > the resolved existing files > cds.resolve(['db','srv']) // > the resolved existing files > cds.resolve('none') // > undefined ``` > Try this in cds repl launched from your project root to see that in action. ## Lifecycle Events > Source: /docs/node.js/cds-compile#lifecycle-events The following [lifecycle events](cds-facade#lifecycle-events) are emitted via the `cds` facade object during the server bootstrapping process. You can register event handlers using `cds.on()` like so: ```js const cds = require('@sap/cds') cds.on('compile.for.runtime', ...) cds.on('compile.to.dbx', ...) cds.on('compile.to.edmx', ...) ``` > [!warning] > As we're using Node's standard [EventEmitter](https://nodejs.org/api/events.html#asynchronous-vs-synchronous), > event handlers execute **synchronously** in the order they are registered. > [!tip] Note that several of these events could be emitted for the same model, so ensure your handlers are idempotent. ### compile.for.runtime > Source: /docs/node.js/cds-compile#compileforruntime Emitted every time before the model is compiled for usage in Node.js or Java runtime. This is the right place to, for example, add custom elements required at runtime. ### compile.to.dbx > Source: /docs/node.js/cds-compile#compiletodbx Emitted every time before database-specific artifacts, that is, SQL DDL scripts, are generated from the model. This is the right place to, for example, add custom elements required in your persistence. ### compile.to.edmx > Source: /docs/node.js/cds-compile#compiletoedmx Emitted every time before the model is compiled to edmx. This is the right place to add custom transformations to the model, for example, to add custom Fiori annotations. # Reflecting CDS Models > Source: /docs/node.js/cds-reflect Find here information about reflecting parsed CDS models in CSN representation. [def]: ../cds/csn#definitions [defs]: ../cds/csn#definitions ## cds. linked ([csn](../cds/csn)) > Source: /docs/node.js/cds-reflect#cds-linked-csncdscsn [`cds.linked`]: #cds-linked Method `cds.linked` (or `cds.reflect` which is an alias to the same method) turns a given parsed model into an instance of [class `LinkedCSN`](#linked-csn), and all definitions within into instances of [class `LinkedDefinition`](#any), recursively. Declaration: ```tsx function* cds.linked (csn: CSN | string) => LinkedCSN ``` A typical usage is like that: ```js let csn = cds.load('some-model.cds') let linked = cds.linked(csn) // linked === csn ``` Instead of a already compiled CSN, you can also pass a string containing CDL source code: ```js let linked = cds.linked` entity Books { key ID: UUID; title: String; author: Association to Authors; } entity Authors { key ID: UUID; name: String; } ` ``` The passed in model gets **modified**, and the returned linked model is actually the modified passed-in csn. The operation is **idempotent**, that is, you can repeatedly invoke it on already linked models with zero overhead. ## LinkedCSN > Source: /docs/node.js/cds-reflect#linkedcsn [reflected model]: #linked-csn [linked model]: #linked-csn [`LinkedCSN`]: #linked-csn Models passed through [`cds.linked`] become instances of this class. ### . is_linked > Source: /docs/node.js/cds-reflect#-islinked A tag property which is `true` for linked models. {.indent} ### . definitions > Source: /docs/node.js/cds-reflect#-definitions The [CSN definitions](../cds/csn#definitions) of the model, turned into an instance of [`LinkedDefinitions`]. {.indent} ### . services > Source: /docs/node.js/cds-reflect#-services Convenient shortcut to access all *[service](../cds/cdl#services)* definitions in a model. The returned value is an array of all service definitions, with additional non-enumerable properties to access the service definitions by name. Example: ```js let m = cds.linked` service CatalogService { ... } service AdminService { ... } ` // Object nature let { CatalogService, AdminService } = m.services // Array nature for (let each of m.services) console.log (each.name) ``` ### . entities > Source: /docs/node.js/cds-reflect#-entities Convenient shortcut to access all *[entity](../cds/cdl#entities)* definitions in a model. The returned value is a function that allows to specify a namespace to fetch all matching entity definitions, with initial properties for all entity definitions in the model. For example, given the following model: ```js let m = cds.linked` namespace my.bookshop; entity Books {} entity Authors {} service CatalogService { entity Books as projection on my.bookshop.Books; entity Authors as projection on my.bookshop.Authors; } ` ``` We can use it **as a getter** with object destructuring, to retrieve named entities from the whole model, by their fully qualified names like that: ```js const { 'my.bookshop.Books':Books, 'my.bookshop.Authors':Authors } = m.entities ``` We can also call it **as a function** to specify a namespace once, and retrieve all entities within that namespace, with object destructuring as well: ```js const { Books, Authors } = m.entities ('my.bookshop') ``` In both cases, the returned is an instance of [`LinkedDefinitions`](#iterable), which also allows iterating over entity definitions like that: ```js for (let each of m.entities) console.log (each.name) for (let each of m.entities('my.bookshop')) console.log (each.name) ``` > [!info] > Note: In the dictionaries returned by `.entities` there are no entries for [`.texts`](#-texts) entities, as these are generated, and hence living in a shadow world. They did show up in former releases, which caused a lot of confusion, and was fixed since cds 9.6. > They are always accessible via the main entity's [`.texts`](#-texts) property, for example `Books.texts`. ### each() > Source: /docs/node.js/cds-reflect#each ```tsx function* lm.each ( filter : string | def => true/false, defs? : linked_definitions ) ``` Fetches definitions matching the given filter, returning an iterator on them. ```js let m = cds.reflect (csn) for (let d of m.each('entity')) { console.log (d.kind, d.name) } ``` The first argument **_filter_** specifies a filter to match definitions, which can be one of: - a `string` referring to a _kind_ of definition - a `function` returning `true` or `false` Derived kinds are supported, for example, `m.each('struct')` matches structs as well as entities; kind `'any'` matches all. The second optional argument **_[defs]_** allows to specify the definitions to fetch in, defaults to `this.definitions`. ### all() > Source: /docs/node.js/cds-reflect#all ```tsx function lm.all ( filter : string | def => true/false, defs? : linked_definitions ) ``` Convenience shortcut to [`[... model.each()]`](#each), for example, the following are equivalent: ```js m.all('entity') //> using shortcut [...m.each('entity')] //> using spread operator ``` ### find() > Source: /docs/node.js/cds-reflect#find ```tsx function lm.find ( filter : string | def => true/false, defs? : linked_definitions ) ``` Convenience shortcut to fetch definitions matching the given filter, returning the first match, if any. For example: ```js let service = m.find('service') ``` The implementation uses to [`.each()`](#each) as follows: ```js for (let any of m.each('service')) return any ``` ### foreach() > Source: /docs/node.js/cds-reflect#foreach ```tsx function lm.foreach ( filter : def => true/false | string, visitor : def => {}, defs? : linked_definitions ) ``` Calls the visitor for each definition matching the given filter. `foreach` iterates through the passed in defs only, `forall` in addition walks through all nested element definitions hierarchically. * `filter` / `kind` — the filter or kind used to match definitions [→ see _.each(x)_](#each) * `visitor` — the callback function * `defs` — the definitions to fetch in, default: `this.definitions` Examples: ```js // print the names of all services let m = cds.reflect(csn) m.foreach ('service', s => console.log(s.name)) ``` ```js // print the names of all Associations in Books element let { Books } = m.entities() m.foreach ('Association', a => console.log(a.name), Books.elements) ``` ## LinkedDefinitions > Source: /docs/node.js/cds-reflect#linkeddefinitions [`LinkedDefinitions`]: #iterable All objects of a linked model containing CSN definitions are instances of this class. For example, that applies to: - *`cds.model` [.definitions](#-definitions), [.services](#-services), [.entities](#-entities)* - *`cds.service` [.entities](#-entities-1), [.events](#-events), [.actions](#-actions-1)* - *`cds.entity` [.keys](#-keys), [.associations](#-associations), [.compositions](#-compositions), [.actions](#-actions)* - *`cds.struct` [.elements](#-elements)* (hence also *`cds.entity` .elements*) - *`cds.Association` [.foreignKeys](#-foreignkeys)* Instances of `LinkedDefinitions` allow both, object-style access, as well as array-like access. For example: ```js let linked = cds.linked (model) let { Books, Authors } = linked.entities // object-like let [ Books, Authors ] = linked.entities // array-like ``` > Note: Orders of definitions could change, so you should always prefer object destructuring over array destructuring. The array-like nature also allows using these shortcuts in `for..of` loops, of course. Which means, you can do that: ```js for (let each of linked.definitions) console.log (each.name) ``` ... instead of iterating definitions using `for..in` loops like that: ```js for (let each in linked.definitions) { let d = linked.definitions [each] console.log (d.name) } ``` Each entry in an instance of `LinkedDefinitions` is a [`LinkedDefinition`]. ## LinkedDefinition > Source: /docs/node.js/cds-reflect#linkeddefinition [`LinkedDefinition`]: #any All [`cds.linked`] definitions are instances of this class, or subclasses thereof. It is accessible through [`cds.linked.classes.any`](#cds-linked-classes). ### . is_linked > Source: /docs/node.js/cds-reflect#-islinked-1 A tag property which is `true` for all linked definitions. {.indent} ### . name > Source: /docs/node.js/cds-reflect#-name The linked definition's fully qualified name as a non-enumerable property. {.indent} ### . kind > Source: /docs/node.js/cds-reflect#-kind The linked definition's resolved kind as a non-enumerable property. One of: - `'context'` - `'service'` - `'entity'` - `'type'` - `'aspect'` - `'event'` - `'element'` - `'annotation'` ... as documented in the [CSN specification](../cds/csn#definitions). #### *instanceof* > Source: /docs/node.js/cds-reflect#instanceof You can use JavaScript's standard `instanceof` operator in combination with the built-in classes to check a linked definition's type: ```js let { Foo } = cds.linked(csn).entities if (Foo instanceof cds.entity) console.log ("it's an entity") ``` ## cds. service > Source: /docs/node.js/cds-reflect#cds-service All *[service](../cds/cdl#services)* definitions in a linked model are instances of this class. ```tsx class cds.service extends cds.context {...} ``` ### . is_service > Source: /docs/node.js/cds-reflect#-isservice A tag property which is `true` for linked entity definitions. {.indent} ### . entities > Source: /docs/node.js/cds-reflect#-entities-1 ### . events > Source: /docs/node.js/cds-reflect#-events ### . actions > Source: /docs/node.js/cds-reflect#-actions These properties are convenience shortcuts to access a service definition's exposed [*entity*](../cds/cdl#entities), [*type*](../cds/cdl#types), [*event*](../cds/cdl#events), [*action* or *function*](../cds/cdl#actions) definitions.
Their values are [`LinkedDefinitions`]. {.indent} ## cds. entity > Source: /docs/node.js/cds-reflect#cds-entity All entity definitions in a linked model are instances of this class. ```tsx class cds.entity extends cds.struct {...} ``` > As `cds.entity` is a subclass of [`cds.struct`](#cds-struct) it also inherits all methods from that. ### . is_entity > Source: /docs/node.js/cds-reflect#-isentity A tag property which is `true` for linked entity definitions. {.indent} ### . keys > Source: /docs/node.js/cds-reflect#-keys ### . associations > Source: /docs/node.js/cds-reflect#-associations ### . compositions > Source: /docs/node.js/cds-reflect#-compositions ### . actions > Source: /docs/node.js/cds-reflect#-actions-1 These properties are convenient shortcuts to access an entity definition's declared [*keys*](../cds/cdl#entities), *[Association](../cds/cdl#associations)* or *[Composition](../cds/cdl#associations)* elements, as well as [*bound action* or *function*](../cds/cdl#bound-actions) definitions.
Their values are [`LinkedDefinitions`]. {.indent} ### . texts > Source: /docs/node.js/cds-reflect#-texts If the entity has *[localized](../guides/uis/localized-data)* elements, this property is a reference to the respective generated `.texts` entity. If not, this property is undefined. {.indent} ### . drafts > Source: /docs/node.js/cds-reflect#-drafts If draft is enabled, a definition to easily refer to *[draft](../guides/uis/fiori#draft-support)* data for the current entity is returned. {.indent} ## cds. struct > Source: /docs/node.js/cds-reflect#cds-struct This is the base class of *[struct](../cds/cdl#structured-types)* elements and types, *[aspects](../cds/cdl#aspects)*, and *[entities](../cds/cdl#entities)*. ```tsx class cds.struct extends cds.type {...} ``` ### . is_struct > Source: /docs/node.js/cds-reflect#-isstruct A tag property which is `true` for linked struct definitions (types and elements).
It is also `true` for linked entity definitions, that is, instances of as [`cds.entity`](#cds-entity). {.indent} ### . elements > Source: /docs/node.js/cds-reflect#-elements The entity's declared elements as [documented in the CSN Specification](../cds/csn#entity-definitions)
as an instance of [`LinkedDefinitions`]. { .indent} ## cds. Association > Source: /docs/node.js/cds-reflect#cds-association All linked definitions of type `Association` or `Composition`, including elements, are instances of this class. Besides the properties specified for [Associations in CSN](../cds/csn#associations), linked associations provide the following reflection properties... ### . _target > Source: /docs/node.js/cds-reflect#-target A reference to the association's resolved linked target definition. {.indent} ### . isAssociation > Source: /docs/node.js/cds-reflect#-isassociation A tag property which is `true` for all linked Association definitions, including Compositions. {.indent} ### . isComposition > Source: /docs/node.js/cds-reflect#-iscomposition A tag property which is `true` for all linked Composition definitions. {.indent} ### . is2one / 2many > Source: /docs/node.js/cds-reflect#-is2one--2many Convenient shortcuts to check whether an association definition has to-one or to-many cardinality. { .indent} ### . keys > Source: /docs/node.js/cds-reflect#-keys-1 The declared or derived foreign keys. As specified in [CSN spec](../cds/csn#assoc-keys) this is a *projection* of the association target's elements. {.indent} ### . foreignKeys > Source: /docs/node.js/cds-reflect#-foreignkeys The effective foreign keys of [*managed* association](../cds/cdl#managed-associations) as linked definitions.
The value is an instance of [`LinkedDefinitions`]. {.indent} ## cds. linked .classes > Source: /docs/node.js/cds-reflect#cds-linked-classes This property gives you access to the very roots of `cds`'s type system. When a model is passed through [`cds.linked`] all definitions effectively become instances of one of these classes. In essence they are defined as follows: ```js class any {...} class context extends any {...} cds.service = class service extends context {...} cds.type = class type extends any {...} class scalar extends type {...} class boolean extends scalar {...} class number extends scalar {...} class date extends scalar {...} class string extends scalar {...} cds.array = class array extends type {...} cds.struct = class struct extends type {...} cds.entity = class entity extends struct {...} cds.event = class event extends struct {...} cds.Association = class Association extends type {...} cds.Composition = class Composition extends Association {...} ``` > A few prominent ones of the above classes are available through top-level shortcuts as indicated by the `cds. =` prefixes in the above pseudo code, find more details on these in the following sections. For example, you can use these classes as follows: ```js let m = cds.linked` entity Books { author: Association to Authors; } entity Authors { key ID: UUID; } `) let { Books, Authors } = m.entities let isEntity = Books instanceof cds.entity let keys = Books.keys let { author } = Books.elements if (author.is2many) ... ``` #### mixin() > Source: /docs/node.js/cds-reflect#mixin Provided a convenient way to enhance one or more of the builtin classes with additional methods. Use it like that: ```js const cds = require ('@sap/cds') // simplistic csn2cdl enablement cds.linked.classes .mixin ( class type { toCDL(){ return `${this.kind} ${this.name} : ${this.typeAsCDL()};\n` } typeAsCDL(){ return `${this.type.replace(/^cds\./,'')}` } }, class struct { typeAsCDL() { return `{\n${ Object.values(this.elements).map ( e => ` ${e.toCDL()}` ).join('')}}`} }, class entity extends cds.struct { typeAsCDL() { return ( this.includes ? this.includes+' ' : '' ) + super.typeAsCDL() } }, class Association { typeAsCDL(){ return `Association to ${this.target}` } }, ) // test drive let m = cds.linked` entity Books : cuid { title:String; author: Association to Authors } entity Authors : cuid { name:String; } aspect cuid : { key ID:UUID; } ` m.foreach (d => console.log(d.toCDL())) ``` ## cds. builtin. types > Source: /docs/node.js/cds-reflect#cds-builtin-types [`cds.builtin.types`]: #cds-builtin-types This property gives you access to all prototypes of the builtin classes as well as to all linked definitions of the [builtin pre-defined types](../cds/types). The resulting object is in turn like the `definitions` in a [`LinkedCSN`]. Actually, at runtime CDS is in fact bootstrapped out of this using core [CSN](../cds/csn) object structures and [`cds.linked`] techniques. Think of it to be constructed as follows: ```js cds.builtin.types = cds.linked` using from './roots'; context cds { type UUID : String(36); type Boolean : boolean; type Integer : number; type UInt8 : Integer; type Int16 : Integer; type Int32 : Integer; type Int64 : Integer; type Integer64 : Integer; type Decimal : number; type Double : number; type Date : date; type Time : date; type DateTime : date; type Timestamp : date; type String : string; type Binary : string; type LargeString : string; type LargeBinary : string; type Map : struct; } `.definitions ``` With `./roots` being this in-memory CSN: ```js const { any, context, service , type, scalar, string, number, boolean, date, array, struct, entity, event, aspect Association, Composition } = cds.linked.classes const roots = module.exports = {definitions:{ any: new any, context: new context ({type:'any'}), type: new type ({type:'any'}), scalar: new scalar ({type:'type'}), string: new string ({type:'scalar'}), number: new number ({type:'scalar'}), boolean: new boolean ({type:'scalar'}), date: new date ({type:'scalar'}), array: new array ({type:'type'}), struct: new struct ({type:'type'}), entity: new entity ({type:'struct'}), event: new event ({type:'struct'}), aspect: new aspect ({type:'struct'}), Association: new Association ({type:'type'}), Composition: new Composition ({type:'Association'}), service: new service ({type:'context'}), }} ``` > Indentation indicates inheritance. # Bootstrapping Servers > Source: /docs/node.js/cds-server CAP Node.js servers are bootstrapped through a [built-in `server.js` module](#built-in-serverjs), which can be accessed through [`cds.server`](#cds-server). You can plug-in custom logic to the default bootstrapping choreography using a [custom `server.js`](#custom-server-js) in your project. ## CLI Command `cds serve` > Source: /docs/node.js/cds-server#cli-command-cds-serve A Node.js CAP server process is usually started with the `cds serve` CLI command, with `cds run` and `cds watch` as convenience variants. **For deployment**, when the `@sap/cds-dk` package providing the `cds` CLI executable is not available, use the `cds-serve` binary provided by the `@sap/cds` package: ```json { "scripts": { "start": "cds-serve" } } ``` ## Built-in `server.js` > Source: /docs/node.js/cds-server#built-in-serverjs The built-in `server.js` constructs an [express.js app](cds-facade#cds-app), and bootstraps all CAP services using [`cds.connect`](cds-connect) and [`cds.serve`](cds-serve). Its implementation essentially is as follows: ```js twoslash const cds = require('@sap/cds') module.exports = async function cds_server(options) { // prepare express app const o = { ...options, __proto__:defaults } const app = cds.app = o.app || require('express')() cds.emit ('bootstrap', app) // mount static resources and middlewares if (o.cors) app.use (o.cors) //> if not in prod if (o.health) app.get ('/health', o.health) if (o.static) app.use (express.static (o.static)) //> defaults to ./app if (o.favicon) app.use ('/favicon.ico', o.favicon) //> if none in ./app if (o.index) app.get ('/',o.index) //> if none in ./app and not in prod // load and prepare models const csn = await cds.load('*') .then (cds.minify) cds.model = cds.compile.for.nodejs (csn) cds.emit ('loaded', cds.model) // connect to essential framework services if (cds.requires.db) cds.db = await cds.connect.to ('db') .then (_init) if (cds.requires.messaging) await cds.connect.to ('messaging') // serve all services declared in models await cds.serve ('all') .in (app) await cds.emit ('served', cds.services) // start http server const port = o.port || process.env.PORT || 4004 return app.server = app.listen (port) } ``` ### cds. server() > Source: /docs/node.js/cds-server#cds-server This is essentially a shortcut getter to `require('@sap/cds/server')`, that is, it loads and returns the [built-in `server.js`](#built-in-serverjs) implementation. You'd mainly use this in [custom `server.js`](#custom-server-js) to delegate to the default implementation, [as shown below](#override-cdsserver). ### cds. app > Source: /docs/node.js/cds-server#cds-app The express.js `app` constructed by the server implementation. ## Custom `server.js` > Source: /docs/node.js/cds-server#custom-serverjs
The CLI command `cds serve` optionally bootstraps from project-local `./server.js` or `./srv/server.js`. ### Plug-in to Lifecycle Events > Source: /docs/node.js/cds-server#plug-in-to-lifecycle-events In custom `server.js`, you can plugin to all parts of `@sap/cds`. Most commonly you'd register own handlers to lifecycle events emitted to [the `cds` facade object](cds-facade) as below: ```js twoslash // @noErrors const cds = require('@sap/cds') // react on bootstrapping events... cds.on('bootstrap', ...) cds.on('served', ...) ``` ### Override `cds.server()` > Source: /docs/node.js/cds-server#override-cdsserver Provide an own bootstrapping function if you want to access and process the command line options. This also allows you to override certain options before delegating to the built-in `server.js`. In the example below, we construct the express.js app ourselves and fix the models to be loaded. ```js twoslash // @noErrors const cds = require('@sap/cds') // react on bootstrapping events... cds.on('bootstrap', ...) cds.on('served', ...) // handle and override options module.exports = (o)=>{ o.from = 'srv/precompiled-csn.json' o.app = require('express')() return cds.server(o) //> delegate to default server.js } ``` ::: tip `req` != `req` The `req` object in your express middleware is not the same as `req` in your CDS event handlers. ::: ## Lifecycle Events > Source: /docs/node.js/cds-server#lifecycle-events The following [lifecycle events](cds-facade#lifecycle-events) are emitted via the `cds` facade object during the server bootstrapping process. You can register event handlers using `cds.on()` like so: ```js const cds = require('@sap/cds') cds.on('bootstrap', ...) cds.on('served', ...) cds.on('listening', ...) ``` > [!warning] > As we're using Node's standard [EventEmitter](https://nodejs.org/api/events.html#asynchronous-vs-synchronous), > event handlers execute **synchronously** in the order they are registered, with `served` and `shutdown` > events as the only exceptions. ### bootstrap > Source: /docs/node.js/cds-server#bootstrap A one-time event, emitted immediately after the [express.js app](cds-facade#cds-app) has been created and before any middleware or CDS services are added to it. ```js twoslash // @noErrors const cds = require('@sap/cds') const express = require('express') cds.on('bootstrap', app => { // add your own middleware before any by cds are added // for example, serve static resources incl. index.html app.use(express.static(__dirname+'/srv/public')) }) ``` ### loaded > Source: /docs/node.js/cds-server#loaded Emitted whenever a CDS model got loaded using `cds.load()` ```js twoslash // @noErrors const cds = require('@sap/cds') cds.on('loaded', model => { /* ... */ }) ``` ### connect > Source: /docs/node.js/cds-server#connect Emitted for each service constructed through [`cds.connect`](cds-connect). ```js twoslash // @noErrors const cds = require('@sap/cds') cds.on('connect', service => { /* ... */ }) ``` ### serving > Source: /docs/node.js/cds-server#serving Emitted for each service constructed by [`cds.serve`](cds-serve). ```js twoslash // @noErrors const cds = require('@sap/cds') cds.on('serving', service => { /* ... */ }) ``` ### served > Source: /docs/node.js/cds-server#served A one-time event, emitted when all services have been bootstrapped and added to the [express.js app](cds-facade#cds-app). ```js twoslash // @noErrors const cds = require('@sap/cds') cds.on('served', async (services) => { // We can savely access service instances through the provided argument: const { CatalogService, db } = services // ... }) ``` This event supports _asynchronous_ event handlers. ### listening > Source: /docs/node.js/cds-server#listening A one-time event, emitted when the server has been started and is listening to incoming requests. ```js twoslash // @noErrors const cds = require('@sap/cds') cds.on('listening', ({ server, url }) => { /* ... */ }) ``` ### shutdown > Source: /docs/node.js/cds-server#shutdown A one-time event, emitted when the server is closed and/or the process finishes. Listeners can execute cleanup tasks. This event supports _asynchronous_ event handlers. ```js twoslash // @noErrors const cds = require('@sap/cds') cds.on('shutdown', async () => { /* ... */ }) ``` ## Configuration > Source: /docs/node.js/cds-server#configuration The behavior of the built-in `server.js` can be customized through the options documented in the following sections. ### CORS Middleware > Source: /docs/node.js/cds-server#cors-middleware The built-in CORS middleware can be enabled explicitly with cds.server.cors: true. By default, this is `false` if in production. [Learn more about best practices regarding **Cross-Origin Resource Sharing (CORS)**.](../node.js/best-practices.md#cross-origin-resource-sharing-cors) {.learn-more} ### Toggle Generic Index Page > Source: /docs/node.js/cds-server#toggle-generic-index-page The default generic _index.html_ page is not served if `NODE_ENV` is set to `production`. Set cds.server.index: true to activate explicitly also in production-like test environments, for example for deployed PoCs. You must not do this in real production environments! [See the **Generic *index.html*** page in action.](../get-started/bookshop#generic-indexhtml) {.learn-more} ### Maximum Request Body Size > Source: /docs/node.js/cds-server#maximum-request-body-size There are two ways to restrict the maximum request body size of incoming requests, globally for all endpoints and for individual services. If the payload exceeds the configured value, the request is rejected with _413 - Payload too large_. The configured values are passed through to the underlying Express body parser middlewares. Therefore, the default limit is _100kb_, as this is the default of the Express built-in [body parsers](https://expressjs.com/en/api.html#express.json). The maximum request body size can be limited globally, for all services and protocols, using the configuration `cds.server.body_parser.limit`, like so: ```jsonc { "cds": { "server": { "body_parser": { "limit": "1mb" // also accepts b, kb, etc... } } } } ``` To restrict the maximum request body size of requests received by an individual service, the service specific annotation `@cds.server.body_parser.limit` can be used, like so: ```cds annotate AdminService with @cds.server.body_parser.limit: '1mb'; ``` This is useful when the expected request body sizes might vary for services within the application. If both the global configuration and the service specific annotation are set, the service specific annotation takes precedence for the respective service. ## See Also... > Source: /docs/node.js/cds-server#see-also The [`cds-plugin` package technique](cds-plugins) provides more options to customize server startup. # Serving Provided Services > Source: /docs/node.js/cds-serve ## cds. serve (...) > Source: /docs/node.js/cds-serve#cds-serve- Use `cds.serve()` to construct service providers from the service definitions in corresponding CDS models. Declaration: ```ts:no-line-numbers async function cds.serve ( service : 'all' | string | cds.Service | typeof cds.Service, options : { service = 'all', ... } ) .from ( model : string | CSN ) // default: cds.model .to ( protocol : string | 'rest' | 'odata' | 'odata-v2' | 'odata-v4' | ... ) .at ( path : string ) .in ( app : express.Application ) // default: cds.app .with ( impl : string | function | cds.Service | typeof cds.Service ) ``` ##### Common Usages: > Source: /docs/node.js/cds-serve#common-usages ```js const { CatalogService } = await cds.serve ('my-services') ``` ```js const app = require('express')() cds.serve('all') .in (app) ``` ##### Arguments: > Source: /docs/node.js/cds-serve#arguments * `name` specifies which service to construct a provider for; use `all` to construct providers for all definitions found in the models. ```js cds.serve('CatalogService') //> serve a single service cds.serve('all') //> serve all services found ``` You may alternatively specify a string starting with `'./'` or refer to a file name with a non-identifier character in it, like `'-'` below, as a convenient shortcut to serve all services from that model: ```js cds.serve('./reviews-service') //> is not an identifier through './' cds.serve('reviews-service') //> same as '-', hence both act as: cds.serve('all').from('./reviews-service') ``` The method returns a fluent API object, which is also a _Promise_ resolving to either an object with `'all'` constructed service providers, or to the single one created in case you specified a single service: ```js const { CatalogService, AdminService } = await cds.serve('all') const ReviewsService = await cds.serve('ReviewsService') ``` ##### Caching: > Source: /docs/node.js/cds-serve#caching The constructed service providers are cached in [`cds.services`](cds-facade#cds-services), which (a) makes them accessible to [`cds.connect`](cds-connect), as well as (b) allows us to extend already constructed services through subsequent invocation of [`cds.serve`](cds-serve). ##### Common Usages and Defaults > Source: /docs/node.js/cds-serve#common-usages-and-defaults Most commonly, you'd use `cds.serve` in a custom file to add all the services to your [express.js](https://expressjs.com) app as follows: ```js const app = require('express')() cds.serve('all').in(app) app.listen() ``` This uses these defaults for all options: | Option | Description | Default | |----------------------|---------------------------------|-----------------------------| | cds.serve ... | which services to construct | `'all'` services | | .from | models to load definitions from | `'./srv'` folder | | .in | express app to mount to | — none — | | .to | client protocol to serve to | `'fiori'` | | .at | endpoint path to serve at | [`@path`](#path) or `.name` | | .with | implementation function | `@impl` or `._source`.js | Alternatively you can construct services individually, also from other models, and also mount them yourself, as document in the subsequent sections on individual fluent API options. If you just want to add some additional middleware, it's recommended to bootstrap from a [custom `server.js`](cds-server#custom-server-js). ### .from (model) > Source: /docs/node.js/cds-serve#from--model Allows to determine the CDS models to fetch service definitions from, which can be specified as one of: - A filename of a single model, which gets loaded and parsed with [`cds.load`] - A name of a folder containing several models, also loaded with [`cds.load`] - The string `'all'` as a shortcut for all models in the `'./srv'` folder - An already parsed model in [CSN](../cds/csn) format The latter allows you to [`cds.load`] or dynamically construct models yourself and pass in the [CSN](../cds/csn) models, as in this example: ```js const csn = await cds.load('my-services.cds') cds.serve('all').from(csn)... ``` **If omitted**, `'./srv'` is used as default. ### .to (protocol) > Source: /docs/node.js/cds-serve#to--protocol Allows to specify the protocol through which to expose the service. Currently supported values are: * `'rest'` plain HTTP rest protocol without any OData-specific extensions * `'odata'` standard OData rest protocol without any Fiori-specific extensions * `'fiori'` OData protocol with all Fiori-specific extensions like Draft enabled **If omitted**, `'fiori'` is used as default. ### .at (path) > Source: /docs/node.js/cds-serve#at--path Allows to programmatically specify the mount point for the service. **Note** that this is only possible when constructing single services: ```js cds.serve('CatalogService').at('/cat') cds.serve('all').at('/cat') //> error ``` **If omitted**, the mount point is determined from annotation [`@path`](#path), if present, or from the service's lowercase name, excluding trailing _Service_. ```cds service MyService @(path:'/cat'){...} //> served at: /cat service CatalogService {...} //> served at: /catalog ``` ### .in ([express app](https://expressjs.com/api.html#app)) > Source: /docs/node.js/cds-serve#in--express-apphttpsexpressjscomapihtmlapp Adds all service providers as routers to the given [express app](https://expressjs.com/api.html#app). ```js const app = require('express')() cds.serve('all').in(app) app.listen() ``` ### .with (impl) > Source: /docs/node.js/cds-serve#with--impl Allows to specify a function that adds [event handlers] to the service provider, either as a function or as a string referring to a separate node module containing the function. ```js cds.serve('./srv/cat-service.cds') .with ('./srv/cat-service.js') ``` ```js cds.serve('./srv/cat-service') .with (srv => { srv.on ('READ','Books', (req) => req.reply([...])) }) ``` [Learn more about using impl annotations.](core-services#implementing-services){.learn-more} [Learn more about adding event handlers.](core-services#srv-on-before-after){.learn-more} **Note** that this is only possible when constructing single services: ```js cds.serve('CatalogService') .with (srv=>{...}) cds.serve('all') .with (srv=>{...}) //> error ``` **If omitted**, an implementation is resolved from annotation `@impl`, if present, or from a `.js` file with the same basename than the CDS model, for example: ```cds service MyService @(impl:'cat-service.js'){...} ``` ```sh srv/cat-service.cds #> CDS model with service definition srv/cat-service.js #> service implementation used by default ``` ## cds. middlewares > Source: /docs/node.js/cds-serve#cds-middlewares For each service served at a certain protocol, the framework registers a configurable set of express middlewares by default like so: ```js app.use (cds.middlewares.before, protocol_adapter) ``` The standard set of middlewares uses the following order: ```js cds.middlewares.before = [ context(), // provides cds.context trace(), // provides detailed trace logs when DEBUG=trace auth(), // provides cds.context.user & .tenant ctx_model(), // fills in cds.context.model, in case of extensibility ] ``` ::: warning _Be aware of the interdependencies of middlewares_ _ctx_model_ requires that _cds.context_ middleware has run before. _ctx_auth_ requires that _authentication_ has run before. ::: ### . context() > Source: /docs/node.js/cds-serve#-context This middleware initializes [cds.context](events#cds-context) and starts the continuation. It's required for every application. ### . trace() > Source: /docs/node.js/cds-serve#-trace The tracing middleware allows you to do a first-level performance analysis. It logs how much time is spent on which layer of the framework when serving a request. To enable this middleware, you can set for example the [environment variable](cds-log#debug-env-variable) `DEBUG=trace`. ### . auth() > Source: /docs/node.js/cds-serve#-auth [By configuring an authentication strategy](./authentication#strategies), a middleware is mounted that fulfills the configured strategy and subsequently adds the user and tenant identified by that strategy to [cds.context](events#cds-context). ### . ctx_model() > Source: /docs/node.js/cds-serve#-ctxmodel It adds the currently active model to the continuation. It's required for all applications using extensibility or feature toggles. ### .add(mw, pos?) > Source: /docs/node.js/cds-serve#addmw-pos Registers additional middlewares at the specified position. `mw` can be either of: - a function that returns an express middleware - an express middleware with the common _req_, _res_, _next_ arguments - an array of express middlewares `pos` specifies the index or a relative position within the middleware chain. If not specified, the middleware is added to the end. ```js cds.middlewares.add (mw, {at:0}) // to the front cds.middlewares.add (mw, {at:2}) cds.middlewares.add (mw, {before:'auth'}) cds.middlewares.add (mw, {after:'auth'}) cds.middlewares.add (mw) // to the end ```
### Custom Middlewares > Source: /docs/node.js/cds-serve#custom-middlewares The configuration of middlewares must be done programmatically before bootstrapping the CDS services, for example, in a [custom server.js](cds-server#custom-server-js). The framework exports the default middlewares itself and the list of middlewares which run before the protocol adapter starts processing the request. ```js cds.middlewares = { auth, context, ctx_model, errors, trace, before = [ context(), trace(), auth(), ctx_model() ] } ``` In order to plug in custom middlewares, you can override the complete list of middlewares or extend the list programmatically. ::: warning Be aware that overriding requires constant updates as new middlewares by the framework are not automatically taken over. ::: [Learn more about the middlewares default order.](#cds-middlewares){.learn-more} #### Customization of `cds.context.user` > Source: /docs/node.js/cds-serve#customization-of-cdscontextuser You can register middlewares to customize `cds.context.user`. It must be done after authentication. If `cds.context.tenant` is manipulated as well, it must also be done before `cds.context.model` is set for the current request. ```js cds.middlewares.before = [ cds.middlewares.context(), cds.middlewares.trace(), cds.middlewares.auth(), function ctx_user (_,__,next) { const ctx = cds.context ctx.user.id = '' + ctx.user.id next() }, cds.middlewares.ctx_model() ] ``` #### Enabling Feature Flags > Source: /docs/node.js/cds-serve#enabling-feature-flags You can register middlewares to customize `req.features`. It must be done before `cds.context.model` is set for the current request. ```js cds.middlewares.before = [ cds.middlewares.context(), cds.middlewares.trace(), cds.middlewares.auth(), function req_features (req,_,next) { req.features = ['', ''] next() }, cds.middlewares.ctx_model() ] ``` [Learn more about Feature Vector Providers.](../guides/extensibility/feature-toggles#feature-vector-providers){.learn-more} ### Current Limitations > Source: /docs/node.js/cds-serve#current-limitations - Configuration of middlewares must be done programmatically. ## cds. protocols > Source: /docs/node.js/cds-serve#cds-protocols The framework provides adapters for OData V4 and REST out of the box. In addition, GraphQL can be served by using our open source package [`@cap-js/graphql`](https://github.com/cap-js/graphql). By default, the protocols are served at the following path: |protocol|path| |---|---| |OData V4|/odata/v4| |REST|/rest| |GraphQL|/graphql| ### @protocol > Source: /docs/node.js/cds-serve#protocol Configures at which protocol(s) a service is served. ```cds @odata service CatalogService {} //> serves CatalogService at: /odata/v4/catalog @protocol: 'odata' service CatalogService {} //> serves CatalogService at: /odata/v4/catalog @protocol: ['odata', 'rest', 'graphql'] service CatalogService {} //> serves CatalogService at: /odata/v4/catalog, /rest/catalog and /graphql @protocol: [{ kind: 'odata', path: 'some/path' }] service CatalogService {} //> serves CatalogService at: /odata/v4/some/path ``` Note, that - the shortcuts `@rest`, `@odata`, `@graphql` are only supported for services served at only one protocol. - `@protocol` has precedence over the shortcuts. - `@protocol.path` has precedence over `@path`. - the default protocol is OData V4. - `odata` is a shortcut for `odata-v4`. - `@protocol: 'none'` will treat the service as _internal_. ### @path > Source: /docs/node.js/cds-serve#path Configures the path at which a service is served. ```cds @path: 'browse' service CatalogService {} //> serves CatalogService at: /odata/v4/browse @path: '/browse' service CatalogService {} //> serves CatalogService at: /browse ``` Be aware that using an absolute path will disallow serving the service at multiple protocols. ### PATCH vs. PUT vs. Replace > Source: /docs/node.js/cds-serve#patch-vs-put-vs-replace The HTTP method `PATCH` is meant for partial modification of an _existing resource_. `PUT`, on the other hand, is meant for ensuring a resource exists , that is, if it doesn't yet exists, it gets created. If it does exist, it gets updated to reflect the request's content. This content, however, may be incomplete. By default, the values for not listed keys are not touched. The rationale being that default values are known and clients have the option to send full representations, if necessary. The following table shows the Node.js runtime's configuration options and their respective default value: | Flag | Behavior | Default | |----------------------------------------------|------------------------------------------|---------| | cds.runtime.patch_as_upsert | Create resource if it does not yet exist | false | | cds.runtime.put_as_upsert | Create resource if it does not yet exist | true | | cds.runtime.put_as_replace | Payload is enriched with default values | false | ### Custom Protocol Adapter > Source: /docs/node.js/cds-serve#custom-protocol-adapter Similar to the configuration of the GraphQL Adapter, you can plug in your own protocol. The `impl` property must point to the implementation of your protocol adapter. Additional options for the protocol adapter are provided on the same level. ```js cds.env.protocols = { 'custom-protocol': { path: '/custom', impl: '', ...options } } ``` ### Current Limitations > Source: /docs/node.js/cds-serve#current-limitations-1 - Configuration of protocols must be done programmatically. - Additional protocols do not respect `@protocol` annotation yet. - The configured protocols do not show up in the `index.html` yet. # Connecting to Required Services > Source: /docs/node.js/cds-connect Services frequently consume other services, which could be **local** services served by the same process, or **external** services, for example consumed through OData. The latter include **database** services. In all cases use `cds.connect` to connect to such services, for example, from your: ## Connecting to Required Services > Source: /docs/node.js/cds-connect#connecting-to-required-services ### cds. connect.to () > Source: /docs/node.js/cds-connect#cds-connectto- Use `cds.connect.to()` to connect to services configured in a project's `cds.requires` configuration. ```js const ReviewsService = await cds.connect.to('ReviewsService') ``` The method returns a _Promise_ resolving to a _[Service](../cds/cdl#services)_ instance which acts as a client proxy to the service's API, allowing you to call its methods and access its data using common [`cds.Service`](core-services#consuming-services) methods, for example: ```js let reviews = await ReviewsService.read ('Reviews') ``` **Arguments** are as follows: ```ts:no-line-numbers async function cds.connect.to ( name? : string, // reference to an entry in `cds.requires` config options? : { kind : string // reference to a preset in `cds.requires.kinds` config impl : string // module name of the implementation } ) : Promise ``` Argument `name` is used to look up connect options from [configured services](#cds-env-requires), which are defined in the `cds.requires` section of your _package.json_ or _.cdsrc.json_ or _.yaml_ files. Argument `options` also allows to pass additional options programmatically. The available and supported properties of options depend on the selected `kind`. Each `kind` defines its own set of expected configuration properties (for example, `credentials`, `model`, `service`). This allows creating services without configurations and [service bindings](#service-bindings). For example, you could connect to a local SQLite database in your tests like this: ```js const db2 = await cds.connect.to ({ kind: 'sqlite', credentials: { url: 'db2.sqlite' } }) ``` ### cds. services > Source: /docs/node.js/cds-connect#cds-services When connecting to a service using `cds.connect.to()`, the service instance is cached in [`cds.services`](cds-facade#cds-services) under the service name. This means that subsequent calls to `cds.connect.to()` with the same service name will all return the same instance. As services constructed by [`cds.serve`](cds-serve#cds-serve-) are registered with [`cds.services`](cds-facade#cds-services) as well, a connect finds and returns them as local service connections. You can also access cached service instance like this: ```js const { ReviewsService } = cds.services ``` > Note: If _ad-hoc_ options are provided, the instance is not cached. ## Configuring Required Services > Source: /docs/node.js/cds-connect#configuring-required-services ###### cds-env-requires > Source: /docs/node.js/cds-connect#cds-env-requires To configure required remote services in Node.js, simply add respective entries to the `cds.requires` sections in your _package.json_ or in _.cdsrc.json_ or _.yaml_. These configurations are constructed as follows: ::: code-group ```json [package.json] {"cds":{ "requires": { "db": { "kind": "sqlite", "credentials": { "url":"db.sqlite" }}, "ReviewsService": { "kind": "odata", "model": "@capire/reviews" }, "OrdersService": { "kind": "odata", "model": "@capire/orders" }, } }} ``` ```yaml [.cdsrc.yaml] cds: requires: db: kind: sqlite credentials: url: db.sqlite ReviewsService: kind: odata, model: @capire/reviews OrdersService: kind: odata, model: @capire/orders ``` ::: Entries in this section tell the service loader to not serve that service as part of your application, but expects a service binding at runtime in order to connect to the external service provider. The options are as follows: ### cds.requires.\`.impl` > Source: /docs/node.js/cds-connect#cdsrequiressrvimpl Service implementations are ultimately configured in `cds.requires` like that: ```json "cds": { "requires": { "some-service": { "impl": "some/node/module/path" }, "another-service": { "impl": "./local/module/path" } }} ``` Given that configuration, `cds.connect.to('some-service')` would load the specific service implementation from `some/node/module/path`. Prefix the module path in `impl` with `./` to refer to a file relative to your project root. ### cds.requires.\`.kind` > Source: /docs/node.js/cds-connect#cdsrequiressrvkind As service configurations inherit from each other along `kind` chains, we can refer to default configurations shipped with `@sap/cds`, as you commonly see that in our [_cap/samples_](https://github.com/capire/samples), like so: ```json "cds": { "requires": { "db": { "kind": "sqlite" }, "remote-service": { "kind": "odata" } }} ``` This is backed by these default configurations: ```json "cds": { "requires": { "sqlite": { "impl": "[...]/sqlite/service" }, "odata": { "impl": "[...]/odata/service" }, }} ``` > Run `cds env get requires` to see all default configurations. > Run `cds env get requires.db.impl` to see the impl used for your database. Given that configuration, `cds.connect.to('db')` would load the generic service implementation. [Learn more about `cds.env`.](cds-env){.learn-more} ### cds.requires.\`.model` > Source: /docs/node.js/cds-connect#cdsrequiressrvmodel Specify (imported) models for remote services in this property. This allows the service runtime to reflect on the external API and add generic features. The value can be either a single string referring to a CDS model source, resolved as absolute node module, or relative to the project root, or an array of such. ```json "cds": { "requires": { "remote-service": { "kind": "odata", "model":"some/imported/model" } }} ``` Upon [bootstrapping](./cds-serve), all these required models will be loaded and compiled into the effective [`cds.model`](cds-facade#cds-model) as well. ### cds.requires.\`.service` > Source: /docs/node.js/cds-connect#cdsrequiressrvservice If you specify a model, then a service definition for your required service must be included in that model. By default, the name of the service that is checked for is the name of the required service. This can be overwritten by setting `service` inside the required service configuration. ```json "cds": { "requires": { "remote-service": { "kind": "odata", "model":"some/imported/model", "service": "BusinessPartnerService" } }} ``` The example specifies `service: 'BusinessPartnerService'`, which results in a check for a service called `BusinessPartnerService` instead of `remote-service` in the model loaded from `some/imported/model`. ## Service Bindings > Source: /docs/node.js/cds-connect#service-bindings A service binding connects an application with a cloud service. For that, the cloud service's credentials need to be injected in the CDS configuration: ```jsonc { "requires": { "db": { "kind": "hana", "credentials": { /* from service binding */ } } } } ``` ### cds.requires.\.credentials > Source: /docs/node.js/cds-connect#cdsrequiressrvcredentials All service binding information goes into this property. It's filled from the process environment when starting server processes, managed by deployment environments. Service bindings provide the details about how to reach a required service at runtime, that is, providing requisite credentials, most prominently the target service's `url`. You specify the credentials to be used for a service by using one of the following: - Process environment variables - Command line options - File system - Auto binding For example, in development, you can add them to a _.env_ file as follows: ```properties # .env file > Source: /docs/node.js/cds-connect#env-file cds.requires.remote-service.credentials = { "url":"http://...", ... } ``` ::: warning ❗ Never add secrets or passwords to _package.json_ or _.cdsrc.json_! General rule of thumb: `.credentials` are always filled (and overridden) from process environment on process start. ::: ### Basic Mechanism > Source: /docs/node.js/cds-connect#basic-mechanism The CAP Node.js runtime expects to find the service bindings in `cds.env.requires`. 1. Configured required services constitute endpoints for service bindings. ```json "cds": { "requires": { "ReviewsService": {...}, } } ``` 2. These are made available to the runtime via `cds.env.requires`. ```js const { ReviewsService } = cds.env.requires ``` 3. Service Bindings essentially fill in `credentials` to these entries. ```js const { ReviewsService } = cds.env.requires ReviewsService.credentials = { url: "http://localhost:4005/reviews" } ``` The latter is appropriate in test suites. In productive code, you never provide credentials in a hard-coded way. Instead, use one of the options presented in the following sections. ### In Cloud Foundry > Source: /docs/node.js/cds-connect#in-cloud-foundry Find general information about how to configure service bindings in Cloud Foundry: - [Deploying Services using MTA Deployment Descriptor](https://help.sap.com/docs/SAP_HANA_PLATFORM/4505d0bdaf4948449b7f7379d24d0f0d/33548a721e6548688605049792d55295.html) - [Binding Service Instances to Cloud Foundry Applications](https://help.sap.com/docs/SERVICEMANAGEMENT/09cc82baadc542a688176dce601398de/0e6850de6e7146c3a17b86736e80ee2e.html) - [Binding Service Instances to Applications using the Cloud Foundry CLI](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/296cd5945fd84d7d91061b2b2bcacb93.html) Cloud Foundry uses auto configuration of service credentials through the `VCAP_SERVICES` environment variable. [Learn more about environment variables on Cloud Foundry and `cf env`.](https://docs.cloudfoundry.org/devguide/deploy-apps/environment-variable.html){.learn-more} #### Through `VCAP_SERVICES` env var > Source: /docs/node.js/cds-connect#through-vcapservices-env-var When deploying to Cloud Foundry, service bindings are provided in `VCAP_SERVICES` process environment variables, which is JSON-stringified array containing credentials for multiple services. The entries are matched to the entries in `cds.requires` as follows, in order of precedence: 1. The service's `name` is matched against the `name` property of `VCAP_SERVICE` entries 2. The service's `name` is matched against the `binding_name` property 3. The service's `name` is matched against entries in the `tags` array 4. The service's `kind` is matched against entries in the `tags` array 5. The service's `kind` is matched against the `label` property, for example, 'hana' 6. The service's `kind` is matched against the `type` property (The type property is only relevant for [servicebinding.io](https://servicebinding.io) bindings) 7. The service's `vcap.name` is matched against the `name` property All the config properties found in the first matched entry will be copied into the cds.requires.\.credentials property. Here are a few examples:
CAP config VCAP_SERVICES
```json { "cds": { "requires": { "hana": { "vcap": { "label": false, "name": "myHana", "tags": "database" } } } } } ``` ```json { "VCAP_SERVICES": { "myHana-binding": [{ "label": "not-hana", "plan": "standard", "name": "myHana", "tags": ["database"] }] } } ```
::: tip To see the default configuration of a CAP service, use: ```js cds env get requires. ``` ::: ### In Kubernetes / Kyma > Source: /docs/node.js/cds-connect#in-kubernetes--kyma CAP supports [servicebinding.io](https://servicebinding.io/) service bindings and SAP BTP service bindings created by the [SAP BTP Service Operator](https://github.com/SAP/sap-btp-service-operator). 1. Specify a root directory for all service bindings using `SERVICE_BINDING_ROOT` environment variable: ```yaml spec: containers: - name: bookshop-srv env: # ... - name: SERVICE_BINDING_ROOT value: /bindings ``` 2. Create service bindings Use the `ServiceBinding` custom resource of the [SAP BTP Service Operator](https://github.com/SAP/sap-btp-service-operator) to create bindings to SAP BTP services: ```yaml apiVersion: services.cloud.sap.com/v1alpha1 kind: ServiceBinding metadata: name: bookshop-xsuaa-binding spec: serviceInstanceName: bookshop-xsuaa-binding externalName: bookshop-xsuaa-binding secretName: bookshop-xsuaa-secret ``` Bindings to other services need to follow the [servicebinding.io workload projection specification](https://servicebinding.io/spec/core/1.0.0-rc3/#workload-projection). 3. Mount the secrets of the service bindings underneath the root directory: ```yaml spec: containers: - name: bookshop-srv # ... volumeMounts: - name: bookshop-auth mountPath: "/bindings/auth" readOnly: true volumes: - name: bookshop-auth secret: secretName: bookshop-xsuaa-secret ``` The `secretName` property refers to an existing Kubernetes secret, either manually created or by the `ServiceBinding` resource. The name of the sub directory (`auth` in the example) is recognized as the binding name. CAP services receive their credentials from these bindings [as if they were provided using VCAP_SERVICES](#vcap-services). #### Through environment variables > Source: /docs/node.js/cds-connect#through-environment-variables All values of a secret can be added as environment variables to a pod. A prefix can be prepended to each of the environment variables. To inject the values from the secret in the right place of your CDS configuration, you use the configuration path to the `credentials` object of the service as the prefix: `cds_requires__credentials_` Please pay attention to the underscore ("`_`") character at the end of the prefix. *Example:* ```yaml spec: containers: - name: app-srv # ... envFrom: - prefix: cds_requires_db_credentials_ secretRef: name: app-db ``` ::: warning For the _configuration path_, you **must** use the underscore ("`_`") character as delimiter. CAP supports dot ("`.`") as well, but Kubernetes won't recognize variables using dots. Your _service name_ **mustn't** contain underscores. ::: #### Through the file system > Source: /docs/node.js/cds-connect#through-the-file-system CAP can read configuration from a file system by specifying the root path of the configuration in the `CDS_CONFIG` environment variable. Set `CDS_CONFIG` to the path that should serve as your configuration root, for example: `/etc/secrets/cds`. Put the service credentials into a path that is constructed like this: `/requires//credentials` Each file will be added to the configuration with its name as the property name and the content as the value. If you have a deep credential structure, you can add further sub directories or put the content in a file as a JSON array or object. For Kubernetes, you can create a volume with the content of a secret and mount it on your container. *Example:* ```yaml spec: volumes: - name: app-db-secret-vol secret: secretName: app-db containers: - name: app-srv # ... env: - name: CDS_CONFIG value: /etc/secrets/cds volumeMounts: - name: app-db-secret-vol mountPath: /etc/secrets/cds/requires/db/credentials readOnly: true ``` #### Provide Service Bindings (`VCAP_SERVICES`) > Source: /docs/node.js/cds-connect#provide-service-bindings-vcapservices If your application runs in a different environment than Cloud Foundry, the `VCAP_SERVICES` env variable is not available. But it may be needed by some libraries, for example the SAP Cloud SDK. By enabling the CDS feature `features.emulate_vcap_services`, the `VCAP_SERVICES` env variable will be populated from your configured services. For example, you can enable it in the _package.json_ file for your production profile: ```json { "cds": { "features": { "[production]": { "emulate_vcap_services": true } } } } ``` ::: warning This is a backward compatibility feature.
It might be removed in a next [major CAP version](/releases/schedule#yearly-major-releases). ::: Each service that has credentials and a `vcap.label` property is put into the `VCAP_SERVICES` env variable. All properties from the service's `vcap` object will be taken over to the service binding. The `vcap.label` property is pre-configured for some services used by CAP. For example, for the XSUAA service you only need to provide credentials and the service kind: ```json { "requires": { "auth": { "kind": "xsuaa", "credentials": { "clientid": "cpapp", "clientsecret": "dlfed4XYZ" } } } } ``` The `VCAP_SERVICES` variable is generated like this: ```json { "xsuaa": [ { "label": "xsuaa", "tags": [ "auth" ], "credentials": { "clientid": "cpapp", "clientsecret": "dlfed4XYZ" } } ] } ``` The generated value can be displayed using the command: ```sh cds env get VCAP_SERVICES --process-env ``` A list of all services with a preconfigured `vcap.label` property can be displayed with this command: ```sh cds env | grep vcap.label ``` You can include your own services by configuring `vcap.label` properties in your CAP configuration. For example, in the _package.json_ file: ```json { "cds": { "requires": { "myservice": { "vcap": { "label": "myservice-label" } } } } } ``` The credentials can be provided in any supported way. For example, as env variables: ```sh cds_requires_myservice_credentials_user=test-user cds_requires_myservice_credentials_password=test-password ``` The resulting `VCAP_SERVICES` env variable looks like this: ```json { "myservice-label": [ { "label": "myservice-label", "credentials": { "user": "test-user", "password": "test-password" } } ] } ``` ### Through _.cdsrc-private.json_ File for Hybrid Testing > Source: /docs/node.js/cds-connect#through-cdsrc-privatejson-file-for-hybrid-testing [Learn more about hybrid testing using _.cdsrc-private.json_.](../tools/cds-bind#bind-to-cloud-services) ```json { "requires": { "ReviewsService": { "credentials": { "url": "http://localhost:4005/reviews" } }, "db": { "credentials": { "url": "db.sqlite" } } } } ``` ::: warning Make sure that the _.cdsrc-private.json_ file is not checked into your project. ::: ### Through `process.env` Variables > Source: /docs/node.js/cds-connect#through-processenv-variables You could pass credentials as process environment variables, for example in ad-hoc tests from the command line: ```sh export cds_requires_ReviewsService_credentials_url=http://localhost:4005/reviews export cds_requires_db_credentials_database=sqlite.db cds watch fiori ``` #### In _.env_ Files for Local Testing > Source: /docs/node.js/cds-connect#in-env-files-for-local-testing Add environment variables to a local _.env_ file for repeated local tests: ```properties cds.requires.ReviewsService.credentials = { "url": "http://localhost:4005/reviews" } cds.requires.db.credentials.database = sqlite.db ``` > Never check in or deploy such _.env_ files!
# Core Services > Source: /docs/node.js/core-services ## Provided Services > Source: /docs/node.js/core-services#provided-services A CAP application mainly consists of the services it provides to clients. Such *provided services* are commonly declared through service definitions in CDS, and served automatically during bootstrapping as follows... #### CDS-Modeling *Provided* Services > Source: /docs/node.js/core-services#cds-modeling-provided-services For example, a simplified all-in-one variant of [*capire/bookshop/srv/cat-service.cds*](https://github.com/capire/bookshop/blob/main/srv/cat-service.cds): ```cds using { User, sap.capire.bookshop as my } from '../db/schema'; service CatalogService { entity Books { key ID : UUID; title : String; descr : String; author : Association to my.Authors; } action submitOrder ( book: UUID, quantity: Integer ); event OrderedBook: { book: UUID; quantity: Integer; buyer: User } } ``` [Learn more about defining services using CDS](../guides/services/providing-services) {.learn-more} #### Serving Provided Services → `cds.serve` > Source: /docs/node.js/core-services#serving-provided-services---cdsserve When starting a server with `cds watch` or `cds run` this uses `cds.serve` to automatically create instances of `cds.Service` for all such service definitions found in our models, and serve them to respective endpoints via corresponding protocols. In essence, the built-in bootstrapping logic works like that: ```js cds.app = require('express')() const csn = await cds.load('*') cds.model = cds.compile.for.nodejs(csn) cds.services = await cds.serve('all').from(cds.model).in(cds.app) ``` [Learn more about `cds.serve`](cds-serve) {.learn-more} ## Required Services > Source: /docs/node.js/core-services#required-services In addition to provided services, your applications often need to consume other services as *required services*. The most prominent example for that is the primary database `cds.db`. Other examples include the application services provided by other enterprise applications, or micro services, and other platform services, such as secondary databases or message brokers. #### Configuring *Required* Services > Source: /docs/node.js/core-services#configuring-required-services We need to configure required services with `cds.requires.<...>` config options. These configurations act like sockets for service bindings to fill in missing credentials later on. ::: code-group ```json [package.json] {"cds":{ "requires": { "ReviewsService": { "kind": "odata", "model": "@capire/reviews" }, "db": { "kind": "sqlite", "credentials": { "url":"db.sqlite" }}, } }} ``` ::: *Learn more about [configuring required services](cds-connect#cds-env-requires) and [service bindings](cds-connect#service-bindings)* {.learn-more} #### Connecting to Required Services → `cds.connect` > Source: /docs/node.js/core-services#connecting-to-required-services--cdsconnect Given such configurations, we can connect to the configured services like so: ```js const ReviewsService = await cds.connect.to('ReviewsService') const db = await cds.connect.to('db') ``` [Learn more about `cds.connect`](cds-connect) {.learn-more} ## Implementing Services > Source: /docs/node.js/core-services#implementing-services By default `cds.serve` creates an instance of `cds.ApplicationService` for each service definition it finds. Each instance provides generic implementations for all CRUD operations, including full support for deep document structures, declarative input validation and many other out-of-the-box features. Yet, you'd likely need to provide domain-specific custom logic, especially for custom actions and functions, or for custom validations. In the next sections, you can learn the following: - **How** to provide custom implementations? - **Where**, that is, in which files, to add the implementation? #### In sibling `.js` files, next to `.cds` sources > Source: /docs/node.js/core-services#in-sibling-js-files-next-to-cds-sources The easiest way to add custom service implementations is to simply place a `.js` file with the same name next to the `.cds` file containing the respective service definition. For example, as in [*cap/samples/bookshop*](https://github.com/capire/bookshop): ```zsh bookshop/ ├─ srv/ │ ├─ admin-service.cds │ ├─ admin-service.js │ ├─ cat-service.cds # [!code focus] │ └─ cat-service.js # [!code focus] └─ ... ``` ::: details Alternatively in subfolders `lib/` or `handlers/`... In addition to adding the implementation in a neighbouring file you can place them in nested subfolders called `lib/` or `handlers/`, for example: ```zsh bookshop/ ├─ srv/ │ └─ lib/ # or handlers/ # [!code focus] │ │ ├─ admin-service.js │ │ └─ cat-service.js │ ├─ admin-service.cds │ └─ cat-service.cds └─ ... ``` ::: #### Specified by `@impl` Annotation, or `impl` Configuration > Source: /docs/node.js/core-services#specified-by-impl-annotation-or-impl-configuration You can explicitly specify sources for service implementations using... The `@impl` annotation in CDS definitions for [provided services](#provided-services): ::: code-group ```cds [srv/cat-service.cds] @impl: 'srv/cat-service.js' // [!code focus] service CatalogService { ... } ``` ::: The `impl` configuration property for [required services](#required-services): ::: code-group ```json [package.json] { "cds": { "requires": { "ReviewsService": { "impl": "srv/reviews-services.js" // [!code focus] } } }} ``` ::: #### How to provide custom service implementations? > Source: /docs/node.js/core-services#how-to-provide-custom-service-implementations Implement your custom logic as a subclass of `cds.Service`, or more commonly of `cds.ApplicationService` to benefit from generic out-of-the-box implementations. The actual implementation goes into event handlers, commonly registered in method [`srv.init()`](#srv-init): ```js class BooksService extends cds.ApplicationService { init() { const { Books, Authors } = this.entities this.before ('READ', Authors, req => {...}) this.after ('READ', Books, books => {...}) this.on ('submitOrder', req => {...}) return super.init() } } module.exports = BooksService ``` [Learn more about `cds.ApplicationService`](app-services) {.learn-more} ::: details Alternatively using old-style `cds.service.impl` functions... As an alternative to providing subclasses of `cds.Service` as service implementations, you can simply provide a single function like so: ```js const cds = require('@sap/cds') module.exports = cds.service.impl (function(){ ... }) // [!code focus] ``` > Note: `cds.service.impl()` is just a noop wrapper that enables [IntelliSense in VS Code](https://code.visualstudio.com/docs/editor/intellisense). This will be translated behind the scenes to the equivalent of this: ```js const cds = require('@sap/cds') module.exports = new class extends cds.ApplicationService { async init() { await srv_impl_fn .call (this,this) // [!code focus] return super.init() } } ``` ::: ::: details Multiple implementations in one file... In case you have multiple service definition is one `.cds` file like that: ```cds // services.cds namespace foo.bar; service Foo {...} service Bar {...} ``` ... you may also want to have multiple implementations provided through one corresponding `.js` file. Simply do so by by having multiple exports like that: ```js // services.js exports['foo.bar.Foo'] = class Foo {...} exports['foo.bar.Boo'] = class Bar {...} ``` The exports' names must **match the fully-qualified names of the service definitions**. ::: ## Consuming Services > Source: /docs/node.js/core-services#consuming-services Given access to a service instance — for example, through `cds.connect` — we can send requests, queries or asynchronously processed event messages to it: ```js const srv = await cds.connect.to ('BooksService') ``` [Using REST-style APIs](#rest-style-api): ```js await srv.create ('/Books', { title: 'Catweazle' }) await srv.read ('GET','/Books/206') await srv.send ('submitOrder', { book:206, quantity:1 }) ``` [Using typed APIs for actions and functions](../guides/services/custom-actions#calling-actions--functions): ```js await srv.submitOrder({ book:206, quantity:1 }) await srv.submitOrder(206,1) ``` [Using Query-style APIs](#srv-run-query): ```js await srv.run( INSERT.into(Books).entries({ title: 'Wuthering Heights' }) ) await srv.run( SELECT.from(Books,201) ) await srv.run( UPDATE(Books,201).with({stock:111}) ) await srv.run( UPDATE(Books).set({discount:'10%'}).where({stock:{'>':111}}) ) ``` [Same with CRUD-style convenience APIs](#crud-style-api): ```js await srv.create(Books).entries({ title: 'Wuthering Heights' }) await srv.read(Books,201) await srv.update(Books,201).with({stock:111}) await srv.update(Books).set({discount:'10%'}).where({stock:{'>':111}}) ``` [Emitting Asynchronous Event Messages:](#srv-emit-event) ```js await srv.emit ('SomeEvent', {foo:'bar'}) ``` ```js await srv.emit ({ event: 'OrderedBooks', data: { book: 206, quantity: 1, buyer: 'alice@wonderland.com' }}) ``` ```js await srv.emit ('OrderedBooks', { book: 206, quantity: 1, buyer: 'alice@wonderland.com' }) ``` ::: tip Prefer Platform-Agnostic APIs REST-style APIs using `srv.send()` tend to become protocol-specific, for example if you'd use OData `$filter` query options, or alike. In contrast to that, the `cds.ql`-based CRUD-style APIs using `srv.run()` are platform-agnostic to a very large extent. We can translate these to local API calls, remote service calls via GraphQL, OData, or REST, or to plain SQL queries sent to underlying databases. ::: ## `cds.Service` > Source: /docs/node.js/core-services#cdsservice Every active thing in CAP is a service, and class `cds.Service` is the base class for all of which. Services react to events through execution of registered event handlers. So, the following code snippets show the essence of how you'd use services. You register **[event handlers](#srv-on-before-after)** with them as implementation: ```js const srv = (new cds.Service) .on('READ','Books', req => console.log (req.event, req.entity)) .on('foo', req => console.log (req.event, req.data)) .on('*', msg => console.log (msg.event)) ``` You send **[queries](#srv-run-query)**, **[requests](#srv-send-request)** or **[events](#srv-emit-event)** to them for consumption: ```js await srv.read('Books') //> READ Service.Books await srv.send('foo',{bar:1}) //> foo {bar:1} await srv.emit('foo',{bar:1}) //> foo {bar:1} //> foo await srv.emit('bar') //> bar ``` > Most commonly, instances are not created like this but during bootstrapping via [`cds.serve()`](#provided-services) for provided services, or [`cds.connect()`](#required-services) for required ones. ### Service ( ... ) > Source: /docs/node.js/core-services#service--- ```tsx function constructor ( name : string, model : CSN, options : { kind: string, ... } ) ``` > *Arguments fill in equally named properties [`name`](#-name), [`model`](#-model), [`options`](#-options).* **Don't override the constructor** in subclasses, rather override [`srv.init()`](#srv-init). ### . name > Source: /docs/node.js/core-services#-name The service's name as passed to the constructor, and under which it is found in `cds.services`. - If constructed by [`cds.serve()`](cds-serve) it's the fully-qualified name of the CDS service definition. - If constructed by [`cds.connect()`](cds-connect) it's the lookup name: ```js const srv = await cds.connect.to('audit-log') srv.name //> 'audit-log' ``` ### . model > Source: /docs/node.js/core-services#-model ```tsx var srv.model : LinkedCSN var srv.definition : LinkedCSN service definition ``` - `model`, a [`LinkedCSN`](cds-reflect#linked-csn), is the CDS model from which this service was constructed - `definition`, a [`LinkedCSN` definition](cds-reflect#any) from which this service was constructed ### . options > Source: /docs/node.js/core-services#-options ```tsx var srv.options : { //> from cds.requires config service : string, // the definition's name if different from srv.name kind : string, impl : string, } ``` ### . actions > Source: /docs/node.js/core-services#-actions ### . events > Source: /docs/node.js/core-services#-events ### . types > Source: /docs/node.js/core-services#-types ### . entities > Source: /docs/node.js/core-services#-entities ###### srv-entities > Source: /docs/node.js/core-services#srv-entities ```tsx var srv.entities/events/actions/types : Iterable <{ name : CSN definition }> ``` These properties provide convenient access to the CSN definitions of the *entities*, *types*, *events*, and *actions* (incl. *functions*) exposed by this service. They return instances of [`LinkedDefinitions`](cds-reflect#iterable) which you can use in all of these ways: ```js // Assumed `this` is an instance of cds.Service const { Books, Authors } = this.entities const all_entities = [ ... this.entities ] for (let k in this.entities) //... k is a CSN definition's name for (let d of this.entities) //... d is a CSN definition ``` #### Similarity _and_ difference to `cds.entities` > Source: /docs/node.js/core-services#similarity-and-difference-to-cdsentities These properties are very similar in nature and behavior to [`cds.entities`](cds-facade#cds-entities), which is a sortcut to [`cds.model.entities`](cds-reflect#-entities). However, note this difference: While both of these work with [`cds.entities`](cds-facade#cds-entities): ```js const { 'some.namespace.Books':Books, ... } = cds.entities //> works const { Books, Authors } = cds.entities ('some.namespace') //> works ``` Only the first one works with [`srv.entities`](#srv-entities): ```js const { Books, Authors } = srv.entities //> works const { Books, Authors } = srv.entities ('some.namespace') //> FAILS! [!code --] ``` Reason is that `cds.entities` is sort of a chimera, which can be used both **as a getter** returning _all_ definitions, and **as a function** which accepts a namespace to fetch definitions for. The latter doesn't make sense in the context of a service, as the namespace is already implied by the service's name. ### srv. init() > Source: /docs/node.js/core-services#srv-init ###### srv-init > Source: /docs/node.js/core-services#srv-init-1 ```tsx async function srv.init() ``` Override this method in subclasses to register custom event handlers. As shown in the example, you would usually derive from [`cds.ApplicationService`](app-services): ```js class BooksService extends cds.ApplicationService { init(){ const { Books, Authors } = this.entities this.before ('READ', Authors, req => {...}) this.after ('READ', Books, books => {...}) this.on ('submitOrder', req => {...}) return super.init() } } ``` Ensure to call `super.init()` to allow subclasses to register their handlers. Do that after your registrations to go before the ones from subclasses, or before to have theirs go before yours. ### srv. prepend() > Source: /docs/node.js/core-services#srv-prepend ###### srv-prepend > Source: /docs/node.js/core-services#srv-prepend-1 ```tsx function srv.prepend(()=>{...}) ``` If you need to register a handler that has to run before the existing ones, use `srv.prepend()` to do so. For example: ```js cds.on('served',()=>{ const { SomeService } = cds.services SomeService.prepend (()=>{ SomeService.on('READ','Foo', (req,next) => {...}) }) }) ``` ### srv. on, before, after() > Source: /docs/node.js/core-services#srv-on-before-after ###### srv-on-before-after > Source: /docs/node.js/core-services#srv-on-before-after-1 ```tsx function srv.on/before/after ( event : string | string[] | '*', entity? : CSN definition | CSN definition[] | string | string[] | '*', handler : function ) ``` Use these methods to register event handlers with a service, usually in your service implementation's [`init()`](#srv-init) method: ```js class BooksService extends cds.ApplicationService { init(){ const { Books, Authors } = this.entities this.on ('READ',[Books,Authors], req => {...}) this.after ('READ',Books, books => {...}) this.after ('each',Books, book => {...}) this.before (['CREATE','UPDATE'],Books, req => {...}) this.on ('CREATE',Books, req => {...}) this.on ('UPDATE',Books, req => {...}) this.on ('submitOrder', req => {...}) this.before ('*', console.log) return super.init() } } ``` **Methods `.on`, `.before`, `.after`** refer to corresponding *phases* during request processing: |Method | Description | Example | --- | --- | --- | | `.on`| handlers _fulfill_ requests | reading/writing data from/to databases | | `.before` | handlers run before the `.on` handlers | validating inbound data | | `.after` | handlers run after the `.on` handlers | enrich outbound data | **Argument `event`** can be one of: - String `CREATE`, `READ`, `UPDATE`, `UPSERT`, `DELETE` - String `SELECT`, `INSERT` → aliases for: `READ` and `CREATE` - String `GET`, `PUT`, `POST`, `PATCH` → aliases for: `READ`, `CREATE`, `UPDATE` - String `each` → shorthand for `.after` `READ` handler ran for _each_ result entry - String `error` to register an error handler for *all* potential events - A name of a custom action or function – for example, `submitOrder` **Argument `entity`** can be one of: - A `CSN definition` of an entity served by this service → from [`this.entities`](#-entities) - A `string` corresponding to the _name_ of an entity served by this service - A `path` navigating from a served entity to associated ones, e.g., `Books/author` **Multiple `events` or `entities`** – for both parameters, you can also specify: - An `array` of the above to register a handler for _multiple_ events or entities - String `*` to register a handler for _all_ potential events or entities. ::: tip Best Practices Use named functions as event handlers instead of anonymous ones as that will improve both, code comprehensibility as well as debugging experiences. Moreover `this` in named functions are the [transactional derivates](cds-tx#srv-tx) of your service, with access to transaction and tenant-specific information, while for arrow functions it is the base instance. ::: ::: tip Custom domain logic mostly goes into `.before` or `.after` handlers Your services are mostly constructed by [`cds.serve()`](cds-serve) based on service definitions in CDS models. And these are mostly instances of [`cds.ApplicationService`](app-services), which provide generic handlers for a broad range of CRUD requests. So, the need to provide own `.on` handlers reduces to custom actions and functions. ::: ### srv. before (request) > Source: /docs/node.js/core-services#srv-before-request ###### srv-before-request > Source: /docs/node.js/core-services#srv-before-request-1 ```tsx function srv.before (event, entity?, handler: ( req : cds.Request )) ``` *Find details on `event` and `entity` in [srv.on,before,after()](#srv-on-before-after) above*. {.learn-more} Use this method to register handlers to run *before* `.on` handlers, frequently used for validating user input. The handlers receive a single argument `req`, an instance of [`cds.Request`](./events.md#cds-request). Examples: ```js this.before ('UPDATE',Books, req => { const { stock } = req.data if (stock < 0) req.error `${{ stock }} must be >= ${0}` }) this.before ('submitOrder', req => { const { quantity } = req.data if (quantity > 11) req.error `${{ quantity }} must not exceed ${11}` }) ``` You can as well run additional operations in before handlers, of course: ```js this.before ('submitOrder', async req => { await UPDATE(Books).set ('stock -=', req.data.quantity) }) ``` ::: details Collecting input errors with `req.error()`... The input validation handlers above collect input errors with [`req.error()`](./events#req-error) . This method collects all failures in property `req.errors`, allowing to display them on UIs all at once. If there are `req.errors` after the before phase, request processing is aborted with a corresponding error response returned to the client. ::: [Learn more about how requests are processed by `srv.handle(req)`](#srv-handle-event) {.learn-more} ### srv. after (request) > Source: /docs/node.js/core-services#srv-after-request ###### srv-after-request > Source: /docs/node.js/core-services#srv-after-request-1 ```tsx function srv.after (event, entity?, handler: ( results : object[] | any, req : cds.Request )) ``` *Find details on `event` and `entity` in [srv.on,before,after()](#srv-on-before-after) above*. {.learn-more} Use this method to register handlers to run *after* the `.on` handlers, frequently used to enrich outbound data. The handlers receive two arguments: - `results` — the outcomes of the `.on` handler which ran before; see [Results of Generic CRUD Handlers](app-services#results-of-generic-crud-handlers) for the shape returned by the built-in handler - `req` — an instance of [`cds.Request`](./events.md#cds-request) ::: warning Asynchronous functions can be registered, but all `.after` handlers are executed in parallel, which can lead to race conditions in case multiple handlers apply to the respective request. Hence, use with caution! ::: As a convenience feature, `.after` handlers that are registered on the event `'each'` are called for each individual result entry on `'READ'`. ::: warning Only synchronous functions are allowed to be registered as `.after('each',...)` handlers as they are run in a `.forEach()` loop without promise handling. ::: Examples: ```js this.after ('READ', Books, books => { for (let b of books) if (b.stock > 111) b.discount = '11%' }) this.after ('each', Books, book => { if (book.stock > 111) book.discount = '11%' }) ``` [Learn more about how requests are processed by `srv.handle(req)`](#srv-handle-event) {.learn-more} ### srv. on (request) > Source: /docs/node.js/core-services#srv-on-request ###### srv-on-request > Source: /docs/node.js/core-services#srv-on-request-1 ```tsx function srv.on (event, entity?, handler: ( req : cds.Request, next : function )) ``` *Find details on `event` and `entity` in [srv.on,before,after()](#srv-on-before-after) above*. {.learn-more} Use this method to register handlers meant to actually fulfill requests, for example, by reading/writing data from/to databases. The handlers receive two arguments: - `req` — an instance of [`cds.Request`](./events.md#cds-request) providing access to all request data - `next` — a function which allows handlers to pass control down the [interceptor stack](#interceptor-stack-with-next) Examples: ```js const { Books, Authors } = this.entities this.on ('READ',[Books,Authors], req => req.target.data) // [!code focus] this.on ('UPDATE',Books, req => { // [!code focus] let [ ID ] = req.params return Object.assign (Books.data[ID], req.data) }) ``` ::: details Using mock data structures... ```js Authors.data = { 111: { ID:111, name:'Emily Brontë' }, 112: { ID:112, name:'Edgar Allan Poe' }, 114: { ID:114, name:'Richard Carpenter' }, } Books.data = { 211: { ID:211, title:'Wuthering Heights', author: Authors.data[111], stock:11 }, 212: { ID:212, title:'Eleonora', author: Authors.data[112], stock:14 }, 214: { ID:214, title:'Catweazle', author: Authors.data[114], stock:114 }, } ``` ::: ::: details Noteworthy in these examples... - The `READ` handler is using the [`req.target`](./events.md#-target) property which points to the CSN definition of the entity addressed by the incoming request → matching one of `Books` or `Authors` we obtained from [`this.entities`](#-entities) above. - The `UPDATE` handler is using the [`req.params`](./events.md#-params) property which provides access to passed in entity keys. ::: #### Interceptor stack with `next()` > Source: /docs/node.js/core-services#interceptor-stack-with-next When processing requests, `.on(request)` handlers are **executed in sequence** on a first-come-first-serve basis: Starting with the first registered one, each in the chain can decide to call subsequent handlers via `next()` or not, hence breaking the chain: ```js // Authorization check -> shadowing all other handlers registered below this.on ('*', function authorize (req,next) { if (!req.user.is('authenticated-user')) return req.reject('FORBIDDEN') else return next() // [!code focus] }) this.on ('READ',[Books,Authors], req => req.target.data) ... ``` > Alternatively, such authorization checks could also be placed in *.before* handlers. [Learn more about how requests are processed by `srv.handle(req)`](#srv-handle-event) {.learn-more} ### srv. on (event) > Source: /docs/node.js/core-services#srv-on-event ###### srv-on-event > Source: /docs/node.js/core-services#srv-on-event-1 ```tsx function srv.on (event, handler: ( msg : cds.Event )) ``` *Find details on `event` in [srv.on,before,after()](#srv-on-before-after) above*. {.learn-more} Handlers for asynchronous events, as emitted by [`srv.emit()`](#srv-emit-event), are registered in the same way as [`.on(request)`](#srv-on-request) handlers for synchronous requests, but work slightly different: 1. They are usually registered 'from the outside', not as part of a service's implementation. 2. They receive only a single argument: `msg`, an instance of [`cds.Event`](./events.md#cds-request); no `next`. 3. *All* of them get executed *concurrently*, not first-come-first-serve thru `next()`. For example, assumed *BooksService* would emit an event whenever books are ordered: ```js this.on ('submitOrder', async req => { // ... handle the request, and inform whoever might be interested: await this.emit('BooksOrdered', req.data) // [!code focus] }) ``` We could subscribe to this event to mashup with an `OrdersService` like so: ```js const BooksService = await cds.connect.to('BooksService') const OrdersService = await cds.connect.to('OrdersService') BooksService.on ('BooksOrdered', async msg => { // [!code focus] const { buyer, books } = msg.data await OrdersService.create ('Orders', { customer: buyer, items: books }) }) ``` Moreover, `.on(event)` handlers are *listeners*, not *interceptors*: **all** registered handlers are **executed concurrently **, not just the ones called thru `next()` chains — actually there is no argument `next`. So, if we had another consumer like that: ```js const audit = await cds.connect.to('audit-log') BooksService.on ('BooksOrdered', msg => audit.log ({ // [!code focus] timestamp: msg.timestamp, user: msg.data.buyer, event: msg.event, details: msg.data })) ``` All these registered handlers would get executed concurrently, and independently. [Learn more about how requests are processed by `srv.handle(event)`](#srv-handle-event) {.learn-more} ### srv. on (error) > Source: /docs/node.js/core-services#srv-on-error ###### srv-on-error > Source: /docs/node.js/core-services#srv-on-error-1 ```ts function srv.on ('error', handler: ( err : Error, req : cds.Event | cds.Request )) ``` Use the special event name `'error'` to register a custom error handler. The handler receives the error object `err` and the respective request object `req`, an instance of [`cds.Event`](./events.md#cds-request) or [`cds.Request`](./events.md#cds-request). Example: ```js this.on ('error', (err, req) => { err.message = 'Oh no! ' + err.message }) ``` Error handlers are invoked whenever an error occurs during event processing of *all* potential events and requests, and are used to augment or modify error messages, before they go out to clients. They are expected to be a sync function, that is, **not `async`**, not returning Promises. ### srv. send (request) > Source: /docs/node.js/core-services#srv-send-request ###### srv-send-request > Source: /docs/node.js/core-services#srv-send-request-1 ```ts async function srv.send ( method : string | { method, path?, data?, headers? } | { query, headers? }, path? : string, data? : object | any, headers? : object ) return : result of this.dispatch(req) ``` Use this method to send synchronous requests to a service for execution. - `method` is an HTTP method - `path` can be an arbitrary URL, starting with a leading `'/'`, it is passed to a service without any modification as a string To call bound / unbound actions and functions from the service, further variants of `srv.send` are additionally supported, as described in the section [Calling Actions / Functions](../guides/services/custom-actions#calling-actions--functions). Basically, use the action or function name instead of the HTTP method. Examples: ```js await srv.send('POST','/Books', { title: 'Catweazle' }) await srv.send('GET','/Books') await srv.send('GET','/Books/201') await srv.send('submitOrder',{...}) ``` These requests would be processed by respective [event handlers](#srv-on-before-after) registered like that: ```js srv.on('CREATE','Books', req => {...}) srv.on('READ','Books', req => {...}) srv.on('submitOrder', req => {...}) ``` The implementation essentially constructs and [dispatches](#srv-dispatch-event) instances of [`cds.Request`](./events.md#cds-request) like so: ```js let req = new cds.Request ( (method is object) ? method : (path is object) ? { method, data:path, headers:data } : { method, path, data, headers } ) return this.dispatch(req) ``` Use this method instead of [`srv.run(query)`](#srv-run-query), if headers should be added to the request object. For example: ```js await srv.send({ query: SELECT.from('Books'), headers: { some: 'header' } }) ``` *See also [REST-Style Convenience API](#rest-style-api) below* {.learn-more} ### srv. emit (event) > Source: /docs/node.js/core-services#srv-emit-event ###### srv-emit-event > Source: /docs/node.js/core-services#srv-emit-event-1 ```ts async function srv.emit ( event : string | { event, data?, headers? }, data? : object | any, headers? : object ) return : nothing ``` Use this method to emit asynchronous event messages to a service, for example: ```js await srv.emit ({ event: 'SomeEvent', data: { foo: 'bar' }}) await srv.emit ('SomeEvent', { foo:'bar' }) ``` Consumers would subscribe to such events through [event handlers](#srv-on-before-after) like that: ```js Emitter.on('SomeEvent', msg => {...}) ``` The implementation essentially constructs and [dispatches](#srv-dispatch-event) instances of [`cds.Event`](./events.md#cds-event) like so: ```js let msg = new cds.Event ( (event is object) ? event : { event, data, headers } ) return this.dispatch(msg) ``` ::: tip **INTRINSIC MESSAGING** All *cds.Services* are intrinsically events & messaging-enabled. The core implementation provides local in-process messaging, while [*cds.MessagingService*](messaging) plugs in to that to extend it to cross-process messaging via common message brokers. [**⇨ Read the Messaging Guide**](../guides/events/index) for the complete story. ::: ::: danger **PLEASE NOTE** Although emitters do not handle any return values from consumers, it is necessary to always call them with `await`. Keep in mind that `srv.emit()` is an *`async`* method, it is **very important** to properly handle the returned *Promises* by using `await`. Not handling them will likely lead to invalid transaction states and deadlocks. ::: ### srv. run (query) > Source: /docs/node.js/core-services#srv-run-query ###### srv-run-query > Source: /docs/node.js/core-services#srv-run-query-1 ```ts async function srv.run ( query : CQN | CQN[] ) return : result of this.dispatch(req) ``` Use this method to send queries to the service for execution.
It accepts single [`CQN`](../cds/cqn) query objects, or arrays of which: ```js await srv.run( INSERT.into(Books,{ title: 'Catweazle' }) ) await srv.run( SELECT.from(Books,201) ) await srv.run([ SELECT.from(Authors), SELECT.from(Books) ]) ``` These queries would be processed by respective [event handlers](#srv-on-before-after) registered like that: ```js srv.on('CREATE',Books, req => {...}) srv.on('READ',Books, req => {...}) ``` The implementation essentially constructs and [dispatches](#srv-dispatch-event) instances of [`cds.Request`](./events.md#cds-request) like so: ```js let req = new cds.Request({query}) return this.dispatch(req) ``` *See also [CRUD-Style Convenience API](#crud-style-api) below*{.learn-more} ### srv. run ( fn ) > Source: /docs/node.js/core-services#srv-run--fn- ###### srv-run-fn > Source: /docs/node.js/core-services#srv-run-fn ```tsx function srv.run ( fn? : tx => {...} ) => Promise ``` Use this method to ensure operations in the given functions are executed in a proper transaction, either a new root transaction or a nested one to an already existing root transaction. For example: ```js const db = await cds.connect.to('db') await db.run (tx => { let [ Emily, Charlotte ] = await db.create (Authors, [ { name: 'Emily Brontë' }, { name: 'Charlotte Brontë' }, ]) await db.create (Books, [ { title: 'Wuthering Heights', author: Emily }, { title: 'Jane Eyre', author: Charlotte }, ]) }) ``` > Without the enclosing `db.run(...)` the two INSERTs would be executed in two separate transactions, if that code would have run without an outer tx in place already. This method is also used by [`srv.dispatch()`](#srv-dispatch-event) to ensure single operations happen within a transaction. All subsequent nested operations started from within an event handler, will all be nested transactions to the root transaction started by the outermost service operation. [Learn more about transactions and `tx` transaction objects in `cds.tx` docs](cds-tx) {.learn-more} ### srv. dispatch (event) > Source: /docs/node.js/core-services#srv-dispatch-event ###### srv-dispatch-event > Source: /docs/node.js/core-services#srv-dispatch-event-1 ```ts async function srv.dispatch ( this : srv | Transactional , event : cds.Event | cds.Request | cds.Event[] | cds.Request[] ) return : result of this.handle(event) ``` This is the central method handling all requests or event messages sent to a service. Argument `event` is expected to be an instance of [`cds.Event`](./events.md#cds-event) or [`cds.Request`](./events.md#cds-request). The implementation basically works like that: ```js // Ensure we are running in a proper tx, nested or root if (!this.context) return this.run (tx => tx.dispatch(req)) // Handle batches of queries if (req.query is array) return Promise.all (req.query.map(this.dispatch)) // Ensure req.target is properly determined if (!req.target) req.target = _infer_target (req) // Actually handle the request return this.handle(req) ``` Basically, methods `srv.dispatch()` and `.handle()` are designed as a pair, with the former caring for all preparatory work, and the latter actually processing the request by executing matching event handlers. ::: tip When looking for overriding central event processing, rather choose [`srv.handle()`](#srv-handle-event) as that doesn't have to deal with all such input variants, and is guaranteed to be in [*tx* mode](cds-tx#srv-tx). ::: ### srv. handle (event) > Source: /docs/node.js/core-services#srv-handle-event ###### srv-handle-event > Source: /docs/node.js/core-services#srv-handle-event-1 ```ts async function srv.handle ( this : Transactional , event : cds.Event | cds.Request ) return : result of executed .on handlers ``` This is the internal method called by [`this.dispatch()`](#srv-dispatch-event) to actually process requests or events by executing registered event handlers. See [Results of Generic CRUD Handlers](app-services#results-of-generic-crud-handlers) for the return value shape of the built-in handler. Argument `event` is expected to be an instance of [`cds.Event`](./events.md#cds-event) or [`cds.Request`](./events.md#cds-request). The implementation basically works like that: ```js // before phase await Promise.all (matching .before handlers) if (req.errors) throw req.reject() // on phase await (event.reply //> synchronous? ? Promise.seq (matching .on handlers) // for synchronous requests : Promise.all (matching .on handlers) // for asynchronous events ) if (req.errors) throw req.reject() // after phase await Promise.all (matching .after handlers) if (req.errors) throw req.reject() return req.results ``` With `Promise.seq()` defined like this: ```js Promise.seq = handlers => async function next(){ req.results = await handlers.shift()?.(req, next) }() ``` All matching `.before`, `.on`, and `.after` handlers are executed in corresponding phases, with the next phase being started only if no `req.errors` have occurred. In addition, note that... - **`before`** handlers are always executed *concurrently* - **`on`** handlers are executed... - ***sequentially*** for instances of `cds.Requests` - ***concurrently*** for instances of `cds.Event` - **`after`** handlers are always executed *concurrently* In effect, for asynchronous event messages, that is, instances of `cds.Event`, sent via [`srv.emit()`](#srv-emit-event), all registered `.on` handlers are always executed. In contrast to that, for synchronous requests, that is, instances of `cds.Requests` this is up to the individual handlers calling `next()`. See [`srv.on(request)`](#interceptor-stack-with-next) for an example. ### srv. foreach (entity) > Source: /docs/node.js/core-services#srv-foreach-entity ###### srv-foreach-entity > Source: /docs/node.js/core-services#srv-foreach-entity-1 ```ts function foreach( query: CQN, callback: (row: object) => void ) ``` Executes the statement and processes the result set row by row. Use this API instead of [`cds.run`](#srv-run-query) if you expect large result sets. Then they're processed in a streaming-like fashion instead of materializing the full result set in memory before processing. > As of now, this API is only implemented by `cds.DatabaseService`. For all other subclasses, the full result set is currently materialized in memory. _**Common Usage:**_ ```js cds.foreach (SELECT.from('Foo'), each => console.log(each)) ``` {.indent} ## REST-style API > Source: /docs/node.js/core-services#rest-style-api As an alternative to `srv.send(method,...)` you can use these convenience methods: - srv. **get** (path, ...) {.method} - srv. **put** (path, ...) {.method} - srv. **post** (path, ...) {.method} - srv. **patch** (path, ...) {.method} - srv. **delete** (path, ...) {.method} Essentially they call `srv.send()` with method filled in as follows: ```js srv.get('/Books',...) --> srv.send('GET','/Books',...) srv.put('/Books',...) --> srv.send('PUT','/Books',...) srv.post('/Books',...) --> srv.send('POST','/Books',...) srv.patch('/Books',...) --> srv.send('PATCH','/Books',...) srv.delete('/Books',...) --> srv.send('DELETE','/Books',...) ``` Leading slash in the `path` argument results in the same behaviour as in `srv.send()`: `path` is sent unmodified to a service. Omitting the leading slash, or passing a reflected entity definition instead, constructs *bound* [`cds.ql` query objects](cds-ql), equivalent to [CRUD-style API](#crud-style-api): ```js await srv.get(Books,201) await srv.get(Books).where({author_ID:106}) await srv.post(Books).entries({title:'Wuthering Heights'}) await srv.post(Books).entries({title:'Catweazle'}) await srv.patch(Books).set({discount:'10%'}).where({stock:{'>':111}}) await srv.patch(Books,201).with({stock:111}) await srv.delete(Books,201) ``` ## CRUD-style API > Source: /docs/node.js/core-services#crud-style-api As an alternative to [`srv.run(query)`](#srv-run-query) you can use these convenience methods: - srv. **read** (entity, ...) {.method} - srv. **create** (entity, ...) {.method} - srv. **insert** (...).into(entity) {.method} - srv. **upsert** (...).into(entity) {.method} - srv. **update** (entity, ...) {.method} - srv. **delete** (entity, ...) {.method} Essentially, they start constructing *bound* [`cds.ql` query objects](cds-ql) as follows: ```js srv.read('Books',...)... --> SELECT.from ('Books',...)... srv.create('Books',...)... --> INSERT.into ('Books',...)... srv.insert(...).into('Books')... --> INSERT.into ('Books',...)... srv.upsert(...).into('Books')... --> UPSERT.into ('Books',...)... srv.update('Books',...)... --> UPDATE.entity ('Books',...)... srv.delete('Books',...)... --> DELETE.from ('Books',...)... ``` You can further construct the queries using the `cds.ql` fluent APIs, and then `await` them for execution thru `this.run()`. See [Results of Generic CRUD Handlers](app-services#results-of-generic-crud-handlers) for the return value shape. Here are some examples: ```js await srv.read(Books,201) await srv.read(Books).where({author_ID:106}) await srv.create(Books).entries({title:'Wuthering Heights'}) await srv.insert(Books).entries({title:'Catweazle'}) await srv.update(Books).set({discount:'10%'}).where({stock:{'>':111}}) await srv.update(Books,201).with({stock:111}) await srv.delete(Books,201) ``` Which are equivalent to these usages of `srv.run(query)`: ```js await srv.run( SELECT.from(Books,201) ) await srv.run( SELECT.from(Books).where({author_ID:106}) ) await srv.run( INSERT.into(Books).entries({title:'Wuthering Heights'}) ) await srv.run( INSERT.into(Books).entries({title:'Catweazle'}) ) await srv.run( UPDATE(Books).set({discount:'10%'}).where({stock:{'>':111}}) ) await srv.run( UPDATE(Books,201).with({stock:111}) ) await srv.run( DELETE.from(Books,201) ) ``` We can also use tagged template strings as provided by `cds.ql`: ```js await srv.read `Books` .where `ID=${201}` await srv.create `Books` .entries ({title:'Wuthering Heights'}) await srv.update `Books` .where `ID=${201}` .with `title=${'Sturmhöhe'}` await srv.delete `Books` .where `ID=${201}` ``` # Application Services > Source: /docs/node.js/app-services ## Class `cds.ApplicationService` > Source: /docs/node.js/app-services#class-cdsapplicationservice Class `cds.ApplicationService` is the default service provider implementation, adding generic handlers as introduced in the Cookbook guides on [Providing Services](../guides/services/providing-services), [Localized Data](../guides/uis/localized-data.md) and [Temporal Data](../guides/domain/temporal-data.md). Take this service definition for example: ```cds service AdminService { entity Authors as projection on my.Authors; entity Books as projection on my.Books; entity Genre as projection on my.Genre; } ``` Without any custom service implementation in place, `cds.serve` would create and instantiate instances of `cds.ApplicationService` by default like so: ```js // srv/admin-service.cds let name = 'AdminService', options = {...} let srv = new cds.ApplicationService (name, cds.model, options) await srv.init() ``` If you add a custom implementation, this would comonly be derived from `cds.ApplicationService`: ```js // srv/admin-service.js const cds = require('@sap/cds') module.exports = class AdminService extends cds.ApplicationService { init() { // register your handlers ... return super.init() } } ``` ### Generic Handlers in `srv.init()` > Source: /docs/node.js/app-services#generic-handlers-in-srvinit Generic handlers are registered by via respective class methods documented below in `cds.ApplicationService.prototype.init()` like so: ```tsx class cds.ApplicationService extends cds.Service { init() { const generics = //... all static method with prefix 'handle_' for (let each of generics) this[each].call(this) return super.init() } static handle_authorization() {...} static handle_etags() {...} static handle_validations() {...} static handle_temporal_data() {...} static handle_localized_data() {...} static handle_managed_data() {...} static handle_paging() {...} static handle_fiori() {...} static handle_crud() {...} } ``` > The reason we used `static` methods was to **(a)** give you an easy way of overriding and adding new generic handlers / features, and **(b)** without getting into conflicts with instance methods of subclasses. ### _static_ handle_authorization() > Source: /docs/node.js/app-services#static-handleauthorization This method is adding request handlers for initial authorization checks, as documented in the [Authorization guide](../guides/security/authorization.md). ### _static_ handle_etags() > Source: /docs/node.js/app-services#static-handleetags This method is adding request handlers for out-of-the-box concurrency control using ETags, as documented in the [Providing Services guide](../guides/services/served-ootb#concurrency-control). ### _static_ handle_validations() > Source: /docs/node.js/app-services#static-handlevalidations This method is adding request handlers for input validation based in `@assert` annotations, and other, as documented in the [Providing Services guide](../guides/services/constraints). ### _static_ handle_temporal_data() > Source: /docs/node.js/app-services#static-handletemporaldata This method is adding request handlers for handling temporal data, as documented in the [Temporal Data guide](../guides/domain/temporal-data.md). ### _static_ handle_localized_data() > Source: /docs/node.js/app-services#static-handlelocalizeddata This method is adding request handlers for handling localized data, as documented in the [Localized Data guide](../guides/uis/localized-data.md). ### _static_ handle_managed_data() > Source: /docs/node.js/app-services#static-handlemanageddata This method is adding request handlers for handling managed data, as documented in the [Providing Services guide](../guides/domain/index#managed-data). ### _static_ handle_paging() > Source: /docs/node.js/app-services#static-handlepaging This method is adding request handlers for paging & implicit sorting, as documented in the [Providing Services guide](../guides/services/served-ootb#pagination--sorting). ### _static_ handle_fiori() > Source: /docs/node.js/app-services#static-handlefiori This method is adding request handlers for handling Fiori Drafts and other Fiori-specifics, as documented in the [Serving Fiori guide](../guides/uis/fiori.md). ### _static_ handle_crud() > Source: /docs/node.js/app-services#static-handlecrud This method is adding request handlers for all CRUD operations including *deep* CRUD, as documented in the [Providing Services guide](../guides/services/served-ootb). ## Overriding Generic Handlers > Source: /docs/node.js/app-services#overriding-generic-handlers You can override some of these methods in subclasses, for example to skip certain generic features, or to add additional ones. For example like that: ```js class YourService extends cds.ApplicationService { static handle_validations() { // Note: this is an instance of YourService here: this.on('CREATE','*', req => {...}) return super.handle_validations() } } ``` > ## Adding Generic Handlers > Source: /docs/node.js/app-services#adding-generic-handlers You can also add own sets of generic handlers to all instances of `cds.ApplicationService`, and subclasses thereof, by simply adding a new class method prefixed with `handle_` like so: ```js const cds = require('@sap/cds') cds.ApplicationService.handle_log_events = cds.service.impl (function(){ this.on('*', req => console.log(req.event)) }) ``` ## Results of Generic CRUD Handlers > Source: /docs/node.js/app-services#results-of-generic-crud-handlers When CAP's generic handlers run a CRUD operation, the result follows a consistent shape (custom `.on` handlers may return any value): | Operation | Return value | |-----------------------|-----------------------------------------------------------------------------------------------| | `READ` | Array of matching records, or a single record / `null` when read by key | | `CREATE` | Array with `.affected` (rows created); iterate to access the created rows' generated keys | | `UPDATE` | Array with `.affected` (rows changed); reserved for rows from a `RETURNING` clause | | `UPSERT` | Array with `.affected` (rows written); reserved for rows from a `RETURNING` clause | | `DELETE` | Array with `.affected` (rows deleted); reserved for rows from a `RETURNING` clause | For `CREATE`, the array will be populated with rows from an SQL `RETURNING` clause once that is supported. Until then, the result is a lazy array that computes the created rows' generated primary keys on demand: iterating it (`[...result]`, `for…of`, `JSON.stringify`) populates those keys, avoiding the cost when you don't need them. > [!warning] Iterate before indexing > Direct index access (`result[0]`) returns `undefined` until the array has been iterated at least once. Spread or loop over the result first. ```js const created = await srv.create(Books).entries({title:'Catweazle'}) created.affected // 1 const [row] = [...created] // iterate first — row holds the generated key ``` For `UPDATE`, `UPSERT`, and `DELETE`, the array is reserved for rows returned by an SQL `RETURNING` clause. Unlike `CREATE`, there are no generated keys to synthesize client-side, so — with `RETURNING` not yet supported — the array is currently always empty: ```js const updated = await srv.update(Books).set({discount:'10%'}).where({stock:{'>':111}}) updated.affected // number of rows updated ``` When a write targets a single row by key (for example, `srv.update(Books, 201)` or `srv.delete(Books, '1')`) and no row matches, the handler throws a 404 error. A `where` clause that matches zero rows returns an array with `affected: 0` without throwing. > [!tip] Consistent Results Across Local and Remote Services > This shape was introduced in cds 10 so that local services, HCQL-proxied remote services, and database services return the same thing. To restore the previous behavior, set cds.features.legacy_srv_results: true. [See the migration guide for opt-out options.](../releases/migration/cds10#fixed-service-results){.learn-more} # Remote Services > Source: /docs/node.js/remote-services Class `cds.RemoteService` is a service proxy class to consume remote services via different [protocols](cds-serve#cds-protocols), like OData or plain REST. ## cds.**RemoteService** class > Source: /docs/node.js/remote-services#cdsremoteservice----class ### class cds.**RemoteService** extends cds.Service > Source: /docs/node.js/remote-services#class-cdsremoteservice---extends-cdsservice ## cds.RemoteService — Configuration > Source: /docs/node.js/remote-services#cdsremoteservice--configuration [remoteservice configuration]: #remoteservice-configuration The `cds.RemoteService` configuration allows you to define various options for connecting to remote services. ### CSRF-Token Handling > Source: /docs/node.js/remote-services#csrf-token-handling If the remote system you want to consume requires it, you can enable the new CSRF-token handling of `@sap-cloud-sdk/core` via configuration options `csrf` and `csrfInBatch`. These options allow to configure CSRF-token handling for each remote service separately. #### Basic Configuration > Source: /docs/node.js/remote-services#basic-configuration ```json "cds": { "requires": { "API_BUSINESS_PARTNER": { "kind": "odata", "model": "srv/external/API_BUSINESS_PARTNER", "csrf": true, "csrfInBatch": true } } } ``` In this example, CSRF handling is enabled for the `API_BUSINESS_PARTNER` service, for regular requests (`csrf: true`) and requests made within batch operations (`csrfInBatch: true`). #### Advanced Configuration > Source: /docs/node.js/remote-services#advanced-configuration Actually `csrf: true` is a convenient preset. If needed, you can further customize the CSRF-token handling with additional parameters: ```json "cds": { "requires": { "API_BUSINESS_PARTNER": { ... "csrf": { // [!code focus] "method": "get", // [!code focus] "url": "..." // [!code focus] } } } } ``` Here, the CSRF-token handling is customized at a more granular level: - `method`: The HTTP method for fetching the CSRF token. The default is `head`. - `url`: The URL for fetching the CSRF token. The default is the resource path without parameters. ### Timeout Handling > Source: /docs/node.js/remote-services#timeout-handling The `requestTimeout` setting in the `cds.RemoteService` configuration specifies the maximum duration, in milliseconds (default: 60000), to wait for a response from the remote service before timing out. #### Configuration Option > Source: /docs/node.js/remote-services#configuration-option ```json { "API_BUSINESS_PARTNER": { "kind": "odata", "credentials": { ... "requestTimeout": 1000000 // [!code focus] } } } ``` ::: tip See [Using Destinations](../guides/services/consuming-services#using-destinations) for more details on destination configuration. ::: ## More to Come > Source: /docs/node.js/remote-services#more-to-come This documentation is not complete yet, or the APIs are not released for general availability. There's more to come in this place in upcoming releases. # Messaging > Source: /docs/node.js/messaging Learn details about using messaging services and outbox for asynchronous communications. ## Overview > Source: /docs/node.js/messaging#overview Messaging enables decoupled communication between services using events. CAP distinguishes between the logical and technical messaging layers, separating business concerns from technical infrastructure. The **logical layer** consists of three primary components: **Modeled Events**: Events are defined in CDS models with typed schemas, providing compile-time validation and IDE support. These events represent business occurrences like `'orderProcessed'`, or `'stockUpdated'`. **Event Topics**: Topics organize events into logical channels and are responsible for event routing. Topics can be explicitly defined as annotation or derived from service and event names. **CAP Services**: Services act as event producers or consumers, using simple APIs like `srv.emit('reviewed', data)` or `srv.on('orderProcessed', handler)`. Services communicate using logical event names without needing to know the underlying infrastructure details. The **technical layer** handles the actual message transport and delivery: **CAP Messaging Service**: The translation layer between logical events and technical infrastructure. It manages topic resolution, message serialization, and routing logic. For topic resolution the logical events are delegated to the messaging service, the corresponding event name on the technical service is either the fully qualified event name or the value of the @topic annotation if given. **Message Brokers**: The core of the technical infrastructure, handling message persistence, delivery guarantees, and cross-service communication. Examples include SAP Event Mesh, Apache Kafka, or Redis Streams. The message flow follows a clear path through both layers: **Outbound Flow (Publisher)**: A CAP service calls `srv.emit('reviewed', data)` → CAP Messaging Service resolves the event name to a fully qualified topic (for example, `OrderSrv.reviewed`) → Message is serialized and sent to the Event Broker → Broker stores and distributes the message to all subscribers. **Inbound Flow (Subscriber)**: Event Broker delivers message from subscribed topic → CAP Messaging Service receives the message → Service name and event name are resolved from the topic → Message is routed to the appropriate CAP service handler via `srv.on('reviewed', handler)`. Registering a modeled `srv.on(...)` event handler causes the broker to listen to those events, for example, creates a subscription for Event Mesh. **Alternatively** custom handlers can bypass the service layer and work directly with the messaging service. ### Summary Table > Source: /docs/node.js/messaging#summary-table | CDS Event Declaration | Emitting via `srv.emit` | Emitting via `messaging.emit` | Broker Topic | Receiving via `srv.on` | Receiving via `messaging.on` | |------------------------------|-------------------------|-------------------------------|----------------------|------------------------|------------------------------| | No `@topic` | `'reviewed'` | `'OrderSrv.reviewed'` | `OrderSrv.reviewed` | `'reviewed'` | `'OrderSrv.reviewed'` | | With `@topic: 'foo.bar'` | `'reviewed'` | `'foo.bar'` | `foo.bar` | `'reviewed'` | `'foo.bar'` | ## cds.**MessagingService** class > Source: /docs/node.js/messaging#cdsmessagingservice----class Class `cds.MessagingService` and subclasses thereof are technical services representing asynchronous messaging channels. They can be used directly/low-level, or behind the scenes on higher-level service-to-service eventing. ### class cds.**MessagingService** extends cds.Service > Source: /docs/node.js/messaging#class-cdsmessagingservice----extends-cdsservice ## Declaring Events > Source: /docs/node.js/messaging#declaring-events In your CDS model, you can model events using the `event` keyword inside services. Once you created the `messaging` section in `cds.requires`, all modeled events are automatically enabled for messaging. You can then use the services to emit events (for your own service) or receive events (for external services). Example: In your _package.json_: ```json { "cds": { "requires": { "ExternalService": { "kind": "odata", "model": "srv/external/external.cds" }, "messaging": { "kind": "enterprise-messaging" } } } } ``` In _srv/external/external.cds_: ```cds service ExternalService { event ExternalEvent { ID: UUID; rating: Decimal; } } ``` In _srv/own.cds_: ```cds service OwnService { event OwnEvent { ID: UUID; rating: Decimal; } } ``` The implementation can use CAP application services or bypass them and work directly with the messaging service. In _srv/own.js_ (CAP Application Services): ```js module.exports = async srv => { const externalService = await cds.connect.to('ExternalService') externalService.on('ExternalEvent', async msg => { await srv.emit('OwnEvent', msg.data) }) } ``` In _srv/own.js_ (CAP Messaging Services): ```js module.exports = async srv => { const externalService = await cds.connect.to('messaging') messaging.on('ExternalService.ExternalEvent', async msg => { await srv.emit('OwnService.OwnEvent', msg.data) }) } ``` ### Custom Topics with Declared Events > Source: /docs/node.js/messaging#custom-topics-with-declared-events You can specify topics to modeled events using the `@topic` annotation. ::: tip If no annotation is provided, the topic will be set to the fully qualified event name. ::: Example: ```cds service OwnService { @topic: 'my.custom.topic' event OwnEvent { ID: UUID; rating: Decimal; } } ``` ## CloudEvents Protocol > Source: /docs/node.js/messaging#cloudevents-protocol [CloudEvents](https://cloudevents.io/) is a commonly used specification for describing event data. An example event looks like this: ```js { "type": "sap.s4.beh.salesorder.v1.SalesOrder.Created.v1", "specversion": "1.0", "source": "/default/sap.s4.beh/ER9CLNT001", "id": "0894ef45-7741-1eea-b7be-ce30f48e9a1d", "time": "2020-08-14T06:21:52Z", "datacontenttype": "application/json", "data": { "SalesOrder":"3016329" } } ``` To help you adhere to this standard, CAP prefills these header fields automatically. To enable this, you need to set the option `format: 'cloudevents'` in your message broker. Example: ```js { cds: { requires: { messaging: { kind: 'enterprise-messaging-shared', format: 'cloudevents' } } } } ``` You can always overwrite the default values. ### Topic Prefixes > Source: /docs/node.js/messaging#topic-prefixes If you want the topics to start with a certain string, you can set a publish and/or a subscribe prefix in your message broker. Example: ```js { cds: { requires: { messaging: { kind: 'enterprise-messaging-shared', publishPrefix: 'default/sap.cap/books/', subscribePrefix: 'default/sap.cap/reviews/' } } } } ``` ### Topic Manipulations > Source: /docs/node.js/messaging#topic-manipulations #### SAP Event Mesh > Source: /docs/node.js/messaging#sap-event-mesh If you specify your format to be `cloudevents`, the following default prefixes are set: ```js { publishPrefix: '$namespace/ce/', subscribePrefix: '+/+/+/ce/' } ``` In addition to that, slashes in the event name are replaced by dots and the `source` header field is derived based on `publishPrefix`. Examples: | publishPrefix | derived source | |--------------------------|---------------------| | `my/own/namespace/ce/` | `/my/own/namespace` | | `my/own.namespace/-/ce/` | `/my/own.namespace` | ## Emitting Events > Source: /docs/node.js/messaging#emitting-events To send a message to the message broker, you can use the `emit` method on a transaction for the connected service. Example: ```js const messaging = await cds.connect.to('messaging') this.after(['CREATE', 'UPDATE', 'DELETE'], 'Reviews', async (_, req) => { const { ID } = req.data const { rating } = await cds.run( SELECT.one(['round(avg(rating),2) as rating']) .from(Reviews) .where({ ID })) // send to a topic await messaging.emit('my/custom/topic', { ID, rating }) // alternative if you want to send custom headers await messaging.emit('my/custom/topic', { ID, rating }, { 'X-Correlation-ID': req.headers['X-Correlation-ID'] }) // or use the object parameter await messaging.emit({ event: 'my/custom/topic', data: { ID, rating }, headers: { 'X-Correlation-ID': req.headers['X-Correlation-ID'] }}) }) ``` ::: tip The messages are sent once the transaction is successful. By default, a persistent queue is used. See [Event Queues](./event-queues) for more information. ::: ## Receiving Events > Source: /docs/node.js/messaging#receiving-events To listen to messages from a message broker, you can use the `on` method on the connected service. The necessary topic subscriptions are automatically created. Example: ```js const messaging = await cds.connect.to('messaging') // listen to a topic messaging.on('my/custom/topic', msg => { const { ID, rating } = msg.data return cds.run(UPDATE(Books, ID).with({ rating })) }) ``` Once all handlers are executed successfully, the message is acknowledged. If one handler throws an error, the message broker is informed that the message couldn't be consumed properly. In this case, the broker sends the message again. To avoid endless cycles, consider catching all errors. If you want to receive all messages without creating topic subscriptions, you can register on `'*'`. This feature is useful when consuming messages from a dead letter queue. ```js messaging.on('*', async msg => { /*...*/ }) ``` ::: tip In general, messages don't contain user information but operate with a technical user. As a consequence, the user of the message processing context (`cds.context.user`) is set to [`cds.User.privileged`](/node.js/authentication#privileged-user) and, hence, any necessary authorization checks must be done in custom handlers. ::: ### Inbox > Source: /docs/node.js/messaging#inbox-beta- You can store received messages in an inbox before they're processed. Internally, it uses the [task queue](./event-queues) for reliable asynchronous processing. Enable it by setting the `inboxed` option to `true`, for example: ```js { cds: { requires: { messaging: { kind: 'enterprise-messaging', inboxed: true } } } } ``` ## Message Brokers > Source: /docs/node.js/messaging#message-brokers To safely send and receive messages between applications, you need a message broker in-between where you can create queues that listen to topics. All relevant incoming messages are first stored in those queues before they're consumed. This way messages aren't lost when the consuming application isn't available. In CDS, you can configure one of the available broker services in your [`requires` section](cds-connect#cds-env-requires). According to our [grow as you go principle](../get-started/features#grow-as-you-go), it makes sense to first test your application logic without a message broker and enable it later. Therefore, we provide support for [local messaging](#local-messaging) (if everything is inside one Node.js process) as well as [file-based messaging](#file-based). ### Configuring Message Brokers > Source: /docs/node.js/messaging#configuring-message-brokers You must provide all necessary credentials by [binding](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/296cd5945fd84d7d91061b2b2bcacb93.html) the message broker to your app. For local environments, use [`cds bind`](../tools/cds-bind#cds-bind-usage) in a [hybrid setup](../guides/events/event-mesh#run-tests-in-hybrid-setup). ::: tip For local testing use [`kind`: `enterprise-messaging-shared`](#event-mesh-shared) to avoid the complexity of HTTP-based messaging. ::: ### SAP Event Mesh (Shared) > Source: /docs/node.js/messaging#sap-event-mesh-shared `kind`: `enterprise-messaging-shared` Use this if you want to communicate using [SAP Event Mesh](https://help.sap.com/docs/SAP_EM/bf82e6b26456494cbdd197057c09979f/df532e8735eb4322b00bfc7e42f84e8d.html) in a shared way. If you register at least one handler, a queue will automatically be created if not yet existent. Keep in mind that unused queues aren't automatically deleted, this has to be done manually. You have the following configuration options: - `queue`: An object containing the `name` property as the name of your queue, additional properties are described [in the SAP Business Accelerator Hub](https://hub.sap.com/api/SAPEventMeshDefaultManagementAPIs/path/putQueue). - `amqp`: AQMP client options as described in the [`@sap/xb-msg-amqp-v100` documentation](https://www.npmjs.com/package/@sap/xb-msg-amqp-v100?activeTab=readme) If the queue name isn't specified, it's derived from `application_name` and the first four characters of `application_id` of your `VCAP_APPLICATION` environmental variable, as well as the `namespace` property of your SAP Event Mesh binding in `VCAP_SERVICES`: `{namespace}/{application_name}/{truncated_application_id}`. This makes sure that every application has its own queue. Example: ```json { "requires": { "messaging": { "kind": "enterprise-messaging-shared", "queue": { "name": "my/enterprise/messaging/queue", "accessType": "EXCLUSIVE", "maxMessageSizeInBytes": 19000000 }, "amqp": { "incomingSessionWindow": 100 } } } } ``` ::: warning When using `enterprise-messaging-shared` in a multitenant scenario, only the provider account will have an event bus. There is no tenant isolation. ::: ::: tip You need to install the latest version of the npm package `@sap/xb-msg-amqp-v100`. ::: ::: tip For optimal performance, you should set the correct access type. To make sure your server is not flooded with messages, you should set the incoming session window. ::: ### SAP Event Mesh > Source: /docs/node.js/messaging#sap-event-mesh-1 `kind`: `enterprise-messaging` This is the same as `enterprise-messaging-shared` except that messages are transferred through HTTP. For incoming messages, a webhook is used. Compared to `enterprise-messaging-shared` you have the additional configuration option: - `webhook`: An object containing the `waitingPeriod` property as the time in milliseconds until a webhook is created after the application is listening to incoming HTTP requests (default: 5000). Additional properties are described in the `Subscription` object in [SAP Event Mesh - REST APIs Messaging](https://help.sap.com/doc/3dfdf81b17b744ea921ce7ad464d1bd7/Cloud/en-US/messagingrest-api-spec.html). Example: ```json { "requires": { "messaging": { "kind": "enterprise-messaging", "queue": { "name": "my/enterprise/messaging/queue", "accessType": "EXCLUSIVE", "maxMessageSizeInBytes": 19000000 }, "webhook": { "waitingPeriod": 7000 } } } } ``` If your server is authenticated using [XSUAA](authentication#jwt), you need to grant the scope `$XSAPPNAME.emcallback` to SAP Event Mesh for it to be able to trigger the handshake and send messages. ::: code-group ```js [xs-security.json] { ..., "scopes": [ ..., { "name": "$XSAPPNAME.emcallback", "description": "Event Mesh Callback Access", "grant-as-authority-to-apps": [ "$XSSERVICENAME()" ] } ] } ``` ::: Make sure to add this to the service descriptor of your SAP Event Mesh instance: ```js { ..., "authorities": [ "$ACCEPT_GRANTED_AUTHORITIES" ] } ``` ::: warning This will not work in the `dev` plan of SAP Event Mesh. ::: ::: warning If you enable the [cors middleware](https://www.npmjs.com/package/cors), [handshake requests](https://help.sap.com/docs/SAP_EM/bf82e6b26456494cbdd197057c09979f/6a0e4c77e3014acb8738af039bd9df71.html?q=handshake) from SAP Event Mesh might be intercepted. ::: ### Event Mesh in SAP Integration Suite > Source: /docs/node.js/messaging#event-mesh-in-sap-integration-suite-beta- [Event Mesh in SAP Integration Suite](https://help.sap.com/docs/integration-suite/sap-integration-suite/event-mesh) is supported via two `kind` entries, mirroring the SAP Event Mesh variants: | Kind | Protocol | Equivalent to | |------|----------|---------------| | `event-mesh` | HTTP + webhooks | `enterprise-messaging` | | `event-mesh-shared` | AMQP | `enterprise-messaging-shared` | ::: tip Setup via `cds add` Use `cds add event-mesh` or `cds add event-mesh-shared` to automatically configure `package.json`, `mta.yaml`, `event-mesh.json`, and Kyma deployment descriptors. Both support a `--cloudevents` flag. ::: #### `event-mesh` > Source: /docs/node.js/messaging#event-mesh Same as `enterprise-messaging` except it connects to an Event Mesh in SAP Integration Suite service instance (`event-mesh-message-client`). Example: ```json { "requires": { "messaging": { "[production]": { "kind": "event-mesh" } } } } ``` The configuration options are the same as for [`enterprise-messaging`](#sap-event-mesh). #### `event-mesh-shared` > Source: /docs/node.js/messaging#event-mesh-shared Same as `enterprise-messaging-shared` but connects to an Event Mesh in SAP Integration Suite service instance. Best suited for local hybrid testing. Example: ```json { "requires": { "messaging": { "[production]": { "kind": "event-mesh" }, "[hybrid]": { "kind": "event-mesh-shared" } } } } ``` The configuration options are the same as for [`enterprise-messaging-shared`](#event-mesh-shared). ### SAP Integration Suite, Advanced Event Mesh > Source: /docs/node.js/messaging#sap-integration-suite-advanced-event-mesh-beta- `kind`: `advanced-event-mesh` Use this if you want to communicate using [SAP Integration Suite, advanced event mesh](https://help.sap.com/docs/sap-integration-suite/advanced-event-mesh/). The integration with SAP Integration Suite, advanced event mesh is provided using the plugin [`@cap-js/advanced-event-mesh`](https://github.com/cap-js/advanced-event-mesh). Please see the plugin's [setup guide](https://github.com/cap-js/advanced-event-mesh/blob/main/README.md#setup) for more details. ### SAP Cloud Application Event Hub > Source: /docs/node.js/messaging#sap-cloud-application-event-hub `kind`: `event-broker` Use this if you want to communicate using [SAP Cloud Application Event Hub](https://help.sap.com/docs/event-broker). The integration with SAP Cloud Application Event Hub is provided using the plugin [`@cap-js/event-broker`](https://github.com/cap-js/event-broker). Please see the plugin's [setup guide](https://github.com/cap-js/event-broker/blob/main/README.md#setup) for more details.
### Redis PubSub > Source: /docs/node.js/messaging#redis-pubsub-beta- ::: warning This is a beta feature. Beta features aren't part of the officially delivered scope that SAP guarantees for future releases. ::: `kind`: `redis-messaging` Use [Redis PubSub](https://redis.io/) as a message broker. There are no queues: - Messages are lost when consumers are not available. - All instances receive the messages independently. ::: warning No tenant isolation in multitenant scenario When using `redis-messaging` in a multitenant scenario, only the provider account will have an event bus. There is no tenant isolation. ::: ::: tip You need to install the latest version of the npm package `redis`. ::: ### File Based > Source: /docs/node.js/messaging#file-based `kind`: `file-based-messaging` Don't use this in production, only if you want to test your application _locally_. It creates a file and uses it as a simple message broker. >You can have at most one consuming app per emitted event. You have the following configuration options: * `file`: You can set the file path (default is _~/.cds-msg-box_). Example: ```json { "requires": { "messaging": { "kind": "file-based-messaging", "file": "../msg-box" } } } ``` ::: warning No tenant isolation in multitenant scenario When using `file-based-messaging` in a multitenant scenario, only the provider account will have an event bus. There is no tenant isolation. ::: ### Local Messaging > Source: /docs/node.js/messaging#local-messaging `kind`: `local-messaging` You can use local messaging to communicate inside one Node.js process. It's especially useful in your automated tests. ### Composite-Messaging > Source: /docs/node.js/messaging#composite-messaging `kind`: `composite-messaging` If you have several messaging services and don't want to mention them explicitly in your code, you can create a `composite-messaging` service where you can define routes for incoming and outgoing messages. In those routes, you can use glob patterns to match topics (`**` for any number of any character, `*` for any number of any character except `/` and `.`, `?` for a single character). Example: ```json { "requires": { "messaging": { "kind": "composite-messaging", "routes": { "myEnterpriseMessagingReview": ["cap/msg/system/review/*"], "myEnterpriseMessagingBook": ["**/book/*"] } }, "myEnterpriseMessagingReview": { "kind": "enterprise-messaging", "queue": { "name": "cap/msg/system/review" } }, "myEnterpriseMessagingBook": { "kind": "enterprise-messaging", "queue": { "name": "cap/msg/system/book" } } } } ``` ```js module.exports = async srv => { const messaging = await cds.connect.to('messaging') messaging.on('book/repository/book/modified', msg => { // comes from myEnterpriseMessagingBook }) messaging.on('cap/msg/system/review/reviewed', msg => { // comes from myEnterpriseMessagingReview }) } ``` # Database Services > Source: /docs/node.js/databases
## cds.**DatabaseService** class > Source: /docs/node.js/databases#cdsdatabaseservice----class ### class cds.**DatabaseService** extends cds.Service > Source: /docs/node.js/databases#class-cdsdatabaseservice----extends-cdsservice ### srv.begin () → this > Source: /docs/node.js/databases#spansrvspanbegin----this In case of database services this actually starts the transaction by acquiring a physical connection from the connection pool, and optionally sends a command to the database like `BEGIN TRANSACTION`. This method is called automatically by the framework on the first query, so **you never have to call it** in application coding. There are only very rare cases where you'd want to do so, for example to reuse a `tx` object to start subsequent physical transactions after a former `commit` or `rollback`. But this is not considered good practice. ## cds.DatabaseService — Consumption > Source: /docs/node.js/databases#cdsdatabaseservice--consumption ###### databaseservice-consumption > Source: /docs/node.js/databases#databaseservice-consumption ### `InsertResult` (Beta) > Source: /docs/node.js/databases#insertresult-beta - On INSERT, DatabaseServices return an instance of `InsertResult` defined as follows: - Iterator that returns the keys of the created entries, for example: - Example: `[...result]` -> `[{ ID: 1 }, { ID: 2 }, ...]` - In case of `INSERT...as(SELECT...)`, the iterator returns `{}` for each row - `affectedRows`: the number inserted (root) entries or the number of affectedRows in case of INSERT into SELECT - `valueOf()`: returns `affectedRows` such that comparisons like `result > 0` can be used ::: tip `===` can't be used as it also compares the type ::: ## cds.DatabaseService — Configuration > Source: /docs/node.js/databases#cdsdatabaseservice--configuration ###### databaseservice-configuration > Source: /docs/node.js/databases#databaseservice-configuration ### Pool > Source: /docs/node.js/databases#pool Instead of opening and closing a database connection for every request, we use a pool to reuse connections. The following parameters are provided in the pool configuration: - _acquireTimeoutMillis_: The parameter specifies how much time it is allowed to wait an existing connection is fetched from the pool or a new connection is established. - _evictionRunIntervalMillis_: The parameter specifies how often to run eviction checks. In case of 0 the check is not run. - _min_: Minimum number of database connections to keep in pool at any given time. ::: warning This should be kept at the default 0. Otherwise every eviction run destroys all unused connections older than `idleTimeoutMillis` and afterwards creates new connections until `min` is reached. ::: - _max_: Maximum number of database connections to keep in pool at any given time. - _numTestsPerEvictionRun_: Number of database connections to be checked with one eviction run. - _softIdleTimeoutMillis_: Amount of time database connection may sit idle in the pool before it is eligible for eviction. At least "min" connections should stay in the pool. In case of -1 no connection can get evicted. - _idleTimeoutMillis_: The minimum amount of time that a database connection may stay idle in the pool before it is eligible for eviction due to idle time. This parameter supercedes softIdleTimeoutMillis. - _testOnBorrow_: Should the pool validate the database connections before giving them to the clients? - _fifo_: If false, the most recently released resources will be the first to be allocated (stack). If true, the oldest resources will be first to be allocated (queue). Default value: false. Pool configuration can be adjusted by setting the `pool` option as shown in the following example: ```json { "cds": { "requires": { "db": { "kind": "hana", "pool": { "acquireTimeoutMillis": 5000, "min": 0, "max": 100, "fifo": true } } } } } ``` ::: warning The parameters are very specific to the current technical setup, such as the application environment and database location. Even though we provide a default pool configuration, we expect that each application provides its own configuration based on its specific needs. :::
## cds.DatabaseService — UPSERT > Source: /docs/node.js/databases#cdsdatabaseservice--upsert ###### databaseservice-upsert > Source: /docs/node.js/databases#databaseservice-upsert The main use case of upsert is data replication. [Upsert](../cds/cqn.md#upsert) updates existing entity records from the given data or inserts new ones if they don't exist in the database. ::: warning Even if an entity doesn't exist in the database:
→ Upsert is **not** equivalent to Insert. ::: `UPSERT` statements can be created with the [UPSERT](cds-ql#upsert) query API: ```js UPSERT.into('db.Books') .entries({ ID: 4711, title: 'Wuthering Heights', stock: 100 }) ``` `UPSERT` queries are translated into DB native upsert statements, more specifically they unfold to an [UPSERT SQL statement](https://help.sap.com/docs/HANA_CLOUD_DATABASE/c1d3f60099654ecfb3fe36ac93c121bb/ea8b6773be584203bcd99da76844c5ed.html) on SAP HANA and to an [INSERT ON CONFLICT SQL statement](https://www.sqlite.org/lang_upsert.html) on SQLite. - The rows to be upserted need to have the same structure, that is, all rows needs to specify the same named values. - The upsert data must contain all key elements of the entity. - If upsert data is incomplete only the given values are updated or inserted, which means the `UPSERT` statement has "PATCH semantics". - `UPSERT` statements don't have a where clause. The key values of the entity that is upserted are extracted from the data. The following actions are *not* performed on upsert: * UUID key values are _not generated_. * Generic CAP handlers, such as audit logging, are not invoked. ::: warning In contrast to the Java runtime, deep upserts and delta payloads are not yet supported. ::: ## More to Come > Source: /docs/node.js/databases#more-to-come This documentation is not complete yet, or the APIs are not released for general availability. Stay tuned to upcoming releases for further updates. # Events and Requests > Source: /docs/node.js/events ## cds. context > Source: /docs/node.js/events#cds-context This property provides seemingly static access to the current [`cds.EventContext`], that is, the current `tenant`, `user` , `locale`, and so on, from wherever you are in your code. For example: ```js let { tenant, user } = cds.context ``` Usually that context is set by inbound middleware. The property is realized as a so-called continuation-local variable, implemented using [Node.js' async local storage](https://nodejs.org/api/async_context.html) technique, and a getter/setter pair: The getter is a shortcut for[`getStore()`](https://nodejs.org/api/async_context.html#asynclocalstoragegetstore). The setter coerces values into valid instances of [`cds.EventContext`]. For example: ```js [dev] cds repl > cds.context = { tenant:'t1', user:'u2' } > let ctx = cds.context > ctx instanceof cds.EventContext //> true > ctx.user instanceof cds.User //> true > ctx.tenant === 't1' //> true > ctx.user.id === 'u2' //> true ``` If a transaction object is assigned, its `tx.context` is used, hence `cds.context = tx` acts as a convenience shortcut for `cds.context = tx.context`: ```js let tx = cds.context = cds.tx({ ... }) cds.context === tx.context //> true ``` ::: tip Prefer local `req` objects in your handlers for accessing event context properties, as each access to `cds.context` happens through [`AsyncLocalStorage.getStore()`](https://nodejs.org/api/async_context.html#asynclocalstoragegetstore), which induces some minor overhead. ::: ## `cds.EventContext` > Source: /docs/node.js/events#cdseventcontext [`cds.EventContext`]: #cds-event-context "Class cds.EventContext" Instances of this class represent the invocation context of incoming requests and event messages, such as `tenant`, `user`, and `locale`. Classes [`cds.Event`] and [`cds.Request`] inherit from it and hence provide access to the event context properties: ```js this.on ('*', req => { let { tenant, user } = req ... }) ``` In addition, you can access the current event context from wherever you are in your code via the continuation-local variable [`cds.context`](#cds-context): ```js let { tenant, user } = cds.context ``` ### . http > Source: /docs/node.js/events#-http If the inbound process came from an HTTP channel, you can now access express's common [`req`](https://expressjs.com/en/4x/api.html#req) and [`res`](https://expressjs.com/en/4x/api.html#res) objects through this property. It is propagated from `cds.context` to all child requests, so `Request.http` is accessible in all handlers including your database service ones like so: ```js this.on ('*', req => { let { res } = req.http res.set('Content-Type', 'text/plain') res.send('Hello!') }) ``` Keep in mind that multiple requests (that is, instances of `cds.Request`) may share the same incoming HTTP request and outgoing HTTP response (for example, in case of an OData batch request). See sections [`req`](#-req) and [`res`](#-res) of `cds.Request` to learn more about accessing the request and response objects of individual requests within an incoming batch request. ### . id > Source: /docs/node.js/events#-id A unique string used for request correlation. For inbound HTTP requests the implementation fills it from these sources in order of precedence: - `x-correlation-id` header - `x-correlationid` header - `x-request-id` header - `x-vcap-request-id` header - a newly created UUID On outgoing HTTP messages, it's propagated as `x-correlation-id` header. ### . locale > Source: /docs/node.js/events#-locale The current user's preferred locale, taken from the HTTP Accept-Language header of incoming requests and resolved to [_normalized_](../guides/uis/i18n#normalized-locales). ### . tenant > Source: /docs/node.js/events#-tenant A unique string identifying the current tenant, or `undefined` if not in multitenancy mode. In the case of multitenant operation, this string is used for tenant isolation, for example as keys in the database connection pools. ### . timestamp > Source: /docs/node.js/events#-timestamp A constant timestamp for the current request being processed, as an instance of [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date). The CAP framework uses that to fill in values for the CDS pseudo variable `$now`, with the guaranteed same value. [Learn more in the **Managed Data** guide.](../guides/domain/index#managed-data){.learn-more} ### . user > Source: /docs/node.js/events#-user The current user, an instance of `cds.User` as identified and verified by the authentication strategy. If no user is authenticated, `cds.User.anonymous` is returned. [See reference docs for `cds.User`.](authentication#cds-user){.learn-more .indent} ::: tip Please note the difference between `req` in a service handler (instance of `cds.EventContext`) and `req` in an express middleware (instance of `http.IncomingMessage`). Case in point, `req.user` in a service handler is an official API and, if not explicitely set, points to `cds.context.user`. On the other hand, setting `req.user` in a custom authentication middleware is deprecated. ::: ## `cds.Event` > Source: /docs/node.js/events#cdsevent [`cds.Event`]: #cds-event "Class cds.Event" Class [`cds.Event`] represents event messages in [asynchronous messaging](messaging), providing access to the [event](#-event) name, payload [data](#-data), and optional [headers](#-headers). It also serves as **the base class for [`cds.Request`](#cds-request)** and hence for all synchronous interactions. ### . event > Source: /docs/node.js/events#-event The name of the incoming event, which can be one of: * The name of an incoming CRUD request like `CREATE`, `READ`, `UPDATE`, `DELETE` * The name of a custom action or function like `submitOrder` * The name of a custom event like `OrderedBook` ### . data > Source: /docs/node.js/events#-data Contains the event data. For example, the HTTP body for `CREATE` or `UPDATE` requests, or the payload of an asynchronous event message. Use `req.data` for modifications as shown in the following: ```js this.before ('UPDATE',Books, req => { req.data.author = 'Schmidt' // [!code ++] req.query.UPDATE.data.author = 'Schmidt' // [!code --] }) ``` ### . headers > Source: /docs/node.js/events#-headers Provides access to headers of the event message or request. In the case of asynchronous event messages, it's the headers information sent by the event source. For HTTP requests it's the [standard Node.js request headers](https://nodejs.org/api/http.html#http_message_headers). ### eve. before 'commit' > Source: /docs/node.js/events#eve-before-commit ### eve. on 'succeeded' > Source: /docs/node.js/events#eve-on-succeeded ### eve. on 'failed' > Source: /docs/node.js/events#eve-on-failed ### eve. on 'done' > Source: /docs/node.js/events#eve-on-done Register handlers to these events on a per event / request basis. The events are executed when the whole top-level request handling is finished Use this method to register handlers, executed when the whole request is finished. ```js req.before('commit', () => {...}) // immediately before calling commit req.on('succeeded', () => {...}) // request succeeded, after commit req.on('failed', () => {...}) // request failed, after rollback req.on('done', () => {...}) // request succeeded/failed, after all ``` ::: danger The events `succeeded` , `failed`, and `done` are emitted *after* the current transaction ended. Hence, they **run outside framework-managed transactions**, and handlers can't veto the commit anymore. ::: To veto requests, either use the `req.before('commit')` hook, or service-level `before` `COMMIT` handlers. To do something that requires databases in `succeeded`/`failed` handlers, use `cds.spawn()`, or one of the other options of [manual transactions](./cds-tx#manual-transactions). Preferably use a variant with automatic commit/ rollback. Example: ```js req.on('done', async () => { await cds.tx(async () => { await UPDATE `Stats` .set `views = views + 1` .where `book_ID = ${book.ID}` }) }) ``` Additional note about OData: For requests that are part of a changeset, the events are emitted once the entire changeset was completed. If at least one of the requests in the changeset fails, following the atomicity property ("all or nothing"), all requests fail. ## `cds.Request` > Source: /docs/node.js/events#cdsrequest [`cds.Request`]: #cds-request "Class cds.Request" Class `cds.Request` extends [`cds.Event`] with additional features to represent and deal with synchronous requests to services in [event handlers](./core-services#srv-handle-event), such as the [query](#-query), additional [request parameters](#-params), the [authenticated user](#-user), and [methods to send responses](#req-reply-results). [Router]: https://expressjs.com/en/4x/api.html#router [routing]: https://expressjs.com/en/guide/routing.html [middleware]: https://expressjs.com/en/guide/using-middleware.html ### . req > Source: /docs/node.js/events#-req Provides access to the express request object of individual requests within an incoming batch request. For convenience, in the case of non-batch requests, it points to the same request object as [`req.http.req`](#-http). ### . res > Source: /docs/node.js/events#-res Provides access to the express response object of individual requests within an incoming batch request. For convenience, in the case of non-batch requests, it points to the same response object as [`req.http.res`](#-http). ### . method > Source: /docs/node.js/events#-method The HTTP method of the incoming request: | `msg.event` | → | `msg.method` | |-------------|--------|--------------| | CREATE | → | POST | | READ | → | GET | | UPDATE | → | PATCH | | DELETE | → | DELETE | {} ### . target > Source: /docs/node.js/events#-target Refers to the current request's target entity definition, if any; `undefined` for unbound actions/functions and events. The returned definition is a [linked](cds-reflect#linked-csn) definition as reflected from the [CSN](../cds/csn) model. For OData navigation requests along associations, `msg.target` refers to the last target. For example: | OData Request | `req.target` | |-------------------|----------------------| | Books | AdminService.Books | | Books/201/author | AdminService.Authors | | Books(201)/author | AdminService.Authors | {} [See also `req.path` to learn how to access full navigation paths.](#-path){.learn-more} [See _Entity Definitions_ in the CSN reference.](../cds/csn#entity-definitions){.learn-more} [Learn more about linked models and definitions.](cds-reflect){.learn-more} ### . path > Source: /docs/node.js/events#-path Captures the full canonicalized path information of incoming requests with navigation. For requests without navigation, `req.path` is identical to [`req.target.name`](#-target) (or [`req.entity`](#-entity), which is a shortcut for that). Examples based on [cap/samples/bookshop AdminService](https://github.com/capire/bookshop/blob/main/srv/admin-service.cds): | OData Request | `req.path` | `req.target.name` | |-------------------|---------------------------|----------------------| | Books | AdminService.Books | AdminService.Books | | Books/201/author | AdminService.Books/author | AdminService.Authors | | Books(201)/author | AdminService.Books/author | AdminService.Authors | {} [See also `req.target`](#-target){.learn-more} ### . entity > Source: /docs/node.js/events#-entity This is a convenience shortcut to [`msg.target.name`](#-target). ### . params > Source: /docs/node.js/events#-params Provides access to parameters in URL paths as an [*iterable*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterable_protocol) with the contents matching the positional occurrence of parameters in the url path. The respective entry is the key value pair matching the entity definition. For example, the parameters in an HTTP request like that: ```http GET /catalog/Authors(101)/books(title='Eleonora',edition=2) HTTP/1.1 ``` The provided parameters can be accessed as follows: ```js const [ author, book ] = req.params // > author === { ID: 101 } // > book === { title: 'Eleonora', edition: 2 } ``` ### . query > Source: /docs/node.js/events#-query Captures the incoming request as a [CQN query](cds-ql#class-cds-ql-query). For example, an HTTP request like `GET http://.../Books` is captured as follows: ```js req.query = {SELECT:{from:{ref:['Books']}}} ``` For bound custom operations, `req.query` contains the query to the entity on which the operation is called. For unbound custom operations, `req.query` contains an empty object. ### . subject > Source: /docs/node.js/events#-subject Acts as a pointer to the instances targeted by the request. The _target_ of a request is equivalent to the [`source` of a query](../cds/cqn#from). That is, additional query options, such as CQL's `.where()` or OData's `$filter`, are not considered. For example, for the equivalents of inbound requests, addressing _single rows_ like these: ```js AdminService.read(Books,201) AdminService.update(Books,201).with({...}) AdminService.delete(Books,201) ``` ... `req.subject` would always look like that: ```js req.subject //> ... { ref: [{ id: 'AdminService.Books', // == req.target.name where: [ { ref: [ 'ID' ] }, '=', { val: 201 } ] }]} ``` ... which allows it to be used in custom handlers of each inbound request to easily read or write this very target row using [cds.ql](cds-ql) as follows: ```js SELECT.from(req.subject) //> returns the single target row UPDATE(req.subject)... //> updates the single target row DELETE.from(req.subject) //> deletes the single target row ``` > [!warning] > You can use `req.subject` in custom handlers for inbound `READ`, `UPDATE` and `DELETE` requests, as well as in _bound_ actions, addressing **_single rows_**. > **You can't use it** reasonably in custom handlers for `INSERT` requests or other requests addressing **_multiple rows_**. The following example further illustrates the difference between request target and additional query options: ```js // GET Books/201 req.subject = { ref: [{ id: 'AdminService.Books', where: [{ ref: ['ID']}, '=', { val: 201 }] }] } // GET Books?$filter=ID eq 201 req.subject = { ref: [{ id: 'AdminService.Books' }] } ``` ### req. reply (results) > Source: /docs/node.js/events#req-reply-results ```tsx function req.reply ( results : object | object[] | string | number | true | false | null ) ``` Stores the given argument in `req.results`, which is subsequently sent back to the client, rendered in a protocol-specific way. ```js this.on ('READ', Books, req => { req.reply ([ { ID: 1, title: 'Wuthering Heights' }, { ID: 2, title: 'Catweazle' } ]) }) ``` Alternatively, you can also just return a value from your `.on` handler, which is then automatically used as the reply: ```js this.on ('READ', Books, req => { return [ { ID: 1, title: 'Wuthering Heights' }, { ID: 2, title: 'Catweazle' } ] }) ``` ### req. reject () {.method #req-reject} > Source: /docs/node.js/events#req-reject--method-req-reject Constructs and throws an error with the given arguments, which is then sent back to the client in an error response. This is the preferred way to reject requests with errors. ```js this.on('CREATE', Books, req => { const { title } = req.data if (!title?.trim().length) return req.reject ({ // [!code focus] status: 400, // [!code focus] code: 'MISSING_INPUT', // [!code focus] message: 'Input is required', // [!code focus] target: 'title', // [!code focus] }) // [!code focus] }) ``` ::: details **Best Practice:**{.good} Use the `@mandatory` annotation instead. The sample above is just for illustration. Instead, use the [`@mandatory`](../guides/services/constraints#mandatory) annotation in your CDS model to define mandatory inputs like that: ```cds entity Books { key ID : Integer; title : String(111) @mandatory; // [!code focus] ... } ``` This way, the framework automatically checks for mandatory inputs and rejects requests with errors if they are missing. So you don't have to (and should not) implement such checks manually in your code at all. ::: The basic variant used above accepts a single object as argument with these properties: ```tsx function req.reject ({ status? : number, code? : string | number, message? : string, target? : string, args? : string[], ... // custom properties }) ``` | Property | Description | | -------- | ----------- | | `status` | The numeric [HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status). | | `code` | A string code for clients to identify the error, also used as [i18n](cds-i18n) key. | | `message`| A user-readable, potentially localized error message. | | `target` | The name of an input field/element an error is related to. | | `args` | Values to fill in to localized error messages. | [Learn more about `target` for Fiori UIs](https://ui5.sap.com/#/topic/fbe1cb5613cf4a40a841750bf813238e){.learn-more} If `status` is omitted, and `code` is a number, that number is interpreted as the status code. The `code` is used as [i18n](cds-i18n) key to lookup translations for [error responses](#error-responses). If `code` is omitted, a given `message` will be used as [i18n](cds-i18n) key. ### req. reject ( ... ) > Source: /docs/node.js/events#req-reject--- This is a convenience variant of the [`req.reject()`](#req-reject) method, with these arguments: ```tsx function req.reject ( status? : number, message? : string, target? : string, args? : string[] ) ``` For example, it would allow rewriting the [above](#req-reject) sample like that: ```js this.on('CREATE', Books, req => { const { title } = req.data if (!title?.trim().length) req.reject (400, 'MISSING_INPUT', 'title') // [!code focus] }) ``` ### req. error() > Source: /docs/node.js/events#req-error Constructs and records an error with the given arguments. The method is similar to [`req.reject()`](#req-reject), and accepts the same arguments, but does not throw the error immediately. Instead, it collects errors in `req.errors`, which are sent back to the client in an [error response](#error-responses) subsequently. For example: ```js req.error (400, 'Invalid input', 'some_field') req.error (404, 'Not found') ``` All errors are collected in property `req.errors`, which is initially `undefined`, and initialized as an array on the first call. This allows to easily check, whether errors occurred with: ```js if (req.errors) ... //> errors occurred ``` After each phase of request processing, that is, _before_ / _on_ / _after_, the framework checks whether errors got recorded in `req.errors`. If so, it automatically [rejects](#req-reject) the request with an aggregate error containing all recorded errors, and the request is not processed further. So, in essence, the above ends up in the equivalent of: ```js return req.reject ({ code: 'MULTIPLE_ERRORS', details: [ { status: 400, message: 'Invalid input', target: 'some_field' }, { status: 404, message: 'Not found' } ] }) ``` ### req. warn() > Source: /docs/node.js/events#req-warn ### req. info() > Source: /docs/node.js/events#req-info ### req. notify() > Source: /docs/node.js/events#req-notify Use these methods to record messages to be sent back to the client not in an error response but in addition to a successful response. ```js req.notify ('Some notification message') req.info ('Some information message') req.warn ('Some warning message') ``` The methods are similar to [`req.error()`](#req-error), also accepting the [same arguments](#req-reject), but the messages are collected in `req.messages` instead of `req.errors`, not decorated with stack traces, and returned in a HTTP response header (for example, `sap-messages`), instead of the response body. ::: warning User Input & Injection Vulnerabilities Ensure proper validation of the message text if it contains values ​​from user input. ::: ## Error Responses > Source: /docs/node.js/events#error-responses When a request is rejected with an error, the protocol adapters provided with the CAP framework automatically renders them in a protocol-specific way, for example, like that in case of _OData_ as well as _REST_ endpoints: ```http Status: 400 Content-Type: application/json { "error": { "code": "MISSING_INPUT", "message": "Input is required", "target": "title" } } ``` ::: details OData error responses get cleansed In order to be compliant with the spec, all custom properties not foreseen in the spec are purged from the error response. If a custom property shall reach the client, it must be prefixed with `@` to not be purged. ::: [Learn more about OData Error Responses](https://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793091){.learn-more} The error response is generated from the error object constructed via [`req.reject()`](#req-reject) or [`req.error()`](#req-error), and the properties are used and normalized as follows: 1. If `status` is given, it is used as the HTTP status code of the response. If `status` is omitted, and `code` is a number in the range of 300...600, that number is used as the HTTP status code of the response. 2. If `code` is given, and a string, it is used to look up a user-readable error `message` from the [`i18n/messages`](cds-i18n) bundles. If `code` is omitted, the given `message` is used as the [i18n](cds-i18n) key to look up the `message`, and if found, the original value of `message` is used as `code` in the response. 3. If an `Accept-Language` header is present in the request, a localized message is looked up in addition, using the preferred language specified in the header, and used for the `message` property in the HTTP response. If no suitable localization is found, the original message as resolved in step 2 is returned. For example: ```js req.reject ({ code: 400, message: 'MISSING_INPUT', target: 'title' }) req.reject (400, 'MISSING_INPUT', 'title') // same as above ``` ... would result in a response like this for `Accept-Language: de`: ```http Status: 400 Content-Type: application/json { "error": { "code": "MISSING_INPUT", "message": "Eingabe ist erforderlich", "target": "title" } } ``` > [!warning] Error Sanitization > In production, error responses should never disclose internal information that could be exploited by attackers. To ensure that, all errors with a `5xx` status code are returned to the client with only the respective generic message (example: `500 Internal Server Error`). > > In very rare cases, you might want to return 5xx errors with a meaningful message to the client. This can be achieved with `err.$sanitize = false`. Use that option with care! ## Translations for Validation Errors > Source: /docs/node.js/events#translations-for-validation-errors For the following annotations/error codes, the runtime provides default translations: | Annotation | Error Code | |-------------------------|---------------------------------| | `@mandatory` | ASSERT_MANDATORY(1) | | `@assert.range` | ASSERT_RANGE | | `@assert.range` on enum | ASSERT_ENUM | | `@assert.format` | ASSERT_FORMAT | | `@assert.target` | ASSERT_TARGET | (1) Falls back to error code `ASSERT_NOT_NULL` if provided in custom translations. These can be overridden by the known technique of providing [custom i18n messages](cds-i18n#localized-messages). # Querying in JavaScript > Source: /docs/node.js/cds-ql ## Constructing Queries > Source: /docs/node.js/cds-ql#constructing-queries Module `cds.ql` provides facilities to construct queries in [*Core Query Notation (CQN)*](../cds/cqn) in different flavours and styles: 1. Fluent API style, with query-by-example objects for where clauses and order by clauses: ```js let q = SELECT.from('Books').where({ID:201}).orderBy({title:1}) ``` 2. Using with [tagged template literals (TTL)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates): ```js let q = cds.ql `SELECT from Books where ID=${201} order by title` ``` 3. Fluent API with interspersed [tagged template literals (TTL)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates): ```js let q = SELECT.from `Books where ID=${201} order by title` let p = SELECT.from `Books`.where`ID=${201}`.orderBy`title` ``` 4. Manually constructing CQN objects: ```js const { expr, ref, val, columns, expand, where, orderBy } = cds.ql ``` ```js let q = { SELECT: { from: ref`Books`, where: [ref`ID`, '=', val(201)], orderBy: [ref`title`], } } ``` ```js let q = { SELECT: { from: ref`Authors`, columns: [ ref`ID`, ref`name`, expand (ref`books`, where`stock>7`, orderBy`title`, columns`ID,title` ) ], where: [ref`name`, 'like', val('%Poe%')] } } ``` #### API Facades > Source: /docs/node.js/cds-ql#api-facades The API is made available through global objects `SELECT`, `INSERT`, `UPSERT`, `UPDATE`, `DELETE`. Alternatively, you can obtain these objects from `cds.ql` like so: ```js const cds = require('@sap/cds') const { SELECT, INSERT, UPDATE, DELETE } = cds.ql ``` #### Using Reflected Definitions > Source: /docs/node.js/cds-ql#using-reflected-definitions It is recommended best practice to use entity definitions reflected from a service's model to construct queries. Doing so simplifies code as it avoids repeating namespaces all over the place. ```js const { Books } = cds.entities let q1 = SELECT.from (Books) .where `ID=${201}` ``` [Learn more about using reflected definitions from a service's model](core-services#-entities){.learn-more} #### Not Locked in to SQL > Source: /docs/node.js/cds-ql#not-locked-in-to-sql While both [CQL](../cds/cql) / [CQN](../cds/cqn) as well as the fluent API of `cds.ql` resemble well-known SQL syntax neither of them are locked in to SQL. In fact, queries can be sent to any kind of services, including NoSQL databases or remote services for execution. ## Executing Queries > Source: /docs/node.js/cds-ql#executing-queries Queries are executed by passing them to a service's [`srv.run()`](core-services#srv-run-query) method, for example, to the primary database: ```js let query = SELECT `ID,title` .from `Books` let books = await cds.db.run (query) ``` Alternatively, you can just `await` a constructed query, which by default passes the query to `cds.db.run()`. So, the following is equivalent to the above: ```js let books = await SELECT `ID,title` .from `Books` ``` Instead of a database service, you can also send queries to other services, local or remote ones. For example: ```js const cats = await cds.connect.to ('CatalogService') let books = await cats.run (query) ``` > `CatalogService` might be a remote service connected via OData. In this case, the query would be translated to an OData request sent via HTTP. The APIs are also available through [`cds.Service`'s CRUD-style Convenience API](core-services#crud-style-api), for example: ```js const db = cds.db let books = await db.read`Books`.where`ID=${201}`.orderBy`title` ``` ## First-Class Objects > Source: /docs/node.js/cds-ql#first-class-objects Constructing queries doesn't execute them immediately, but just captures the given query information. Very much like functions in JavaScript, queries are first-class objects, which can be assigned to variables, modified, passed as arguments, or returned from functions. Let's investigate this somewhat more, given this example: ```js let cats = await cds.connect.to('CatalogService') //> connected via OData let PoesBooks = SELECT.from (Books) .where `name like '%Poe%'` let books = await cats.get (PoesBooks) ``` This is what happens behind the scenes: 1. We use the fluent API to construct a query as a CQN and assign it to `PoesBooks` 2. We pass the query as an argument to function `cats.get()` 3. The get event handler translates the query to an OData request sent to the remote service 4. The remote OData protocol adapter translates the inbound query back to CQN 5. This CQN query is passed on to the remote service provider 6. A registered event handler forwards that query to the local `cds.db` service 7. The database service implementation translates the query to plain SQL and sends that to the database for execution #### Leveraging Late Materialization > Source: /docs/node.js/cds-ql#leveraging-late-materialization You can also combine queries much like sub selects in SQL to form more complex queries as shown in this example: ```sql let input = '%Brontë%' let Authors = SELECT `ID` .from `Authors` .where `name like ${ input }` let Books = SELECT.from `Books` .where `author_ID in ${ Authors }` ``` ```js await cds.run (Books) //> late/no materialization of Authors ``` With that we leverage late materialization, offered by SQL databases. Compare that to inferior imperative programming: ```js let input = '%Brontë%' let Authors = await SELECT `ID` .from `Authors` .where `name like ${ input }` for (let a of Authors) { //> looping over eagerly materialized Authors let Books = await SELECT.from `Books` .where `author_ID = ${ a.ID }` } ``` ## Avoiding SQL Injection > Source: /docs/node.js/cds-ql#avoiding-sql-injection All the APIs are designed to avoid [SQL Injection](https://wikipedia.org/wiki/SQL_injection) by default. For example, let's see how the following code would be executed: ```js let input = 201 //> might be entered by end users let books = await SELECT.from `Books` .where `ID=${input}` ``` The query is... 1. captured as a CQN object with the where clause represented as: ```js ..., where:[ {ref:['ID']}, '=', {val:201} ] ``` 2. translated to plain SQL string with binding parameters ```sql SELECT ID from Books where ID=? ``` 3. executed with binding parameters provided from `{val}`entries in CQN ```js dbc.run (sql, [201]) ``` The only mistake you could make is to imperatively concatenate user input with CQL or SQL fragments, instead of using the tagged strings or other options promoted by `cds.ql`. For example, assumed you had written the above code sample like that: ```js let input = 201 //> might be entered by end users let books = await SELECT.from `Books` .where ('ID='+input) let bookz = await SELECT.from `Books` .where (`ID=${input}`) ``` > **Note** also that tagged template strings never have surrounding parentheses! That means, the third line above does the very same string concatenation as the second line. A malicious user might enter some SQL code fragment like that: ```sql 0; DELETE from Books; -- gotcha! ``` {} In effect, your generated SQL statements would effectively look like that: ```sql SELECT ID from Books where ID=0; DELETE from Books; -- gotcha! ``` ::: danger Whenever there's user input involved... Never use string concatenation when constructing queries! Never surround tagged template strings with parentheses! ::: ## Using `cds repl` > Source: /docs/node.js/cds-ql#using-cds-repl Event though being a reference doc, the sections below will never be able to cover any possible query you might want to construct. For that reason, we recommend to use the `cds repl` command to experiment with queries interactively. It is a great way to learn how to construct queries and to experiment with them. Here is an example session: ```sh cds repl -u ql ``` ```js cds.ql`SELECT from Authors { ID, name, books [order by title] { ID, title, genre.name as genre } } where exists books.genre[name = 'Mystery']` ``` ... which will display this: ```js cds.ql { SELECT: { from: { ref: [ 'Authors' ] }, columns: [ { ref: [ 'ID' ] }, { ref: [ 'name' ] }, { ref: [ { id: 'books', orderBy: [ { ref: [ 'title' ] } ] } ], expand: [ { ref: [ 'ID' ] }, { ref: [ 'title' ] }, { ref: [ 'genre', 'name' ], as: 'genre' } ] } ], where: [ 'exists', { ref: [ 'books', { id: 'genre', where: [ { ref: [ 'name' ] }, '=', { val: 'Mystery' } ] } ] } ] } } ``` You can also test-drive the query by executing it with a running application: ```sh cds repl -u ql -r cap/samples/bookshop ``` ```js await cds.ql`SELECT from Authors { ID, name, books [order by title] { ID, title, genre.name as genre } } where exists books.genre[name = 'Mystery']` ``` ... which would display the results like that: ```js [ { ID: 150, name: 'Edgar Allan Poe', books: [ { ID: 251, title: 'The Raven', genre: 'Mystery' }, { ID: 252, title: 'Eleonora', genre: 'Romance' } ] } ] ``` > [!TIP] > Using `cds repl` as shown above is likely the best way to learn how to construct queries in detail. > When doing so, ensure to use the `cds.ql` functions with full queries in CQL syntax, as shown in the example above, as that is the most reliable way to ensure correctness. [An article by DJ Adams exploring `cds repl`.](https://qmacro.org/blog/posts/2025/03/21/level-up-your-cap-skills-by-learning-how-to-use-the-cds-repl/){.learn-more} ## cds.ql() > Source: /docs/node.js/cds-ql#cdsql Use the `cds.ql()` method to construct instances of [`cds.Query`](#class-cds-ql-query) from these inputs: - tagged template strings (SELECT only) - normal strings (SELECT only) - plain CQN objects For example: ```js let q = cds.ql ({ SELECT: { from: {ref:[ Books.name ]} }}) let q = cds.ql (`SELECT from Books { ID, title }`) let q = cds.ql `SELECT from ${Books} { ID, title }` q instanceof cds.ql.Query //> true ``` If the input is already a `cds.Query` instance, it is returned unchanged: ```js let q1 = cds.ql `SELECT from Books` let q2 = cds.ql (q1) q1 === q2 //> true ``` ## cds.ql.clone() > Source: /docs/node.js/cds-ql#cdsqlclone ###### cds-ql-clone > Source: /docs/node.js/cds-ql#cds-ql-clone Use the `cds.ql.clone()` method to create clones of given queries, which can be plain CQN objects, or instances of `cds.Query` themselves. This is useful to avoid side effects when modifying queries prior to execution. The returned clone is always an instance of [`cds.Query`](#class-cds-ql-query). For example, given this original query, which would be captured in CQN as shown below: ```js q1 = SELECT.from`Books` .where`title like 'Wu%'`.orderBy`genre.name` ``` ```zsh => cds.ql { SELECT: { from: { ref: [ 'Books' ] }, where: [ { ref: [ 'title' ] }, 'like', { val: 'Wu%' } ], orderBy: [ { ref: [ 'genre', 'name' ] } ] } } ``` We can create a clone and modify it like this: ```js q2 = cds.ql.clone (q1) ``` We can then modify `q2` without changing `q1`, for example like this: ```js // Override where clause q2.SELECT.where = cds.ql.predicate`author.name = 'Emily%'` ``` ```js // Append an additional order by clause q2.orderBy`title asc` ``` We can use the `.flat()` method to see the effective modified query: ```js q2.flat() ``` ```zsh => cds.ql { SELECT: { from: { ref: [ 'Books' ] }, where: [ { ref: [ 'author', 'name' ] }, '=', { val: 'Emily%' } ], orderBy: [ { ref: [ 'genre', 'name' ] }, { ref: [ 'title' ], sort: 'asc' } ] } } ``` ## cds.ql. Query > Source: /docs/node.js/cds-ql#cdsql-query ###### class-cds-ql-query > Source: /docs/node.js/cds-ql#class-cds-ql-query Instances of `cds.Query` capture queries at runtime. Subclasses provide [fluent APIs](#constructing-queries) to construct queries as highlighted below. ### .kind > Source: /docs/node.js/cds-ql#kind The kind of query, that is one of these strings: - `'SELECT'` - `'INSERT'` - `'UPSERT'` - `'UPDATE'` - `'DELETE'` This is usefull for generic query processors, such as outbound protocol adapters or database services, which need to translate given queries into target representations. ### then() > Source: /docs/node.js/cds-ql#then Instances of `cds.Query` are thenables. `await`ing them executes the query with the bound service or the primary database service. ```js await SELECT.from(Books) // is equivalent to: await cds.db.run( SELECT.from(Books) ) ``` ### bind (srv) > Source: /docs/node.js/cds-ql#bind-srv Binds a query for execution with the given `srv` . ```js let srv = new cds.Service await SELECT.from(Books).bind(srv) // is equivalent to: await srv.run( SELECT.from(Books) ) ``` ## SELECT > Source: /docs/node.js/cds-ql#select Fluent API to construct [CQN SELECT](../cds/cqn#select) query objects in a [CQL](../cds/cql)/SQL-like style. In contrast to SQL, though, the clauses can be arrayed in arbitrary order. `SELECT` itself is a function acting as a shortcut to `SELECT.columns`, thereby resembling SQL syntax: ```sql SELECT `a, b` .from `Foo` -- is a shortcut for: SELECT .columns `a, b` .from `Foo` ``` Moreover, it accepts a single tagged template string starting with `from`: ```js const limit = 11, sort_column = 'a' const q = SELECT `from Foo { a, b as c, sum(d) } where x < ${limit} group by a,b order by ${sort_column} asc` const foos = await q ``` This allows constructing [CQN](../cds/cqn) query objects using [CQL](../cds/cql) language constructs which are not covered by `cds.ql` fluent API. ### .one > Source: /docs/node.js/cds-ql#one ###### select-one > Source: /docs/node.js/cds-ql#select-one Start constructing a query with `SELECT.one` to indicate we're interested in only the first row. At runtime, a single entry, if any, is returned instead of an array: ```js const one = await SELECT.one.from (Authors) ``` > same effect, but potentially more expensive: ```js const [one] = await SELECT.from (Authors) ``` ### .elements > Source: /docs/node.js/cds-ql#elements ###### select-elements > Source: /docs/node.js/cds-ql#select-elements The CSN outline of the selected elements as an object. Key is the selected element or alias, value is the CSN definition: Let's assume the following query: ```js SELECT.from('sap.capire.bookshop.Books').columns('ID', 'title') ``` This query is represented within `.elements` as: ```js { ID: number { key: true, type: 'cds.Integer' }, title: string { '@mandatory': true, localized: true, type: 'cds.String', length: 111, '@Common.FieldControl': { '#': 'Mandatory' } } } ``` This is useful for custom implementations that act on the selection of specific elements. ### .distinct > Source: /docs/node.js/cds-ql#distinct ###### select-distinct > Source: /docs/node.js/cds-ql#select-distinct Start the query with `SELECT.distinct` to skip duplicates as in SQL: ```js SELECT.distinct.from (Authors) ``` ### columns() > Source: /docs/node.js/cds-ql#columns ###### select-columns > Source: /docs/node.js/cds-ql#select-columns ```tsx function SELECT.columns ( projection : function ) function SELECT.columns ( cql : tagged template string ) function SELECT.columns ( columns[] : CQL expr string | CQN expr object ) function SELECT.columns ( ...columns[] : CQL expr string | CQN expr object ) ``` Specifies which columns to be fetched, very much like SQL select clauses, enhanced by [CQL](../cds/cql) projections and path expressions. The arguments can be a projection function, a tagged template string, or individual column expressions as CQL string snippets, or as [CQN column expression objects](../cds/cqn.md#select). ```sql SELECT.from `Books` .columns (b => { b.title, b.author.name.as('author') }) SELECT.from `Books` .columns `{ title, author.name as author }` SELECT.from `Books` .columns `title, author.name as author` SELECT.from `Books` .columns ( 'title', 'author.name as author') SELECT.from `Books` .columns ( 'title', {ref:['author','name'],as:'author'} ) SELECT.from `Books` .columns (['title', {ref:['author','name'],as:'author'} ]) ``` Projection functions are the **most recommended** way to specify projections as they have several advantages (with tagged templates coming closest): - they support nested projections, aka expands - they don't need to call a parser - they resemble CQL very much - they use standard JavaScript constructs - we can perspectively offer type inference and code completion With respect to resembling CQL let's compare this query in CQL using entity aliases to the `cds.ql` code sample below: ```sql SELECT from Authors a { a.ID, a.name, a.books { *, createdAt as since, suppliers[city='Paris']{*} } } ``` Here is the same using `cds.ql` with projection functions: ```js SELECT.from ('Authors', a => { a.ID, a.name, a.books (b => { b`.*`, b.createdAt`as since`, b.suppliers`[city='Paris']`('*') }) }) ``` Projection functions use these mechanisms: - projections are single-argument arrow functions: `a => { ... }` - with the argument as entity alias in column expressions: `a.name` - with functions for nested projections: `a.books (b => {...})` - with `*` as special case of that: ```b`.*` ```, and `b.suppliers('*')` - with template strings for aliases: ```b.createdAt`as since` ``` - as well as for infix filters: ```b.suppliers`[city='Paris']` ``` **Note:** Not every CQL or SQL construct can be expressed with projection functions. This is where tagged template strings kick in ### from() > Source: /docs/node.js/cds-ql#from ###### select-from > Source: /docs/node.js/cds-ql#select-from ```tsx function SELECT.from ( entity : string | CSN definition | tagged template string, key? : string | number | object, cols? : array | projection ) ``` Fills in [CQN `from` clauses](../cds/cqn.md#select), optionally adding a primary key, and a projection. The latter are alternatives for using separate `.one`, `.where` and `.columns` clauses.
For example, these queries: ```js SELECT.from (Books,201) SELECT.from (Books,201, b => { b.ID, b.title }) ``` ... are equivalent to these: ```js SELECT.one.from (Books) .where ({ID:201}) SELECT.one.from (Books) .where ({ID:201}) .columns (b => { b.ID, b.title }) ``` > NOTE: Specifying a `key` argument automatically [enables `SELECT.one`](#select-one). Argument `key` can be a single string or number value, or a [query-by-example](#select-where) object: ```js SELECT.from (Books,201) //> shortcut for {ID:201} SELECT.from (Books, {ID:201}) SELECT.from (Books.texts, {ID:201, locale:'de'}) ``` Argument `cols` is a projection [as accepted by `.columns (cols)`](#select-columns) ### alias() > Source: /docs/node.js/cds-ql#alias Specifies the alias which you can refer to in other functions: ```js SELECT.from ('Authors').alias('a').where({ exists: SELECT.from('Books').where('author_ID = a.ID') }) ``` ### where() > Source: /docs/node.js/cds-ql#where ###### select-where > Source: /docs/node.js/cds-ql#select-where ### having() > Source: /docs/node.js/cds-ql#having ###### select-having > Source: /docs/node.js/cds-ql#select-having These two methods fill in corresponding [CQL](../cds/cql) clauses with predicate expressions. ```tsx function SELECT.where/having ( qbeobj : query-by-example object ) function SELECT.where/having ( clause : tagged template string ) function SELECT.where/having ( expr: string, value: any, ... ) ``` Expressions can be specified as a query-by-example object, a tagged template string, or as an alternating string / value arguments list: ```js SELECT.from `Books` .where ({ ID: req.data.ID }) // qbe SELECT.from `Books` .where `ID = ${req.data.ID}` // tts SELECT.from `Books` .where ('ID =', req.data.ID) // expr/value list ``` Assumed we got some user input as follows: ```js const name='foo', kinds=[1,2,3], min=0.1, max=0.9, stock=111 ``` With tagged template strings we could construct a query like that: ```js SELECT.from `Foo` .where `name like ${name} and ( kind in ${kinds} or ratio between ${min} and ${max} or stock >= ${stock} )` ``` Doing the same with object literals would look like that: ```js SELECT.from('Foo') .where ({ name: {like:'%foo%'}, and: { kind: { in: kinds }, or: { ratio: { between: min, and: max }, or: { stock: { '>=': stock } } } }}) ``` The provided expression is consistently accounted for by wrapping the existing where clause in an `xpr` if needed. ### groupBy() > Source: /docs/node.js/cds-ql#groupby ###### select-group-by > Source: /docs/node.js/cds-ql#select-group-by Fills in SQL `group by` clauses. Arguments are a single tagged template string, or column expression strings or [CXN](../cds/cxn.md) objects, like that: ```js SELECT ... .groupBy `a.name, b` SELECT ... .groupBy ('a.name', 'b') SELECT ... .groupBy ({ref:['a','name']}, {ref:['b']}) ``` ### orderBy() > Source: /docs/node.js/cds-ql#orderby ###### select-order-by > Source: /docs/node.js/cds-ql#select-order-by Fills in SQL `order by` clauses. Arguments are a single tagged template string, or column expression strings, optionally followed by `asc` or `desc`, or [CXN](../cds/cxn.md) objects, like that: ```js SELECT ... .orderBy `a.name, b desc` SELECT ... .orderBy ('a.name', 'b desc') SELECT ... .orderBy ({ref:['a','name']}, {ref:['b'],sort:'desc'}) ``` ### limit() > Source: /docs/node.js/cds-ql#limit ###### select-limit > Source: /docs/node.js/cds-ql#select-limit Equivalent of the standard SQL `limit` and `offset` clauses. Arguments can be standard numbers or [CXN](../cds/cxn.md) expression objects. ```js SELECT ... .limit (25) //> first page SELECT ... .limit (25,100) //> fifth page ``` ### forUpdate() > Source: /docs/node.js/cds-ql#forupdate ###### select-for-update > Source: /docs/node.js/cds-ql#select-for-update Exclusively locks the selected rows for subsequent updates in the current transaction, thereby preventing concurrent updates by other parallel transactions. ```js try { let book = await SELECT.from(Books,201).forUpdate() //> book is locked for other transactions await UPDATE (Books,201) .with ({...}) } catch (e) { //> failed to acquire the lock, likely because of timeout } ``` The `options` argument is optional; currently supported is: * `wait` — an integer specifying the timeout after which to fail with an error in case a lock couldn't be obtained. The time unit is database-specific. On SAP HANA, for example, the time unit is seconds. A default `wait` value that is used if `options.wait == null` can be specified via cds.sql.lock_acquire_timeout: -1. A value of `-1` can be used to deactivate the default for the individual call. If the wait option isn't specified, the database-specific default behavior applies. All acquired locks are released when the current transaction is finished, that is, committed or rolled back. ### forShareLock() > Source: /docs/node.js/cds-ql#forsharelock Locks the selected rows in the current transaction, thereby preventing concurrent updates by other parallel transactions, until the transaction is committed or rolled back. Using a shared lock allows all transactions to read the locked record. If a queried record is already exclusively locked by another transaction, the `.forShareLock()` method waits for the lock to be released. ### hints() > Source: /docs/node.js/cds-ql#hints Passes hints to the database query optimizer that can influence the execution plan. The hints can be passed as individual arguments or as an array. ```js SELECT ... .hints ('IGNORE_PLAN_CACHE') SELECT ... .hints ('IGNORE_PLAN_CACHE', 'MAX_CONCURRENCY(1)') SELECT ... .hints (['IGNORE_PLAN_CACHE', 'MAX_CONCURRENCY(1)']) ``` ### pipeline() > Source: /docs/node.js/cds-ql#pipeline Pipes the data from the database into the given writable stream. ```js SELECT ... .pipeline (cds.context.http.res) ``` > Please note that the after handlers don't have effect if this stream is piped to the HTTP response. ### stream() > Source: /docs/node.js/cds-ql#stream Returns the data from the database as a raw stream. ```js SELECT ... .stream () ``` ### foreach() > Source: /docs/node.js/cds-ql#foreach Creates an object stream and calls the provided callback for each object. ```js await SELECT.from(Books).foreach ((book) => { ... }) ``` Since the SELECT query implements the async iterator protocol, you can also use it with `for await`. ```js for await (const book of SELECT.from(Books)) { ... } ``` :::warning Streaming APIs only implemented by Database Services As of now, `SELECT.foreach()` and `SELECT.pipeline()` are only supported by `cds.DatabaseService`. `cds.RemoteService` does not support the streaming APIs yet. ::: ## INSERT > Source: /docs/node.js/cds-ql#insert ###### insert > Source: /docs/node.js/cds-ql#insert-1 Fluent API to construct [CQN INSERT](../cds/cqn#insert) query objects in a [CQL](../cds/cql)/SQL-like style. In contrast to SQL, though, the clauses can be arrayed in arbitrary order. `INSERT` itself is a function acting as a shortcut to `INSERT.entries`, allowing uses like that: ```js const books = [ { ID:201, title:'Wuthering Heights', author_id:101, stock:12 }, { ID:251, title:'The Raven', author_id:150, stock:333 }, { ID:271, title:'Catweazle', author_id:170, stock:222 } ] INSERT (books) .into (Books) ``` ### into() > Source: /docs/node.js/cds-ql#into ###### insert-into > Source: /docs/node.js/cds-ql#insert-into ```tsx function INSERT.into ( entity : string | CSN definition | tagged template string, entries? : object[] ) ``` Specifies the target entity to insert data into, either as a string or a reflected definition: ```js const { Books } = cds.entities INSERT.into (Books) .entries (...) INSERT.into ('Books') .entries (...) INSERT.into `Books` .entries (...) ``` You can optionally pass records of data [as accepted by `.entries`](#insert-entries) as a shortcut to which: ```js INSERT.into (Books, [ { ID:201, title:'Wuthering Heights', author_id:101, stock:12 }, { ID:251, title:'The Raven', author_id:150, stock:333 }, { ID:271, title:'Catweazle', author_id:170, stock:222 } ]) ``` ### entries() > Source: /docs/node.js/cds-ql#entries ###### insert-entries > Source: /docs/node.js/cds-ql#insert-entries ```tsx function INSERT.entries (records : object[] | Query | Readable) ``` Allows inserting multiple rows with one statement. The arguments can be one of... - one or more records as variable list of arguments - an array of one or more records - a readable stream - a sub SELECT query Using individual records: ```js await INSERT.into (Books) .entries ( { ID:201, title:'Wuthering Heights', author_id:101, stock:12 }, { ID:251, title:'The Raven', author_id:150, stock:333 }, { ID:271, title:'Catweazle', author_id:170, stock:222 } ) ``` Using an **array** of records, read from a JSON: ```js let books = JSON.parse (fs.readFileSync('books.json')) await INSERT(books).into(Books) // same as INSERT.into(Books).entries(books) ``` Using a **stream** instead of reading and parsing the full JSON into memory: ```js let stream = fs.createReadStream('books.json') await INSERT(stream).into(Books) // same as INSERT.into(Books).entries(stream) ``` Using a **subselect** query to copy *within* the database: ```js await INSERT.into (Books) .entries (SELECT.from(Products)) ``` ::: details Pushed down to database.... Note that the sub select variant creates a single [native `INSERT INTO SELECT` SQL statement](https://www.w3schools.com/sql/sql_insert_into_select.asp), which is most efficient, as the data is copied **within** the database. In contrast to that, ... ```js INSERT.into(Books).entries(await SELECT.from(Products)) ``` ... would also work, but would be much less efficient, as it would (1) first read all data from database into the client and then (2) insert the read data back into the database. ::: ### values() > Source: /docs/node.js/cds-ql#values ### rows() > Source: /docs/node.js/cds-ql#rows Use `.columns` with `.values` as in SQL: ```js INSERT.into (Books) .columns ( 'ID', 'title', 'author_id', 'stock' ) .values ( 201, 'Wuthering Heights', 101, 12 ) ``` > Both, `.columns` and `.values` can alternatively wrapped into an array. Use `.rows` instead of `.values` to insert multiple rows with one statement: ```js INSERT.into (Books) .columns ( 'ID', 'title', 'author_id', 'stock' ) .rows ( [ 201, 'Wuthering Heights', 101, 12 ], [ 251, 'The Raven', 150, 333 ], [ 252, 'Eleonora', 150, 234 ] ) ``` ::: tip In Essence: [Managed fields](../guides/domain/index#managed-data) and [UUIDs](../guides/domain/index#prefer-uuids-for-keys) are automatically filled with `INSERT.entries()`, but not when using `INSERT.columns().values()` or `INSERT.columns().rows()`. ::: ### from() > Source: /docs/node.js/cds-ql#from-1 Constructs a _INSERT into SELECT_ statement. ```js INSERT.into('Bar') .from (SELECT.from('Foo')) ``` ## UPSERT > Source: /docs/node.js/cds-ql#upsert ###### upsert > Source: /docs/node.js/cds-ql#upsert-1 Fluent API to construct [CQN UPSERT](../cds/cqn#upsert) query objects in a [CQL](../cds/cql)/SQL-like style. In contrast to SQL, though, the clauses can be arrayed in arbitrary order. `UPSERT` itself is a function acting as a shortcut to `UPSERT.entries`, allowing uses like that: ```js const books = [ { ID:201, title:'Wuthering Heights', author_id:101, stock:12 }, { ID:251, title:'The Raven', author_id:150, stock:333 }, { ID:271, title:'Catweazle', author_id:170, stock:222 } ] UPSERT (books) .into (Books) ``` ### into() > Source: /docs/node.js/cds-ql#into-1 ```tsx function UPSERT.into ( entity : string | CSN definition | tagged template string, entries? : object[] ) ``` Specifies the target entity to upsert data into, either as a string or a reflected definition.. ```js const { Books } = cds.entities UPSERT.into (Books) .entries (...) UPSERT.into ('Books') .entries (...) UPSERT.into `Books` .entries (...) ``` You can optionally pass records of data [as accepted by `.entries`](#upsert-entries) as a shortcut to which: ```js UPSERT.into (Books, [ { ID:201, title:'Wuthering Heights', author_id:101, stock:12 }, { ID:251, title:'The Raven', author_id:150, stock:333 }, { ID:271, title:'Catweazle', author_id:170, stock:222 } ]) ``` ### entries() > Source: /docs/node.js/cds-ql#entries-1 ###### upsert-entries > Source: /docs/node.js/cds-ql#upsert-entries Allows upserting multiple rows with one statement where each row is a record with named values, for example, as could be read from a JSON source. ```js UPSERT.into (Books) .entries ( { ID:201, title:'Wuthering Heights', author_id:101, stock:12 }, { ID:251, title:'The Raven', author_id:150, stock:333 }, { ID:271, title:'Catweazle', author_id:170, stock:222 } ) ``` The entries can be specified as individual method parameters of type object — as shown above —, or as a single array of which. [Learn more about limitations when using it with databases.](databases#databaseservice-upsert){.learn-more} ## UPDATE > Source: /docs/node.js/cds-ql#update ###### update > Source: /docs/node.js/cds-ql#update-1 Fluent API to construct [CQN UPDATE](../cds/cqn#update) query objects in a [CQL](../cds/cql)/SQL-like style. In contrast to SQL, though, the clauses can be arrayed in arbitrary order. `UPDATE` itself is a function acting as a shortcut to `UPDATE.entity`, allowing usages like this: ```sql UPDATE `Books` .set `stock = stock - ${quantity}` -- as shortcut to: UPDATE.entity `Books` .set `stock = stock - ${quantity}` ``` ### entity() > Source: /docs/node.js/cds-ql#entity ```tsx function UPDATE.entity ( entity : string | CSN definition | tagged template string, key? : string | number | object, ) ``` Specifies the target of the update operation, optionally followed by a primary key, and a projection. The latter provides an alternative for using separate `.where` clauses.
For example, these queries are equivalent: ```js UPDATE (Books,201)... UPDATE (Books) .where ({ID:201}) ... ``` Argument `key` can be a single string or number value, or a [query-by-example](#select-where) object: ```js UPDATE (Books,201) ... //> shortcut for {ID:201} UPDATE (Books, {ID:201}) ... UPDATE (Books.texts, {ID:201, locale:'de'}) ... ``` ### set() > Source: /docs/node.js/cds-ql#set ### with() > Source: /docs/node.js/cds-ql#with Specifies the data to update... 1. As a single-expression tagged template string ```js let [ ID, quantity ] = [ 201, 1 ] UPDATE `Books` .set `stock = stock - ${quantity}` .where `ID=${ID}` ``` 2. As an object with keys being element names of the target entity and values being simple values, [query-by-example](#select-where) expressions, or [CQN](../cds/cqn.md) expressions: ```js let [ ID, quantity ] = [ 201, 1 ] UPDATE (Books,ID) .with ({ title: 'Sturmhöhe', //> simple value stock: {'-=': quantity}, //> qbe expression descr: {xpr: [{ref:[descr]}, '||', 'Some addition to descr.']} }) ``` > Method `.set` and `.with` are aliases to the same method. ### where() > Source: /docs/node.js/cds-ql#where-1 [As in SELECT.where](#select-where) {.learn-more} ## DELETE > Source: /docs/node.js/cds-ql#delete ###### delete > Source: /docs/node.js/cds-ql#delete-1 Fluent API to construct [CQN DELETE](../cds/cqn#delete) query objects in a [CQL](../cds/cql)/SQL-like style. In contrast to SQL, though, the clauses can be arrayed in arbitrary order. ```js DELETE.from('Books').where ({stock:{'<':1}}) ``` ### from() > Source: /docs/node.js/cds-ql#from-2 ```tsx function DELETE.from ( entity : string | CSN definition | tagged template string, key? : string | number | object ) ``` [As in SELECT.from](#select-from) {.learn-more} ### where() > Source: /docs/node.js/cds-ql#where-2 [As in SELECT.where](#select-where) {.learn-more} ## Expressions > Source: /docs/node.js/cds-ql#expressions The following methods facilitate constructing CXN objects manually. > [!note] > Many sections below are still under construction. We are working on it... Please refer to the [CXL](../cds/cxn) documentation for more information on the CXN syntax for the time being. ### expr() > Source: /docs/node.js/cds-ql#expr ###### expr > Source: /docs/node.js/cds-ql#expr-1 Constructs a CXN expression object from given input. Same as [`xpr`](#xpr), but if the result contains only single entries these are returned as is. ```js const { expr } = cds.ql expr([ref`foo`,'=',val(11)]) //> {xpr:[{ref:['foo']},'=',{val:11}]} expr(ref`foo`,'=',val(11)) //> {xpr:[{ref:['foo']},'=',{val:11}]} expr`foo = 11` //> {xpr:[{ref:['foo']},'=',{val:11}]} expr`foo` //> {ref:['foo']} expr`11` //> {val:11} ``` ### ref() > Source: /docs/node.js/cds-ql#ref ###### ref > Source: /docs/node.js/cds-ql#ref-1 Constructs a CXN `{ref}` object from given input, which can be one of: - several path segment strings - a single array of the same - a tagged template literal in CXL path syntax ```js const { ref } = cds.ql ref('foo') //> {ref:['foo']} ref('foo','bar') //> {ref:['foo','bar']} ref`foo.bar` //> {ref:['foo','bar']} ref`foo` //> {ref:['foo']} ``` Note that only simple paths are supported, that is, without infix filters or functions. [Use `expr()` to parse paths with infix filters via a tagged template literals.](#expr) {.learn-more} ### val() > Source: /docs/node.js/cds-ql#val ###### val > Source: /docs/node.js/cds-ql#val-1 Constructs CXN `{val}` object from given input, which can be one of: - a single `string`, `number`, `boolean`, or `null` - a tagged template literal in CXL literal syntax ```js const { val } = cds.ql val(`foo`) //> {val:'foo'}` val`foo` //> {val:'foo'} val`11` //> {val:11} val(11) //> {val:11} ``` ### xpr() > Source: /docs/node.js/cds-ql#xpr ###### xpr > Source: /docs/node.js/cds-ql#xpr-1 Constructs a CXN `xpr` object from given input, which can be one of: - multiple CXN `expr` objects, or strings representing keywords or operators - a single array of the same - a tagged template literal in CXL syntax ```js const { xpr } = cds.ql xpr([ref`foo`,'=',val(11)]) //> {xpr:[{ref:['foo']},'=',{val:11}]} xpr(ref`foo`,'=',val(11)) //> {xpr:[{ref:['foo']},'=',{val:11}]} xpr`foo = 11` //> {xpr:[{ref:['foo']},'=',{val:11}]} xpr`foo` //> {xpr:[{ref:['foo']}]} xpr`'foo'` //> {xpr:[{val:'foo'}]} xpr`11` //> {xpr:[{val:11}]} xpr('=') //> {xpr:['=']} xpr('like') //> {xpr:['like']} ``` [See also `expr()`](#expr) {.learn-more} ### list() > Source: /docs/node.js/cds-ql#list Constructs a CXN `list` object from given input, with can be one of: - multiple CXN `expr` objects, or values turned into `{val}`s, including strings - a single array of the same ```js const { list } = cds.ql list([`foo`,11]) //> {list:[{val:'foo'},{val:11}]} list(`foo`,11) //> {list:[{val:'foo'},{val:11}]} expr`'foo',11` //> {list:[{val:'foo'},{val:11}]} expr`foo,11` //> {list:[{ref:['foo']},{val:11}]} ``` [Use `expr()` to get the same via a tagged template literals.](#expr) {.learn-more} ### func() > Source: /docs/node.js/cds-ql#func Constructs a CXN `func` object from given input. The first argument is the function name, the remaining `args` can the same as in {@link ql.list `list()`}, and are handled the same way. ```js const { func } = cds.ql func('substring',[`foo`,1]) //> {func:'substring',args:[{val:'foo'},{val:1}]} func('substring',`foo`,1) //> {func:'substring',args:[{val:'foo'},{val:1}]} expr`substring('foo',1)` //> {func:'substring',args:[{val:'foo'},{val:1}]} expr`substring(foo,1)` //> {func:'substring',args:[{ref:['foo']},{val:1}]} expr`substring(foo,1)` //> {func:'substring',args:[{ref:['foo']},{val:1}]} ``` [Use `expr()` to get the same via a tagged template literals.](#expr) {.learn-more} ### predicate() > Source: /docs/node.js/cds-ql#predicate TODO: Add description ```js const { predicate } = cds.ql predicate`a=1 and b=2 or c=3 and d=4` predicate ({ a:1, b:2, or:{ c:3, d:4 }}) predicate ('a=',1,'and ( b=',2,'or c=',3,')') ``` ### columns() > Source: /docs/node.js/cds-ql#columns-1 TODO ### nested() > Source: /docs/node.js/cds-ql#nested TODO ### expand() > Source: /docs/node.js/cds-ql#expand TODO: Add description ```js expand (ref`books`, where`stock>7`, orderBy`title`, columns`ID,title` ) ``` ### inline() > Source: /docs/node.js/cds-ql#inline TODO ### where() > Source: /docs/node.js/cds-ql#where-3 TODO ### orderBy() > Source: /docs/node.js/cds-ql#orderby-1 TODO ### orders() > Source: /docs/node.js/cds-ql#orders TODO # Minimalistic Logging Facade > Source: /docs/node.js/cds-log ## cds.log (id?, options?) > Source: /docs/node.js/cds-log#cdslog----id-options Returns a logger identified by the given id. ```js const LOG = cds.log('sql') LOG.info ('whatever', you, 'like...') ``` #### *Arguments* > Source: /docs/node.js/cds-log#arguments - `id?` — the id for which a logger is requested — default: `'cds'` - `options?` — alternative to `level` pass an options object with: - `level?` — the [log level](#log-levels) specified as string or number — default: `'info'` - `label?` — the [log label](#logger-label) to add to each log output — default: `id` - `level?` — specify a string instead of `options` as a shorthand for `{level}` ```js // all following are equivalent... const LOG = cds.log('foo', 'warn') //> shorthand for: const LOG = cds.log('foo', { level: 'warn' }) // including case-insensitivity... const LOG = cds.log('foo', 'WARN') //> shorthand for: const LOG = cds.log('foo', { level: 'WARN' }) ``` ### *Logger `id` — cached & shared loggers* > Source: /docs/node.js/cds-log#logger-id--cached--shared-loggers The loggers constructed by `cds.log()` are cached internally, and the same instances are returned on subsequent invocations of `cds.log()` with the same `id`. This allows to use and share the same logger in different modules. ```js const LOG1 = cds.log('foo') const LOG2 = cds.log('foo') console.log (LOG1 === LOG2) //> true ``` ### *Logger `label` — used to prefix log output* > Source: /docs/node.js/cds-log#logger-label--used-to-prefix-log-output By default, each log output is prefixed with `[] -`, for example, as in `[cds] - server listening `. Sometimes you may want to use different ids and labels. Use option `label` to do so as in this examples: ```js const LOG = cds.log('foo',{label:'bar'}) LOG.info("it's a foo") //> [bar] - it's a foo ``` ### _Logger usage → much like `console`_ > Source: /docs/node.js/cds-log#logger-usage--much-like-console Loggers returned by `cds.log()` look and behave very much like [JavaScript's standard `console` object](https://nodejs.org/api/console.html) a log method for each [log level](#log-levels): ```js cds.log() → { trace(...), _trace, debug(...), _debug, info(...), _info, log(...), // alias for info() warn(...), _warn, error(...), _error, } ``` In addition, there is a boolean indicator to check which levels are active through corresponding underscored property, for example, `LOG._debug` is true if debug is enabled. ### *Recommendations* > Source: /docs/node.js/cds-log#recommendations 1. **Leave formatting to the log functions** — for example don't expensively construct debug messages, which aren't logged at all if debug is not switched on. For example: ```js // DONT: const { format } = require('util') LOG.debug (`Expected ${arg} to be a string, but got: ${format(value)}`) // DO: LOG.debug ('Expected', arg, 'to be a string, but got', value) ``` 2. **Check levels explicitly** — to further minimize overhead you can check whether a log level is switched on using the boolean `Logger._` properties like so: ```js const LOG = cds.log('sql') LOG._info && LOG.info ('whatever', you, 'like...') ``` ## cds.log.format > Source: /docs/node.js/cds-log#cdslogformat ### _Setting Formats for New Loggers_ > Source: /docs/node.js/cds-log#setting-formats-for-new-loggers You can provide a custom log formatter function by setting `cds.log.format` programmatically as shown below, for example in your custom `server.js`. ```js // the current default: cds.log.format = (id, level, ...args) => [ `[${id}]`, '-', ...args ] ``` ```js // a verbose format: const _levels = [ 'SILENT', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE' ] cds.log.format = (id, level, ...args) => [ '[', (new Date).toISOString(), '|', _levels[level].padEnd(5), '|', cds.context?.tenant || '-', '|', cds.context?.id || '-', '|', id, '] -', ...args ] ``` Formatter functions are expected to return an array of arguments, which are passed to the logger functions — same as the arguments for `console.log()`. ### _Setting Formats for Existing Loggers_ > Source: /docs/node.js/cds-log#setting-formats-for-existing-loggers You can also change the format used by newly or formerly constructed loggers using `.setFormat()` function: ```js const _levels = [ 'SILENT', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE' ] const LOG = cds.log('foo') .setFormat ((id, level, ...args) => [ '[', (new Date).toISOString(), '|', _levels[level].padEnd(5), '|', cds.context?.tenant || '-', '|', cds.context?.id || '-', '|', id, '] -', ...args ]) ``` ## cds.log.levels > Source: /docs/node.js/cds-log#cdsloglevels Constants of supported log levels: ```js cds.log.levels = { SILENT: 0, // all log output switched off ERROR: 1, // logs errors only WARN: 2, // logs errors and warnings only INFO: 3, // logs errors, warnings and general infos DEBUG: 4, // logs errors, warnings, info, and debug // (and trace when using default logger implementation) TRACE: 5, // most detailed log level SILLY: 5, // alias for TRACE VERBOSE: 5 // alias for TRACE } ``` You can use these constants when constructing loggers, for example: ```js const LOG = cds.log('foo', cds.log.levels.WARN) ``` ### *Configuring Log Levels* > Source: /docs/node.js/cds-log#configuring-log-levels Configure initial log-levels per module through `cds.log.levels`, for example like that in your `package.json`: ```json { "cds": { "log": { "levels": { "sql": "debug", "cds": "info" } } } } ``` [Learn more about `cds.env`.](cds-env){.learn-more} [See pre-defined module names below.](#cds-log-modules){.learn-more} ### *Programmatically Set Log Levels* > Source: /docs/node.js/cds-log#programmatically-set-log-levels You can specify a default log level to use when constructing a logger as shown above. When called subsequently with a *different* log level, the cached and shared logger's log level will be changed dynamically. For example: ```js // some-module.js const LOG = cds.log('foo') // using default log level 'info' ``` ```js // some-other-module.js const LOG = cds.log('foo') // shares the same logger as above ``` ```js // some-controller-module.js cds.log('foo','debug') // switches the 'foo' logger to 'debug' level ``` ### *Log Levels as Used by the CAP Node.js Runtime* > Source: /docs/node.js/cds-log#log-levels-as-used-by-the-cap-nodejs-runtime The CAP Node.js runtime uses the following guidelines with regards to which log level to use in which situation: - `error`: Something went horribly wrong and it's unclear what to do (that is, an unexpected error). - `warn`: Something off the happy trail happened, but it can be handled (that is, an expected error). - `info`: Brief information about what is currently happening. - `debug`: Detailed information about what is currently happening. - `trace`/`silly`/`verbose` (not used by the CAP Node.js runtime): Exhaustive information about what is currently happening. ## cds.log.Logger > Source: /docs/node.js/cds-log#cdsloglogger Constructs a new logger with the method signature of `{ trace, debug, log, info, warn, error }` (cf. [`console`](https://nodejs.org/api/console.html)). The default implementation maps each method to the equivalent methods of `console`. You can assign different implementations by exchanging the factory with your own, for example, in order to integrate advanced logging frameworks such as [winston](#winston). #### *Arguments* > Source: /docs/node.js/cds-log#arguments-1 - `label`— the log label to use with each log output, if applicable - `level`— the log level to enable → *0=off, 1=error, 2=warn, 3=info, 4=debug, 5=trace* ### *Using `winston` Loggers* > Source: /docs/node.js/cds-log#using-winston-loggers **Prerequisites:** You need to add [winston](https://www.npmjs.com/package/winston) to your project: ```sh npm add winston ``` Being designed as a simple log facade, `cds.log` can be easily integrated with advanced logging frameworks such as [`winston`](https://www.npmjs.com/package/winston). For example, using the built-in convenience method `cds.log.winstonLogger()` in your project's server.js like that: ```js cds.log.Logger = cds.log.winstonLogger() ``` You can specify winston custom options to that method [as documented for `winston.createLogger()`](https://github.com/winstonjs/winston#creating-your-own-logger), for example like that: ```js cds.log.Logger = cds.log.winstonLogger({ format: winston.format.simple(), transports: [ new winston.transports.Console(), new winston.transports.File({ filename: 'errors.log', level: 'error' }) ], }) ``` ### _Custom Loggers_ > Source: /docs/node.js/cds-log#custom-loggers Custom loggers basically have to return an object fulfilling the `console`-like [`cds.log` loggers API](#logger-api) as in this example: ```js const winston = require("winston") const util = require('util') const cds = require('@sap/cds') cds.log.Logger = (label, level) => { // construct winston logger const logger = winston.createLogger({ levels: cds.log.levels, // use cds.log's levels level: Object.keys(cds.log.levels)[level], transports: [new winston.transports.Console()], }) // winston's log methods expect single message strings const _fmt = (args) => util.formatWithOptions( {colors:false}, `[${label}] -`, ...args ) // map to cds.log's API return Object.assign (logger, { trace: (...args) => logger.TRACE (_fmt(args)), debug: (...args) => logger.DEBUG (_fmt(args)), log: (...args) => logger.INFO (_fmt(args)), info: (...args) => logger.INFO (_fmt(args)), warn: (...args) => logger.WARN (_fmt(args)), error: (...args) => logger.ERROR (_fmt(args)), }) } ``` Actually, the above is essentially the implementation of `cds.log.winstonLogger()`. ## `DEBUG` env variable > Source: /docs/node.js/cds-log#debug-env-variable Use env variable `DEBUG` to quickly switch on debug output from command line like that: ```sh DEBUG=app,sql cds watch DEBUG=all cds watch ``` Values can be - comma-separated list of [logger ids](#logger-id), or - the value `all` to switch on all debug output. ### *Matching multiple values of `DEBUG`* > Source: /docs/node.js/cds-log#matching-multiple-values-of-debug When obtaining loggers with `cds.log()` you can specify alternate ids that will all be matched against the entries of the `DEBUG` env variable; for example: ```js const LOG = cds.log('db|sql') ``` Will be debug-enabled by both, `DEBUG=db`, as well as `DEBUG=sql ...`. **Note:** The alternative ids specified after `|` have no impact on the unique logger ids. That is, the logger above will have the id `'db'`, while `'sql'` will only be used for matching against `DEBUG` env variable. ## Configuration > Source: /docs/node.js/cds-log#configuration Configuration for `cds.log()` can be specified through `cds.env.log`, for example like that in your `package.json`: ```json { "cds": { "log": { "levels": { "sql": "debug", "cds": "info" } } } } ``` [Learn more about `cds.env`.](cds-env){.learn-more} The following configuration options can be applied: - `levels` — configures log levels for logged modules. The keys refer to the [loggers' `id`](#logger-id), the values are lower-case names of [log levels](#log-levels). - `user` — Specify `true` to log the user's ID (`req.user.id`) as `remote_user` (Kibana formatter only). Consider the data privacy implications! Default: `false`. - `sanitize_values`— Specify `false` to deactivate the default behavior of sanitizing payload data in debug logs in production. Default: `true`. ## Common IDs > Source: /docs/node.js/cds-log#common-ids The runtime uses the same logger facade, that is `cds.log()`. For each component, it requires a separate logger. So projects can set different log levels for different components/layers. The following table lists the ids used to set the log levels: | Component | Logger IDs(s) | |------------------------------------------|-------------------| | Server and common output | `cds` | | CLI output | `cli` | | CDS build output | `build` | | [Application Service](./app-services) | `app` | | [Databases](databases) | `db\|sql` | | [Messaging Service](messaging) | `messaging` | | [Remote Service](remote-services) | `remote` | | AuditLog Service | `audit-log` | | OData Protocol Adapter | `odata` | | REST Protocol Adapter | `rest` | | GraphQL Protocol Adapter | `graphql` | | [Authentication](./authentication) | `auth` | | Database Deployment | `deploy` | | Multitenancy and Extensibility | `mtx` | ## Logging in Development > Source: /docs/node.js/cds-log#logging-in-development During development, we want concise, human-readable output in the console, with clickable stack traces in case of errors. You should not be overloaded with information that is additionally obfuscated by a bad rendering. Hence, [console.log()](https://nodejs.org/api/console.html#console_console_log_data_args), that makes use of [util.format()](https://nodejs.org/api/util.html#util_util_format_format_args) out of the box, with raw arguments is a good choice. The *plain log formatter*, which is the default in non-production environments, prepends the list of arguments with `[ -]`. The following screenshot shows the log output for the previous warning and rejection with the plain log formatter. ![The screenshot is explained in the accompanying text.](./assets/plain-formatter-output.png) The plain log formatter is the default formatter in non-production. ## Logging in Production > Source: /docs/node.js/cds-log#logging-in-production SAP BTP offers two services, [SAP Cloud Logging](https://help.sap.com/docs/cloud-logging) and [SAP Application Logging Service](https://help.sap.com/docs/application-logging-service), to which bound Cloud Foundry applications can stream logs. In both services, operators can access and analyze observability data, as described in [Access and Analyze Observability Data](https://help.sap.com/docs/cloud-logging/cloud-logging/access-and-analyze-observability-data) for SAP Cloud Logging and [Access and Analyze Application Logs, Container Metrics and Custom Metrics](https://help.sap.com/docs/application-logging-service/sap-application-logging-service/access-and-analyze-application-logs-container-metrics-and-custom-metrics) for SAP Application Logging Service. To get connected with either of those services, the application needs to be bound to the respective service instance(s) as described for [SAP Cloud Logging](https://help.sap.com/docs/cloud-logging/cloud-logging/ingest-via-cloud-foundry-runtime?version=Cloud) and [SAP Application Logging Service](https://help.sap.com/docs/application-logging-service/sap-application-logging-service/produce-logs-container-metrics-and-custom-metrics). Additionally, the log output needs to be formatted in a way that enables the respective dashboard technology to optimally support the user, for example, filtering for logs of specific levels, modules, status, etc. The *JSON log formatter* constructs a loggable object from the passed arguments as well as [cds.context](events#cds-event-context) and the headers of the incoming request (if available). The JSON log formatter is the default formatter in production. ::: tip Since `@sap/cds 7.5`, running `cds add kibana-logging` or setting cds.features.kibana_formatter: true are no longer needed. If you want to opt-out of the JSON formatter in production, set cds.log.format: plain. ::: Further, there are two formatting aspects that are activated automatically, if appropriate, and add the following information to the loggable object: 1. Running on Cloud Foundry: `tenant_subdomain`, `CF_INSTANCE_IP` and information from `VCAP_APPLICATION` 1. Bound to an instance of the [SAP Application Logging Service](https://help.sap.com/docs/application-logging-service/sap-application-logging-service/sap-application-logging-service-for-cloud-foundry-environment) or [SAP Cloud Logging](https://help.sap.com/docs/cloud-logging/sap-cloud-logging/what-is-sap-cloud-logging): `categories` and *custom fields* as described in [Custom Fields](#custom-fields) The following screenshot shows the log output for the rejection in the previous example with the JSON log formatter including the two aspects. ![The screenshot is explained in the accompanying text.](assets/json-formatter-output.png) ::: warning The SAP Application Logging Service offers [different plans with different quotas](https://help.sap.com/docs/application-logging-service/sap-application-logging-service/service-plans-and-quotas). Please make sure the plan you use is sufficient, that is, no logs are being dropped so that the information is available in Kibana. As soon as logs are dropped, you cannot reliably assess what is going on in your app. ::: ### Header Masking > Source: /docs/node.js/cds-log#header-masking Some header values shall not appear in logs, for example when pertaining to authorization. Configuration option cds.log.mask_headers: ["/authorization/i", "/cookie/i", "/cert/i", "/ssl/i"] allows to specify a list of matchers for which the header value shall be masked. Masked values are printed as `***`. The default value is `["/authorization/i", "/cookie/i", "/cert/i", "/ssl/i"]`. ::: warning In case your application shares any sensitive data (for example, secrets) via headers, please ensure that you adjust the configuration as necessary. ::: ::: tip In the log entry, header field names are normalized to lowercase with `_` instead of `-`. Make sure your matchers work on the original header name, for example, `"/Foo-Bar/"` instead of the normalized `"/foo_bar/"`. ::: ### Custom Fields > Source: /docs/node.js/cds-log#custom-fields Information that is not included in the [list of supported fields](https://help.sap.com/docs/application-logging-service/sap-application-logging-service/supported-fields) of the SAP Application Logging Service can be shown as additional information. This information needs to be provided as custom fields. By default, the JSON formatter uses the following custom fields configuration for SAP Application Logging Service: ```jsonc { "log": { "als_custom_fields": { // : "query": 0, //> sql "target": 1, "details": 2, //> generic validations "reason": 3 //> errors } } } ``` Up to 20 such custom fields can be provided using this mechanism. The advantage of this approach is that the additional information can be indexed. Besides being a manual task, it has the drawback that the indexes should be kept stable. ::: details Background The SAP Application Logging Service requires the following formatting of custom field content inside the JSON object that is logged: ```js { ..., '#cf': { strings: [ { k: '', v: '', i: }, ... ] } } ``` That is, a generic collection of key-value-pairs that are treated as opaque strings. The information is then rendered as follows: ```txt custom.string.key0: custom.string.value0: ``` Hence, in order to analyze, for example, the SQL statements leading to errors, you'd need to look at field `custom.string.value0` (given the default of `cds.env.log.als_custom_fields`). In a more practical example, the log would look something like this: ```log msg: SQL Error: Unknown column "IDONTEXIST" in table "DUMMY" ... custom.string.key0: query custom.string.value0: SELECT IDONTEXIST FROM DUMMY ``` Without the additional custom field `query` and it's respective value, it would first be necessary to reproduce the issue locally to know what the faulty statement is. ::: For SAP Cloud Logging, the JSON formatter uses the following default configuration: ```jsonc { "log": { "cls_custom_fields": [ "query", //> sql "target", "details", //> generic validations "reason" //> errors ] } } ``` In order for the JSON formatter to detect the binding to SAP Cloud Logging via user-provided service, the user-provided service must have either tag `cloud-logging` or `Cloud Logging`. (For existing user-provided services, tags can be added via [`cf update-user-provided-service`](https://cli.cloudfoundry.org/en-US/v7/update-user-provided-service.html).) The key-value pairs can either be part of the first argument or an exclusive object thereafter: ```js LOG.info({ message: 'foo', reason: 'bar' }) LOG.info('foo', { reason: 'bar' }) ``` As always, both defaults are overridable via [cds.env](cds-env#cds-env). ## Request Correlation > Source: /docs/node.js/cds-log#request-correlation Unfortunately, there is no standard correlation ID header. `x-correlation-id` and `x-request-id` are the most commonly used, but SAP products often use `x-correlationid` (that is, without the second hyphen) and SAP BTP uses `x-vcap-request-id` when logging incoming requests. As CAP aims to be platform independent, we check an array of headers (or generate a new ID if none hits) and ensure the value available at `cds.context.id` as well as `req.headers['x-correlation-id']`: ```js const { headers: h } = req const id = h['x-correlation-id'] || h['x-correlationid'] || h['x-request-id'] || h['x-vcap-request-id'] || uuid() if (!cds.context) cds.context = { id } req.headers['x-correlation-id'] = cds.context.id ``` Subsequently, the JSON log formatter (see [Logging in Production](#logging-in-production)) sets the following fields: - `cds.context.id` → `correlation_id` - Request header `x_vcap_request_id` → `request_id` - Request header `traceparent` (cf. [W3C Trace Context](https://www.w3.org/TR/trace-context/)) → `w3c_traceparent` Specifically field `w3c_traceparent` is then used by both SAP Application Logging Service and SAP Cloud Logging to determine field `trace_id` in order to correlate requests, logs, and traces across multiple applications. The following screenshot shows an example for log correlation based on field `correlation_id` in a log analytic dashboard of the [SAP Application Logging Service for SAP BTP](https://help.sap.com/docs/application-logging-service). ![Default Formatter Output](assets/correlation.png) # Localization / i18n > Source: /docs/node.js/cds-i18n ## Introduction > Source: /docs/node.js/cds-i18n#introduction The `cds.i18n` module supports internationalization. It's mostly used by the framework automatically behind the scenes for both, [localization of UIs](#localized-fiori-uis), that is, labels or headers, as well as localized [runtime error messages](#localized-messages). In addition, you can [use it directly](#direct-usage) in your application-specific custom code. There are two standard i18n bundles available through these static properties: - [`cds.i18n.labels`](#labels) are used for generating localized UIs. - [`cds.i18n.messages`](#messages) are used for error messages generated at runtime. ### Localized (Fiori) UIs > Source: /docs/node.js/cds-i18n#localized-fiori-uis The former, that is [`cds.i18n.labels`](#labels), is used automatically when generating OData `$metadata` documents for SAP Fiori elements to look up translations for respective [`{i18n>...}` placeholders](../guides/uis/i18n#externalizing-texts-bundles). For example, localized texts for annotations like that will be looked up from `cds.i18n.labels`: ::: code-group ```cds [app/fiori-annotations.cds] annotate CatalogService.Books with @title: '{i18n>Book}' ``` ::: ### Localized Messages > Source: /docs/node.js/cds-i18n#localized-messages The latter, that is [`cds.i18n.messages`](#messages), is used automatically for all error or notification messages created through [`req.reject/error/info/warn(...)`](./events#req-reject), which includes all framework-created error messages, like input validation errors, as well as custom errors. For example you could add a new entry to the `_i18n/messages.properties`: ::: code-group ```properties [_i18n/messages.properties] ORDER_EXCEEDS_STOCK = The order of {quantity} books exceeds available stock {stock} ``` ::: ... and refer to that by key in your error messages like that: ::: code-group ```js [srv/cat-service.js] srv.before ('submitOrder', async req => { let { book:id, quantity } = req.data let {stock} = await SELECT `stock` .from (Books,id) if (stock < quantity) req.reject (409, 'ORDER_EXCEEDS_STOCK', { stock, quantity }) }) ``` ::: ### Direct Usage > Source: /docs/node.js/cds-i18n#direct-usage In addition, you can use both standard bundles directly in your code, with [`.at(key)`](#at-key-) the central method to obtain localized texts: ```js [dev] cds repl > cds.i18n.labels.at('CreatedAt','de') //> 'Erstellt am' > cds.i18n.labels.at('CreatedAt') //> 'Created At' > cds.i18n.messages.at('ASSERT_FORMAT', [11,12]) ``` You can also introduce and use your own, separate bundles: ```js const b = cds.i18n.bundle4('yours') b.at('some key') ``` And provide texts and translations in corresponding files like *_i18n/yours.properties*. ## `cds.i18n` > Source: /docs/node.js/cds-i18n#cdsi18n This is a global object acting as the facade to the i18n features as outlined in the following. ### `.file` > Source: /docs/node.js/cds-i18n#file ### `.folders` > Source: /docs/node.js/cds-i18n#folders Shortcuts to corresponding i18n [config options](#config). {.indent} ### `.messages` > Source: /docs/node.js/cds-i18n#messages The I18n bundle used for runtime messages, for example, for translated validation errors, such as `ASSERT_RANGE` or `ASSERT_FORMAT`. Translations are loaded from properties with base name `messages`, like that in the [*bookstore* sample](https://github.com/capire/bookstore/tree/main/app/_i18n): {.indent} ```zsh cap/samples/bookshop/ ├─ _i18n/ │ ├─ messages_de.properties │ ├─ messages_en.properties │ └─ messages_fr.properties │ ... ``` [See also the list of pre-defined message texts below](#messages-texts){.learn-more} ### `.labels` > Source: /docs/node.js/cds-i18n#labels The I18n bundle used for UI labels, such as `CreatedAt` or `CreatedBy`, referenced from respective [Fiori annotations](../guides/uis/i18n#externalizing-texts-bundles). Translations are loaded from properties with base name `i18n`, like that in the [*bookstore* sample](https://github.com/capire/bookstore/tree/main/app/_i18n): {.indent} ```zsh cap/samples/bookshop/ ├─ _i18n/ │ ├─ i18n_de.properties │ ├─ i18n_en.properties │ ├─ i18n_fr.properties │ └─ i18n.properties │ ... ``` ### `bundle4()` > Source: /docs/node.js/cds-i18n#bundle4 ```tsx function cds.i18n.bundle4 (file : string, options?) function cds.i18n.bundle4 (model : CSN, options?) ``` Factory method to create instances of [`I18nBundle`](#i18nbundle). The first argument is either a string used as the bundle's [`file`/`basename`](#-file--basename), or a CDS model. ```js const b1 = cds.i18n.bundle4('foo') ``` ```js const mm = await cds.load('my-model.cds') const b2 = cds.i18n.bundle4(mm) ``` When using the string variant, the created bundle is additionally cached under the given string, and subsequent calls will return the cached instance: ```js const b1 = cds.i18n.bundle4('foo') //> creates a new I18nBundle for 'foo' const b2 = cds.i18n.bundle4('foo') //> returns the formerly created one b1 === cds.i18n.foo //> true – cached under specified name b1 === b2 //> true ``` ## `I18nBundle` > Source: /docs/node.js/cds-i18n#i18nbundle Instances of this class provide access to translated texts in different languages. ::: details Prefer using [`cds.i18n.bundle4()`](#bundle4) to create instances... Yet, you can refer to this class from the `cds.i18n.Bundle` facade property, for example to create subclasses: ```js class YourI18nBundle extends cds.i18n.Bundle {...} ``` ::: ### `constructor` > Source: /docs/node.js/cds-i18n#constructor ```tsx function I18nBundle (options: { //... as in I18nFiles constructor }) ``` Constructs a new instance with the provided options forwarded to the [`I18nFiles` constructor](#constructor-1) for [`this.files`](#files). {.indent} ### `.defaults` > Source: /docs/node.js/cds-i18n#defaults The default translations used as a first-level fallback if a locale-specific translation is not found. Can be provided as constructor option, else loads the translations for the default language as configured in [config option](#config) `cds.i18n.default_language` . {.indent} ### `.fallback` > Source: /docs/node.js/cds-i18n#fallback The texts used as second-level fallback if a locale-specific translation is not found and also none in [`.defaults`](#defaults). Can be provided as constructor option, else loads the translations from `.properties`, that is, without language suffix. {.indent} ### `.files` > Source: /docs/node.js/cds-i18n#files An instance of [`I18nFiles`](#i18nfiles) with the found folders and files to load i18n content from. {.indent} ### `at (key, ...)` > Source: /docs/node.js/cds-i18n#at-key- ### `for (key, ...)` > Source: /docs/node.js/cds-i18n#for-key- ```tsx function at ( key : number | string | object, locale? : string, args? : object | array ) => string ``` This is the central method to look up localized texts for given keys and locales, with `at` and `for` being synonyms. Basic usage, for example, with the standard [`cds.i18n.messages`](#messages) bundle, looks like that: {.indent} ```js [dev] cds repl > cds.i18n.messages.at(404) //> 'Not Found' > cds.i18n.messages.at(404,'de') //> 'Nicht Gefunden' ``` #### Using Default Locales > Source: /docs/node.js/cds-i18n#using-default-locales If `locale` is omitted, the current default locale is taken from [`cds.context.locale`](events#cds-context). {.indent} ```js cds.context = {locale:'de'} //> as automatically set by protocol adapters cds.i18n.messages.at(404) //> 'Nicht Gefunden' ``` #### Using Message Templates > Source: /docs/node.js/cds-i18n#using-message-templates If `args` are specified, corresponding `{}` placeholders in texts are replaced by the values from `args`. For example, given these entries in the respective *.properties* files: {.indent} ```properties WRONG_FORMAT = '{0}' is not in format '{1}' OUT_OF_RANGE = {val} is not in range {min}..{max} ``` You would obtain respective messages like that: {.indent} ```js const msg = cds.i18n.messages msg.for('WRONG_FORMAT', ['x',/.../]) //> 'x' is not in format '...' msg.for('OUT_OF_RANGE', {val:0,min:1,max:11}) //> 0 is not in range 1..11 ``` #### Looking up labels for CSN definitions > Source: /docs/node.js/cds-i18n#looking-up-labels-for-csn-definitions You can alternatively pass in a CSN definition instead of an i18n key to look up the localized UI label for that an entity or element. For example, try this in `cds repl` from within the [*cap/samples* root folder](https://github.com/capire/samples): {.indent} ```js [dev] cds repl > .run fiori > let {Books} = CatalogService.entities, {title} = Books.elements > cds.context = {locale:'fr'} // as automatically set by protocol adapters > cds.i18n.labels.at(Books) //> 'Livre' > cds.i18n.labels.at(title) //> 'Titre' ``` > Uses the [`.key4 (csn)`](#key4-csn) method to determine the i18n key for CSN definitions. ### `key4 (csn)` > Source: /docs/node.js/cds-i18n#key4-csn This method is used by [`bundle.at()`](#at-key-) to determine an i18n key for a CSN definition. In essence, the implementation works like that: ```js const a = csn['@title'] || csn['@Common.Label'] || csn['@UI.HeaderInfo.TypeName'] //> e.g. '{i18n>Books}' return a.match(/{i18n>(.+)}/)[1] //> 'Books' ``` > If no such annotation is found, the CSN definition's `name` is returned. ### `texts4 (locale)` > Source: /docs/node.js/cds-i18n#texts4-locale ```tsx function texts4 (locale: string) => Texts ``` This method is used by [`bundle.at()`](#at-key-) to obtain the set of translated texts for a specific locale. For example, try this in `cds repl`: {.indent} ```js [dev] cds repl > var texts = cds.i18n.labels.texts4('de') > texts.CreatedBy // or texts[] in general ``` ### `translations4 (locales)` > Source: /docs/node.js/cds-i18n#translations4-locales ```tsx function translations4 (...locales : 'all' | string[]) => { [locale]: Texts } ``` Obtains one or more sets of translated texts for multiple locales.
For example, try this in `cds repl`: {.indent} ```js [dev] cds repl > var { de, en, fr } = cds.i18n.labels.translations4('de','en','fr') > de.CreatedBy //> Angelegt von > en.CreatedBy //> Created by > fr.CreatedBy //> Auteur de la création ``` ```js [dev] cds repl > var all = cds.i18n.labels.translations4('all') > JSON.stringify(all) ``` ## `I18nFiles` > Source: /docs/node.js/cds-i18n#i18nfiles Instances of this class are used through [`I18nBundle.files`](#files) to fetch and construct a lookup dictionary of i18n folders and files matching a given configuration in a files-by-folders structure. By default fetches i18n folders and files from the [neighborhood](#from-models-neighborhood) of a given model's sources, by default using `cds.model`. For example, try this in `cds repl` run from the project root of *[cap/samples](https://github.com/capire/samples)*: ```js [dev] cds repl > cds.model = await cds.load('bookstore') // [!code focus] > cds.i18n.labels.files //> displays: // [!code focus] I18nFiles { '/cap/samples/node_modules/@sap/cds/_i18n': [ 'i18n.properties', 'i18n_de.properties', 'i18n_en.properties', 'i18n_fr.properties', // ... ], '/cap/samples/orders/_i18n': [ 'i18n_de.properties', 'i18n_en.properties', 'i18n_fr.properties' ], '/cap/samples/reviews/_i18n': [ 'i18n_de.properties', 'i18n_en.properties', 'i18n_fr.properties' ], '/cap/samples/bookstore/_i18n': [ 'i18n_de.properties', 'i18n_en.properties', 'i18n_fr.properties' ] } ``` [Learn more about that in Fetching i18n Folders below](#fetching-i18n-folders) {.learn-more} ### `constructor` > Source: /docs/node.js/cds-i18n#constructor-1 ```tsx function I18nFiles (options: { file? : string = cds.env.i18n.file, basename = file, model? : CSN = cds.model roots? : string[] = [ cds.root, cds.home ], leafs? : string[] = model?.$sources.map(path.dirname) ?? roots, folders? : string[] = cds.env.i18n.folders, }) ``` Constructs a new instance which fetches i18n folders and files according to the specified options. For example the following creates a new I18nBundle with the content read from `./_i18n/messages_*.properties` files in the current working directory: ```js const msg = cds.i18n.bundle4 ({ file:'messages', folders:['/_i18n'] }) ``` The options are as follows... ### – `file` / `basename` > Source: /docs/node.js/cds-i18n#-file--basename The basename of *.properties* files to load translations from (either of both can be used).
*Default*: as [configured](#config) through cds.i18n.file: i18n {.indent} ### – `model` > Source: /docs/node.js/cds-i18n#-model The model to fetch i18n files and folders from respective `$sources`' [neighborhood](#from-models-neighborhood).
*Default*: [`cds.model`](cds-facade#cds-model). {.indent} ### – `roots` > Source: /docs/node.js/cds-i18n#-roots An array of root directories up to which to recurse up the filesystem hierarchy when searching for i18n folders.
*Default*: `[` [`cds.root`](cds-facade#cds-root), [`cds.home`](cds-facade#cds-home) `]`. {.indent} ### – `leafs` > Source: /docs/node.js/cds-i18n#-leafs The leafs of the filesystem hierarchy to start fetch i18n folders recursively. Determined by `model?.$sources.map(path.dirname)` if a [`model`](#-model) (or [`cds.model`](cds-facade#cds-model)) is given.
*Default*: [`roots`](#-roots). {.indent} ### – `folders` > Source: /docs/node.js/cds-i18n#-folders An array of folder names to fetch i18n files from. Can contain relative names of subfolders or absolute names as explained in [Fetching i18n Folders...](#fetching-i18n-folders).
*Default*: as [configured](#config) through cds.i18n.folders: [ "_i18n", "i18n" ] . {.indent} ### `locales()` > Source: /docs/node.js/cds-i18n#locales Returns an array of all locales for which translations have been found. {.indent} ```js [dev] cds repl > cds.i18n.labels.files.locales() //> [ '', 'de', 'en', 'fr', ... ] ``` ## Fetching i18n Folders... > Source: /docs/node.js/cds-i18n#fetching-i18n-folders ### From Models' Neighborhood > Source: /docs/node.js/cds-i18n#from-models-neighborhood By default, the config option `cds.i18n.folders` is defined using relative folder names (that is, ***without* leading slash**) as follows: ::: code-group ```json [package.json] "cds": { "i18n": { "folders": ["_i18n","i18n"] } } ``` ::: In effect i18n folders and hence files are fetched from the neighborhood of the current `cds.model`'s `$sources` as follows... #### 1. Starting from the current model's `$sources` > Source: /docs/node.js/cds-i18n#1-starting-from-the-current-models-sources For example given these model sources from [cap/samples](https://github.com/capire/samples): ```js [dev] cds repl > cds.model = await cds.load('bookstore') // [!code focus] > $sources = cds.model.$sources // [!code focus] [ '/cap/samples/bookstore/index.cds', '/cap/samples/bookstore/srv/mashup.cds', '/cap/samples/reviews/index.cds', '/cap/samples/orders/index.cds', '/cap/samples/orders/app/fiori.cds', '/cap/samples/bookshop/index.cds', '/cap/samples/reviews/srv/reviews-service.cds', '/cap/samples/orders/srv/orders-service.cds', '/cap/samples/bookshop/srv/user-service.cds', '/cap/samples/bookshop/srv/cat-service.cds', '/cap/samples/bookshop/srv/admin-service.cds', '/cap/samples/reviews/db/schema.cds', '/cap/samples/orders/db/schema.cds', '/cap/samples/bookshop/db/schema.cds', '/cap/samples/common/index.cds', '/cap/samples/node_modules/@sap/cds/common.cds' ] ``` #### 2. Get distinct source directories > Source: /docs/node.js/cds-i18n#2-get-distinct-source-directories ```js [dev] cds repl > $sourcedirs = $sources.map(path.dirname) // [!code focus] [ '/cap/samples/bookstore', '/cap/samples/bookstore/srv', '/cap/samples/reviews', '/cap/samples/orders', '/cap/samples/orders/app', '/cap/samples/bookshop', '/cap/samples/reviews/srv', '/cap/samples/orders/srv', '/cap/samples/bookshop/srv', '/cap/samples/reviews/db', '/cap/samples/orders/db', '/cap/samples/bookshop/db', '/cap/samples/common', '/cap/samples/node_modules/@sap/cds' ] ``` #### 3. Check for existing & matching `i18n.folders` > Source: /docs/node.js/cds-i18n#3-check-for-existing--matching-i18nfolders To fetch i18n folder, these source directories are processed in reverse order, and each is checked for existence of a sub directory from the `i18n.folders` array containing files matching the bundle's [`.file`](#file) basename. If none matches, we move up the directory tree and repeat these checks, as depicted in this matrix: > 🎯
>Marks existing i18n subfolders containing matching `_*.properties` files. | $sourcedirs | \_i18n | i18n | | ----------- | :---: | :--: | | /cap/samples/node_modules/@sap/cds | 🎯 | | | /cap/samples/common | | | | /cap/samples/bookshop/db | | | | /cap/samples/bookshop/srv | | | | /cap/samples/bookshop | | | | /cap/samples/reviews/db | | | | /cap/samples/reviews/srv | | | | /cap/samples/reviews | 🎯 | | | /cap/samples/orders/db | | | | /cap/samples/orders/srv | | | | /cap/samples/orders/app | | | | /cap/samples/orders | 🎯 | | | /cap/samples/bookstore/srv | | | | /cap/samples/bookstore | 🎯 | | > Note on _reverse order_: means entries in `app` override same entries in `db`, and so on. #### 4. Result: i18n folders used by bundle > Source: /docs/node.js/cds-i18n#4-result-i18n-folders-used-by-bundle So, we would end up in having found these four directories from which we would load *.properties* files subsequently: ```js [dev] cds repl > Object.keys (cds.i18n.labels.files) // [!code focus] [ '/cds/samples/node_modules/@sap/cds/_i18n', '/cap/samples/orders/_i18n', '/cap/samples/reviews/_i18n', '/cap/samples/bookstore/_i18n' ] ``` ::: tip Why fetching from a model's neighborhood? The reason we do this fetching in the neighborhood of the current model's *.cds* source files is to find i18n content from reuse packages with zero configuration: As such reuse packages frequently come with their own CDS models, we simply use the locations of these *.cds* sources as the starting points to search for i18n folders up the file system hierarchy. ::: ### From Static Project Folders > Source: /docs/node.js/cds-i18n#from-static-project-folders In addition to fetching i18n folders from models' neighborhood as explained above, you can also specify static folders to be used as is, by adding a **leading slash**. For example: ::: code-group ```jsonc [package.json] "cds": { "i18n": { "folders": [ "_i18n", // fetched from model's neighborhood "/app/browse/webapp/i18n" // static folder in project's root ] } } ``` ::: With that configuration, we'll search for subfolders named `_i18n` in the neighborhood of model sources, plus load .properties files from `/app/browse/webapp/i18n`, that is: ```js [dev] cds repl > Object.keys (cds.i18n.labels.files) // [!code focus] [ '.../node_modules/@sap/cds/_i18n', // found in model's neighborhood '.../_i18n', // found in model's neighborhood '.../app/browse/webapp/i18n' // found statically ] ``` You can specify static folders only to not fetch i18n folders in the model's neighborhood at all, both by default configuration as well as for individual bundles. For example: ```js const b = cds.i18n.bundle4 ({ folders: ['/_i18n', ...] }) ``` ### From Absolute Folders > Source: /docs/node.js/cds-i18n#from-absolute-folders Static folders can also be fully qualified absolute filenames. For example, plugins could use that to add their own translations or bundles like so: ::: code-group ```js [cds-plugin.js] cds.i18n.folders .push (path.join(__dirname,'_i18n')) ``` ::: ## Configuration Options > Source: /docs/node.js/cds-i18n#configuration-options Find the configuration options to customize `cds.i18n` in the following table. You can use these options in your package.json like so: ::: code-group ```json [package.json] "cds": { "i18n": { "default_language": "fr" } } ``` ```js [defaults.js by @sap/cds] cds.env.i18n = { default_language: "en", folders: [ "_i18n", "i18n" ], file: "i18n", } ``` ::: [Learn more about configuration in the reference docs for `cds.env`](cds-env){.learn-more} | Config Option | Description | | --------------------------- | ------------------------------------------------------------ | | `cds.i18n.file` | The [`.file` basename](#file) used for the [`cds.i18n.labels`](#labels) bundle.
*Default:* `"i18n"`. | | `cds.i18n.folders` | An array of (relative) folder names that will be appended to the source directories in a cross-product fashion of the default `cds.model` when fetching for existing i18n [`folders`](#folders).
*Default:* `["_i18n","i18n"]` | | `cds.i18n.default_language` | The locale used for [default translations](#defaults).
*Default:* `"en"` | ::: danger Do not switch defaults without proper evaluation Changing these configurations does not only affect your usage of your i18n bundles, but also all bundles provided by reuse packages you might use, including the ones provided by the CAP framework itself, such as the labels for the `@sap/cds/common` types, or the default messages used by the Node.js runtime. It is therefore highly recommended to leave this setting as is and adhere to the default name `_i18n` for your i18n directory. ::: ::: warning Ensure you correctly understand how the config option `cds.i18n.folders` work before changing it: essentially a **cartesian product** (*source dirs **x** i18n folders*) of all source directories with the entries in this config option is created to check each if such a directory exists and contains files matching the respective bundle's basename. ::: ## Messages Texts > Source: /docs/node.js/cds-i18n#messages-texts These are the current i18n entries for [`cds.i18n.messages`](#messages) used by the CAP runtime, which you can provide own translations for in your app-specific `_i18n/messages_.properties` files: ```properties MULTIPLE_ERRORS = Multiple errors occurred, see details below. ASSERT_FORMAT = Enter a value matching the pattern {1}. ASSERT_RANGE = Enter a value between {1} and {2}. ASSERT_ENUM = Enter one of the allowed values: {1}. ASSERT_MANDATORY = Provide the missing value. ``` In addition the following HTTP status codes can be translated: ```properties 400 = Bad Request 401 = Unauthorized 403 = Forbidden 404 = Not Found 405 = Method Not Allowed 406 = Not Acceptable 407 = Proxy Authentication Required 408 = Request Timeout 409 = Conflict 410 = Gone 411 = Length Required 412 = Precondition Failed 413 = Payload Too Large 414 = URI Too Long 415 = Unsupported Media Type 416 = Range Not Satisfiable 417 = Expectation Failed 422 = Unprocessable Content 424 = Failed Dependency 428 = Precondition Required 429 = Too Many Requests 431 = Request Header Fields Too Large 451 = Unavailable For Legal Reasons 500 = Internal Server Error 501 = The server does not support the functionality required to fulfill the request 502 = Bad Gateway 503 = Service Unavailable 504 = Gateway Timeout ``` # Project-Specific Configurations > Source: /docs/node.js/cds-env Learn here about using cds.env to specify and access configuration options for the Node.js runtimes as well as the @sap/cds-dk CLI commands. ## CLI `cds env` Command > Source: /docs/node.js/cds-env#cli-cds-env-command Run the `cds env` command in the root folder of your project to see the effective configuration. The listed settings include [global defaults](#defaults) as well as [project-specific settings](#project-settings) and [process environment settings](#process-env). Here's a brief intro how to use it: ```sh cds env #> shortcut to `cds env ls` cds env ls #> lists all settings in properties format cds env ls folders #> lists the `folders` settings cds env get #> prints all settings in JSON-like format cds env get folders #> prints the `folders` settings cds env get defaults #> prints defaults only cds env ? #> get help ``` For example:
> cds env ls requires.db

requires.db.credentials.url = ':memory:'
requires.db.data = [ 'db/data', 'db/csv', 'test/data' ]
requires.db.impl = '@cap-js/sqlite'
requires.db.kind = 'sqlite'
requires.db.pool.evictionRunIntervalMillis = 0
requires.db.pool.max = 1
requires.db.pool.min = 1

> cds env requires.db

{
  impl: '@cap-js/sqlite',
  credentials: { url: ':memory:' },
  data: [ 'db/data', 'db/csv', 'test/data' ],
  pool: { evictionRunIntervalMillis: 0, min: 1, max: 1 },
  kind: 'sqlite'
}
Alternatively, you can also use the `cds eval` or `cds repl` CLI commands to access the `cds.env` property, which provides programmatic access to the effective settings:
> cds -e .env.requires.db

{
  impl: '@cap-js/sqlite',
  credentials: { url: ':memory:' },
  data: [ 'db/data', 'db/csv', 'test/data' ],
  pool: { evictionRunIntervalMillis: 0, min: 1, max: 1 },
  kind: 'sqlite'
}

$ cds -r
Welcome to cds repl ...
> cds.env.requires.db
{
  impl: '@cap-js/sqlite',
  credentials: { url: ':memory:' },
  kind: 'sqlite'
}
## The `cds.env` Module > Source: /docs/node.js/cds-env#the-cdsenv-module The `cds env` CLI command and all configuration-related tasks and features in Node.js-based tools and runtimes are backed by the `cds.env` module, which can be accessed through the central `cds` facade. For example, you can use it as follows: ```js const cds = require('@sap/cds') console.log (cds.env.requires.sql) ``` > This would print the same output as the one above for `cds env get requires.sql`. As depicted in the figure below `cds.env` provides one-stop convenient and transparent access to the effective configuration read from various sources, including global defaults, static, project-specific configuration as well as dynamic settings from process environment and service bindings. Different environments, for example, dev vs prod can be identified and selected by [profiles](#profiles). !['cds env' in the middle, targeted by arrows coming from project content, service bindings and environment.](./assets/cds.env.drawio.svg) ## Sources for `cds.env` > Source: /docs/node.js/cds-env#sources-for-cdsenv `cds.env` is actually a getter property, which on first usage loads settings from the following sources: | Order | Source | Remarks | |------:|------------------------------------------------------|---------------------------------------------------------| | 1 | [`@sap/cds`](#defaults) | built-in defaults | | 2 | [_~/.cdsrc.yaml (.json,.js)_](#defaults) | user-specific defaults | | 3 | [_./.cdsrc.yaml (.json,.js)_](#project-settings) | static project settings, also in plugins | | 4 | [_./package.json_](#project-settings) | static project settings → `{"cds":{ ... }}` | | 5 | [_./.cdsrc-private.json_](#private-project-settings) | user-specific project config | | 6 | [_./default-env.json_](#process-env) | *deprecated, see cds bind* | | 7 | [_./.env_](#process-env) | user-specific project env (lines of `name=value`) | | 8 | [_./.\.env_](#profiles) | profile-specific project env, like `.hybrid.env` | | 9 | [`process.env.CDS_CONFIG`](#env-cds-config) | runtime settings from shell or cloud | | 10 | [`process.env`](#process-env) | runtime env vars from shell or cloud | | 11 | [`process.env.VCAP_SERVICES`](#services) | service bindings | | 12 | [_~/.cds-services.json_](#services) | service bindings for [_development_ profile](#profiles) | > - `./` represents a project's root directory. > - `~/` represents a user's home directory. ::: warning Private files are for you only and should not be checked into your source code management. ::: The settings are merged into `cds.env` starting from lower to higher order. Meaning that propertiers specified in a source of higher order will overwrite the value from a lower order. For example, given the following sources: ::: code-group ```jsonc [cdsrc.json] { "requires": { "db": { "kind": "sql", "model": "./db", "credentials": { "url": ":memory:" } } } } ``` ::: ::: code-group ```jsonc [package.json] { "cds": { "requires": { "db": { "kind": "sqlite" } } } } ``` ::: ::: code-group ```properties [env.properties] cds.requires.db.credentials.database = my.sqlite ``` ::: This would result in the following effective configuration: ```js cds.env = { ..., requires: { db: { kind: "sqlite", model: "./db", credentials: { database:"my.sqlite" } } } } ``` ### Programmatic Settings > Source: /docs/node.js/cds-env#programmatic-settings Node.js programs can also add and change settings by simply assigning values like so: ```js const cds = require('@sap/cds') cds.env.requires.sql.kind = 'sqlite' cds.env.requires.sql.credentials = { database:'my.sqlite' } ``` > This would change the respective settings in the running program only, without writing back to the sources listed above. ## Global Defaults > Source: /docs/node.js/cds-env#global-defaults ### Built-In to `@sap/cds` > Source: /docs/node.js/cds-env#built-in-to-sapcds The lowest level of settings is read from built-in defaults, which comprise settings for these top-level properties: | Settings | Description | |------------|----------------------------------------------| | `build` | for build-related settings | | `features` | to switch on/off cds features | | `folders` | locations for `app`, `srv`, and `db` folders | | `i18n` | for i18n-related settings | | `odata` | for OData protocol-related settings | | `requires` | to configure required services | > As these properties are provided in the defaults, apps can safely access them, for example, through `cds.env.requires.sql`, without always checking for null values on the top-level entries. ### User-Specific Defaults in _~/.cdsrc.json_ > Source: /docs/node.js/cds-env#user-specific-defaults-in-cdsrcjson You can also create a _.cdsrc.json_ file in your user's home folder to specify settings to be used commonly across several projects. ## Project Configuration > Source: /docs/node.js/cds-env#project-configuration Settings, which are essential to your project topology go into static project settings. Examples are the `folders` layout of your project, specific `build` tasks, or the list of required services in `requires` — most frequently your primary database configured under `requires.db`. ::: tip The settings described here are part of your project's static content and delivery. They're checked in to your git repos and used also in productive deployments. **Don't** add environment-specific options as static settings but use one of the [dynamic process environment options](#process-env) for that. ::: ### In _./package.json_ > Source: /docs/node.js/cds-env#in-packagejson You can provide static settings in a `"cds"` section of your project's _package.json_ as in the following example: ```json "cds": { "requires": { "db": "sql" } } ``` ### In _./.cdsrc.json_ > Source: /docs/node.js/cds-env#in-cdsrcjson Alternatively, you can put static settings in _.cdsrc.json_ file in your project root: ```json "requires": { "db": "sql" } ``` ## Private Project Settings > Source: /docs/node.js/cds-env#private-project-settings ### In _./.cdsrc-private.json_ > Source: /docs/node.js/cds-env#in-cdsrc-privatejson A _.cdsrc.json_ equivalent for your private settings used in local testing. The file should not be committed to your version control system. ## Process Environment > Source: /docs/node.js/cds-env#process-environment ### On the Command Line > Source: /docs/node.js/cds-env#on-the-command-line On UNIX-based systems (Mac, Linux) you can specify individual process env variables as prefixes to the command to start your server. For example: ```sh CDS_REQUIRES_DB_KIND=sql cds run ``` ### In _./default-env.json_ > Source: /docs/node.js/cds-env#in-default-envjson The use of _default-env.json_ is deprecated. Please use [`cds bind`](../tools/cds-bind#run-with-service-bindings). ### In `./.env` > Source: /docs/node.js/cds-env#in-env Example for `.env`: ```properties cds_requires_db_kind = sql ``` or ```properties cds.requires.db.kind = sql ``` or ```properties cds.requires.db = { "kind": "sql" } ``` ::: warning The dot ("`.`") notation can only be used in `.env` files, because the dot is not a valid environment variable character. You can use it here if your config string contains underscore ("`_`") characters. ::: ### `CDS_CONFIG` env variable > Source: /docs/node.js/cds-env#cdsconfig-env-variable You can use the `CDS_CONFIG` env variable in three different ways to add settings to the CDS environment: 1. Using a JSON string ```sh CDS_CONFIG='{"requires":{"db":{"kind":"sqlite"}}}' cds serve ``` 2. Using a JSON file ```sh CDS_CONFIG=./my-cdsrc.json cds serve ``` 3. Using a directory ```sh CDS_CONFIG=/etc/secrets/cds cds serve ``` For each file and folder, a new property is added to the configuration with its name. For a file the property value is the string content of the file. But if a file contains a parsable JSON string starting with `[` or `{` character, it is parsed and added as a substructure. For a directory an object is added and the algorithm continues there. ```yaml /etc/secrets/cds/requires/auth/kind: xsuaa /etc/secrets/cds/requires/auth/credentials/clientid: capapp /etc/secrets/cds/requires/auth/credentials/clientsecret: dlfed4XYZ /etc/secrets/cds/requires/db: { kind: "hana", "credentials": { "user": "hana-user" } } ``` Results in: ```json { "requires": { "auth": { "kind": "xsuaa", "credentials": { "clientid": "cpapp", "clientsecret": "dlfed4XYZ" } }, "db": { "kind": "hana", "credentials": { "user": "hana-user" } } } } ``` ## Required Services > Source: /docs/node.js/cds-env#required-services If your app requires external services (databases, message brokers, ...), you must add them to the `cds.requires` section. ### In `cds.requires.` Settings > Source: /docs/node.js/cds-env#in-cdsrequiresservice-settings Here, you can configure the services. Find details about the individual options in the documentation of [`cds.connect`](cds-connect#cds-env-requires). ### Prototype-Chained Along `.kind` References > Source: /docs/node.js/cds-env#prototype-chained-along-kind-references You can use the `kind` property to reference other services for prototype chaining. > CDS provides default service configurations for all supported services (`hana`, `enterprise-messaging`, ...). Example: ::: code-group ```json [package.json] { "cds": { "requires": { "serviceA": { "kind": "serviceB", "myProperty": "my overwritten property" }, "serviceB": { "kind": "hana", "myProperty": "my property", "myOtherProperty": "my other property" } } } } ``` ::: `serviceA` will have the following properties: ```json { "kind": "serviceB", "myProperty": "my overwritten property", "myOtherProperty": "my other property", // from serviceB "impl": "[...]/hana/Service.js", // from hana "use": "hana" // where impl is defined } ``` ## Configuration Profiles > Source: /docs/node.js/cds-env#configuration-profiles Wrap entries into `[]:{ ... }` to provide settings for different environments. For example: ::: code-group ```json [package.json] { "cds": { "requires": { "db": { "[development]": { "kind": "sqlite" }, "[production]": { "kind": "hana" } } } } } ``` ::: The profile is determined at bootstrap time as follows: 1. from `--production` command line argument, if specified 2. from `--profile` command line argument, if specified 3. from `NODE_ENV` property, if specified 4. from `CDS_ENV`, if specified If the profile is not set to `production`, the `development` profile is automatically enabled. You can also introduce own custom profile names and use them as follows: ```sh cds run --profile my-custom-profile ``` or ::: code-group ```sh [macOS/Linux] CDS_ENV=my-custom-profile cds run ``` ```cmd [Windows] set CDS_ENV=my-custom-profile cds run ``` ```powershell [Powershell] $Env:CDS_ENV=my-custom-profile cds run ``` ::: ## App-Specific Settings > Source: /docs/node.js/cds-env#app-specific-settings You can use the same machinery as documented above for app-specific configuration options: ::: code-group ```json [package.json] "cds": { ... }, "my-app": { "myoption": "value" } ``` ::: And access them from your app as follows: ```js const { myoption } = cds.env.for('my-app') ``` # Common Utility Functions > Source: /docs/node.js/cds-utils ## Module `cds.utils` > Source: /docs/node.js/cds-utils#module-cdsutils Module `cds.utils` provides a set of utility functions, which can be used like that: ```js const { uuid, read, fs, path, decodeURI } = cds.utils let id = uuid() // generates a new UUID let uri = decodeURI("%E0%A4%A") let json = await fs.promises.readFile( path.join(cds.root,'package.json'), 'utf8') let pkg = await read ('package.json') ``` ### uuid() > Source: /docs/node.js/cds-utils#uuid Generates new UUIDs. For example: ```js const { uuid } = cds.utils let id = uuid() // generates a new UUID ``` ### decodeURI (*uri*) > Source: /docs/node.js/cds-utils#decodeuri-uri ### decodeURIComponent (*uri*) > Source: /docs/node.js/cds-utils#decodeuricomponent-uri These are 'safe' variants for [`decodeURI`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURI) and [`decodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent) which in case of non-decodable input return the input string instead of throwing `URIErrors`. This allows simplifying our code. For example given we have to handle input like this: ```js let input = "%E0%A4%A" ``` Instead of this: ```js let uri try { uri = decodeURI(input) } catch { uri = input } ``` We can simply do this: ```js const { decodeURI } = cds.utils let uri = decodeURI(input) ``` ### local (*filename*) > Source: /docs/node.js/cds-utils#local-filename Returns a relative representation of `filename` to the original `process.cwd()`. We commonly use that in CAP implementations to print filenames to stdout that you can click to open, for example in VS Code terminal output, regardless from where you started your server. For example, if we run bookshop from the parent folder, filenames are correctly printed with a `bookshop/` prefix: ```log [samples] cds run bookshop [cds] - loaded model from 5 file(s): bookshop/srv/user-service.cds bookshop/srv/cat-service.cds bookshop/srv/admin-service.cds bookshop/db/schema.cds ... ``` If we run it from within the *bookshop* folder, no prefixes show up: ```log [bookshop] cds run [cds] - loaded model from 5 file(s): srv/user-service.cds srv/cat-service.cds srv/admin-service.cds db/schema.cds ... ``` ### exists (*file*) > Source: /docs/node.js/cds-utils#exists-file Checks if a given file or folder exists; `file` is resolved relative to [`cds.root`](cds-facade#cds-root). ```js const { exists } = cds.utils if (exists('server.js')) // ... ``` Basically the implementation looks like that: ```js if (file) return fs.existsSync (path.resolve (cds.root,file)) ``` ### isdir (*file*) > Source: /docs/node.js/cds-utils#isdir-file Checks if the given filename refers to an existing directory, and returns the fully resolved absolute filename, if so. ```js const { isdir, fs } = cds.utils let dir = isdir ('app') if (dir) { let entries = fs.readdirSync(dir) ... } ``` Returns `undefined` or a fully resolved absolute filename of the existing directory, including recursively resolving symbolic links. Relative fileames are resolved in relation to [`cds.root`](cds-facade#cds-root), ### isfile (*file*) > Source: /docs/node.js/cds-utils#isfile-file Checks if the given filename pints to an existing file, and returns the fully resolved absolute filename, if so. ```js const { isfile, fs } = cds.utils let file = isdir ('package.json') let json = fs.readFileSync (file,'utf8') ``` Returns `undefined` or a fully resolved absolute filename of the existing directory, including recursively resolving symbolic links. Relative fileames are resolved in relation to [`cds.root`](cds-facade#cds-root), ### async read (*file*) > Source: /docs/node.js/cds-utils#async-read-file Reads content of the given file. ```js const { read } = cds.utils let pkg = await read ('package.json') ``` Relative fileames are resolved in relation to [`cds.root`](cds-facade#cds-root). The implementation uses `utf8` encoding by default. If the file is a `.json` file, the read content is automatically `JSON.parse`d. ### async write (*data*) .to (...*file*) > Source: /docs/node.js/cds-utils#async-write-data-to-file Writes content to a given file, optionally with a fluent API. ```js const { write } = cds.utils await write ({foo:'bar'}) .to ('some','file.json') await write ({foo:'bar'}) .to ('some/file.json') await write ('some/file.json', {foo:'bar'}) ``` Relative fileames are resolved in relation to [`cds.root`](cds-facade#cds-root). If provided data is an object, it is automatically `JSON.stringify`ed. ### async copy (*src*) .to (...*dst*) > Source: /docs/node.js/cds-utils#async-copy-src-to-dst Copies `src` to `dst`, optionally with a fluent API. Both can be files or folders. ```js const { copy } = cds.utils await copy('db/data').to('dist','db','data') await copy('db/data').to('dist/db/data') await copy('db/data','dist/db/data') ``` The implementation essentially uses `fs.promises.cp()`, with relative fileames resolved in relation to [`cds.root`](cds-facade#cds-root). ### async mkdirp (...*path*) > Source: /docs/node.js/cds-utils#async-mkdirp-path Creates a directory at the given path. ```js const { mkdirp } = cds.utils await mkdirp('dist','db','data') await mkdirp('dist/db/data') ``` The implementation essentially uses `fs.promises.mkdir(...,{recursive:true})`, with relative fileames resolved in relation to [`cds.root`](cds-facade#cds-root). ### async rmdir (...*path*) > Source: /docs/node.js/cds-utils#async-rmdir-path Deletes the *directory* at the given path, throwing an error if it doesn't exist. ```js const { rmdir } = cds.utils await rmdir('dist','db','data') await rmdir('dist/db/data') ``` The implementation essentially uses `fs.promises.rm(...,{recursive:true})`, with relative fileames resolved in relation to [`cds.root`](cds-facade#cds-root). ### async rimraf (...*path*) > Source: /docs/node.js/cds-utils#async-rimraf-path Deletes the *directory* at the given path, if exists, doing nothing, if not. ```js const { rimraf } = cds.utils await rimraf('dist','db','data') await rimraf('dist/db/data') ``` The implementation essentially uses `fs.promises.mkdir(...,{recursive:true, force:true})`, with relative fileames resolved in relation to [`cds.root`](cds-facade#cds-root). ### async rm (...*path*) > Source: /docs/node.js/cds-utils#async-rm-path Deletes the *file* at the given path. ```js const { rm } = cds.utils await rm('dist','db','data') await rm('dist/db/data') ``` The implementation essentially uses `fs.promises.rm()`, with relative fileames resolved in relation to [`cds.root`](cds-facade#cds-root). ### colors > Source: /docs/node.js/cds-utils#colors Provides utilities for coloring terminal output. Colors are automatically enabled if the terminal supports it, but can be overridden via environment variables `NO_COLOR` or `FORCE_COLOR`. ```js const { BRIGHT, RED, RESET, bg } = cds.utils.colors console.log(BRIGHT, RED, 'this is bright red text', RESET) console.log(bg.BLUE, 'this is text with a blue background', RESET) ``` | **Category** | **Values** | |----------------------|-------------------------------------------------------------------------------------------| | **Formatting** | `RESET`, `BOLD`, `BRIGHT`, `DIMMED`, `ITALIC`, `UNDER`, `BLINK`, `FLASH`, `INVERT` | | **Text Colors** | `DEFAULT`, `BLACK`, `RED`, `GREEN`, `YELLOW`, `BLUE`, `PINK`, `CYAN`, `WHITE`, `LIGHT_GRAY`, `LIGHT_RED`, `LIGHT_GREEN`, `LIGHT_YELLOW`, `LIGHT_BLUE`, `LIGHT_PINK`, `LIGHT_CYAN`, `GRAY` | | **Background Colors** | `DEFAULT`, `BLACK`, `RED`, `GREEN`, `YELLOW`, `BLUE`, `PINK`, `CYAN`, `WHITE`, `LIGHT_GRAY`, `LIGHT_RED`, `LIGHT_GREEN`, `LIGHT_YELLOW`, `LIGHT_BLUE`, `LIGHT_PINK`, `LIGHT_CYAN`, `LIGHT_WHITE` | ## Shortcuts to Node.js Modules > Source: /docs/node.js/cds-utils#shortcuts-to-nodejs-modules In addition, `cds.utils` provides shortcuts to common Node.js functions and libraries... | `cds.utils.`... | → shortcut to: | | --------------- | ------------------------------ | | `inspect` | `require('node:util').inspect` | | `path` | `require('node:path')` | | `fs` | `require('node:fs')` | # Event Queues in Node.js > Source: /docs/node.js/event-queues For concepts, use cases, and guarantees, see the [Transactional Event Queues](../guides/events/event-queues) guide. This page covers the Node.js-specific APIs and configuration. In Node.js, you wrap a service with `cds.queued()` to queue its events, or enable queueing through configuration. The persistent queue is the default for all queued services. > [!info] Event queues vs. `cds.spawn` > [`cds.spawn`](cds-tx#cds-spawn) runs a *detached continuation*, which means an in-memory background job in a fresh root transaction, optionally with `every` / `after` recurrence. It does not persist anything: a crash before the job completes loses it, and concurrent app instances each run their own copy. > > Use `cds.spawn` when the work is in-process, idempotent, and tolerates being dropped, for example, a periodic cache refresh. Use an event queue when you need **transactional integration with the calling request** (the message is committed or discarded with the surrounding transaction) or **persistence and retries across restarts and instances**. ## Programmatic API > Source: /docs/node.js/event-queues#programmatic-api ### Queueing a Service > Source: /docs/node.js/event-queues#queueing-a-service #### `cds.queued(srv)` > Source: /docs/node.js/event-queues#cdsqueuedsrv ```tsx function cds.queued ( srv: Service ) => QueuedService ``` Wrap a non-database service in `cds.queued()` to obtain a queued proxy. All `emit` / `send` / `run` calls on the proxy are persisted in the current transaction and dispatched after commit: ```js const srv = await cds.connect.to('yourService') const qd_srv = cds.queued(srv) await qd_srv.emit('someEvent', { some: 'message' }) // persisted, dispatched async await qd_srv.send('someEvent', { some: 'message' }) ``` ::: tip `await` is still needed The persistent queue writes the message to the database within the current transaction; you still need to `await` to keep that write inside the transaction. ::: For backward compatibility, `cds.outboxed(srv)` works as a synonym. #### `cds.unqueued(srv)` > Source: /docs/node.js/event-queues#cdsunqueuedsrv ```tsx function cds.unqueued ( srv: QueuedService ) => Service ``` Get back the original synchronous service from a queued proxy: ```js const srv = cds.unqueued(qd_srv) ``` This is useful when a service is queued through configuration and you need a synchronous call site. For backward compatibility, `cds.unboxed(srv)` works as a synonym. #### Queueing through Configuration > Source: /docs/node.js/event-queues#queueing-through-configuration Set the `outboxed` flag in the *outbound* service's configuration: ```json { "requires": { "yourService": { "kind": "odata", "outboxed": true } } } ``` Some services - `cds.MessagingService` and `cds.AuditLogService` - are outboxed by default. See [*Auto-Outboxed Services*](../guides/events/event-queues#auto-outboxed-services) in the Transactional Event Queues guide. ### Scheduling > Source: /docs/node.js/event-queues#scheduling The `srv.schedule()` method queues like `cds.queued(srv).send()`, that is within the current transaction, dispatched after commit. But it **upserts** a singleton task keyed by event name (or by `.as(name)`) instead of inserting a new entry on every call. It accepts optional timing: ```js await srv.schedule('someEvent', { some: 'msg' }) // execute asap await srv.schedule('someEvent', { some: 'msg' }).after('1h') // delay await srv.schedule('someEvent', { some: 'msg' }).every('10m') // recurrence await srv.schedule('someEvent', { some: 'msg' }).every('*/10 * * * *') // cron await srv.unschedule('someEvent') // remove ``` `.after()` accepts milliseconds (as a number) or a time string such as `'1s'`, `'10m'`, `'1h'`. `.every()` accepts the same plus a five-field cron expression. > [!warning] Cron field counts differ between stacks > Java cron expressions are **six fields including seconds** (Spring syntax); Node.js cron expressions are **five fields**. A cron string copied between stacks won't behave the same way. A scheduled task is identified by its event name and exists only once. A subsequent `schedule()` call with the same name overwrites the previous schedule (tasks are upserted, not deduplicated), which is convenient for idempotent registration during application startup. To schedule the same event under separate identities (for example, with different payloads), give each its own task name with `.as()`: ```js // Two independent singleton tasks for the same "replicate" event await srv.schedule('replicate', { entity: 'Airports' }).every('10m') .as('replicate-airports') // [!code highlight] await srv.schedule('replicate', { entity: 'Airlines' }).every('1 hour') .as('replicate-airlines') // [!code highlight] // Each can be removed independently by its task name await srv.unschedule('replicate-airports') await srv.unschedule('replicate-airlines') ``` ### Callbacks > Source: /docs/node.js/event-queues#callbacks-alpha- > [!note] Node.js only > Callback events have no Java equivalent yet, but they're on the roadmap. Once a queued message has been successfully processed, the runtime emits `/#succeeded` on the same service: ```js srv.after('someEvent/#succeeded', (data, req) => { // `data` is the result of the event processor console.log('Message successfully processed:', data) }) ``` Similarly, when a message becomes a dead letter (after all retries are exhausted), the runtime emits `/#failed`: ```js srv.after('someEvent/#failed', (data, req) => { // `data` is the error from the event processor console.log('Message could not be processed:', data) }) ``` ::: tip Register on specific events Callback handlers must be registered for the specific `#succeeded` or `#failed` events. The `*` wildcard handler is not called for these events. ::: ### Manual Processing > Source: /docs/node.js/event-queues#manual-processing > [!note] Node.js only > `cds.flush()` is a Node.js API. Both stacks have built-in recovery mechanisms that pick up pending messages automatically. The background runner picks up pending messages automatically. The main use case for a manual flush is triggering processing immediately after reviving a dead-letter entry — without waiting for the next runner cycle: ```js await cds.flush() ``` The returned promise resolves once the runner has finished dispatching all currently processable messages and goes idle. Handler failures don't reject it — failed messages are rescheduled for the next retry. ## Configuration > Source: /docs/node.js/event-queues#configuration The persistent queue is enabled by default. Messages are stored in the `cds.outbox.Messages` table within the current transaction. `cds.requires.queue` resolves to its default config automatically via `cds.env`. Specify it only when tuning. ```json { "requires": { "queue": { "maxAttempts": 10, "timeout": "1h" } } } ``` > [!warning] Rolling upgrades and `legacyLocking` > The `legacyLocking` flag controls cross-version compatibility for the queue's status check. See [*Locking*](../guides/events/event-queues#locking) in the common guide for the version-by-version behavior and the rolling-upgrade caveat. ::: details Queue options `cds.requires.queue`: | Option | Default | Description | |--------|---------|-------------| | `maxAttempts` | `10` | Maximum retries before a message becomes a dead letter | | `timeout` | `"1h"` | Time after which a `processing` message is considered abandoned and eligible for reprocessing | | `legacyLocking` | `false` | Backward compatibility with `@sap/cds` v9. Planned for removal in a future release | ::: ### Disabling the Queue > Source: /docs/node.js/event-queues#disabling-the-queue Disable event queues globally: ```json { "cds": { "requires": { "queue": false } } } ``` Or disable queueing for a specific service — for example to make `cds.MessagingService` emit immediately: ```json { "requires": { "messaging": { "kind": "enterprise-messaging", "outboxed": false } } } ``` ## Troubleshooting > Source: /docs/node.js/event-queues#troubleshooting ### Inspecting `cds.outbox.Messages` > Source: /docs/node.js/event-queues#inspecting-cdsoutboxmessages To see what's currently queued, query `cds.outbox.Messages` directly. The columns most useful for triage are `status`, `attempts`, `target`, `lastError`, and `lastAttemptTimestamp`: ```js const db = await cds.connect.to('db') const messages = await SELECT.from('cds.outbox.Messages') .columns('ID', 'target', 'status', 'attempts', 'lastAttemptTimestamp', 'lastError') .orderBy('timestamp desc') ``` For a managed view with bound *revive* and *delete* actions, see [*Dead Letter Queue*](../guides/events/event-queues#dead-letter-queue) in the common guide. ### Deleting Entries > Source: /docs/node.js/event-queues#deleting-entries To clear stuck messages programmatically: ```js const db = await cds.connect.to('db') await DELETE.from('cds.outbox.Messages') ``` ### Messages Table Not Found > Source: /docs/node.js/event-queues#messages-table-not-found If the `cds.outbox.Messages` table is missing from the database, the most common cause is insufficient model configuration in *package.json*. If you've overwritten `requires.db.model`, add the outbox model path: ```jsonc "requires": { "db": { ... "model": [..., "@sap/cds/srv/outbox"] } } ``` For projects on `@sap/cds < 6.7.0` with custom build tasks that override `options.model`, add the path there too: ```jsonc "build": { "tasks": [{ ... "options": { "model": [..., "@sap/cds/srv/outbox"] } }] } ``` The model configuration isn't required for CAP projects using the standard project layout with `db`, `srv`, and `app` folders. --- Working in Java? See [Event Queues in Java](../java/event-queues). # Fiori Support > Source: /docs/node.js/fiori See [Cookbook > Serving UIs > Draft Support](../guides/uis/fiori#draft-support) for an overview on SAP Fiori Draft support in CAP. ## Draft Entities > Source: /docs/node.js/fiori#draft-entities Draft-enabled entities have corresponding CSN entities for drafts: ```js const { MyEntity } = srv.entities MyEntity.drafts // points to model.definitions[MyEntity.drafts] ``` In event handlers, the `target` is resolved before the handler execution and points to either the active or draft entity: ```js srv.on('READ', MyEntity.drafts, (req, next) => { assert.equal(req.target.name, MyEntity.drafts) return next() }) ``` In the special case of the Fiori Elements filter "Editing Status: All", two separate `READ` events are triggered for either the active or draft entity. The individual results are then combined behind the scenes. Manual filtering on draft-related properties is not allowed, only certain draft scenarios are supported. ## Draft-specific Events > Source: /docs/node.js/fiori#draft-specific-events In addition to the standard CRUD events, draft entities provide draft-specific events in the lifecycle of a draft, as outlined in the following subsections. ### `NEW` > Source: /docs/node.js/fiori#new ```js srv.before('NEW', MyEntity.drafts, req => { req.data.ID = uuid() }) srv.after('NEW', MyEntity.drafts, /*...*/) srv.on('NEW', MyEntity.drafts, /*...*/) ``` The `NEW` event is triggered when the user created a new draft. As a result `MyEntity.drafts` is created in the database. You can modify the initial draft data in a `before` handler. ### `EDIT` > Source: /docs/node.js/fiori#edit ```js srv.before('EDIT', MyEntity, /*...*/) srv.after('EDIT', MyEntity, /*...*/) srv.on('EDIT', MyEntity, /*...*/) ``` The `EDIT` event is triggered when the user starts editing an active entity. As a result, a new entry to `MyEntity.drafts` is created. For logical reasons handlers for the `EDIT` event are registered on the active entity, that is, `MyEntity` in the code above, not on the `MyEntity.drafts` entity. ### `PATCH` > Source: /docs/node.js/fiori#patch ```js srv.before('PATCH', MyEntity.drafts, /*...*/) srv.after('PATCH', MyEntity.drafts, /*...*/) srv.on('PATCH', MyEntity.drafts, /*...*/) ``` The `PATCH` event is triggered whenever the user edits a field in a draft. It's actually an alias for the standard CRUD `UPDATE` event. ### `SAVE` > Source: /docs/node.js/fiori#save ```js srv.before('SAVE', MyEntity.drafts, /*...*/) srv.after('SAVE', MyEntity.drafts, /*...*/) srv.on('SAVE', MyEntity.drafts, /*...*/) ``` The `SAVE` event is triggered when the user saves / activates a draft. This results in either a CREATE or an UPDATE on the active entity depending on whether the draft was created via `NEW` or `EDIT`. > [!note] > The `SAVE` event is also available for non-draft, that is, active entities. In that case it acts as an convenience shortcut for registering handlers for the combination of `CREATE` and `UPDATE` events. In contrast to that, the `SAVE` event on draft entities is a distinct event that is only triggered when **activating** a draft. ### `DISCARD` > Source: /docs/node.js/fiori#discard ```js srv.before('DISCARD', MyEntity.drafts, /*...*/) srv.after('DISCARD', MyEntity.drafts, /*...*/) srv.on('DISCARD', MyEntity.drafts, /*...*/) ``` The `DISCARD` event is triggered when the user discards a draft started before. In this case, the draft entity is deleted and the active entity isn't changed. `CANCEL`, as a synonym for `DISCARD`, works as well. ### Custom Actions > Source: /docs/node.js/fiori#custom-actions Custom bound actions and functions defined for draft-enabled entities are also inherited by the draft entities. This allows you to implement different logic depending on whether the action/function is called on the active or draft entity, like so: ```js srv.on('someAction', MyEntity, /*...*/) srv.on('someAction', MyEntity.drafts, /*...*/) ``` If you want the same handler logic for both, do that: ```js srv.on('someAction', [ MyEntity, MyEntity.drafts ], /*...*/) ``` ## Draft Locks > Source: /docs/node.js/fiori#draft-locks To prevent inconsistency, the entities with draft are locked for modifications by other users. The lock is released when the draft is saved, canceled or a timeout is hit. The default timeout is 15 minutes. You can configure this timeout by the following application configuration property: ```properties cds.fiori.draft_lock_timeout=30min ``` You can set the property to one of the following: - number of hours like `'1h'` - number of minutes like `'10min'` - number of milliseconds like `1000` :::tip Delete released draft locks If the `draft_lock_timeout` has been reached, every user can delete other users' drafts to create an own draft. There can't be two drafts at the same time on the same entity. ::: ## Draft Timeouts > Source: /docs/node.js/fiori#draft-timeouts Inactive drafts are deleted automatically after the default timeout of 30 days. You can configure or deactivate this timeout by the following configuration: ```json { "cds": { "fiori": { "draft_deletion_timeout": "28d" } } } ``` You can set the property to one of the following: - `false` in order to deactivate the timeout - number of days like `'30d'` - number of hours like `'72h'` - number of milliseconds like `1000` ::: info Technical background It can occur that inactive drafts are still in the database after the configured timeout. The deletion is implemented as a side effect of creating new drafts and there's no periodic job that does the garbage collection. ::: ## Programmatic APIs > Source: /docs/node.js/fiori#programmatic-apis-beta- You can programmatically invoke draft actions with the following APIs: ```js await srv.new (MyEntity.drafts, data) // create new draft await srv.edit (MyEntity, keys) // create draft from active instance await srv.save (MyEntity.drafts, keys) // activate draft await srv.discard (MyEntity.drafts, keys) // discard draft ``` # Transaction Management > Source: /docs/node.js/cds-tx Transaction management in CAP deals with (ACID) database transactions, principal / context propagation on service-to-service calls and tenant isolation. ::: tip **In Essence...** As an application developer, **you don't have to care** about transactions, principal propagation, or tenant isolation at all. CAP runtime manages that for you automatically. Only in rare cases, you need to go beyond that level, and use one or more of the options documented hereinafter. :::
## Automatic Transactions > Source: /docs/node.js/cds-tx#automatic-transactions Whenever an instance of `cds.Service` processes requests, the core framework automatically cares for starting and committing or rolling back database transactions, connection pooling, principal propagation and tenant isolation. For example a call like that: ```js await db.read('Books') ``` ... will cause this to take place on SQL level: ```sql -- ACQUIRE connection from pool CONNECT; -- if no pooled one BEGIN; SELECT * from Books; COMMIT; -- RELEASE connection to pool ```
::: tip **Service-managed Transactions** — whenever a service operation, like `db.read()` above, is executed, the core framework ensures it will either join an existing transaction, or create a new root transaction. Within event handlers, your service always is in a transaction. ::: ## Nested Transactions > Source: /docs/node.js/cds-tx#nested-transactions Services commonly process requests in event handlers, which in turn send requests to other services, like in this simplistic implementation of a bank transfer operation: ```js const log = cds.connect.to('log') const db = cds.connect.to('db') BankingService.on ('transfer', req => { let { from, to, amount } = req.data await db.update('BankAccount',from).set('balance -=', amount), await db.update('BankAccount',to).set('balance +=', amount), await log.insert ({ kind:'Transfer', from, to, amount }) }) ``` Again, all transaction handling is done by the CAP core framework, in this case by orchestrating three transactions: 1. A *root* transaction for `BankingService.transfer` 2. A *nested* transaction for the calls to the `db` service 3. A *nested* transaction for the calls to the `log` service Nested transactions are automatically committed when their root transaction is committed upon successful processing of the request; or rolled back if not.
::: warning **No Distributed Transactions** — Note that in the previous example, the two nested transactions are *synchronized* with respect to a final commit / rollback, but *not as a distributed atomic transaction*. This means, it still can happen, that the commit of one nested transaction succeeds, while the other fails. ::: ## Manual Transactions > Source: /docs/node.js/cds-tx#manual-transactions Use `cds.tx()` to start and commit transactions manually, if you need to ensure two or more queries to run in a single transaction. The easiest way to achieve this is shown below: ```js cds.tx (async ()=>{ const [ Emily ] = await db.insert (Authors, {name:'Emily Brontë'}) await db.insert (Books, { title: 'Wuthering Heights', author: Emily }) }) ``` [Learn more about `cds.tx()`](#srv-tx){.learn-more} This usage variant, which accepts a function with nested operations ... 1. creates a new root transaction 2. executes all nested operations in this transaction 3. automatically finalizes the transaction with commit or rollback
::: tip **Only in non-managed environments** — as said above: you don't need to care for that if you are in a managed environment, that is, when implementing an event handler. In that case, the core service runtime automatically created a transaction for you already. ::: ::: warning _ If you're using the database SQLite, it leads to deadlocks when two transactions wait for each other. Parallel transactions are not allowed and a new transaction is not started before the previous one is finished. ::: ## Background Jobs > Source: /docs/node.js/cds-tx#background-jobs Background jobs are tasks to be executed *outside of the current transaction*, possibly also with other users, and maybe repeatedly. Use `cds.spawn()` to do so: ```js // run in current tenant context but with privileged user // and with a new database transactions each... cds.spawn ({ user: cds.User.privileged, every: 1000 /* ms */ }, async ()=>{ const mails = await SELECT.from('Outbox') await MailServer.send(mails) await DELETE.from('Outbox').where (`ID in ${mails.map(m => m.ID)}`) }) ``` [Learn more about `cds.spawn()`](#cds-spawn){.learn-more} ## cds. context > Source: /docs/node.js/cds-tx#cds-context Automatic transaction management, as offered by the CAP, needs access to properties of the invocation context — most prominently, the current **user** and **tenant**, or the inbound HTTP request object. ### Accessing Context > Source: /docs/node.js/cds-tx#accessing-context Access that information anywhere in your code through `cds.context` like that: ```js // Accessing current user const { user } = cds.context if (user.is('admin')) ... ``` ```js // Accessing HTTP req, res objects const { req, res } = cds.context.http if (!req.is('application/json')) res.send(415) ``` [Learn more about available `cds.context` properties](events#cds-context){.learn-more} ### Setting Contexts > Source: /docs/node.js/cds-tx#setting-contexts Setting `cds.context` usually happens in inbound authentication middlewares or in inbound protocol adapters. You can also set it in your code, for example, you might implement a simplistic custom authentication middleware like so: ```js app.use ((req, res, next) => { const { 'x-tenant':tenant, 'x-user-id':user } = req.headers cds.context = { tenant, user } // Setting cds.context next() }) ``` ### Continuation-local Variable > Source: /docs/node.js/cds-tx#continuation-local-variable `cds.context` is implemented as a so-called *continuation-local* variable. As JavaScript is single-threaded, we cannot capture request-level invocation contexts such (as current user, tenant, or locale) in what other languages like Java call thread-local variables. But luckily, starting with Node v12, means for so-called *"Continuation-Local Storage (CLS)"* were given to us. Basically, the equivalent of thread-local variables in the asynchronous continuations-based execution model of Node.js. ### Context Propagation > Source: /docs/node.js/cds-tx#context-propagation When creating new root transactions in calls to `cds.tx()`, all properties not specified in the `context` argument are inherited from `cds.context`, if set in the current continuation. In effect, this means the new transaction demarcates a new ACID boundary, while it inherits the event context properties unless overridden in the `context` argument to `cds.tx()`. The following applies: ```js cds.context = { tenant:'t1', user:'u1' } cds.context.user.id === 'u1' //> true let tx = cds.tx({ user:'u2' }) tx.context !== cds.context //> true tx.context.tenant === 't1' //> true tx.context.user.id === 'u2' //> true tx.context.user !== cds.context.user //> true cds.context.user.id === 'u1' //> true ``` ## cds/srv. tx() > Source: /docs/node.js/cds-tx#cdssrv-tx ```tsx function srv.tx ( ctx?, fn? : tx => {...} ) => Promise function srv.tx ( ctx? ) => tx var ctx : { tenant, user, locale } ``` Use this method to run the given function `fn` and all nested operations in a new *root* transaction. For example: ```js await srv.tx (async tx => { let exists = await tx.run ( SELECT(1).from(Books,201).forUpdate() ) if (exists) await tx.update (Books,201).with(data) else await tx.create (Books,{ ID:201,...data }) }) ``` ::: details Transaction objects `tx` The `tx` object created by `srv.tx()` and passed to the function `fn` is a derivate of the service instance, constructed like that: ```js tx = { __proto__:srv, context: { tenant, user, locale }, // defaults from cds.context model: cds.model, // could be a tenant-extended variant instead commit(){...}, rollback(){...}, } ``` ::: The new root transaction is also active for all nested operations run from fn, including other services, most important database services. In particular, the following would work as well as expected (this time using `cds.tx` as shortcut `cds.db.tx`): ```js await cds.tx (async () => { let exists = await SELECT(1).from(Books,201).forUpdate() if (exists) await UPDATE (Books,201).with(data) else await INSERT.into (Books,{ ID:201,...data }) }) ``` **Optional argument `ctx`** allows to override values for nested contexts, which are otherwise inherited from `cds.context`, for example: ```js await cds.tx ({ tenant:t0, user: privileged }, async ()=>{ // following + nested will now run with specified tenant and user... let exists = await SELECT(1).from(Books,201).forUpdate() ... }) ``` **If argument `fn` is omitted**, the constructed `tx` would be returned and can be used to manage the transaction in a fully manual fashion: ```js const tx = srv.tx() // [!code focus] try { // [!code focus] let exists = await tx.run ( SELECT(1).from(Books,201).forUpdate() ) if (exists) await tx.update (Books,201).with(data) else await tx.create (Books,{ ID:201,...data }) await tx.commit() // [!code focus] } catch(e) { await tx.rollback(e) // will rethrow e // [!code focus] } // [!code focus] ``` ::: warning Note though, that with this usage we've **not** started a new async context, and all nested calls to other services, like db, will **not** happen within the confines of the constructed `tx`. ::: ### srv.tx (context?, fn?) → tx\ > Source: /docs/node.js/cds-tx#srvtx----context-fn--txsrv Use `srv.tx()` to start new app-controlled transactions manually, most commonly for [database services](databases) as in this example: ```js let db = await cds.connect.to('db') let tx = db.tx() try { await tx.run (SELECT.from(Foo)) await tx.create (Foo, {...}) await tx.read (Foo) await tx.commit() } catch(e) { await tx.rollback(e) } ``` **Arguments:** * `context` – an optional context object → [see below](#srv-tx-ctx) * `fn` – an optional function to run → [see below](#srv-tx-fn) **Returns:** a transaction object, which is constructed as a derivate of `srv` like that: ```js tx = Object.create (srv, Object.getOwnPropertyDescriptors({ commit(){...}, rollback(){...}, })) ``` In effect, `tx` objects ... * are concrete context-specific — that is tenant-specific — incarnations of `srv`es * support all the [Service API](core-services) methods like `run`, `create` and `read` * support methods `tx.commit` and `tx.rollback` as documented below. **Important:** The caller of `srv.tx()` is responsible to `commit` or `rollback` the transaction, otherwise the transaction would never be finalized and respective physical driver connections never be released / returned to pools. ### srv.tx ({ tenant?, user?, ... }) → tx\ > Source: /docs/node.js/cds-tx#srvtx-----tenant-user----txsrv Optionally specify an object with [event context](events#cds-event-context) properties as the *first* argument to execute subsequent operations with different tenant or user context: ```js let tx = db.tx ({ tenant:'t1' user:'u2' }) ``` The argument is an object with these properties: * `user` — a unique user ID string or an [instance of `cds.User`](authentication#cds-user) * `tenant` — a unique string identifying the tenant * `locale` — a locale string in format `_` The implementation constructs a new instance of [cds.EventContext](events#cds-event-context) from the given properties, which is assigned to [tx.context](#tx-context) of the new transaction. [Learn more in section **Continuations & Contexts**.](#event-contexts){.learn-more} ### srv.tx ((tx)=>) → tx\ {#srv-tx-fn} > Source: /docs/node.js/cds-tx#srvtx----tx--txsrv--srv-tx-fn Optionally specify a function as the *last* argument to have `commit` and `rollback` called automatically. For example, the following snippets are equivalent: ```js await db.tx (async tx => { await tx.run (SELECT.from(Foo)) await tx.create (Foo, {...}) await tx.read (Foo) }) ``` ```js let tx = db.tx() try { await tx.run (SELECT.from(Foo)) await tx.create (Foo, {...}) await tx.read (Foo) await tx.commit() } catch(e) { await tx.rollback(e) } ``` In addition to creating a new tx for the current service, ### srv.tx (ctx) → tx\ > Source: /docs/node.js/cds-tx#srvtx----ctx--txsrv If the argument is an instance of [cds.EventContext](events#cds-event-context) the constructed transaction will use this context as it's `tx.context`. If the specified context was constructed for a transaction started with `cds.tx()`, the new transaction will be constructed as a nested transaction. If not, the new transaction will be constructed as a root transaction. ```js cds.context = { tenant:'t1', user:'u2' } const tx = cds.tx (cds.context) //> tx is a new root transaction ``` ```js const tx = cds.context = cds.tx ({ tenant:'t1', user:'u2' }) const tx1 = cds.tx (cds.context) //> tx1 is a new nested transaction to tx ``` ### _↳_ tx.context → [cds.EventContext](events#cds-event-context) > Source: /docs/node.js/cds-tx#-spantxspancontext-----cdseventcontexteventscds-event-context Each new transaction created by [cds.tx()](#srv-tx) will get a new instance of [cds.EventContext](events#cds-event-context) constructed and assigned to this property. If there is a `cds.context` set in the current continuation, the newly constructed context object will inherit properties from that. [Learn more in section **Continuations & Contexts**.](#event-contexts){.learn-more} ### _↳_ tx.commit (res?) ⇢ res > Source: /docs/node.js/cds-tx#-spantxspancommit----res--res In case of database services, this sends a `COMMIT` (or `ROLLBACK`) command to the database and releases the physical connection, that is returns it to the connection pool. In addition, the commit is propagated to all nested transactions. The methods are [bound](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) to the `tx` instance, and the passed-in argument is returned, or rethrown in case of `rollback`, which allows them to be used as follows: ```js let tx = cds.tx() tx.run(...) .then (tx.commit, tx.rollback) ``` ### _↳_ tx.rollback (err?) ⇢ err > Source: /docs/node.js/cds-tx#-spantxspanrollback----err--err In case of database services, this sends `ROLLBACK` command to the database and releases the physical connection. In addition, the rollback is propagated to all nested transactions, and if an `err` object is passed, it is rethrown. [See documentation for `commit` for common details.](#commit){.learn-more}
::: warning **Note:** `commit` and `rollback` both release the physical connection. This means subsequent attempts to send queries via this `tx` will fail. ::: ## cds.spawn() > Source: /docs/node.js/cds-tx#cdsspawn Runs the given function as detached continuation in a specified event context (not inheriting from the current one). Options `every` or `after` allow to run the function repeatedly or deferred. For example: ```js cds.spawn ({ tenant:'t0', every: 1000 /* ms */ }, async (tx) => { const mails = await SELECT.from('Outbox') await MailServer.send(mails) await DELETE.from('Outbox').where (`ID in ${mails.map(m => m.ID)}`) }) ``` ::: tip Even though the callback function is executed as a background job, all asynchronous operations inside the callback function must be awaited. Otherwise, transaction handling does not work properly. ::: **Arguments:** * `options` is the same as the `ctx` argument for `cds.tx()`, plus: * `every: ` number of milliseconds to use in `setInterval(fn,n)` * `after: ` number of milliseconds to use in `setTimeout(fn,n)` * if non of both is given, `setImmediate(fn)` is used to run the job * `fn` is a function representing the background task **Returns:** - An event emitter which allows to register handlers on `succeeded`, `failed`, and `done` events. ```js let job = cds.spawn(...) job.on('succeeded', ()=>console.log('succeeded')) ``` - In addition, property `job.timer` returns the response of `setTimeout` in case option `after` was used, or `setInterval` in case of option `every`. For example, this allows to stop a regular running job like that: ```js let job = cds.spawn({ every:111 }, ...) await sleep (11111) clearInterval (job.timer) // stops the background job loop ``` The implementation guarantees decoupled execution from request-handling threads/continuations, by... - constructing a new root transaction `tx` per run using `cds.tx()` - setting that as the background run's continuation's `cds.context` - invoking `fn`, passing `tx` as argument to it. Think of it as if each run happens in an own thread with own context, with automatic transaction management. By default, the nested context inherits all values except `timestamp` from `cds.context`, especially user and tenant. Use the argument `options` if you want to override values, for example to run the background thread with different user or tenant than the one you called `cds.spawn()` from. ## DEPRECATED APIs > Source: /docs/node.js/cds-tx#deprecated-apis #### srv.tx (req) → tx\ > Source: /docs/node.js/cds-tx#srvtx--req--txsrv Prior to release 5, you always had to write application code like that to ensure context propagation and correctly managed transactions: ```js this.on('READ','Books', req => { const tx = cds.tx(req) return tx.read ('Books') }) ``` This still works but is not required **nor recommended** anymore. # Authentication > Source: /docs/node.js/authentication This guide is about authenticating users on incoming HTTP requests. This is done by [authentication middlewares](#strategies) setting the [`cds.context.user` property](#cds-user) which is then used in [authorization enforcement](#enforcement) decisions. ## cds. User > Source: /docs/node.js/authentication#cds-user [user]: #cds-user [`cds.context.user`]: #cds-user Represents the currently logged-in user as filled into [`cds.context.user`](events#-user) by authentication middlewares. Simply create instances of `cds.User` or of subclasses thereof in custom middlewares. For example: ```js const cds = require('@sap/cds') const DummyUser = new class extends cds.User { is:()=>true } module.exports = (req,res,next) => { cds.context.user = new DummyUser('dummy') next() } ``` Or you can call the constructor of `cds.User` with specific arguments, to create a user instance. For example: ```js const cds = require('@sap/cds') // with user ID as string const user = new cds.User('userId') // a user instance const anotherUser = new cds.User(user) // a user instance like object const yetAnotherUser = new cds.User({id: user.id, roles: user.roles, attr: user.attr}) ``` ### .is (\) > Source: /docs/node.js/authentication#is--role Checks if user has assigned the given role. Example usage: ```js if (req.user.is('admin')) ... ``` The role names correspond to the values of [`@requires` and the `@restrict.grants.to` annotations](../guides/security/authorization) in your CDS models. ### . id > Source: /docs/node.js/authentication#-id A user's unique ID. It corresponds to `$user` in [`@restrict` annotations](../guides/security/authorization) of your CDS models (Also in JavaScript, `user` can act as a shortcut for `user.id` in comparisons.) {.indent} ### . attr > Source: /docs/node.js/authentication#-attr User-related attributes, for example, from JWT tokens These correspond to `$user.` in [`@restrict` annotations](../guides/security/authorization) of your CDS models {.indent} ### . authInfo? > Source: /docs/node.js/authentication#-authinfo Optional generic container for authentication-related information. For `@sap/xssec`-based authentication strategies (`ias`, `jwt`, and `xsuaa`), `cds.context.user.authInfo` is an instance of `@sap/xssec`'s [`SecurityContext`](https://www.npmjs.com/package/@sap/xssec#securitycontext). > **Note:** The availability of this API depends on the implementation of the respective authentication middleware. ::: warning The `cds.User.authInfo` property depends on the authentication library that you use. CAP does not guarantee the content of this property. Use it with caution. Always pin your dependencies as described in the [best practices](./best-practices#deploy). ::: ## cds.**User.Privileged** > Source: /docs/node.js/authentication#cdsuserprivileged In some cases, you might need to bypass authorization checks while [consuming a local service](./core-services). For this, you can create a transaction with a privileged user as follows: ```js this.before('*', function (req) { const user = new cds.User.Privileged return this.tx({ user }, tx => tx.run( INSERT.into('RequestLog').entries({ url: req._.req.url, user: req.user.id }) ) }) ``` Alternatively, you can also use the ready-to-use instance `cds.User.privileged` directly, that is, `const user = cds.User.privileged`. ## cds.**User.Anonymous** > Source: /docs/node.js/authentication#cdsuseranonymous Class `cds.User.Anonymous` allows you to instantiate an anonymous user (`const user = new cds.User.Anonymous`), for example in a [custom authentication](#custom) implementation. Alternatively, you can also use the ready-to-use instance `cds.User.anonymous` directly, that is, `const user = cds.User.anonymous`. ## cds.**User.default** > Source: /docs/node.js/authentication#cdsuserdefault If a request couldn't be authenticated, for example due to a missing authorization header, the framework will use `cds.User.default` as fallback. By default, `cds.User.default` points to `cds.User.Anonymous`. However, you can override this, for example to be `cds.User.Privileged` in tests, or to be any other class that returns an instance of `cds.User`. ## Authorization Enforcement > Source: /docs/node.js/authentication#authorization-enforcement Applications can use the `cds.context.user` APIs to do programmatic enforcement. For example, the authorization of the following CDS service: ```cds service CustomerService @(requires: 'authenticated-user'){ entity Orders @(restrict: [ { grant: ['READ','WRITE'], to: 'admin' }, ]){/*...*/} entity Approval @(restrict: [ { grant: 'WRITE', where: '$user.level > 2' } ]){/*...*/} } ``` can be programmatically enforced by means of the API as follows: ```js const cds = require('@sap/cds') cds.serve ('CustomerService') .with (function(){ this.before ('*', req => req.user.is('authenticated') || req.reject(403) ) this.before (['READ', 'CREATE'], 'Orders', req => req.user.is('admin') || req.reject(403) ) this.before ('*', 'Approval', req => req.user.attr.level > 2 || req.reject(403) ) }) ``` ## Authentication Strategies > Source: /docs/node.js/authentication#authentication-strategies CAP ships with a few prebuilt authentication strategies, used by default: [`mocked`](#mocked) during development and [`jwt`](#jwt) in production. You can override these defaults and configure the authentication strategy to be used through the `cds.requires.auth` [config option in `cds.env`](./cds-env), for example: ::: code-group ```json [package.json] "cds": { "requires": { "auth": "jwt" } } ``` ::: ::: tip Inspect effective configuration Run `cds env get requires.auth` in your project root to find out the effective config for your current environment. ::: ### Dummy Authentication > Source: /docs/node.js/authentication#dummy-authentication This strategy creates a user that passes all authorization checks. It's meant for temporarily disabling the `@requires` and `@restrict` annotations at development time. **Configuration:** Choose this strategy as follows: ::: code-group ```json [package.json] "cds": { "requires": { "auth": "dummy" } } ``` ::: ### Mocked Authentication > Source: /docs/node.js/authentication#mocked-authentication This authentication strategy uses basic authentication with pre-defined mock users during development. ::: warning Mocked authentication is not suitable for production! ::: > **Note:** When testing different users in the browser, it's best to use an incognito window, because logon information might otherwise be reused. **Configuration:** Choose this strategy as follows: ::: code-group ```json [package.json] "cds": { "requires": { "auth": "mocked" } } ``` ::: You can optionally configure users as follows: ::: code-group ```json [package.json] "cds": { "requires": { "auth": { "kind": "mocked", "users": { "": { "password": "", "roles": [ "", ... ], "attr": { ... } } } } } } ``` ::: #### Pre-defined Mock Users > Source: /docs/node.js/authentication#pre-defined-mock-users The default configuration shipped with `@sap/cds` specifies these users: ```jsonc "users": { "alice": { "tenant": "t1", "roles": [ "admin" ] }, "bob": { "tenant": "t1", "roles": [ "cds.ExtensionDeveloper" ] }, "carol": { "tenant": "t1", "roles": [ "admin", "cds.ExtensionDeveloper", "cds.UIFlexDeveloper" ] }, "dave": { "tenant": "t1", "roles": [ "admin" ], "features": [] }, "erin": { "tenant": "t2", "roles": [ "admin", "cds.ExtensionDeveloper", "cds.UIFlexDeveloper" ] }, "fred": { "tenant": "t2", "features": [ "isbn" ] }, "me": { "tenant": "t1", "features": [ "*" ] }, "yves": { "roles": [ "internal-user" ] } "*": true //> all other logins are allowed as well } ``` This default configuration is merged with your custom configuration such that, by default, logins by alice, bob, ... and others (`*`) are allowed. If you want to restrict these additional logins, you need to overwrite the defaults: ```jsonc "users": { "alice": { "roles": [] }, "bob": { "roles": [] }, "*": false //> do not allow other users than the ones specified } ``` ::: tip The pre-defined mock users can be deactivated by using kind `basic` instead of `mocked`. In that case configure users yourself, as described previously. ::: ### JWT-based Authentication > Source: /docs/node.js/authentication#jwt-based-authentication This is the default strategy used in production. User identity, as well as assigned roles and user attributes, are provided at runtime, by a bound instance of the ['User Account and Authentication'](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/419ae2ef1ddd49dca9eb65af2d67c6ec.html) service (UAA). This is done in form of a JWT token in the `Authorization` header of incoming HTTP requests. This authentication strategy also adds [`cds.context.user.authInfo`](#user-auth-info). **Prerequisites:** You need to add [@sap/xssec](https://help.sap.com/docs/HANA_CLOUD_DATABASE/b9902c314aef4afb8f7a29bf8c5b37b3/54513272339246049bf438a03a8095e4.html#loio54513272339246049bf438a03a8095e4__section_atx_2vt_vt) to your project: ```sh npm add @sap/xssec ``` **Configuration:** Choose this strategy as follows: ::: code-group ```json [package.json] "cds": { "requires": { "auth": "jwt" } } ``` ::: [Learn more about testing JWT-based authentication in **XSUAA in Hybrid Setup**.](#with-ias){.learn-more} ### XSUAA-based Authentication > Source: /docs/node.js/authentication#xsuaa-based-authentication Authentication kind `xsuaa` is a logical extension of kind [`jwt`](#jwt) that additionally offers access to SAML attributes through `cds.context.user.attr` (for example, `cds.context.user.attr.familyName`). **Prerequisites:** You need to add [@sap/xssec](https://help.sap.com/docs/HANA_CLOUD_DATABASE/b9902c314aef4afb8f7a29bf8c5b37b3/54513272339246049bf438a03a8095e4.html#loio54513272339246049bf438a03a8095e4__section_atx_2vt_vt) to your project: ```sh npm add @sap/xssec ``` **Configuration:** Choose this strategy as follows: ::: code-group ```json [package.json] "cds": { "requires": { "auth": "xsuaa" } } ``` ::: [See **XSUAA in Hybrid Setup** below for additional information of how to test this](#with-ias){.learn-more} ### IAS-based Authentication > Source: /docs/node.js/authentication#ias-based-authentication This is an additional authentication strategy using the [Identity Authentication Service](https://help.sap.com/docs/IDENTITY_AUTHENTICATION) (IAS) that can be used in production. User identity and user attributes are provided at runtime, by a bound instance of the IAS service. This is done in form of a JWT token in the `Authorization` header of incoming HTTP requests. This authentication strategy also adds [`cds.context.user.authInfo`](#user-auth-info). To allow forwarding to remote services, JWT tokens issued by IAS service don't contain authorization information. In particular, no scopes are included. Closing this gap is up to you as application developer. **Prerequisites:** You need to add [@sap/xssec](https://help.sap.com/docs/HANA_CLOUD_DATABASE/b9902c314aef4afb8f7a29bf8c5b37b3/54513272339246049bf438a03a8095e4.html#loio54513272339246049bf438a03a8095e4__section_atx_2vt_vt) to your project: ```sh npm add @sap/xssec ``` **Configuration:** Choose this strategy as follows: ::: code-group ```json [package.json] "cds": { "requires": { "auth": "ias" } } ``` ::: #### Token Validation > Source: /docs/node.js/authentication#token-validation For tokens issued by SAP Cloud Identity Service, `@sap/xssec` offers two additional validations: (1) token ownership via x5t thumbprint and (2) proof-of-possession. These validations are enabled by default for requests to the app's `cert` route (`.cert` segment in the domain). The default behavior can be overwritten using additional configuration as follows: ```json "requires": { "auth": { "kind": "ias", "config": { // passed to @sap/xssec as is "validation": { "x5t": { "enabled": false }, "proofToken": { "enabled": false } } } } } ``` Please see [`@sap/xssec` documentation](https://www.npmjs.com/package/@sap/xssec) for more details. #### XSUAA Fallback > Source: /docs/node.js/authentication#xsuaa-fallback To ease your migration from XSUAA-based to IAS-based authentication, the `ias` strategy automatically supports tokens issued by XSUAA when you provide the necessary credentials at `cds.env.requires.xsuaa.credentials`. For standard bindings, add `xsuaa` to the list of required services as follows: ```json "requires": { "auth": "ias", //> as above "xsuaa": true } ``` In case additional configuration is necessary, you can also provide an object: ```json "requires": { "xsuaa": { "config": { // passed to @sap/xssec as is [...] } } } ``` ### Custom Authentication > Source: /docs/node.js/authentication#custom-authentication You can configure an own implementation by specifying an own `impl` as follows: ```json "requires": { "auth": { "impl": "srv/custom-auth.js" // > relative path from project root } } ``` Essentially, custom authentication middlewares must do two things: First, they _must_ [fulfill the `cds.context.user` contract](#cds-user) by assigning an instance of `cds.User` or a look-alike to the continuation of the incoming request at `cds.context.user`. Second, if running in a multitenant environment, `cds.context.tenant` must be set to a string identifying the tenant that is addressed by the incoming request. ```js module.exports = function custom_auth (req, res, next) { // do your custom authentication cds.context.user = new cds.User({ id: '', roles: ['', ''], attr: { : '', : '' } }) cds.context.tenant = '' } ``` The TypeScript equivalent has to use the default export. ```ts import cds from "@sap/cds"; import {Request, Response, NextFunction} from "express"; type Req = Request & { user: cds.User, tenant: string }; export default function custom_auth(req: Req, res: Response, next: NextFunction) { // do your custom authentication ... } ``` [Learn more about customizing the user ID in this example.](cds-serve#customization-of-cdscontextuser){.learn-more} ## Authentication in Production > Source: /docs/node.js/authentication#authentication-in-production ### Enforced by Default > Source: /docs/node.js/authentication#enforced-by-default In a productive scenario with an authentication strategy configured, for example the default `jwt`, all CAP service endpoints are authenticated by default, regardless of the authorization model. That is, all services without `@restrict` or `@requires` implicitly get `@requires: 'authenticated-user'`. This can be disabled via feature flag cds.requires.auth.restrict_all_services: false, or by using [mocked authentication](#mocked) explicitly in production. ### Cached by Default > Source: /docs/node.js/authentication#cached-by-default `@sap/xssec^4.8` provides a way to improve latency on subsequent requests with the same token by introducing two caches for CPU-intensive operations: - **Signature cache**: This cache handles the cryptographic signature validation of a JWT token. - **Token decode cache**: This cache manages the base64-decoding of a JWT token. Both caches are enabled by default. The _signature cache_ can be configured or deactivated via cds.requires.auth.config (which is passed through to `@sap/xssec`). [Learn more about signature cache and its configuration.](https://www.npmjs.com/package/@sap/xssec#signature-cache){}.learn-more} The _token decode cache_, on the other hand, can only be configured programmatically during bootstrapping, for example in a [custom `server.js`](cds-server#custom-server-js) file, as follows: ```js require('@sap/xssec').Token.enableDecodeCache(config?) ``` and deactivated via ```js require('@sap/xssec').Token.decodeCache = false ``` [Learn more about caching CPU intensive operations in `@sap/xssec`](https://www.npmjs.com/package/@sap/xssec#caching-cpu-intensive-operations){.learn-more} ## Authentication in Hybrid Setup > Source: /docs/node.js/authentication#authentication-in-hybrid-setup ### with XSUAA > Source: /docs/node.js/authentication#with-xsuaa The following steps assume you've set up the [**Cloud Foundry Command Line Interface**](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/856119883b8c4c97b6a766cc6a09b48c.html). 1. Log in to Cloud Foundry: ```sh cf l -a ``` If you don't know the API endpoint, refer to [Regions and API Endpoints Available for the Cloud Foundry Environment](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/350356d1dc314d3199dca15bd2ab9b0e.html#loiof344a57233d34199b2123b9620d0bb41). 2. Go to the project you have created in [Getting started in a Nutshell](../get-started/bookshop). #### Configure the Application > Source: /docs/node.js/authentication#configure-the-application 1. Configure your app for XSUAA-based authentication if not done yet: ```sh cds add xsuaa --for hybrid ``` This command creates the XSUAA configuration file `xs-security.json` and adds the service and required dependencies to your `package.json` file. 2. Make sure `xsappname` is configured and `tenant-mode` is set to `dedicated` in `xs-security.json` file: ```json { "xsappname": "bookshop-hybrid", "tenant-mode": "dedicated", ... } ``` 3. Configure the redirect URI: Add the following OAuth configuration to the `xs-security.json` file: ```json "oauth2-configuration": { "redirect-uris": [ "http://localhost:5000/login/callback" ] } ``` 4. Create an XSUAA service instance with this configuration: ```sh cf create-service xsuaa application bookshop-uaa -c xs-security.json ``` > Later on, if you've changed the scopes, you can use `cf update-service bookshop-uaa -c xs-security.json` to update the configuration. ::: tip This step is necessary for locally running apps and for apps deployed on Cloud Foundry. ::: 1. Create a service key: ```sh cf create-service-key bookshop-uaa bookshop-uaa-key ``` This lets you gain access to the XSUAA credentials from your local application. 1. Bind to the new service key: ```sh cds bind -2 bookshop-uaa ``` This adds an `auth` section containing the binding and the kind `xsuaa` to the _.cdsrc-private.json_ file. This file is created if it doesn't exist and keeps the local and private settings of your app: ```json { "requires": { "[hybrid]": { "auth": { "kind": "xsuaa", "binding": { ... } } } } } ``` >If your running in BAS, you can alternatively [create a new run configuration](https://help.sap.com/products/SAP%20Business%20Application%20Studio/9c36fdb911ae4cadab467a314d9e331f/cdbc00244452483e9582a4f486b42d64.html), connecting the `auth` to your XSUAA service instance. >In that case you need to add the environment variable `cds_requires_auth_kind=xsuaa` to the run configuration. 1. Check authentication configuration: ```sh cds env list requires.auth --resolve-bindings --profile hybrid ``` This prints the full `auth` configuration including the credentials. #### Set Up the Roles for the Application > Source: /docs/node.js/authentication#set-up-the-roles-for-the-application By creating a service instance of the `xsuaa` service, all the roles from the _xs-security.json_ file are added to your subaccount. Next, you create a role collection that assigns these roles to your users. 1. Open the SAP BTP Cockpit. > For your trial account, this is: [https://cockpit.hanatrial.ondemand.com](https://cockpit.hanatrial.ondemand.com) 2. Navigate to your subaccount and then choose *Security* > *Role Collections*. 3. Choose *Create New Role Collection*: ![Create role collections in SAP BTP cockpit](./assets/create-role-collection.png) 4. Enter a *Name* for the role collection, for example `BookshopAdmin`, and choose *Create*. 5. Choose your new role collection to open it and switch to *Edit* mode. 6. Add the `admin` role for your bookshop application (application id `bookshop!a`) to the *Roles* list. 7. Add the email addresses for your users to the *Users* list. 8. Choose *Save* #### Running App Router > Source: /docs/node.js/authentication#running-app-router The App Router component implements the necessary authentication flow with XSUAA to let the user log in interactively. The resulting JWT token is sent to the application where it's used to enforce authorization and check the user's roles. 1. Add App Router to the `app` folder of your project: ```sh cds add approuter ``` 2. Install `npm` packages for App Router: ```sh npm install --prefix app/router ``` 3. In your project folder run: ::: code-group ```sh [macOS/Linux] cds bind --exec -- npm start --prefix app/router ``` ```cmd [Windows] cds bind --exec -- npm start --prefix app/router ``` ```powershell [Powershell] cds bind --exec '--' npm start --prefix app/router ``` ::: [Learn more about `cds bind --exec`.](../tools/cds-bind#cds-bind-exec){.learn-more} This starts an [App Router](https://help.sap.com/docs/HANA_CLOUD_DATABASE/b9902c314aef4afb8f7a29bf8c5b37b3/0117b71251314272bfe904a2600e89c0.html) instance on [http://localhost:5000](http://localhost:5000) with the credentials for the XSUAA service that you have bound using `cds bind`. > Usually the App Router is started using `npm start` in the `app` folder. But you need to provide the `VCAP_SERVICES` variable with the XSUAA credentials. With the `cds bind --exec` command you can launch an arbitrary command with the `VCAP_SERVICES` variable filled with your `cds bind` service bindings. Since it only serves static files or delegates to the backend service, you can keep the server running. It doesn't need to be restarted after you have changed files. 4. Make sure that your CAP application is running as well with the `hybrid` profile: ```sh cds watch --profile hybrid ``` > If you are using BAS Run Configurations, you need to configure `cds watch` with profile `hybrid`: > 1. Open the context menu for your run configuration. > 2. Choose *Show in File*. > 3. Change the command `args`: > ```json > "args": [ > "cds", > "watch", > "--profile", > "hybrid" > ], > ``` 5. After the App Router and CAP application are started, log in at [http://localhost:5000](http://localhost:5000) and verify that the routes are protected as expected. In our example, if you assigned the `admin` scope to your user in SAP BTP cockpit, you can now access the admin service at [http://localhost:5000/admin](http://localhost:5000/admin).
> To test UIs w/o a running UAA service, just add this to _app/router/xs-app.json_: ```"authenticationMethod": "none"``` **SAP Business Application Studio:** The login fails pointing to the correct OAuth configuration URL that is expected. 1. Replace the URL `http://localhost:5000/` in your `xs-security.json` file with the full URL from the error message: ```json "oauth2-configuration": { "redirect-uris": [ "" ] } ``` ::: warning This is a specific configuration for your dev space and should not be submitted or shared. ::: 2. Update the XSUAA service: ```sh cf update-service bookshop-uaa -c xs-security.json ``` 3. Retry ### with IAS > Source: /docs/node.js/authentication#with-ias #### Configure the Application > Source: /docs/node.js/authentication#configure-the-application-1 1. Add a deployment descriptor, if there is none in the root of your project: ```sh cds add mta ``` 2. Enable IAS authentication for your application by adding and installing the `ams` plugin: ```sh cds add ams npm install ``` This command installs `ams` and `ias` plugins, adds the required dependencies to `package.json` and updates `mta.yaml`. Learn more about [**Adding AMS Support**](../guides/security/cap-users#adding-ams-support) and [**Adding IAS**](../guides/security/authentication#adding-ias).{.learn-more} 3. Generate roles and policies with AMS: ```sh cds build --for ams ``` This compiles the CDS annotations into DCL files. [Learn more about Prepare CDS Model](../guides/security/cap-users#prepare-cds-model).{.learn-more} 4. Add App Router for fetching the IAS token: ```sh cds add approuter ``` ::: details This configures the local App Router callback URI for the `identity` service In _mta.yaml_, this entry should now be present: ```yaml - name: bookshop-ias [...] parameters: service: identity [...] config: display-name: bookshop oauth2-configuration: redirect-uris: - http://localhost:5000/login/callback?authType=ias # [!code ++] post-logout-redirect-uris: - ~{app-api/app-protocol}://~{app-api/app-uri}/*/logout.html ``` ::: 5. Install `npm` packages for App Router: ```sh npm install --prefix app/router ``` #### Deploy the Application > Source: /docs/node.js/authentication#deploy-the-application 1. Log in to Cloud Foundry: ```sh cf l -a ``` If you don't know the API endpoint, refer to [Regions and API Endpoints Available for the Cloud Foundry Environment](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/350356d1dc314d3199dca15bd2ab9b0e.html#loiof344a57233d34199b2123b9620d0bb41). 2. Pack and deploy the application: ```sh cds up ``` #### Assign Policies in the Administrative Console > Source: /docs/node.js/authentication#assign-policies-in-the-administrative-console 1. Log in to your Administrative Console for IAS and go to `Applications & Resources`. [Learn more about the Administrative Console for IAS.](/@external/guides/security/authentication#ias-admin){.learn-more} 2. Assign policies to IAS users or create custom policies, see [Cloud Deployment](../guides/security/cap-users#ams-deployment). #### Start Hybrid Testing > Source: /docs/node.js/authentication#start-hybrid-testing 1. Bind your local application to the Identity Service Instance: ```sh cds bind -2 bookshop-ias ``` ::: details This generates the _.cdsrc-private.json_ ```json .cdsrc-private.json { "requires": { "[hybrid]": { "auth": { "binding": { "type": "cf", "apiEndpoint": "https://...", "org": "cdx-nodejs", "space": "dev", "instance": "bookshop-ias", "key": "bookshop-ias-key" }, "kind": "ias-auth", "vcap": { "name": "auth" } } } } } ``` ::: 2. In your project folder run: ::: code-group ```sh [macOS/Linux] cds bind --exec -- npm start --prefix app/router ``` ```cmd [Windows] cds bind --exec -- npm start --prefix app/router ``` ```powershell [Powershell] cds bind --exec '--' npm start --prefix app/router ``` ::: [Learn more about `cds bind --exec`.](../tools/cds-bind#hybrid-testing){.learn-more} This starts an [App Router](https://help.sap.com/docs/HANA_CLOUD_DATABASE/b9902c314aef4afb8f7a29bf8c5b37b3/0117b71251314272bfe904a2600e89c0.html) instance on [http://localhost:5000](http://localhost:5000) with the credentials for the IAS service that you have bound using `cds bind`. Since it only serves static files or delegates to the backend service, you can keep the server running. It doesn't need to be restarted after you have changed files. 3. Make sure that your CAP application is running as well with the `hybrid` profile: ```sh cds watch --profile hybrid ``` 4. After the App Router and CAP application are started, log in at [http://localhost:5000](http://localhost:5000) and verify that the routes are protected as expected. # CDS Plugin Packages > Source: /docs/node.js/cds-plugins The `cds-plugin` technique allows to provide extension packages with auto-configuration. ## Add a `cds-plugin.js` > Source: /docs/node.js/cds-plugins#add-a-cds-pluginjs Simply add a file `cds-plugin.js` next to the `package.json` of your package to have this detected and loaded automatically when bootstrapping CAP Node.js servers through `cds serve`, or other CLI commands. Within such `cds-plugin.js` modules you can use [the `cds` facade](cds-facade) object, to register to lifecycle events or plugin to other parts of the framework. For example, they can react to lifecycle events, the very same way as in [custom `server.js`](cds-server#custom-server-js) modules: ::: code-group ```js [cds-plugin.js] const cds = require('@sap/cds') cds.on('served', ()=>{ ... }) ``` ::: Sometimes `cds-plugin.js` files can also be empty, for example if your plugin only registers new settings. ## Auto-Configuration > Source: /docs/node.js/cds-plugins#auto-configuration Plugins can also add new configuration settings, thereby providing auto configuration. Simply add a `cds` section to your *package.json* file, as you would do in a project's *package.json*. For example, this is the configuration provided by the new SQLite service package `@cap-js/sqlite`: ::: code-group ```json [package.json] { "cds": { "requires": { "db": "sql", "kinds": { "sql": { "[development]": { "kind": "sqlite" } }, "sqlite": { "impl": "@cap-js/sqlite" } }, } } } ``` ::: In effect this automatically configures a required `db` service using the `sql` preset. This preset is configured below to use the `sqlite` preset in development. The `sqlite` preset is in turn configured below, to use the plugin package's main as implementation. ## cds. plugins > Source: /docs/node.js/cds-plugins#cds-plugins This property refers to a module that implements the plugin machinery in cds, by fetching and loading installed plugins along these lines: 1. For all entries in your *package.json*'s `dependencies` and `devDependencies` ... 2. Select all target packages having a `cds-plugin.js` file in their roots ... 3. Add all target packages' `cds` entry in their *package.json* to [`cds.env`](cds-env) 4. Load all target packages' `cds-plugin.js` module The plugin mechanism is activated by adding this to CLI commands: ```js await cds.plugins ``` Currently, the following commands support plugins: `cds-serve`, `cds watch`, `cds run`, `cds env`, `cds deploy`, `cds build`, `cds.test()`. ## Configuration Schema > Source: /docs/node.js/cds-plugins#configuration-schema-beta- To help developers conveniently add configuration for a plugin with code completion, plugin developers can declare additions to the `cds` schema in their plugin. #### Declaration in Plugin > Source: /docs/node.js/cds-plugins#declaration-in-plugin All schema definitions must be below the `schema` node: ::: code-group ```jsonc [package.json] "cds": { "schema": { "buildTaskType": { "name": "new-buildTaskType", "description": "A text describing the new build task type." }, "databaseType": { "name": "new-databaseType", "description": "A text describing the new database type." }, "cds": { "swagger": { // example from cds-swagger-ui-express "description": "Swagger setup", "oneOf": [ ... ] } } } } ``` ::: Currently, the following schema contribution points are supported: | Contribution Point | Description | |--------------------|-------------------------------------------| | `buildTaskType` | Additional build task type | | `databaseType` | Additional database type | | `cds` | One or more additional top level settings | #### Usage In a CAP Project > Source: /docs/node.js/cds-plugins#usage-in-a-cap-project