From 7398d0c5ef525a80f59e3f7e5db032d31a0f3f80 Mon Sep 17 00:00:00 2001 From: Rene Jeglinsky Date: Mon, 25 Sep 2023 15:17:08 +0200 Subject: [PATCH 01/45] addition for on-prem dest --- guides/using-services.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guides/using-services.md b/guides/using-services.md index 5bf32f98d..5e6989b36 100644 --- a/guides/using-services.md +++ b/guides/using-services.md @@ -1454,12 +1454,14 @@ With the destination service, you can access destinations in your provider accou Customers want to see business partners from, for example, their SAP S/4 HANA system. -As provider, you need to define a name for a destination, which enables access to systems of the subscriber of your application. In addition, your multitenant application or service needs to have a dependency to the destination service. +As provider, you need to define a name for a destination, which enables access to systems of the subscriber of your application. In addition, your multitenant application or service needs to have a dependency to the destination service. For destinations in an on-premise system, the connectivity service must be bound. The subscriber needs to create a destination with that name in their subscriber account, for example, pointing to their SAP S/4HANA system. + + #### Destination Resolution The destination is read from the tenant of the request's JWT (authorization) token. If no JWT token is present, the destination is read from the tenant of the application's XSUAA binding.{.impl .java} From 0961554e5f1d6769108d76d012b9b4fb26897b0e Mon Sep 17 00:00:00 2001 From: Christian Georgi Date: Mon, 25 Sep 2023 14:17:43 +0200 Subject: [PATCH 02/45] log: use slightly darker green --- .vitepress/theme/custom.scss | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/.vitepress/theme/custom.scss b/.vitepress/theme/custom.scss index 08e6c39fa..00ff7e788 100644 --- a/.vitepress/theme/custom.scss +++ b/.vitepress/theme/custom.scss @@ -109,7 +109,7 @@ main { } } em { - color: #00ff00e0; + color: #00ab00e0; } b { color: #ee0000e0; @@ -119,7 +119,6 @@ main { } background: var(--vp-code-block-bg); - color: #fafafa; } } @@ -306,19 +305,6 @@ html.node .java { } -// enable for light code theme -// pre.log { -// background: #f8f8f8; // matches to light theme -// color: #032F62; // matches to light theme -// em { -// color: #db8b0b; -// } -// :root.dark & { -// background: var(--vp-code-block-bg); -// color: #fafafa; -// } -// } - /* expand width on big screens */ @media screen and (min-width: 1600px) { div[class^='language-']:hover, From 7c6a0128632d3328b6545ec8f66a835543fc6f40 Mon Sep 17 00:00:00 2001 From: Christian Georgi Date: Mon, 25 Sep 2023 16:01:43 +0200 Subject: [PATCH 03/45] Draw wide code box over outline --- .vitepress/theme/custom.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/.vitepress/theme/custom.scss b/.vitepress/theme/custom.scss index 00ff7e788..7483a559c 100644 --- a/.vitepress/theme/custom.scss +++ b/.vitepress/theme/custom.scss @@ -313,6 +313,7 @@ html.node .java { pre.log:focus { min-width: fit-content; padding-right: 40px; + z-index: 1; // draw over outline } table:hover, table:focus { From ed35670ac64040c756fb54dfa416d651fed94bd7 Mon Sep 17 00:00:00 2001 From: Rene Jeglinsky Date: Tue, 26 Sep 2023 08:21:58 +0200 Subject: [PATCH 04/45] added hint on toggle --- get-started/hello-world.md | 1 + get-started/in-a-nutshell.md | 2 ++ guides/databases-hana.md | 2 +- guides/databases-postgres.md | 2 ++ guides/databases-sqlite.md | 1 + guides/databases.md | 2 ++ guides/security/aspects.md | 6 ++++-- guides/using-services.md | 2 ++ 8 files changed, 15 insertions(+), 3 deletions(-) diff --git a/get-started/hello-world.md b/get-started/hello-world.md index ee5494ba4..251426b5b 100644 --- a/get-started/hello-world.md +++ b/get-started/hello-world.md @@ -22,6 +22,7 @@ Let's create a simple _Hello World_ OData service using the SAP Cloud Applicatio +>This guide is available for Node.js and Java. Press v to switch, or use the toggle. ## Create a Project diff --git a/get-started/in-a-nutshell.md b/get-started/in-a-nutshell.md index 379c6a1df..7187db857 100644 --- a/get-started/in-a-nutshell.md +++ b/get-started/in-a-nutshell.md @@ -16,6 +16,8 @@ Using a minimal setup This guide is a step-by-step walkthrough to build a CAP application, using a minimal setup with Node.js and SQLite. +>This guide is available for Node.js and Java. Press v to switch, or use the toggle. + [[toc]] diff --git a/guides/databases-hana.md b/guides/databases-hana.md index 703bb4398..3983ea496 100644 --- a/guides/databases-hana.md +++ b/guides/databases-hana.md @@ -5,7 +5,7 @@ impl-variants: true # Using SAP HANA Cloud for Production - +>This guide is available for Node.js and Java. Press v to switch, or use the toggle. [[toc]] diff --git a/guides/databases-postgres.md b/guides/databases-postgres.md index f2a650593..8b0cffa46 100644 --- a/guides/databases-postgres.md +++ b/guides/databases-postgres.md @@ -21,6 +21,8 @@ CAP Java SDK is tested on [PostgreSQL](https://www.postgresql.org/) 15. Most CAP +>This guide is available for Node.js and Java. Press v to switch, or use the toggle. + [[toc]] diff --git a/guides/databases-sqlite.md b/guides/databases-sqlite.md index f91de54a5..65ebbcf7b 100644 --- a/guides/databases-sqlite.md +++ b/guides/databases-sqlite.md @@ -22,6 +22,7 @@ This guide focuses on the new SQLite Service provided through *[@cap-js/sqlite]( +>This guide is available for Node.js and Java. Press v to switch, or use the toggle. [[toc]] diff --git a/guides/databases.md b/guides/databases.md index 768c3a5d7..d660a319d 100644 --- a/guides/databases.md +++ b/guides/databases.md @@ -13,6 +13,8 @@ impl-variants: true {{ $frontmatter.synopsis }} +>This guide is available for Node.js and Java. Press v to switch, or use the toggle. + [[toc]] diff --git a/guides/security/aspects.md b/guides/security/aspects.md index accbe79b8..f53d1cd6a 100644 --- a/guides/security/aspects.md +++ b/guides/security/aspects.md @@ -11,6 +11,8 @@ impl-variants: true {{ $frontmatter.synopsis }} +>This guide is available for Node.js and Java. Press v to switch, or use the toggle. + ## Secure Communications { #secure-communications } @@ -362,7 +364,7 @@ Attackers can send malicious input data in a regular request to make the server Be aware that injections are still possible even via CQL when the query structure (e.g. target entity, columns etc.) is based on user input:
- + ```java String entity = ; String column = ; @@ -373,7 +375,7 @@ Select.from(entity).columns(b -> b.get(column));
- + ```js const entity = const column = diff --git a/guides/using-services.md b/guides/using-services.md index 5e6989b36..0b8eee073 100644 --- a/guides/using-services.md +++ b/guides/using-services.md @@ -22,6 +22,8 @@ impl-variants: true # Consuming Services +>This guide is available for Node.js and Java. Press v to switch, or use the toggle. + [[toc]] ## Introduction From c65bd3af53576f2fd12666eb171707524f7091cb Mon Sep 17 00:00:00 2001 From: Daniel Hutzel Date: Tue, 26 Sep 2023 10:41:23 +0200 Subject: [PATCH 05/45] . --- .vitepress/theme/custom.scss | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.vitepress/theme/custom.scss b/.vitepress/theme/custom.scss index 7483a559c..355882d7f 100644 --- a/.vitepress/theme/custom.scss +++ b/.vitepress/theme/custom.scss @@ -224,17 +224,20 @@ main { aside.VPSidebar { .group { padding-top: 5px !important; - .VPSidebarItem.level-0 { - padding-bottom: 11px; - &.collapsed { - padding-bottom: 1px; + .VPSidebarItem { + &.level-0 { + padding-bottom: 11px; + &.collapsed { + padding-bottom: 1px; + } } - .VPSidebarItem.level-1 { + &.level-1 { padding-left: 15px; h2, h3, p { padding: 2px 0; } } + .caret { height: inherit; } } } } From 71a4713ada2e96486ab5a99d9972bada8ae19771 Mon Sep 17 00:00:00 2001 From: sjvans <30337871+sjvans@users.noreply.github.com> Date: Tue, 26 Sep 2023 11:52:32 +0200 Subject: [PATCH 06/45] postgres credentials formatting (#442) --- guides/databases-postgres.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guides/databases-postgres.md b/guides/databases-postgres.md index 8b0cffa46..a0f7f7e70 100644 --- a/guides/databases-postgres.md +++ b/guides/databases-postgres.md @@ -173,7 +173,8 @@ If you don't use the default credentials and want to use just `cds deploy`, you "db": { "kind": "postgres", "credentials": { - "host": "localhost", "port": 5432, + "host": "localhost", + "port": 5432, "user": "postgres", "password": "postgres", "database": "postgres" From ced7be1f16078ffe87f5932ccb5f47800ef53bd5 Mon Sep 17 00:00:00 2001 From: Johannes Vogel <31311694+johannes-vogel@users.noreply.github.com> Date: Tue, 26 Sep 2023 12:28:13 +0200 Subject: [PATCH 07/45] cds.test: overhaul and warning for static file imports (#429) --- node.js/cds-test.md | 626 +++++++++++++++++++++++++++++--------------- 1 file changed, 414 insertions(+), 212 deletions(-) diff --git a/node.js/cds-test.md b/node.js/cds-test.md index fb102143a..0aed59104 100644 --- a/node.js/cds-test.md +++ b/node.js/cds-test.md @@ -1,104 +1,60 @@ --- -shorty: cds.test -# synopsis: How test CAP Node.js applications. status: released --- -# Testing +# Testing with `cds.test` -Learn more about best practices to test CAP Node.js applications using the `cds.test` toolkit. -Find samples for such tests in [cap/samples](https://github.com/sap-samples/cloud-cap-samples/tree/master/test) and in [CAP SFLIGHT app](https://github.com/SAP-samples/cap-sflight/tree/main/test). -For more details how to test Java applications, see the [Java documentation](../java/development/#testing-cap-java-applications). +[[toc]] -## Preliminaries +## Overview -Add optional dependencies required by `cds.test`: +The `cds.test` library provides best practice utils for writing tests for CAP Node.js applications. -```sh -npm add -D axios chai chai-as-promised chai-subset jest -``` - -::: tip -If you have cloned [cap/samples]( https://github.com/sap-samples/cloud-cap-samples), you get that for free. +::: tip Find examples in [*cap/samples*](https://github.com/sap-samples/cloud-cap-samples/tree/main/test) and in the [*SFlight sample*](https://github.com/SAP-samples/cap-sflight/tree/main/test). ::: -## Running a CAP Server -Use function `cds.test()` to easily launch and test a CAP server as follows: +### Running a CAP Server -```js{3} -const project = __dirname+'/..' // The project's root folder -const cds = require('@sap/cds') -cds.test(project) -``` -[Learn more about tests in the SFLIGHT app.](https://github.com/SAP-samples/cap-sflight/blob/main/test/odata.test.js){.learn-more} - -##### Behind the Scenes `cds.test()` ... +Use function [`cds.test()`](#cds-test) to easily launch and test a CAP server. For example, given your CAP application has a `./test` subfolder containing tests as follows: -- Ensures the server is launched before tests (→ in `before()`/`beforeAll()` hooks) -- With the equivalent of `cds serve --project <...> --in-memory?` cli command -- With a controlled shutdown when all tests have finished - -##### Running in a Specific Folder +```zsh +project/ # your project's root folder +├─ srv/ +├─ db/ +├─ test/ # your .test.js files go in here +└─ package.json +``` -By default, the `cds` APIs read files from the current working directory. To run test simulating whole projects, use `cds.test.in(<...>)` to specify the test project's root folder. +Start your app's server in your `.test.js` files like that: -```js{2} +```js{3} const cds = require('@sap/cds') -cds.test.in(__dirname) +describe(()=>{ + const test = cds.test(__dirname+'/..') +}) ``` +This launches a cds server from the specified target folder in a `beforeAll()` hook, with controlled shutdown when all tests have finished in an `afterAll()` hook. -For example, this would have `cds.env` loading the configuration from _package.json_ and _.cdsrc.json_ files found next to the test file, that is, in the same folder. - -::: danger -**Important:** Don't use `process.chdir()` in Jest tests, as they may leave test containers in screwed state, leading to failing subsequent tests. +::: warning Don't use `process.chdir()`! +Doing so in Jest tests may leave test containers in screwed state, leading to failing subsequent tests. Use [`cds.test.in()`](#test-in-folder) instead. ::: -::: tip -Prefer using relative filenames derived from `__dirname` as arguments to `cds.test` to allow your tests be started from whatever working directory. +::: danger Don't load [`cds.env`](cds-env) before [`cds.test()`](#cds-test)! +To ensure `cds.env`, and hence all plugins, are loaded from the test's target folder, the call to `cds.test()` should be the very first thing you do in your tests. Any references to [`cds`](cds-facade) sub modules or any imports of which have to go after. → [Learn more below.](#cds-test-env-check) ::: -##### Silenced Server Log -To reduce noise, `cds.test()` by default suppresses the usual bootstrap output of `cds serve`. You can skip this silent mode programmatically like that: -```js -cds.test(project).verbose() -``` +### Testing Service APIs -Or by setting process env variable `CDS_TEST_VERBOSE`, for example like that from the command line: - -::: code-group - -```sh [Mac/Linux] -CDS_TEST_VERBOSE=y mocha -``` - -```cmd [Windows] -set CDS_TEST_VERBOSE=y -mocha -``` - -```powershell [Windows Powershell] -$Env:CDS_TEST_VERBOSE=y -mocha -``` - -::: - -To get a completely clutter-free log, check out the test runners for such a feature, like [jest --silent](https://jestjs.io/docs/cli#--silent). - - -## Testing Service APIs - -As `cds.test()` launches the server in the current process, you can access all services programmatically using the respective [Node.js APIs](core-services). -Here is an example for that taken from [cap/samples](https://github.com/SAP-samples/cloud-cap-samples/blob/a8345122ea5e32f4316fe8faef9448b53bd097d4/test/consuming-services.test.js#L2): +As `cds.test()` launches the server in the current process, you can access all services programmatically using the respective [Node.js Service APIs](core-services). Here is an example for that taken from [cap/samples](https://github.com/SAP-samples/cloud-cap-samples/blob/a8345122ea5e32f4316fe8faef9448b53bd097d4/test/consuming-services.test.js#L2): ```js it('Allows testing programmatic APIs', async () => { @@ -111,68 +67,48 @@ it('Allows testing programmatic APIs', async () => { ``` -## Testing HTTP APIs +### Testing HTTP APIs -To test HTTP APIs we can use the test object returned by `cds.test()`, which uses `axios` and mirrors the axios API methods like `.get()`, `.put()`, `.post()` etc. +To test HTTP APIs we can use bound functions like so: -```js{2-3} -const test = cds.test('@capire/bookshop') -const {data} = await test.get('/browse/Books', { // [!code focus] - params: { $search: 'Po', $select: `title,author` -}}) +```js +const { GET, POST } = cds.test(...) +const { data } = await GET ('/browse/Books') +await POST (`/browse/submitOrder`, { book: 201, quantity: 5 }) ``` -[Learn more about the axios APIs.](https://axios-http.com){.learn-more} +[Learn more below.](#http-bound) {.learn-more} -In addition we provide uppercase bound function variants like `GET` or `POST`, which allow this usage variant: -```js{3,6} -const { GET, POST } = cds.test('@capire/bookshop') -const input = 'Wuthering Heights' // simulating user input -const order = await POST (`/browse/submitOrder`, { // [!code focus] - book: 201, quantity: 5 -}) -const { data } = await GET ('/browse/Books', { // [!code focus] - params: { $search: 'Po', $select: `title,author` -}}) -``` +### Using Jest or Mocha -## Using Mocha or Jest - -Mocha and Jest are the most used test runners at the moment, with each having its fan base. -The `cds.test` library is designed to write tests that run with both, as shown in the following sample code: + [*Mocha*](https://mochajs.org) and [*Jest*](https://jestjs.io) are the most used test runners at the moment, with each having its fan base. +The `cds.test` library is designed to write tests that run with both, as in this sample: ```js -const { GET, expect } = cds.test('@capire/bookshop') describe('my test suite', ()=>{ - beforeAll(()=>{ }) // Jest style - before(()=>{ }) // Mocha style - test ('something', ()=>{}) // Jest style - it ('should test', ()=>{ // Jest & Mocha style - const { data } = await GET ('/browse/Books', { - params: { $search: 'Po', $select: `title,author` } - }) - expect(data.value).to.eql([ // Chai tests, working in Jest and Mocha + const { GET, expect } = cds.test(...) + it ('should test', ()=>{ // Jest & Mocha + const { data } = await GET ('/browse/Books') + expect(data.value).to.eql([ // chai style expect { ID: 201, title: 'Wuthering Heights', author: 'Emily Brontë' }, - { ID: 207, title: 'Jane Eyre', author: 'Charlotte Brontë' }, - { ID: 251, title: 'The Raven', author: 'Edgar Allen Poe' }, { ID: 252, title: 'Eleonora', author: 'Edgar Allen Poe' }, + //... ]) }) }) ``` -To be portable, you need to use a specific implementation of `expect`, like the one from `chai` provided through `cds.test()`, as shown in the previous sample. -You can use Mocha-style `before/after` or Jest-style `beforeAll/afterAll` in your tests, as well as the common `describe, test, it` methods. -::: tip -[All tests in cap/samples](https://github.com/sap-samples/cloud-cap-samples/blob/master/test) are written in that portable way.
+You can use Mocha-style `before/after` or Jest-style `beforeAll/afterAll` in your tests, as well as the common `describe, test, it` methods. In addition, to be portable, you should use the [Chai Assertion Library's](#chai) variant of `expect`. + +::: tip [All tests in cap/samples](https://github.com/sap-samples/cloud-cap-samples/blob/master/test) are written in that portable way.
Run them with `npm run jest` or with `npm run mocha`. ::: -##### Using Watchers {.h3} +### Using Test Watchers You can also start the tests in watch mode, for example: @@ -203,185 +139,451 @@ Similarly, you can use other test watchers like `mocha -w`. -## Using `cds.test` in REPL -You can use `cds.test` in REPL, for example, by running this from your command line: + +## Class `cds.test.Test` + +Instances of this class are returned by [`cds.test()`](#cds-test), for example: + +```js +const test = cds.test(_dirname) +``` + +You can also use this class and create instances yourself, for example, like that: + +```js +const { Test } = cds.test +let test = new Test +test.run().in(_dirname) +``` + + + +### cds.test() {.method} + +This method is the most convenient way to start a test server. It is actually just a convenient shortcut to construct a new instance of class `Test` and call [`test.run()`](#test-run), defined as follows: + +```js +const { Test } = cds.test +cds.test = (...args) => (new Test).run(...args) +``` + + + + + +### .chai, ... {.property} + +To write tests that run in [*Mocha*](https://mochajs.org) as well as in [*Jest*](https://jestjs.io) you should use the [*Chai Assertion Library*](https://www.chaijs.com/) through the following convenient methods ... + +:::warning Using `chai` requires these dependencies added to your project: ```sh -[samples](https://github.com/sap-samples/cloud-cap-samples) cds repl -Welcome to cds repl v5.5.0 +npm add -D chai chai-as-promised chai-subset jest +``` + +::: + + + +#### .expect { .property} + +Shortcut to the [`chai.expect()`](https://www.chaijs.com/guide/styles/#expect) function, used like that: + +```js +const { expect } = cds.test(), foobar = {foo:'bar'} +it('should support chai.except style', ()=>{ + expect(foobar).to.have.property('foo') + expect(foobar.foo).to.equal('bar') +}) ``` + +If you prefer Jest's `expect()` functions you can just use the respective global: + ```js -> cds.test('@capire/bookshop') -Test {} +cds.test() +it('should use jest.expect', ()=>{ + expect({foo:'bar'}).toHaveProperty('foo') +}) ``` -```log -[cds] - model loaded from 6 file(s): - ./bookshop/db/schema.cds - ./bookshop/srv/admin-service.cds - ./bookshop/srv/cat-service.cds - ./bookshop/app/services.cds - ./../../cds/common.cds - ./common/index.cds -[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 - > filling sap.common.Currencies from ./common/data/sap.common-Currencies.csv - > filling sap.common.Currencies.texts from ./common/data/sap.common-Currencies_texts.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' } +#### .assert { .property} -[cds] - server listening on { url: 'http://localhost:64914' } -[cds] - launched at 9/8/2021, 5:36:20 PM, in: 767.042ms -[ terminate with ^C ] +Shortcut to the [`chai.assert()`](https://www.chaijs.com/guide/styles/#assert) function, used like that: + +```js +const { assert } = cds.test(), foobar = {foo:'bar'} +it('should use chai.assert style', ()=>{ + assert.property(foobar,'foo') + assert.equal(foobar.foo,'bar') +}) ``` + + + +#### .should { .property} + +Shortcut to the [`chai.should()`](https://www.chaijs.com/guide/styles/#should) function, used like that: + ```js -> await SELECT `title` .from `Books` .where `exists author[name like '%Poe%']` -[ { title: 'The Raven' }, { title: 'Eleonora' } ] +const { should } = cds.test(), foobar = {foo:'bar'} +it('should support chai.should style', ()=>{ + foobar.should.have.property('foo') + foobar.foo.should.equal('bar') + should.equal(foobar.foo,'bar') +}) ``` + + + +#### .chai {.property} + +This getter provides access to the [*chai*](https://www.chaijs.com) library, preconfigured with the [chai-subset](https://www.chaijs.com/plugins/chai-subset/) and [chai-as-promised](https://www.chaijs.com/plugins/chai-as-promised/) plugins. These plugins contribute the `containSubset` and `eventually` APIs, respectively. The getter is essentially implemented like this: + ```js -> var { CatalogService } = cds.services -> await CatalogService.read `title, author` .from `ListOfBooks` -[ - { title: 'Wuthering Heights', author: 'Emily Brontë' }, - { title: 'Jane Eyre', author: 'Charlotte Brontë' }, - { title: 'The Raven', author: 'Edgar Allen Poe' }, - { title: 'Eleonora', author: 'Edgar Allen Poe' }, - { title: 'Catweazle', author: 'Richard Carpenter' } -] +get chai() { + return require('chai') + .use (require('chai-subset')) + .use (require('chai-as-promised')) +} +``` + + + +### .axios {.property} + +Provides access to the [Axios](https://github.com/axios/axios) instance used as HTTP client. +It comes preconfigured with the base URL of the running server, that is, `http://localhost:`. This way, you only need to specify host-relative URLs in tests, like `/catalog/Books`. {.indent} + +:::warning Using `axios` requires adding this dependency: + +```sh +npm add -D axios ``` -## Providing Test Data +::: + -Data can be supplied: -- Programmatically as part of the test code -- In CSV files from `db/data` folders - +### GET / PUT / POST ... {#http-bound .method} -This following example shows how data can be inserted into the database using regular [CDS service APIs](core-services#srv-run-query) (using [CQL INSERT](cds-ql#insert) under the hood): +These are bound variants of the [`test.get/put/post/...` methods](#http-methods) allowing to write HTTP requests like that: ```js -beforeAll(async () => { - const db = await cds.connect.to('db') // [!code focus] - const {Books} = db.model.entities('my.bookshop') // [!code focus] - await db.create(Books).entries([ // [!code focus] - {ID:401, title: 'Book 1'}, // [!code focus] - {ID:402, title: 'Book 2'} // [!code focus] - ]) - // verify new data through API - const { data } = await GET `/catalog/Books` - expect(data.value).to.containSubset([{ID: 401}, {ID: 402}]) +const { GET, POST } = cds.test() +const { data } = await GET('/browse/Books') +await POST('/browse/submitOrder', + { book:201, quantity:1 }, + { auth: { username: 'alice' }} +) +``` + +[Learn more about Axios.](https://axios-http.com) {.learn-more} + +In case of single url arguments the functions can be used in tagged template string style, which essentially allows omitting the parentheses from function calls: + +```js +let { data } = await GET('/browse/Books') +let { data } = await GET `/browse/Books` +``` + + + + + +### test. get/put/post/...() {#http-methods .method} + +These are mirrored version of the corresponding [methods from `axios`](https://github.com/axios/axios#instance-methods), which prefix each request with the started server's url and port, which simplifies your test code: + +```js +const test = cds.test() //> served at localhost with an arbitrary port +const { data } = await test.get('/browse/Books') +await test.post('/browse/submitOrder', + { book:201, quantity:1 }, + { auth: { username: 'alice' }} +) +``` + +[Learn more about Axios.](https://axios-http.com) {.learn-more} + + + +### test .data .reset() {.method} + +This is a bound method, which can be used in a `beforeEach` handler to automatically reset and re-deploy the database for each test like so: + +```js +const { test } = cds.test() +beforeEach (test.data.reset) +``` + +Instead of using the bound variant, you can also call this method the standard way: + +```js +beforeEach (async()=>{ + await test.data.reset() // [!code focus] + //... }) ``` -This example also demonstrates the difference of accessing the database or the service layer: inserting data through the latter would fail because `CatalogService.Books` is read-only. In contrast, accessing the database as part of such test fixture code is fine. Just keep in mind that the data is not validated through your custom handler code, and that the database layer, that is, the table layout, is no API for users. -##### Data Reset {.h3} -Using the [cds.test.data](#data) API, you can have all data deleted and redeployed before each test: +### test .log() {.method} + +Allows to capture console output in the current test scope. The method returns an object to control the captured logs: + +```tsx +function cds.test.log() => { + output : string + clear() + release() +} +``` + +Usage examples: ```js -const { GET, expect, data } = cds.test ('@capire/bookshop') -data.autoReset(true) // delete + redeploy from CSV before each test +describe('cds.test.log()', ()=>{ + let log = cds.test.log() + + it ('should capture log output', ()=>{ + expect (log.output.length).to.equal(0) + console.log('foo',{bar:2}) + expect (log.output.length).to.be.greaterThan(0) + expect (log.output).to.contain('foo') + }) + + it('should support log.clear()', ()=> { + log.clear() + expect (log.output).to.equal('') + }) + + it('should support log.release()', ()=> { + log.release() // releases captured log + console.log('foobar') // not captured + expect (log.output).to.equal('') + }) +}) ``` -or reset it whenever needed: +The implementation redirects any console operations in a `beforeAll()` hook, clears `log.output` before each test, and releases the captured console in an `afterAll()` hook. + + + +### test. run (...) {.method} + +This is the method behind [`cds.test()`](#cds-test) to start a cds server, that is the following are equivalent: + +```js +cds.test(...) +``` + +```js +(new cds.test.Test).run(...) +``` + +It asynchroneously launches a cds server in a `beforeAll()` hook with an arbitrary port, with controlled shutdown when all tests have finished in an `afterAll()` hook. + +The arguments are as supported by the `cds serve` CLI command. + +Specify the command `'serve'` as the first argument to serve specific cds files or services: ```js -await data.reset() +cds.test('serve','srv/cat-service.cds') +cds.test('serve','CatalogService') ``` -or only delete it: +You can optionally add [`test.in(folder)`](#test-in-folder) in fluent style to run the test in a specific folder: ```js -await data.delete() +cds.test('serve','srv/cat-service.cds').in('/cap/samples/bookshop') ``` -## `cds.test` Reference { #cds-test} +If the first argument is **not** `'serve'` it is interpreted as a target folder: -### `cds.test (projectDir)` → `Test` { #run} +```js +cds.test('/cap/samples/bookshop') +``` -Launches a CDS server with an arbitrary port and returns a subclass which also acts as an [Axios]( https://github.com/axios/axios) lookalike, providing methods to send requests. -Launch a server in the given project folder, using a default command of `cds serve --in-memory?`. +Essentially this variant is a convenient shortcut for: + +```js +cds.test('serve','all','--in-memory?').in('/cap/samples/bookshop') +cds.test().in('/cap/samples/bookshop') //> equivalent +``` -The server is shut down after all tests have been executed. -### `cds.test (command, ...args)` → `Test` { #run-2} -Launch a server with the given command and arguments.
-Example: `cds.test ('serve', '--in-memory', '--project', )` +### test. in (folder, ...) {.method} -### class `Test` +Safely switches [`cds.root`](cds-facade#cds-root) to the specified target folder. Most frequently you'd use it in combination with starting a server with [`cds.test()`](#cds-test) in fluent style like that: -Instances of this class are returned by `cds.test()`. See below for its functions and properties. +```js +let test = cds.test(...).in(__dirname) +``` +It can also be used as static method to only change `cds.root` without starting a server: -#### `.GET/PATCH/POST/PUT/DELETE (url, ...)` ⇢ [`response`](https://github.com/axios/axios#response-schema) { #get} +```js +cds.test.in(__dirname) +``` -Aliases for corresponding [`get/patch/...` methods from Axios](https://github.com/axios/axios#instance-methods). For calls w/o additional parameters, a simplified call style is available where the `()` can be omitted. For example,
GET `/foo`
and GET(`/foo`) are equivalent. {.indent} -#### `.expect` → [`expect`](https://www.chaijs.com/api/bdd/) { #expect} -Provides the [expect](https://www.chaijs.com/api/bdd/) function from the [chai](#chai) assertion library. {.indent} +### `CDS_TEST_ENV_CHECK` -#### `.chai` → [`chai`](https://www.chaijs.com/api/) { #chai} +It is important to ensure [`cds.env`](cds-env), and hence all plugins, are loaded from the test's target folder. To ensure this, any references to [`cds`](cds-facade) sub modules or any imports of which have to go after. For example if you had a test like that: -Provides the [chai](https://www.chaijs.com/) assertion library. -It is preconfigured with the [chai-subset](https://www.chaijs.com/plugins/chai-subset/) and [chai-as-promised](https://www.chaijs.com/plugins/chai-as-promised/) plugins. These plugins contribute the `containSubset` and `eventually` APIs, respectively. {.indent} +```js +cds.env.fiori.lean_draft = true //> cds.env loaded from ./ // [!code --] +cds.test(__dirname) //> target folder: __dirname +``` -#### `.axios` → [`axios`](https://github.com/axios/axios#axios-api) { #axios} +This would result in the test server started from `__dirname`, but erroneously using `cds.env` loaded from `./`. -Provides the [Axios](https://github.com/axios/axios) instance that is used as HTTP client. -It comes preconfigured with the base URL of the running server, that is, `http://localhost:...`. This way, you only need to specify host-relative URLs in tests, like `/catalog/Books`. {.indent} +As these mistakes end up in hard-to-resolve follow up errors, [`test.in()`](#test-in-folder) can detect this if environment variable `CDS_TEST_ENV_CHECK` is set. The above code will then result into an error like that: -#### `.data` → `{ }` { #data} +```sh +CDS_TEST_ENV_CHECK=y jest cds.test.test.js +``` +```zsh +Detected cds.env loaded before running cds.test in different folder: +1. cds.env loaded from: ./ +2. cds.test running in: cds/tests/bookshop + + at Test.in (node_modules/@sap/cds/lib/utils/cds-test.js:65:17) + at test/cds.test.test.js:9:41 + at Object.describe (test/cds.test.test.js:5:1) + + 5 | describe('cds.test', ()=>{ +> 6 | cds.env.fiori.lean_draft = true + | ^ + 7 | cds.test(__dirname) + + at env (test/cds.test.test.js:7:7) + at Object.describe (test/cds.test.test.js:5:1) +``` -Provides utilities to manage test data: {.indent} -- `.autoReset (boolean)` enables automatic deletion and redeployment of CSV data before each test. Default is `false`. -- `.delete ()` deletes data in all database tables -- `.reset ()` deletes data in all database tables and deploys CSV data again {.indent} +A similar error would occur if one of the `cds` sub module would be accessed, which frequently load `cds.env` in their global scope, like `cds.Service` in the snippet below: -#### `.in (...paths)` → `Test` { #in} +```js +class MyService extends cds.Service {} //> cds.env loaded from ./ // [!code --] +cds.test(__dirname) //> target folder: __dirname +``` -Sets the given path segments as project root.
-Example: `cds.test.in(__dirname, '..').run('serve', '--in-memory')` {.indent} +To fix this, always ensure your calls to `cds.test.in(folder)` or `cds.test(folder)` goes first, before anything else loading `cds.env`: +```js +cds.test(__dirname) //> always should go first +// anything else goes after that: +cds.env.fiori.lean_draft = true // [!code ++] +class MyService extends cds.Service {} // [!code ++] +``` -#### `.verbose (boolean)` → `Test` { #verbose} +:::warning Do switch on `CDS_TEST_ENV_CHECK` ! -Sets verbose mode, so that, for example, server logs are shown. {.indent} +It is strongly recommended to switch on `CDS_TEST_ENV_CHECK` in all your tests to detect such errors. It will likely become default in upcoming releases. +::: -## Best Practices { #best-practices} -### Check Response Data Instead of Status Codes {.h3} + +## Best Practices + +### Check Status Codes Last Avoid checking for single status codes. Instead simply check the response data: ```js const { data, status } = await GET `/catalog/Books` -expect(status).to.equal(200) // <-- DON'T -expect(data.value).to.containSubset([{ID: 1}]) // just do this +expect(status).to.equal(200) //> DON'T do that upfront // [!code --] +expect(data).to.equal(...) //> do this to see what's wrong +expect(status).to.equal(200) //> Do it at the end, if at all // [!code ++] ``` This makes a difference in case of an error: with the status code check, your test will abort with a useless _Expected: 200, received: xxx_ error, while without it, it fails with a richer error that includes a status text. Note that by default, Axios yields errors for status codes `< 200` and `>= 300`. This can be [configured](https://github.com/axios/axios#handling-errors), though. -### Be Relaxed When Checking Error Messages {.h3} -When expecting errors, compare their text in a relaxed fashion. Don't hard-wire the exact error text, as this might change over time, breaking your test unnecessarily. + +### Minimal Assumptions + +When checking expected errors messages, only check for significant keywords. Don't hardwire the exact error text, as this might change over time, breaking your test unnecessarily. + +**DON'T**{.bad} hardwire on overly specific error messages: + +```js +await expect(POST(`/catalog/Books`,...)).to.be.rejectedWith( + 'Entity "CatalogService.Books" is readonly' +) +``` + +**DO:**{.good} check for the essential information only: + +```js +await expect(POST(`/catalog/Books`,...)).to.be.rejectedWith( + /readonly/i +) +``` + + + +## Using `cds.test` in REPL + +You can use `cds.test` in REPL, for example, by running this from your command line in [cap/samples](https://github.com/sap-samples/cloud-cap-samples): + +```sh +[cap/samples] cds repl +Welcome to cds repl v7.1 +``` + +```js +> var test = await cds.test('bookshop') +``` + +```log +[cds] - model loaded from 6 file(s): + + ./bookshop/db/schema.cds + ./bookshop/srv/admin-service.cds + ./bookshop/srv/cat-service.cds + ./bookshop/app/services.cds + ./../../cds/common.cds + ./common/index.cds + +[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 + > filling sap.common.Currencies from ./common/data/sap.common-Currencies.csv + > filling sap.common.Currencies.texts from ./common/data/sap.common-Currencies_texts.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' } + +[cds] - server listening on { url: 'http://localhost:64914' } +[cds] - launched at 9/8/2021, 5:36:20 PM, in: 767.042ms +[ terminate with ^C ] +``` + +```js +> await SELECT `title` .from `Books` .where `exists author[name like '%Poe%']` +[ { title: 'The Raven' }, { title: 'Eleonora' } ] +``` ```js -await expect(POST(`/catalog/Books`,{ID:333})).to.be.rejectedWith( - 'Entity "CatalogService.Books" is read-only') // DON'T hard-wire entire texts -await expect(POST(`/catalog/Books`,{ID:333})).to.be.rejectedWith( - /read?only/i) // better: check for the essential information, use regexes +> var { CatalogService } = cds.services +> await CatalogService.read `title, author` .from `ListOfBooks` +[ + { title: 'Wuthering Heights', author: 'Emily Brontë' }, + { title: 'Jane Eyre', author: 'Charlotte Brontë' }, + { title: 'The Raven', author: 'Edgar Allen Poe' }, + { title: 'Eleonora', author: 'Edgar Allen Poe' }, + { title: 'Catweazle', author: 'Richard Carpenter' } +] ``` From 720ac4a97ba58b49f2c5272e4e7e8a494a909562 Mon Sep 17 00:00:00 2001 From: Daniel O'Grady <103028279+daogrady@users.noreply.github.com> Date: Tue, 26 Sep 2023 12:43:56 +0200 Subject: [PATCH 08/45] Improve cds-typer Documentation (#430) Hint at 'enabled' option and `import type` for TS projects. --- tools/cds-typer.md | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tools/cds-typer.md b/tools/cds-typer.md index 13e893a69..3c51af446 100644 --- a/tools/cds-typer.md +++ b/tools/cds-typer.md @@ -12,10 +12,11 @@ The following chapter describes the [`cds-typer` package](https://www.npmjs.com/ ## Quickstart using VS Code {#cds-typer-vscode} 1. Make sure you have the [SAP CDS Language Support extension for VSCode](https://marketplace.visualstudio.com/items?itemName=SAPSE.vscode-cds) installed. -2. In your project's root, execute `cds add typer`. -3. Install the newly added dev-dependency using `npm i`. -4. Saving any _.cds_ file of your model from VSCode triggers the type generation process. -5. Model types now have to be imported to service implementation files by traditional imports of the generated files: +2. See that cds-typer is enabled in your VSCode settings (CDS > Type Generator > Enabled). +3. In your project's root, execute `cds add typer`. +4. Install the newly added dev-dependency using `npm i`. +5. Saving any _.cds_ file of your model from VSCode triggers the type generation process. +6. Model types now have to be imported to service implementation files by traditional imports of the generated files: ```js // without cds-typer @@ -102,7 +103,9 @@ service.on(submitOrder, (…) => { /* implementation of 'submitOrder' */ }) Using anything but lambda functions for either CRUD handler or action implementation will make it impossible for the LSP to infer the parameter types. -You can remedy this by specifying the expected type yourself via [JSDoc](https://jsdoc.app/): +You can remedy this by specifying the expected type with one of the following options. + +Using [JSDoc](https://jsdoc.app/) in JavaScript projects: ```js service.on('READ', Books, readBooksHandler) @@ -113,6 +116,18 @@ function readBooksHandler (req) { } ``` +Using `import type` in TypeScript projects: + +```ts +import type { Books } from '#cds-models/sap/capire/bookshop' + +service.on('READ', Books, readBooksHandler) + +function readBooksHandler (req: {{ data: Books }}) { + // req.data is now properly known to be of type Books again +} +``` + ::: From 89b678aa50ab5f0d02636195d0528f14a30f9ae0 Mon Sep 17 00:00:00 2001 From: Steffen Waldmann Date: Tue, 26 Sep 2023 12:45:36 +0200 Subject: [PATCH 09/45] Better formatting in service consumption guide (#441) * Improve warning style in services consumption guide * Better formatting in service consumption guide --- guides/using-services.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/guides/using-services.md b/guides/using-services.md index 0b8eee073..11698d5b8 100644 --- a/guides/using-services.md +++ b/guides/using-services.md @@ -134,11 +134,11 @@ cds compile srv -s OrdersService -2 edmx -o dest/ [You can try it with the orders sample in cap/samples.](https://github.com/SAP-samples/cloud-cap-samples/tree/master/orders){.learn-more} By default, CAP works with OData V4 and the EDMX export is in this protocol version as well. The `cds compile` command offers options for other OData versions and flavors, call `cds help compile` for more information. -::: warning -**Don't just copy the CDS file for a remote CAP service**, for example from a different application. There are issues to use them to call remote services:
-- The effective service API depends on the used protocol.
-- CDS files often use includes, which can't be resolved anymore.
-- CAP creates unneeded database tables and views for all entities in the file.
+::: warning Don't just copy the CDS file for a remote CAP service +Simply copying CDS files from a different application comes with the following issues: +- The effective service API depends on the used protocol. +- CDS files often use includes, which can't be resolved anymore. +- CAP creates unneeded database tables and views for all entities in the file. ::: ### Import API Definition { #import-api} @@ -901,9 +901,7 @@ You need additional logic, if remote entities are in the game. The following tab #### Transient Access vs. Replication -::: tip -The _Integrate and Extend_ chapter shows only techniques for transient access. -::: +> This chapter shows only techniques for transient access. The following matrix can help you to find the best approach for your scenario: From 7af6a5cfea0bba7e0f1423495cf10bd2dfe91f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Jeglinsky?= Date: Tue, 26 Sep 2023 20:39:52 +0200 Subject: [PATCH 10/45] Update link in Node.js Best Practices (#444) Update best-practices.md --- node.js/best-practices.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js/best-practices.md b/node.js/best-practices.md index 4c9f4a7e6..842858a16 100644 --- a/node.js/best-practices.md +++ b/node.js/best-practices.md @@ -361,7 +361,7 @@ In rare cases, throwing a new error is necessary, for example, if the original e ### Further Readings The following articles might be of interest: -- [Error Handling in Node.js](https://www.joyent.com/node-js/production/design/errors) +- [Error Handling in Node.js](https://web.archive.org/web/20220417042018/https://www.joyent.com/node-js/production/design/errors) - [Let It Crash](https://wiki.c2.com/?LetItCrash) - [Don't Catch Exceptions](https://wiki.c2.com/?DontCatchExceptions) - [Report And Die](https://wiki.c2.com/?ReportAndDie) From 6b1e279bb0a69664dc308f9afeeb41d4e0a78ebd Mon Sep 17 00:00:00 2001 From: Daniel Hutzel Date: Wed, 27 Sep 2023 04:02:54 +0200 Subject: [PATCH 11/45] cleanup guides (#446) --- .vitepress/theme/custom.scss | 3 + about/features.md | 2 +- about/index.md | 2 +- advanced/fiori.md | 2 +- cds/common.md | 2 +- get-started/-using-mock-servers.md | 2 +- guides/domain-modeling.md | 496 ++++++++++-------- guides/messaging/index.md | 8 +- guides/providing-services.md | 554 +++++++++------------ guides/security/aspects.md | 2 +- guides/security/data-protection-privacy.md | 2 +- java/consumption-api.md | 2 +- java/migration.md | 2 +- menu.md | 9 +- node.js/app-services.md | 2 +- node.js/events.md | 2 +- node.js/index.md | 2 +- 17 files changed, 564 insertions(+), 530 deletions(-) diff --git a/.vitepress/theme/custom.scss b/.vitepress/theme/custom.scss index 355882d7f..53e14cdf2 100644 --- a/.vitepress/theme/custom.scss +++ b/.vitepress/theme/custom.scss @@ -200,6 +200,7 @@ main { .good { color:teal } .bad, .important { color:darkred }; .dark & { .bad, .important { color:#e00 } } .constructor::before { content: 'Constructor: '; color: #999 } + .annotation::before { content: 'Annotation: '; color: #999 } .property::before { content: 'Property: '; color: #999 } .method::before { content: 'Method: '; color: #999 } .event::before { content: 'Event: '; color: #999 } @@ -209,9 +210,11 @@ main { margin-top: 5em; // font-size: 22px; } + h4.annotation + h4.annotation, h4.property + h4.property, h4.event + h4.event, h4.method + h4.method, + h3.annotation + h3.annotation, h3.property + h3.property, h3.event + h3.event, h3.class + h3.class, diff --git a/about/features.md b/about/features.md index e4969df49..24985aef3 100644 --- a/about/features.md +++ b/about/features.md @@ -119,9 +119,9 @@ Following is an index of the features currently covered by CAP, with status and | [Authorization](../guides/authorization) | | | | | [Analytics in Fiori](../advanced/odata#data-aggregation) | | | | | [Localization/i18n](../guides/i18n) | | | | -| [Managed Data](../guides/providing-services#managed-data) | | | | | [Localized Data](../guides/localized-data) | | | | | [Temporal Data](../guides/temporal-data) | | | | +| [Managed Data](../guides/domain-modeling#managed-data) | | | | | [Dynamic Extensibility](../guides/extensibility/) | | | | | Monitoring / Logging [[Node.js](../node.js/cds-log)\|[Java](../java/observability#logging)] | | | | | Audit Logging [[Node.js](../guides/data-privacy/audit-logging)\|[Java](../java/auditlog)] | | | | diff --git a/about/index.md b/about/index.md index 7563904c5..ca4b698df 100644 --- a/about/index.md +++ b/about/index.md @@ -214,7 +214,7 @@ Following is an excerpt of generic features provided: ###### Enterprise Best Practices - [Common Reuse Types & Aspects](../cds/common) -- [Managed Data](../guides/providing-services#managed-data) +- [Managed Data](../guides/domain-modeling#managed-data) - [Localized Data](../guides/localized-data) - [Temporal Data](../guides/temporal-data) - [Verticalization & Extensibility](../guides/extensibility/) diff --git a/advanced/fiori.md b/advanced/fiori.md index 8eafc9025..49e50d34e 100644 --- a/advanced/fiori.md +++ b/advanced/fiori.md @@ -542,7 +542,7 @@ Here is an example showing how this ends up as OData `Common.ValueList` annotati In our SFLIGHT sample application, we showcase how to use actions covering the definition in your CDS model, the needed custom code and the UI implementation. -[Learn more about Custom Actions & Functions.](../guides/providing-services#custom-actions-functions){.learn-more} +[Learn more about Custom Actions & Functions.](../guides/providing-services#actions-functions){.learn-more} We're going to look at three things. diff --git a/cds/common.md b/cds/common.md index 039048612..d282afe52 100644 --- a/cds/common.md +++ b/cds/common.md @@ -110,7 +110,7 @@ entity Foo { 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/providing-services#managed-data){ .learn-more} +[Learn more about **generic service features**.](../guides/domain-modeling#managed-data){ .learn-more} ### Aspect `temporal` diff --git a/get-started/-using-mock-servers.md b/get-started/-using-mock-servers.md index 6f7442153..6c4578235 100644 --- a/get-started/-using-mock-servers.md +++ b/get-started/-using-mock-servers.md @@ -209,7 +209,7 @@ GET http://localhost:4004/api-business-partner/A_BusinessPartnerAddress(Business ###### Reset Mock Data at Runtime -To reset the mock data at runtime without restarting the mock server, define an [unbound action](../guides/providing-services#custom-actions-functions). +To reset the mock data at runtime without restarting the mock server, define an [unbound action](../guides/providing-services#actions-functions). > When using `cds watch`, executing `rs` in the terminal with the running watch command will restart the mock server and reset the mock data without the need of an unbound action. diff --git a/guides/domain-modeling.md b/guides/domain-modeling.md index df3994606..2997b849d 100644 --- a/guides/domain-modeling.md +++ b/guides/domain-modeling.md @@ -11,7 +11,9 @@ Domain Models capture the static, data-related aspects of a problem domain in te [[toc]] -## Capture Intent → What, not How +## Introduction + +### Capture Intent — *What, not How!* CDS focuses on *conceptual modelling*: we want to capure 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. @@ -62,6 +64,10 @@ type Genre : String enum { +### 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. + ### Fueling 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. @@ -84,6 +90,14 @@ We use CDS as our ubiquitous modelling language, with CDS Aspects giving us the 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 code based. + + + + +## Best Practices + + + ### 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. @@ -100,69 +114,102 @@ Even though domain models should abstract from technical implementations, don't ::: -### 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 [Add Secondary Aspects](#aspects) below. +#### Prefer Flat Models -## ① Define Domain Entities +While CDS provides great support for structured types, you should always think twice before using this, as several technologies that you or your customers might want to integrate with, may have difficulties with this. Moreover, flat structures are easier to understand and consume. -Entities represent a domain's data. When translated to persistence models, especially relational ones, entities become tables. +##### **Good:** {.good} -### With Typed Elements +```cds +entity Contacts { + isCompany : Boolean; + company : String; + title : String; + firstname : String; + lastname : String; +} +``` - Entity definitions essentially declare structured types with named and typed elements, plus the [primary key](#primary-key) elements used to identify entries. +##### **Bad:** {.bad} ```cds -entity name { - key element1 : Type; - element2 : Type; - ... +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; } ``` -[Learn more about entity definitions](../cds/cdl.md#entity-and-type-definitions){.learn-more} -### As Projections of Others -In addition, borrowing powerful view building from SQL, we can declare entities as (denormalized) views on other entities: +### 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. -```cds -entity ProjectedEntity as select from BaseEntity { - element1, element2 as name, /*...*/ -}; -``` -[Learn more about views and projections](../cds/cdl.md#views-and-projections){.learn-more} ### Naming Conventions We recommend adopting the following simple naming conventions as commonly used in many communities, for example, Java, JavaScript, C, SQL, etc. -#### Capitalize *Type / Entity* Names - 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 — e.g., `Authors` * Start **_elements_** with a lowercase letter — e.g., `name` -#### Pluralize *Entity* Names +::: 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_** — e.g., `Authors` * Use **singular** form for **_types_** — e.g., `Genre` -#### Prefer *Concise* Names +::: + +In general always prefer conciseness, comprehensibility and readability, and avoid overly lenghty names, probably dictated by overly strict systematics: + +::: tip Prefer *Concise* Names - Don't repeat contexts → e.g. `Authors.name` instead of `Authors.authorName` - Prefer one-word names → e.g. `address` instead of `addressInformation` - Use `ID` for technical primary keys → see also [Use Canonic Primary Keys](#prefer-canonic-keys) -### Using Namespaces +::: + + -You can use [namespaces](../cds/cdl#namespaces) to help getting to unique names without bloating your code with fully qualified names. For example: +## Core Concepts + + + +### 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; @@ -183,91 +230,47 @@ Note: - **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 short-lived ingredients in namespaces, or names in general, such as your current organization's name, or project code names. ::: -## ② Using Data Types -### Standard Built-in Types -CDS comes with a small set of built-in types: +### Domain Entities -- `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.md#built-in-types){.learn-more} - -### Common Reuse Types - -In addition, a set of common reuse types and aspects is provided with package [_`@sap/cds/common`_](../cds/common.md), such as: - -- Types `Country`, `Currency`, `Language` with corresponding value list entities -- Aspects `cuid`, `managed`, `temporal` +Entities represent a domain's data. When translated to persistence models, especially relational ones, entities become tables. -For example, usage is as simple as this: +Entity definitions essentially declare structured types with named and typed elements, plus the [primary key](#primary-keys) elements used to identify entries. ```cds -using { Country } from '@sap/cds/common'; -entity Addresses : managed { //> using reuse aspect - street : String; - town : String; - country : Country; //> using reuse type +entity name { + key element1 : Type; + element2 : Type; + ... } ``` -[Learn more about reuse types provided by _`@sap/cds/common`_.](../cds/common.md){.learn-more} +[Learn more about entity definitions](../cds/cdl.md#entity-and-type-definitions){.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 +#### Views / Projections -Declare custom-defined types to increase semantic expressiveness of your models, or to share details and annotations as follows: +Borrowing powerful view building from SQL, we can declare entities as (denormalized) views on other entities: ```cds -type User : String; //> merely for increasing expressiveness -type Genre : String enum { Mystery; Fiction; ... } -type DayOfWeek : Number @assert.range:[1,7]; +entity ProjectedEntity as select from BaseEntity { + element1, element2 as name, /*...*/ +}; ``` +[Learn more about views and projections](../cds/cdl.md#views-and-projections){.learn-more} -#### 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; -``` - -## ③ Add Primary Keys {#primary-key} +### Primary Keys Use the keyword `key` to signify one or more elements that form an entity's primary key: @@ -278,24 +281,24 @@ entity Books { } ``` -#### Do: {.good} +##### Do: {.good} - [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: {.bad} +##### Don't: {.bad} - Don't use binary data as keys! - [Don't interpret UUIDs!](#don-t-interpret-uuids) -### Prefer Simple, Technical Keys +#### 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 +#### Prefer Canonic Keys We recommend using canonically named and typed primary keys, as promoted [by aspect `cuid` from @sap/cds/common](../cds/common.md#aspect-cuid). @@ -312,7 +315,7 @@ 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 +#### 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: @@ -353,7 +356,91 @@ On the same note, converting UUID values obtained as strings from the database i [See also: Mapping UUIDs to SQL](../advanced/hana#mapping-uuids-to-sql) {.learn-more} -## ④ Add Associations {#associations} + + + + +### Data Types + +#### 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.md#built-in-types){.learn-more} + +#### Common Reuse Types + +In addition, a set of common reuse types and aspects is provided with package [_`@sap/cds/common`_](../cds/common.md), 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 } 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.md){.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 + +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 + +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 Use _Associations_ to capture relationships between entities. @@ -368,7 +455,7 @@ entity Authors { ... [Learn more about Associations in the _CDS Language Reference_](../cds/cdl#associations){ .learn-more} -### Managed :1 Associations +#### 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. @@ -395,7 +482,7 @@ For the sake of conciseness and comprehensibility of your models always prefer * ::: -### To-Many Associations +#### To-Many Associations Simply add the `many` qualifier keyword to indicate a to-many cardinality: @@ -415,7 +502,7 @@ entity Authors { ... > The `on` condition can either compare a backlink association to `$self`, or a backlink foreign key to the own primary key, e.g. `books.author.ID = ID`. -### Many-to-Many Associations +#### 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: @@ -445,7 +532,7 @@ entity Users { ... 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_`. -## ⑤ Add Compositions +### Compositions Compositions represent contained-in relationships. CAP runtimes provide these special treatments to Compositions out of the box: @@ -453,7 +540,7 @@ Compositions represent contained-in relationships. CAP runtimes provide these sp - **Cascaded Delete** is when deleting Composition roots - **Composition** targets are **auto-exposed** in service interfaces -### Modeling Document Structures +#### 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`. @@ -470,7 +557,7 @@ entity OrderItems { // to be accessed through Orders only [Learn more about Compositions in the _CDS Language Reference_](../cds/cdl#compositions){ .learn-more} -### Composition of Aspects +#### Composition of Aspects We can use anonymous inline aspects to rewrite the above with less noise as follows: @@ -487,7 +574,7 @@ entity Orders { ... 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. -## ⑥ Add Secondary Aspects {#aspects} +## Aspects CDS's [Aspects](../cds/cdl.md#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_. @@ -526,75 +613,9 @@ Consumers always see the merged effective models, with the separation into aspec ::: -### Managed Data - -Package `@sap/cds/common` provides a pre-defined aspect `managed` for managed data, which is defined as follows: -```cds -/** - * Aspect to capture changes by user and name - */ -aspect managed { - 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; -} -``` -We use that as includes when defining our core domain entities: - -```cds -using { managed } from '@sap/cds/common'; -entity Books : managed { ... } -entity Authors : managed { ... } -``` - -With this we keep our core domain model clean and comprehensible. - -### 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:** {.good} - -```cds -entity Books { ... - title : localized String; - descr : localized String; -} -``` - -#### **Don't:** {.bad} - -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 -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.](./localized-data){.learn-more} - -### Authorization Model +### Authorization CAP supports out-of-the-box authorization by annotating services and entities with `@requires` and `@restrict` annotations like that: @@ -635,7 +656,7 @@ annotate Authors with @restrict: [ -### UI-related Annotations +### Fiori Annotations Similarly to authorization annotations we would frequently add annotations which are related to UIs, starting with `@title`s used for field or column labels in UIs, or specific Fiori annotations in `@UI`, `@Common`, etc. vocabularies. @@ -688,53 +709,130 @@ annotate my.Books with @( }; ``` -# Best Practices -## 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. - -## Prefer Flat Models +### Localized Data -While CDS provides great support for structured types, you should always think twice before using this, as several technologies that you or your customers might want to integrate with, may have difficulties with this. Moreover, flat structures are easier to understand and consume. +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. -#### **Good:** {.good} +#### **Do:** {.good} ```cds -entity Contacts { - isCompany : Boolean; - company : String; - title : String; - firstname : String; - lastname : String; +entity Books { ... + title : localized String; + descr : localized String; } ``` -#### **Bad:** {.bad} +#### **Don't:** {.bad} + +In contrast to that, this is what you would have to do without CAP's `localized` support: ```cds -entity Contacts { - isCompany : Boolean; - companyData : CompanyDetails; - personData : PersonDetails; -} -type CompanyDetails { - name : String; +entity Books { + key ID : UUID; + title : String; + descr : String; + texts : Composition of many Books.texts on texts.book = $self; + ... } -type PersonDetails { - titles : AcademicTitles; - name : PersonName; + +entity Books.texts { + key locale : Locale; + key ID : UUID; + title : String; + descr : String; } -type PersonName : { - first : String; - last : 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 +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.](./localized-data){.learn-more} + + + + + +## Managed Data + + + +### `@cds.on.insert` {.annotation} + +### `@cds.on.update` {.annotation} + +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; } -type AcademicTitles : { - primary : String; - secondary : String; +``` + +[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 externa 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`_ + +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 + +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} diff --git a/guides/messaging/index.md b/guides/messaging/index.md index e3bb8a942..e4dbb516c 100644 --- a/guides/messaging/index.md +++ b/guides/messaging/index.md @@ -18,7 +18,7 @@ status: released -## Introduction — Ubiquitous Events in CAP {#intro} +## Ubiquitous Events in CAP {#intro} We're starting with an introduction to the core concepts in CAP. If you want to skip the introduction, you can fast-forward to the samples part starting at [Books Reviews Sample](#books-reviews-sample). @@ -119,7 +119,7 @@ The following explanations walk us through a books review example from cap/sampl Follow the instructions in [*cap/samples/readme*](https://github.com/SAP-samples/cloud-cap-samples#readme) for getting the samples and exercising the following steps. ::: -### Declaring Events in CDS {.h2} +### Declaring Events in CDS Package `@capire/reviews` essentially provides a `ReviewsService`, [declared like that](https://github.com/sap-samples/cloud-cap-samples/blob/main/reviews/srv/reviews-service.cds): @@ -149,7 +149,7 @@ As you can read from the definitions, the service's synchronous API allows to cr **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 {.h2} +### Emitting Events Find the code to emit events in *[@capire/reviews/srv/reviews-service.js](https://github.com/SAP-samples/cloud-cap-samples/blob/139d9574950d1a5ead475c7b47deb174418500e4/reviews/srv/reviews-service.js#L12-L20)*: @@ -173,7 +173,7 @@ Method `srv.emit()` is used to emit event messages. As you can see, emitters usu 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 {.h2} +### Receiving Events Find the code to receive events in *[@capire/bookstore/srv/mashup.js](https://github.com/SAP-samples/cloud-cap-samples/blob/30764b261b6bf95854df59f54a8818a4ceedd462/bookstore/srv/mashup.js#L39-L47)* (which is the basic bookshop app enhanced by reviews, hence integration with `ReviewsService`): diff --git a/guides/providing-services.md b/guides/providing-services.md index ff5ca956f..09fee7d26 100644 --- a/guides/providing-services.md +++ b/guides/providing-services.md @@ -22,7 +22,7 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ .best-practice::before { content: 'Best Practice: '; color: teal } -## Introduction — Core Concepts {#introduction} +## Intro: Core Concepts {#introduction} The following sections give a brief overview of CAP's core concepts. @@ -42,88 +42,15 @@ At runtime, everything happening is in response to events. CAP features a ubiqui Service providers basically react on events in event handlers, plugged in to respective hooks provided by the core service runtimes. -### Generic Providers {#generic-providers-intro} -The CAP runtimes provide sets of event handlers for common tasks as documented hereinafter. -### Single-Purposed Services {.best-practice} +## Service Definitions -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:**{.bad} Single Services Exposing All Entities 1:1 - -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:**{.good} 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. - -### Late-Cut Microservices {.best-practice} - -Compared to Microservices, CAP services are 'Nano'. As shown in the previous sections, you should design your application as a set of loosely coupled, single-purposed services, which can all be served embedded in a single-server process at first (that is, a monolith). - -Yet, given such loosely coupled services, and enabled by CAP's uniform way to define and consume services, you can decide later on to separate, deploy, and run your services as separate microservices, even without changing your models or code. - -This flexibility allows you to, again, focus on solving your domain problem first, and avoid the efforts and costs of premature microservice design and DevOps overhead, at least in the early phases of development. - - - - -## Modeling Services in CDS {#modeling-services} - - -### Services Provide APIs to Consumers {#all-in-one-definitions} +### Services as APIs In its most basic form, a service definition simply declares the data entities and operations it serves. For example: @@ -153,7 +80,7 @@ This definition effectively defines the API served by `BookshopService`. 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 Act as Facades {#services-as-facades} +### Services as Facades In contrast to the all-in-one definition above, services usually expose views, aka projections, on underlying domain model entities: @@ -171,7 +98,7 @@ This way, services become facades to encapsulated domain data, exposing differen ![This graphic is explained in the accompanying text.](assets/providing-services/service-as-facades.drawio.svg) -### Serving Denormalized Views +### Denormalized Views Instead of exposing access to underlying data in a 1:1 fashion, services frequently expose denormalized views, tailored to specific use cases. @@ -217,7 +144,7 @@ service Zoo { [Learn more about Auto-Exposed Entities in the CDS reference docs.](../cds/cdl#auto-expose){.learn-more} -### Auto-Redirected Associations +### Redirected Associations When exposing related entities, associations are automatically redirected. This ensures that clients can navigate between projected entities as expected. For example: @@ -233,11 +160,11 @@ service AdminService { -## Generic Service Providers {#generic-providers} +## Generic Providers The CAP runtimes for [Node.js](../node.js/) and [Java](../java/) 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](#modeling-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 [Adding Custom Logic](#adding-custom-logic) picks that up. +In effect, a service definition [as introduced above](#service-definitions) 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 [Adding Custom Logic](#adding-custom-logic) picks that up. ### Serving CRUD Requests {#serving-crud} @@ -260,26 +187,30 @@ filtering and sorting is not available for `virtual` elements. ::: -### Serving Documents +### Deep Reads / 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` +#### 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) ``` - -same using [`cds.ql` in Node.js](../node.js/cds-ql): - -```js -SELECT.from ('Orders', o => { o`.*`, o.header (h => { h`.*`, h.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: @@ -299,10 +230,11 @@ Both would return an array of nested structures as follows: -### – Deep `INSERT` +#### 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 @@ -314,6 +246,7 @@ POST .../Orders { } } ``` +::: Note that Associations and Compositions are handled differently in (deep) inserts and updates: @@ -330,10 +263,11 @@ POST .../Books { -### – Deep `UPDATE` +#### 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: { @@ -345,6 +279,7 @@ PUT .../Orders/1 { }] } ``` +::: Depending on existing data, child entities will be created, updated, or deleted as follows: @@ -358,14 +293,15 @@ Omitted compositions have no effect, whether during `PATCH` or during `PUT`. Tha -### – Deep `DELETE` +#### 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 ``` - +::: @@ -406,7 +342,7 @@ CAP runtimes will automatically fill in `Orders.ID` with a new uuid, as well as -### Searching Textual Data {#searching-data} +### 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. @@ -419,7 +355,7 @@ 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). -#### Using the `@cds.search` Annotation {#using-cds-search-annotation} +#### The `@cds.search` Annotation {#cds-search} 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: @@ -436,7 +372,7 @@ entity E { } [Learn more about the syntax of annotations.](../cds/cdl#annotations){.learn-more} -#### Restrict to Certain Elements Only +#### Including Fields ```cds @cds.search: { title } @@ -445,20 +381,7 @@ entity Books { ... } Searches the `title` element only. -#### Exclude Elements from Being Searched - -```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. -::: - -#### Extend Search to *Associated* Entities +##### Extend Search to *Associated* Entities ```cds @cds.search: { author } @@ -474,7 +397,7 @@ Searches all elements of the `Books` entity, as well as all searchable elements Extending the search to associated entities is currently only supported on the Java runtime. ::: -#### Extend to Individual Elements in Associated Entities +##### Extend to Individual Elements in Associated Entities ```cds @cds.search: { author.name } @@ -487,10 +410,24 @@ Searches only in the element `name` of the associated `Authors` entity. Extending the search to individual elements in associated entities is currently only supported on the Java runtime. ::: -## Pagination & Sorting {#pagination-sorting} + +#### 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. +::: + +### Pagination & Sorting -### Implicit Pagination +#### 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. @@ -520,7 +457,7 @@ On firing this query, you get the second set of 1,000 records with a link to the 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 +#### Reliable Pagination > Note: This feature is available only for OData V4 endpoints. @@ -541,7 +478,7 @@ The feature can be enabled with the following [configuration options](../node.js - Node.js: `cds.query.limit.reliablePaging` -### Paging Limits +#### Paging Limits You can configure default and maximum page size limits in your [project configuration](../node.js/cds-env#project-settings) as follows: @@ -561,7 +498,7 @@ You can configure default and maximum page size limits in your [project configur - The **default limit** defines the number of items that are retrieved if no `$top` was specified. -#### Annotation `@cds.query.limit` {#annotation-cds-query-limit} +##### Annotation `@cds.query.limit` {#annotation-cds-query-limit} You can override the defaults by applying the `@cds.query.limit` annotation on the service or entity level, as follows: @@ -598,7 +535,7 @@ service AdminService { } ``` -#### Precedence +##### 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. @@ -613,7 +550,7 @@ service CatalogService { ``` -### Implicit Sorting +#### Implicit Sorting Paging requires implied sorting, otherwise records might be skipped accidentally when reading follow-up pages. @@ -669,6 +606,95 @@ SELECT ... from my_Books ORDER BY + + +### 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 {#etag} + +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 {#select-for-update} + +_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 read or updated in a concurrent transaction. + +Use _shared_ locks if you only need to prevent the entity data to be updated in a concurrent transaction, but don't want to block concurrent read operations. + +The records are locked until the end of the transaction by commit or rollback statement. + +[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/query-api#write-lock){.learn-more} +::: warning +Pessimistic locking is not supported by SQLite. H2 supports exclusive locks only. +::: + + + + ## Input Validation CAP runtimes automatically validate user input, controlled by the following annotations. @@ -851,174 +877,10 @@ entity Foo { -## Managed Data - - -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. - - -### Using `@cds.on.insert/update` Annotations Individually - -```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 the syntax of annotations.](../cds/cdl#annotations){.learn-more} - -These **rules** apply: - -- Data is auto-filled, that is, data is ignored if provided in the request payload. -- Data can be filled with initial data, for example, through `.csv` files. -- Data can be set explicitly in custom handlers. For example: -```js -Foo.modifiedBy = req.user.id -Foo.modifiedAt = new Date() -``` -::: tip -In effect, values for these elements are handled automatically and are write-protected for external service clients. -::: -::: warning -Upon `Upsert` the generic handlers for `@cds.on.update` are executed but the handlers for `@cds.on.insert` are not. -::: - - -### Using 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} - - -### Pseudo Variables `$user` and `$now` - -The pseudo variables used in the annotations are resolved as follows: - -- `$now` is replaced by the current server time (in UTC) - + The value of `$now` is stable for the current transaction -- `$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} - -### Differences to `defaults` - -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 `INSERT`s on database-level operations, they differ for `CREATE` requests on higher-level service providers: Values for `managed` in the request payload will be ignored, while provided values for `defaulted` will be written to the database. - - - -## 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 {#etag} - -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 {#select-for-update} - -_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 read or updated in a concurrent transaction. - -Use _shared_ locks if you only need to prevent the entity data to be updated in a concurrent transaction, but don't want to block concurrent read operations. - -The records are locked until the end of the transaction by commit or rollback statement. - -[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/query-api#write-lock){.learn-more} -::: warning -Pessimistic locking is not supported by SQLite. H2 supports exclusive locks only. -::: +## Custom Logic - -## Adding Custom Logic - - - -### Examples for Custom Logic - As most standard tasks and use cases are covered by [generic service providers](#generic-providers), the need to add service implementation code is greatly reduced and minified, and hence the quantity of individual boilerplate coding. The remaining cases that need custom handlers, reduce to real custom logic, specific to your domain and application, such as: @@ -1029,12 +891,9 @@ The remaining cases that need custom handlers, reduce to real custom logic, spec - Triggering follow-up actions, for example calling other services or emitting outbound events in response to inbound events - And more... In general, all the things not (yet) covered by generic handlers -The following sections give an overview how to do so, which links to respective deep dives in the reference documentations for [Java](../java/) and [Node.js](https://nodejs.org). -### Providing Custom Implementations - {#service-impls} -**In Node.js**, the easiest way to provide implementations for services is through equally named _.js_ files placed next to a service definition's _.cds_ file: +**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 @@ -1045,8 +904,6 @@ The following sections give an overview how to do so, which links to respective [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: ```js @@ -1057,9 +914,11 @@ public class FooServiceImpl implements EventHandler {...} [Learn more about Event Handler classes in Java.](../java/provisioning-api#handlerclasses){.learn-more} -### Registering Event Handlers -Given [assigned implementation classes/modules](#service-impls), you can register individual event handlers for each potential event, on different hooks of the event processing cycle, for example: + +### Custom Event Handlers + +Within your custom implementations, you can register event handlers like that: ```js const cds = require('@sap/cds') @@ -1085,7 +944,9 @@ public class BookshopServiceImpl implements EventHandler { [Learn more about **adding event handlers in Java**.](../java/provisioning-api#handlerclasses){.learn-more} -### Hooks for Event Handlers → `on`, `before`, `after` + + +### 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: @@ -1101,9 +962,11 @@ CAP allows to plug in event handlers to these different hooks, that is phases du `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 {#handler-impls} -Event handlers all get a uniform _Request_/_Event Message_ context object as their primary argument, which, among others, provides access to the following: +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 @@ -1113,17 +976,14 @@ Event handlers all get a uniform _Request_/_Event Message_ context object as the - 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/provisioning-api#eventcontext){.learn-more} +[Learn more about **implementing event handlers in Java**.](../java/provisioning-api#eventcontext){.learn-more} -## Custom Actions & Functions {#custom-actions-functions } +## Actions & Functions 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. - -### Modeling in CDS - You can define actions and functions in CDS models like that: ```cds service Sue { @@ -1142,24 +1002,22 @@ service Sue { [Learn more about modeling actions and functions in CDS.](../cds/cdl#actions){.learn-more} -### Actions vs Functions -The differentiation between Actions and Functions stems from the OData specifications and in essence is as follows: -- **Actions** are meant for operations, which add or modify data in the server; they are called through `POST` request with the arguments passed in `application/json` bodies. -- **Functions** are meant for operations, which only retrieve data from the server; they are called through `GET` requests with the arguments passed in the URL path. +The differentiation between *Actions* and *Functions* as well as *bound* and *unbound* stems from the OData specifications, and in essence is as follows: -### Bound vs Unbound +- **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. -Also from OData stems the concept of bound and unbound actions and functions: - -- **Bound** actions/functions are similar to class methods in Java, with the first implicit argument always being the bound entity's primary key. -- **Unbound** actions/functions are like functions in JavaScript. -::: tip +::: tip Prefer *Unbound* Actions/Functions From CDS perspective we recommend **preferring unbound** actions/functions, as these are much more straightforward to implement and invoke. ::: -### Implementing Actions or Functions + + +### Implementing Actions / Functions In general, implement actions or functions like that: @@ -1189,7 +1047,7 @@ module.exports = class Sue extends cds.Service { -### Calling Actions or Functions +### Calling Actions / Functions **HTTP Requests** to call the actions/function declared above look like that: @@ -1238,3 +1096,81 @@ POST .../sue/Foo(2)/Sue.order {"x":1} // bound action ``` > Note: Even with that typed APIs, always pass the target entity name as second argument for bound actions/functions. + + + +

+ + + +# Best Practices + + + +## Single-Purposed Services {.best-practice} + + +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:**{.bad} Single Services Exposing All Entities 1:1 + +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:**{.good} 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. + +## Late-Cut Microservices {.best-practice} + +Compared to Microservices, CAP services are 'Nano'. As shown in the previous sections, you should design your application as a set of loosely coupled, single-purposed services, which can all be served embedded in a single-server process at first (that is, a monolith). + +Yet, given such loosely coupled services, and enabled by CAP's uniform way to define and consume services, you can decide later on to separate, deploy, and run your services as separate microservices, even without changing your models or code. + +This flexibility allows you to, again, focus on solving your domain problem first, and avoid the efforts and costs of premature microservice design and DevOps overhead, at least in the early phases of development. diff --git a/guides/security/aspects.md b/guides/security/aspects.md index f53d1cd6a..9259341bc 100644 --- a/guides/security/aspects.md +++ b/guides/security/aspects.md @@ -568,8 +568,8 @@ 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: -- [Sorting](../providing-services#using-cds-search-annotation) - [Pagination (implicit sort order)](../providing-services#pagination-sorting) +- [Searching Data](../providing-services#searching-data) - Filtering ::: warning diff --git a/guides/security/data-protection-privacy.md b/guides/security/data-protection-privacy.md index ef2347293..9c8d725ea 100644 --- a/guides/security/data-protection-privacy.md +++ b/guides/security/data-protection-privacy.md @@ -34,7 +34,7 @@ Connect an adequate logging service to meet compliance requirements such as [SAP - Messages temporarily written to transaction outbox might contain personal data. The entries are mandatory to operate the system. If necessary, applications can process these messages by standard CAP functionality (CDS model `@sap/cds/srv/outbox`). -- Be aware that personal data might be added automatically when using the [managed](../providing-services#managed-data) aspect. +- Be aware that personal data might be added automatically when using the [managed](../domain-modeling#managed-data) aspect. Dependent on the business scenario, custom CDS models served by CAP runtime will most likely contain personal data that is also stored in a backing service. diff --git a/java/consumption-api.md b/java/consumption-api.md index eeac0eba5..941feafca 100644 --- a/java/consumption-api.md +++ b/java/consumption-api.md @@ -144,7 +144,7 @@ A Persistence Service isn't bound to a specific service definition in the CDS mo Transaction management is built in to Persistence Services. They take care of lazily initializing and maintaining database transactions as part of the active changeset context. -Some generic providers are registered on Persistence Services instead of on Application Services, like the ones for [managed data](../guides/providing-services#managed-data). +Some generic providers are registered on Persistence Services instead of on Application Services, like the ones for [managed data](../guides/domain-modeling#managed-data). This ensures that the functionality is also triggered, when directly interacting with a Persistence Service. The Persistence Service is used when implementing event handlers for Application Services, for example when additional data needs to be read when performing custom validations. diff --git a/java/migration.md b/java/migration.md index 245606e7b..66dc44129 100644 --- a/java/migration.md +++ b/java/migration.md @@ -298,7 +298,7 @@ Some CdsProperties were already marked as deprected in CAP Java 1.x and are now ### Removed Annotations Overview -- `@search.cascade` is no longer supported. It's replaced by [@cds.search](../guides/providing-services#using-cds-search-annotation). +- `@search.cascade` is no longer supported. It's replaced by [@cds.search](../guides/providing-services#cds-search). ### Changed Behavior diff --git a/menu.md b/menu.md index c96f37360..bcb87f157 100644 --- a/menu.md +++ b/menu.md @@ -16,14 +16,11 @@ - [Domain Modeling](guides/domain-modeling) - [Providing Services](guides/providing-services) - [Introduction](guides/providing-services#introduction) - - [Modeling Services](guides/providing-services#modeling-services) + - [Modeling Services](guides/providing-services#service-definitions) - [Generic Providers](guides/providing-services#generic-providers) - - [Pagination & Sorting](guides/providing-services#pagination-sorting) - [Input Validation](guides/providing-services#input-validation) - - [Managed Data](guides/providing-services#managed-data) - - [Concurrency Control](guides/providing-services#concurrency-control) - - [Adding Custom Logic](guides/providing-services#adding-custom-logic) - - [Custom Actions & Functions](guides/providing-services#custom-actions-functions) + - [Custom Logic](guides/providing-services#adding-custom-logic) + - [Actions & Functions](guides/providing-services#actions-functions) - [Consuming Services](guides/using-services) - [Introduction](guides/using-services#introduction) - [Import APIs](guides/using-services#external-service-api) diff --git a/node.js/app-services.md b/node.js/app-services.md index 03ec04fed..a4eabb7b5 100644 --- a/node.js/app-services.md +++ b/node.js/app-services.md @@ -108,7 +108,7 @@ This method is adding request handlers for handling localized data, as documente ### _static_ handle_managed_data() {.method} -This method is adding request handlers for handling managed data, as documented in the [Providing Services guide](../guides/providing-services#managed-data). +This method is adding request handlers for handling managed data, as documented in the [Providing Services guide](../guides/domain-modeling#managed-data). diff --git a/node.js/events.md b/node.js/events.md index b47027d67..345eba28b 100644 --- a/node.js/events.md +++ b/node.js/events.md @@ -128,7 +128,7 @@ A unique string identifying the current tenant, or `undefined` if not in multite 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/providing-services#managed-data){.learn-more} +[Learn more in the **Managed Data** guide.](../guides/domain-modeling#managed-data){.learn-more} diff --git a/node.js/index.md b/node.js/index.md index 99aacab41..59bd2a09d 100644 --- a/node.js/index.md +++ b/node.js/index.md @@ -13,7 +13,7 @@ Reference Documentation 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/providing-services#modeling-services) +1. Define services in CDS → see [Cookbook > Providing & Consuming Services](../guides/providing-services#service-definitions) 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) From 88ccc0db1346f167e3819da949de3f3691990878 Mon Sep 17 00:00:00 2001 From: Daniel Hutzel Date: Wed, 27 Sep 2023 04:16:28 +0200 Subject: [PATCH 12/45] . --- about/index.md | 2 +- advanced/fiori.md | 2 +- cds/compiler-v2.md | 2 +- get-started/-using-mock-servers.md | 2 +- get-started/in-a-nutshell.md | 2 +- menu.md | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/about/index.md b/about/index.md index ca4b698df..af3639a5a 100644 --- a/about/index.md +++ b/about/index.md @@ -84,7 +84,7 @@ That might sound like a contradiction, but isn't: While CAP certainly gives *opi | CAP is *Opinionated* in... | CAP is *Open* as... | | ------------------------------------------------------------ | ------------------------------------------------------------ | | **Higher-level concepts and APIs** abstracting from and avoiding lock-ins to low-level platform features and protocols | All abstractions follow a glass-box pattern that allows unrestricted access to lower-level things, if required | -| **Best Practices served out of the box** with generic solutions for many recurring tasks | You can always handle things your way in [custom handlers](../guides/providing-services#adding-custom-logic), decide whether to adopt [CQRS](./related#cqrs) or [Event Sourcing](./related#event-sourcing), for example ... while CAP simply tries to get the tedious tasks out of your way. | +| **Best Practices served out of the box** with generic solutions for many recurring tasks | You can always handle things your way in [custom handlers](../guides/providing-services#custom-logic), decide whether to adopt [CQRS](./related#cqrs) or [Event Sourcing](./related#event-sourcing), for example ... 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](../get-started/in-a-nutshell#vue), or databases, by providing new database integrations. | | **Dedicated tools support** provided in [SAP Business Application Studio](../tools/#bastudio), and [Visual Studio Code](../tools/#vscode) or [Eclipse](../java/getting-started#eclipse). | CAP doesn't depend on those tools. Everything in CAP can be done using the [`@sap/cds-dk`](../get-started/) CLI and any editor or IDE of your choice. | diff --git a/advanced/fiori.md b/advanced/fiori.md index 49e50d34e..9ecb3a89a 100644 --- a/advanced/fiori.md +++ b/advanced/fiori.md @@ -427,7 +427,7 @@ If you're editing data in multiple languages, the _General_ tab in the example a ### Validating Drafts -You can add [custom handlers](../guides/providing-services#adding-custom-logic) to add specific validations, as usual. In addition, for a draft, you can register handlers to the `PATCH` events to validate input per field, during the edit session, as follows. +You can add [custom handlers](../guides/providing-services#custom-logic) to add specific validations, as usual. In addition, for a draft, you can register handlers to the `PATCH` events to validate input per field, during the edit session, as follows. ###### ... in Java diff --git a/cds/compiler-v2.md b/cds/compiler-v2.md index bae2c0d52..5121efab3 100644 --- a/cds/compiler-v2.md +++ b/cds/compiler-v2.md @@ -46,7 +46,7 @@ before upgrading to v2 → find respective hints in the following sections. ### Fix ambiguous `redirects` -When there's no unique target for [**_auto-redirection_**](../guides/providing-services#auto-redirected-associations), +When there's no unique target for [**_auto-redirection_**](../guides/providing-services#redirected-associations), compiler v1 'silently' skipped respective associations with a warning. Yet, many ignored these warnings, which lead to hard-to-detect subsequent errors. Therefore, we raised that to an error-level message with compiler v2. diff --git a/get-started/-using-mock-servers.md b/get-started/-using-mock-servers.md index 6c4578235..8ac4a6353 100644 --- a/get-started/-using-mock-servers.md +++ b/get-started/-using-mock-servers.md @@ -179,7 +179,7 @@ module.exports = (db)=>{ ###### Mock Custom Responses -To extend the mock server with custom logic, you can [create a custom handler](../guides/providing-services#adding-custom-logic). To do so, create a `.js` file with the same name next to the imported service definition file, in our case `srv/external/API_BUSINESS_PARTNER.js`. Add the custom logic there: +To extend the mock server with custom logic, you can [create a custom handler](../guides/providing-services#custom-logic). To do so, create a `.js` file with the same name next to the imported service definition file, in our case `srv/external/API_BUSINESS_PARTNER.js`. Add the custom logic there: ```js module.exports = cds.service.impl (srv => { diff --git a/get-started/in-a-nutshell.md b/get-started/in-a-nutshell.md index 7187db857..b570847fa 100644 --- a/get-started/in-a-nutshell.md +++ b/get-started/in-a-nutshell.md @@ -653,7 +653,7 @@ With this getting started guide we introduced many of the basics of CAP, such as - [Consuming Services](../guides/using-services) - [Using Databases](../guides/databases) - [Adding/Serving UIs](../advanced/fiori) -- [Adding Custom Logic](../guides/providing-services#adding-custom-logic) +- [Adding Custom Logic](../guides/providing-services#custom-logic) [**Visit our Cookbook**](../guides/) to find more task-oriented guides. For example, you can find guides about potential next steps such as adding [Authentication](../node.js/authentication) and [Authorization](../guides/authorization) or [Deploying to SAP BTP](../guides/deployment/). diff --git a/menu.md b/menu.md index bcb87f157..77fdb96b9 100644 --- a/menu.md +++ b/menu.md @@ -19,7 +19,7 @@ - [Modeling Services](guides/providing-services#service-definitions) - [Generic Providers](guides/providing-services#generic-providers) - [Input Validation](guides/providing-services#input-validation) - - [Custom Logic](guides/providing-services#adding-custom-logic) + - [Custom Logic](guides/providing-services#custom-logic) - [Actions & Functions](guides/providing-services#actions-functions) - [Consuming Services](guides/using-services) - [Introduction](guides/using-services#introduction) From e597e4eef527473d967d107f23673177486b19cc Mon Sep 17 00:00:00 2001 From: Daniel Hutzel Date: Wed, 27 Sep 2023 04:36:33 +0200 Subject: [PATCH 13/45] . --- about/features.md | 2 +- about/index.md | 2 +- guides/providing-services.md | 2 +- java/query-api.md | 6 +++--- java/query-execution.md | 2 +- node.js/core-services.md | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/about/features.md b/about/features.md index 24985aef3..c171316cb 100644 --- a/about/features.md +++ b/about/features.md @@ -99,7 +99,7 @@ Following is an index of the features currently covered by CAP, with status and | Core Framework Features | CDS | Node.js | Java | |-----------------------------------------------------------------------------------------|:-----:|:-------:|:----:| | [Automatically Serving CRUD Requests](../guides/providing-services#generic-providers) | | | | -| [Deep-Read/Write Structured Documents](../guides/providing-services#serving-documents) | | | | +| [Deep-Read/Write Structured Documents](../guides/providing-services#deep-reads-writes) | | | | | [Automatic Input Validation](../guides/providing-services#input-validation) | | | | | [Auto-filled Primary Keys](../guides/domain-modeling#prefer-uuids-for-keys) | | | | | [Implicit Paging](../guides/providing-services#implicit-pagination) | | | | diff --git a/about/index.md b/about/index.md index af3639a5a..36c48198e 100644 --- a/about/index.md +++ b/about/index.md @@ -198,7 +198,7 @@ Following is an excerpt of generic features provided: ###### Automatically Serving Requests - [Serving CRUD Requests](../guides/providing-services#generic-providers) -- [Serving Nested Documents](../guides/providing-services#serving-documents) +- [Serving Nested Documents](../guides/providing-services#deep-reads-writes) - [Serving Media Data](../guides/media-data) - [Serving Draft Choreography](../advanced/fiori#draft-support) diff --git a/guides/providing-services.md b/guides/providing-services.md index 09fee7d26..4cada4153 100644 --- a/guides/providing-services.md +++ b/guides/providing-services.md @@ -164,7 +164,7 @@ service AdminService { The CAP runtimes for [Node.js](../node.js/) and [Java](../java/) 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](#service-definitions) 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 [Adding Custom Logic](#adding-custom-logic) picks that up. +In effect, a service definition [as introduced above](#service-definitions) 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-logic) picks that up. ### Serving CRUD Requests {#serving-crud} diff --git a/java/query-api.md b/java/query-api.md index 7ed2bc3eb..7260b75d6 100644 --- a/java/query-api.md +++ b/java/query-api.md @@ -936,7 +936,7 @@ Bulk upserts with entries updating/inserting the same set of elements can be exe ### Deep Upsert { #deep-upsert} -Upsert can operate on deep [document structures](./data#nested-structures-and-associations) modeled via [compositions](../guides/domain-modeling#_5-add-compositions), such as an `Order` with many `OrderItems`. +Upsert can operate on deep [document structures](./data#nested-structures-and-associations) modeled via [compositions](../guides/domain-modeling#compositions), such as an `Order` with many `OrderItems`. Such a _Deep Upsert_ is similar to [Deep Update](#deep-update), but it creates the root entity if it doesn't exist and comes with some [limitations](#upsert) as already mentioned. The [full set](#deep-update-full-set) and [delta](#deep-update-delta) representation for to-many compositions are supported as well. @@ -988,9 +988,9 @@ Update.entity(BOOKS, b -> b.matching(Books.create(100))) ### Deep Update { #deep-update} -Use deep updates to update _document structures_. A document structure comprises a single root entity and one or multiple related entities that are linked via compositions into a [contained-in-relationship](../guides/domain-modeling#_5-add-compositions). Linked entities can have compositions to other entities, which become also part of the document structure. +Use deep updates to update _document structures_. A document structure comprises a single root entity and one or multiple related entities that are linked via compositions into a [contained-in-relationship](../guides/domain-modeling#compositions). Linked entities can have compositions to other entities, which become also part of the document structure. -By default, only target entities of [compositions](../guides/domain-modeling#_5-add-compositions) are updated in deep updates. Nested data for managed to-one associations is used only to [set the reference](./data#setting-managed-associations-to-existing-target-entities) to the given target entity. This can be changed via the [@cascade](query-execution#cascading-over-associations) annotation. +By default, only target entities of [compositions](../guides/domain-modeling#compositions) are updated in deep updates. Nested data for managed to-one associations is used only to [set the reference](./data#setting-managed-associations-to-existing-target-entities) to the given target entity. This can be changed via the [@cascade](query-execution#cascading-over-associations) annotation. For to-many compositions there are two ways to represent changes in the nested entities of a structured document: *full set* and *delta*. In contrast to *full set* representation which describes the target state of the entities explicitly, a change request with *delta* payload describes only the differences that need to be applied to the structured document to match the target state. For instance, in deltas, entities that are not included remain untouched, whereas in full set representation they are deleted. diff --git a/java/query-execution.md b/java/query-execution.md index 472114118..706153747 100644 --- a/java/query-execution.md +++ b/java/query-execution.md @@ -184,7 +184,7 @@ It's possible to work with structured data as the insert, update, and delete ope #### Cascading over Associations { #cascading-over-associations} -By default, *insert*, *update* and *delete* operations cascade over [compositions](../guides/domain-modeling#_5-add-compositions) only. For associations, this can be enabled using the `@cascade` annotation. +By default, *insert*, *update* and *delete* operations cascade over [compositions](../guides/domain-modeling#compositions) only. For associations, this can be enabled using the `@cascade` annotation. ::: warning Cascading operations over associations isn't considered good practice and should be avoided. ::: diff --git a/node.js/core-services.md b/node.js/core-services.md index d581cc9df..a408bb395 100644 --- a/node.js/core-services.md +++ b/node.js/core-services.md @@ -260,7 +260,7 @@ await srv.read ('GET','/Books/206') await srv.send ('submitOrder', { book:206, quantity:1 }) ``` -[Using typed APIs for actions and functions](../guides/providing-services#calling-actions-or-functions): +[Using typed APIs for actions and functions](../guides/providing-services#calling-actions-functions): ```js await srv.submitOrder({ book:206, quantity:1 }) From c922f7fbff4a59b15e23a607f78f4054ae0820d0 Mon Sep 17 00:00:00 2001 From: Nigel James Date: Wed, 27 Sep 2023 16:37:13 +1000 Subject: [PATCH 14/45] add section to readme that explains local setup (#447) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add section to readme that explains local setup * editing * header --------- Co-authored-by: René Jeglinsky --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index 19fc3ee9c..132586e47 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,37 @@ This project is open to feature requests/suggestions, bug reports, etc. via [Git See our [contribution guidelines](CONTRIBUTING.md) for information about how to contribute, the project structure, as well as additional contribution information. +## Running Locally + +If you contribute often to the documentation it's best to create your own fork, clone it to your local machine. + +```sh +git clone https://github.com/njames/sap-cap-js-docs.git +``` + +Install the dependencies: + +```sh +npm run setup +``` + +Start the local server: + +```sh +npm run start +``` + +This will respond with: +```sh + vitepress v1.0.0-rc.15 + + ➜ Local: http://localhost:5173/docs/ + ➜ Network: use --host to expose + ➜ press h to show help +``` + +Once this is running, if you are reading this documentation at https://cap.cloud.sap/ anytime you press l it opens the locally hosted site at the same page. + ## Code of Conduct We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. By participating in this project, you agree to abide by its [Code of Conduct](CODE_OF_CONDUCT.md) at all times. From 74d81000b572393ced3456ad94f310811edfda3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Jeglinsky?= Date: Wed, 27 Sep 2023 09:20:45 +0200 Subject: [PATCH 15/45] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 132586e47..93057bca5 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ See our [contribution guidelines](CONTRIBUTING.md) for information about how to If you contribute often to the documentation it's best to create your own fork, clone it to your local machine. ```sh -git clone https://github.com/njames/sap-cap-js-docs.git +git clone https://github.com/cap-js/docs.git ``` Install the dependencies: From e9eb2091ab4745287b2937a7a34b18adcd045b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Jeglinsky?= Date: Wed, 27 Sep 2023 09:22:49 +0200 Subject: [PATCH 16/45] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 93057bca5..ef06ae68d 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ See our [contribution guidelines](CONTRIBUTING.md) for information about how to If you contribute often to the documentation it's best to create your own fork, clone it to your local machine. ```sh -git clone https://github.com/cap-js/docs.git +git clone https://github.com//sap-cap-js-docs.git ``` Install the dependencies: From 7912eba1968d91748a3ce04baaf2c3147f7b70cf Mon Sep 17 00:00:00 2001 From: Nigel James Date: Wed, 27 Sep 2023 17:51:27 +1000 Subject: [PATCH 17/45] comma in script causes deploy to fail (#448) --- guides/databases-postgres.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/databases-postgres.md b/guides/databases-postgres.md index a0f7f7e70..12bae1832 100644 --- a/guides/databases-postgres.md +++ b/guides/databases-postgres.md @@ -406,7 +406,7 @@ The shell script specified in the previous step is a simple combination of all t echo - copy .csv files - cp -r db/data gen/pg/db/data - echo '{"dependencies": { "@sap/cds": "*", "@cap-js/postgres": "*"}, "scripts": { "start": "cds-deploy",}}' > gen/pg/package.json + echo '{"dependencies": { "@sap/cds": "*", "@cap-js/postgres": "*"}, "scripts": { "start": "cds-deploy"}}' > gen/pg/package.json ``` From 736b3c7ce52aaaec0af81ac64929ee7d33dd2104 Mon Sep 17 00:00:00 2001 From: Rene Jeglinsky Date: Wed, 27 Sep 2023 15:07:36 +0200 Subject: [PATCH 18/45] editing --- node.js/cds-test.md | 64 ++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/node.js/cds-test.md b/node.js/cds-test.md index 0aed59104..6a7a7d742 100644 --- a/node.js/cds-test.md +++ b/node.js/cds-test.md @@ -39,14 +39,14 @@ describe(()=>{ const test = cds.test(__dirname+'/..') }) ``` -This launches a cds server from the specified target folder in a `beforeAll()` hook, with controlled shutdown when all tests have finished in an `afterAll()` hook. +This launches a CDS server from the specified target folder in a `beforeAll()` hook, with controlled shutdown when all tests have finished in an `afterAll()` hook. ::: warning Don't use `process.chdir()`! -Doing so in Jest tests may leave test containers in screwed state, leading to failing subsequent tests. Use [`cds.test.in()`](#test-in-folder) instead. +Doing so in Jest tests may leave test containers in failed state, leading to failing subsequent tests. Use [`cds.test.in()`](#test-in-folder) instead. ::: ::: danger Don't load [`cds.env`](cds-env) before [`cds.test()`](#cds-test)! -To ensure `cds.env`, and hence all plugins, are loaded from the test's target folder, the call to `cds.test()` should be the very first thing you do in your tests. Any references to [`cds`](cds-facade) sub modules or any imports of which have to go after. → [Learn more below.](#cds-test-env-check) +To ensure `cds.env`, and hence all plugins, are loaded from the test's target folder, the call to `cds.test()` is the first thing you do in your tests. Any references to [`cds`](cds-facade) sub modules or any imports of which have to go after. → [Learn more in `CDS_TEST_ENV_CHECK`.](#cds-test-env-check) ::: @@ -54,7 +54,7 @@ To ensure `cds.env`, and hence all plugins, are loaded from the test's target fo ### Testing Service APIs -As `cds.test()` launches the server in the current process, you can access all services programmatically using the respective [Node.js Service APIs](core-services). Here is an example for that taken from [cap/samples](https://github.com/SAP-samples/cloud-cap-samples/blob/a8345122ea5e32f4316fe8faef9448b53bd097d4/test/consuming-services.test.js#L2): +As `cds.test()` launches the server in the current process, you can access all services programmatically using the respective [Node.js Service APIs](core-services). Here's an example for that taken from [*cap/samples*](https://github.com/SAP-samples/cloud-cap-samples/blob/a8345122ea5e32f4316fe8faef9448b53bd097d4/test/consuming-services.test.js#L2): ```js it('Allows testing programmatic APIs', async () => { @@ -69,7 +69,7 @@ it('Allows testing programmatic APIs', async () => { ### Testing HTTP APIs -To test HTTP APIs we can use bound functions like so: +To test HTTP APIs, we can use bound functions like so: ```js const { GET, POST } = cds.test(...) @@ -77,13 +77,13 @@ const { data } = await GET ('/browse/Books') await POST (`/browse/submitOrder`, { book: 201, quantity: 5 }) ``` -[Learn more below.](#http-bound) {.learn-more} +[Learn more in GET/PUT/POST.](#http-bound) {.learn-more} ### Using Jest or Mocha - [*Mocha*](https://mochajs.org) and [*Jest*](https://jestjs.io) are the most used test runners at the moment, with each having its fan base. + [*Mocha*](https://mochajs.org) and [*Jest*](https://jestjs.io) are the most used test runners at the moment, with each having its user base. The `cds.test` library is designed to write tests that run with both, as in this sample: ```js @@ -102,7 +102,7 @@ describe('my test suite', ()=>{ You can use Mocha-style `before/after` or Jest-style `beforeAll/afterAll` in your tests, as well as the common `describe, test, it` methods. In addition, to be portable, you should use the [Chai Assertion Library's](#chai) variant of `expect`. -::: tip [All tests in cap/samples](https://github.com/sap-samples/cloud-cap-samples/blob/master/test) are written in that portable way.
+::: tip [All tests in *cap/samples*](https://github.com/sap-samples/cloud-cap-samples/blob/master/test) are written in that portable way.
Run them with `npm run jest` or with `npm run mocha`. ::: @@ -116,7 +116,7 @@ You can also start the tests in watch mode, for example: jest --watchAll ``` -which should give you green tests, when running in cap/samples root: +This should give you green tests, when running in *cap/samples* root:
 PASS  test/cds.ql.test.js
@@ -161,7 +161,7 @@ test.run().in(_dirname)
 
 ### cds.test() {.method}
 
-This method is the most convenient way to start a test server. It is actually just a convenient shortcut to construct a new instance of class `Test` and call [`test.run()`](#test-run), defined as follows:
+This method is the most convenient way to start a test server. It's actually just a convenient shortcut to construct a new instance of class `Test` and call [`test.run()`](#test-run), defined as follows:
 
 ```js
 const { Test } = cds.test
@@ -174,7 +174,7 @@ cds.test = (...args) => (new Test).run(...args)
 
 ### .chai, ... {.property}
 
-To write tests that run in [*Mocha*](https://mochajs.org) as well as in [*Jest*](https://jestjs.io) you should use the [*Chai Assertion Library*](https://www.chaijs.com/) through the following convenient methods ...
+To write tests that run in [*Mocha*](https://mochajs.org) as well as in [*Jest*](https://jestjs.io), you should use the [*Chai Assertion Library*](https://www.chaijs.com/) through the following convenient methods.
 
 :::warning Using `chai` requires these dependencies added to your project:
 
@@ -198,7 +198,7 @@ it('should support chai.except style', ()=>{
 })
 ```
 
-If you prefer Jest's `expect()` functions you can just use the respective global:
+If you prefer Jest's `expect()` functions, you can just use the respective global:
 
 ```js
 cds.test()
@@ -240,7 +240,7 @@ it('should support chai.should style', ()=>{
 
 #### .chai {.property}
 
-This getter provides access to the [*chai*](https://www.chaijs.com) library, preconfigured with the [chai-subset](https://www.chaijs.com/plugins/chai-subset/) and [chai-as-promised](https://www.chaijs.com/plugins/chai-as-promised/) plugins. These plugins contribute the `containSubset` and `eventually` APIs, respectively. The getter is essentially implemented like this:
+This getter provides access to the [*chai*](https://www.chaijs.com) library, preconfigured with the [chai-subset](https://www.chaijs.com/plugins/chai-subset/) and [chai-as-promised](https://www.chaijs.com/plugins/chai-as-promised/) plugins. These plugins contribute the `containSubset` and `eventually` APIs, respectively. The getter is implemented like this:
 
 ```js
 get chai() {
@@ -282,7 +282,7 @@ await POST('/browse/submitOrder',
 
 [Learn more about Axios.](https://axios-http.com) {.learn-more}
 
-In case of single url arguments the functions can be used in tagged template string style, which essentially allows omitting the parentheses from function calls:
+For single URL arguments, the functions can be used in tagged template string style, which allows omitting the parentheses from function calls:
 
 ```js
 let { data } = await GET('/browse/Books')
@@ -312,7 +312,7 @@ await test.post('/browse/submitOrder',
 
 ### test .data .reset() {.method}
 
-This is a bound method, which can be used in a `beforeEach` handler to automatically reset and re-deploy the database for each test like so:
+This is a bound method, which can be used in a `beforeEach` handler to automatically reset and redeploy the database for each test like so:
 
 ```js
 const { test } = cds.test()
@@ -339,7 +339,7 @@ function cds.test.log() => {
   output : string
   clear()
   release()
-} 
+}
 ```
 
 Usage examples:
@@ -347,7 +347,7 @@ Usage examples:
 ```js
 describe('cds.test.log()', ()=>{
   let log = cds.test.log()
-  
+
   it ('should capture log output', ()=>{
     expect (log.output.length).to.equal(0)
     console.log('foo',{bar:2})
@@ -368,13 +368,13 @@ describe('cds.test.log()', ()=>{
 })
 ```
 
-The implementation redirects any console operations in a `beforeAll()` hook, clears `log.output` before each test, and releases the captured console in an `afterAll()` hook. 
+The implementation redirects any console operations in a `beforeAll()` hook, clears `log.output` before each test, and releases the captured console in an `afterAll()` hook.
 
 
 
 ### test. run (...) {.method}
 
-This is the method behind [`cds.test()`](#cds-test) to start a cds server, that is the following are equivalent:
+This is the method behind [`cds.test()`](#cds-test) to start a CDS server, that is the following are equivalent:
 
 ```js
 cds.test(...)
@@ -384,11 +384,11 @@ cds.test(...)
 (new cds.test.Test).run(...)
 ```
 
-It asynchroneously launches a cds server in a `beforeAll()` hook with an arbitrary port, with controlled shutdown when all tests have finished in an `afterAll()` hook.
+It asynchronously launches a CDS server in a `beforeAll()` hook with an arbitrary port, with controlled shutdown when all tests have finished in an `afterAll()` hook.
 
-The arguments are as supported by the `cds serve` CLI command.
+The arguments are the same as supported by the `cds serve` CLI command.
 
-Specify the command `'serve'` as the first argument to serve specific cds files or services:
+Specify the command `'serve'` as the first argument to serve specific CDS files or services:
 
 ```js
 cds.test('serve','srv/cat-service.cds')
@@ -401,13 +401,13 @@ You can optionally add [`test.in(folder)`](#test-in-folder) in fluent style to r
 cds.test('serve','srv/cat-service.cds').in('/cap/samples/bookshop')
 ```
 
-If the first argument is **not** `'serve'` it is interpreted as a target folder:
+If the first argument is **not** `'serve'`, it's interpreted as a target folder:
 
 ```js
 cds.test('/cap/samples/bookshop')
 ```
 
-Essentially this variant is a convenient shortcut for:
+This variant is a convenient shortcut for:
 
 ```js
 cds.test('serve','all','--in-memory?').in('/cap/samples/bookshop')
@@ -434,7 +434,7 @@ cds.test.in(__dirname)
 
 ### `CDS_TEST_ENV_CHECK`
 
-It is important to ensure [`cds.env`](cds-env), and hence all plugins, are loaded from the test's target folder. To ensure this, any references to [`cds`](cds-facade) sub modules or any imports of which have to go after. For example if you had a test like that:
+It's important to ensure [`cds.env`](cds-env), and hence all plugins, are loaded from the test's target folder. To ensure this, any references to or imports of [`cds`](cds-facade) sub modules have to go after all plugins are loaded. For example if you had a test like that:
 
 ```js
 cds.env.fiori.lean_draft = true   //> cds.env loaded from ./ // [!code --]
@@ -443,7 +443,7 @@ cds.test(__dirname)               //> target folder: __dirname
 
 This would result in the test server started from `__dirname`, but erroneously using `cds.env` loaded from `./`.
 
-As these mistakes end up in hard-to-resolve follow up errors, [`test.in()`](#test-in-folder) can detect this if environment variable `CDS_TEST_ENV_CHECK` is set. The above code will then result into an error like that:
+As these mistakes end up in hard-to-resolve follow up errors, [`test.in()`](#test-in-folder) can detect this if environment variable `CDS_TEST_ENV_CHECK` is set. The previous code will then result into an error like that:
 
 ```sh
 CDS_TEST_ENV_CHECK=y jest cds.test.test.js
@@ -466,7 +466,7 @@ Detected cds.env loaded before running cds.test in different folder:
   at Object.describe (test/cds.test.test.js:5:1)
 ```
 
-A similar error would occur if one of the `cds` sub module would be accessed, which frequently load `cds.env` in their global scope, like `cds.Service` in the snippet below:
+A similar error would occur if one of the `cds` sub modules would be accessed, which frequently load `cds.env` in their global scope, like `cds.Service` in the following snippet:
 
 ```js
 class MyService extends cds.Service {}  //> cds.env loaded from ./ // [!code --]
@@ -484,7 +484,7 @@ class MyService extends cds.Service {} // [!code ++]
 
 :::warning Do switch on `CDS_TEST_ENV_CHECK` !
 
-It is strongly recommended to switch on `CDS_TEST_ENV_CHECK` in all your tests to detect such errors. It will likely become default in upcoming releases.
+We recommended to switch on `CDS_TEST_ENV_CHECK` in all your tests to detect such errors. It's likely to become default in upcoming releases.
 
 :::
 
@@ -494,7 +494,7 @@ It is strongly recommended to switch on `CDS_TEST_ENV_CHECK` in all your tests t
 
 ### Check Status Codes Last
 
-Avoid checking for single status codes. Instead simply check the response data:
+Avoid checking for single status codes. Instead, simply check the response data:
 
 ```js
 const { data, status } = await GET `/catalog/Books`
@@ -503,7 +503,7 @@ expect(data).to.equal(...)     //> do this to see what's wrong
 expect(status).to.equal(200)   //> Do it at the end, if at all // [!code ++]
 ```
 
-This makes a difference in case of an error: with the status code check, your test will abort with a useless _Expected: 200, received: xxx_ error, while without it, it fails with a richer error that includes a status text.
+This makes a difference if there are errors: with the status code check, your test aborts with a useless _Expected: 200, received: xxx_ error, while without it, it fails with a richer error that includes a status text.
 
 Note that by default, Axios yields errors for status codes `< 200` and `>= 300`. This can be [configured](https://github.com/axios/axios#handling-errors), though.
 
@@ -521,7 +521,7 @@ await expect(POST(`/catalog/Books`,...)).to.be.rejectedWith(
 )
 ```
 
-**DO:**{.good} check for the essential information only:
+**DO**{.good} check for the essential information only:
 
 ```js
 await expect(POST(`/catalog/Books`,...)).to.be.rejectedWith(
@@ -533,7 +533,7 @@ await expect(POST(`/catalog/Books`,...)).to.be.rejectedWith(
 
 ## Using `cds.test` in REPL
 
-You can use `cds.test` in REPL, for example, by running this from your command line in [cap/samples](https://github.com/sap-samples/cloud-cap-samples):
+You can use `cds.test` in REPL, for example, by running this from your command line in [*cap/samples*](https://github.com/sap-samples/cloud-cap-samples):
 
 ```sh
 [cap/samples] cds repl

From f180c0ac11f464728a749f1cfcc6066a51bd9070 Mon Sep 17 00:00:00 2001
From: Daniel Hutzel 
Date: Wed, 27 Sep 2023 19:05:04 +0200
Subject: [PATCH 19/45] Improved docs on cds.import()

---
 about/features.md        | 112 +++++++++++++++++++--------------------
 guides/using-services.md |   2 +-
 node.js/cds-dk.md        |  55 +++++++++----------
 3 files changed, 82 insertions(+), 87 deletions(-)

diff --git a/about/features.md b/about/features.md
index c171316cb..c06354b6c 100644
--- a/about/features.md
+++ b/about/features.md
@@ -62,13 +62,13 @@ Following is an index of the features currently covered by CAP, with status and
 
| Editors/IDE Support | Application Studio | VS Code | Eclipse | -|--------------------------|:------------------:|:------:|:-------:| -| CDS Syntax Highlighting | | | | -| CDS Code Completion | | | | -| CDS Prettifier | | | | -| Advanced Debug/Run Tools | | | | -| Project Explorer | | | | -| ... | | | | +|--------------------------|:------------------:|:-------:|:-------:| +| CDS Syntax Highlighting | | | | +| CDS Code Completion | | | | +| CDS Prettifier | | | | +| Advanced Debug/Run Tools | | | | +| Project Explorer | | | | +| ... | | | | ### CDS Language & Compiler @@ -96,20 +96,20 @@ Following is an index of the features currently covered by CAP, with status and ### Providing Services -| Core Framework Features | CDS | Node.js | Java | -|-----------------------------------------------------------------------------------------|:-----:|:-------:|:----:| +| Core Framework Features | CDS | Node.js | Java | +|----------------------------------------------------------------------------------------|:-----:|:-------:|:----:| | [Automatically Serving CRUD Requests](../guides/providing-services#generic-providers) | | | | | [Deep-Read/Write Structured Documents](../guides/providing-services#deep-reads-writes) | | | | | [Automatic Input Validation](../guides/providing-services#input-validation) | | | | -| [Auto-filled Primary Keys](../guides/domain-modeling#prefer-uuids-for-keys) | | | | +| [Auto-filled Primary Keys](../guides/domain-modeling#prefer-uuids-for-keys) | | | | | [Implicit Paging](../guides/providing-services#implicit-pagination) | | | | | [Implicit Sorting](../guides/providing-services#implicit-sorting) | | | | -| [Access Control](../guides/authorization) | | | | -| [Arrayed Elements](../cds/cdl#arrayed-types) | | | | -| [Streaming & Media Types](../guides/media-data) | | | | +| [Access Control](../guides/authorization) | | | | +| [Arrayed Elements](../cds/cdl#arrayed-types) | | | | +| [Streaming & Media Types](../guides/media-data) | | | | | [Conflict Detection through _ETags_](../guides/providing-services#etag) | | | | -| [Authentication via JWT](../guides/authorization#prerequisite-authentication) | | | | -| [Basic Authentication](../guides/authorization#prerequisite-authentication) | | | | +| [Authentication via JWT](../guides/authorization#prerequisite-authentication) | | | | +| [Basic Authentication](../guides/authorization#prerequisite-authentication) | | | |
@@ -121,10 +121,10 @@ Following is an index of the features currently covered by CAP, with status and | [Localization/i18n](../guides/i18n) | | | | | [Localized Data](../guides/localized-data) | | | | | [Temporal Data](../guides/temporal-data) | | | | -| [Managed Data](../guides/domain-modeling#managed-data) | | | | +| [Managed Data](../guides/domain-modeling#managed-data) | | | | | [Dynamic Extensibility](../guides/extensibility/) | | | | | Monitoring / Logging [[Node.js](../node.js/cds-log)\|[Java](../java/observability#logging)] | | | | -| Audit Logging [[Node.js](../guides/data-privacy/audit-logging)\|[Java](../java/auditlog)] | | | | +| Audit Logging [[Node.js](../guides/data-privacy/audit-logging)\|[Java](../java/auditlog)] | | | |
@@ -151,22 +151,22 @@ Following is an index of the features currently covered by CAP, with status and ### Consuming Services | [Service Consumption APIs](../guides/using-services) | Node.js | Java | -|-------------------------------------------------------|:-------:|:----:| -| Uniform Consumption APIs → Hexagonal Architecture | | | -| Dynamic Querying | | | -| Programmatic Delegation | | | -| Generic Delegation | | | -| Resilience (retry, circuit breaking, ...) | | | +|------------------------------------------------------|:-------:|:----:| +| Uniform Consumption APIs → Hexagonal Architecture | | | +| Dynamic Querying | | | +| Programmatic Delegation | | | +| Generic Delegation | | | +| Resilience (retry, circuit breaking, ...) | | |
-| Outbound Protocol Support | CDS 1 | Node.js | Java | -|-------------------------------------------------------|:----------------:|:-------:|:----:| -| [REST/OpenAPI](../node.js/cds-dk#import-from-openapi) | | | | -| OData V2 | | | | -| OData V4 | | | | -| GraphQL2 | | | | +| Outbound Protocol Support | CDS 1 | Node.js | Java | +|-----------------------------------------------------------|:----------------:|:-------:|:----:| +| [REST/OpenAPI](../node.js/cds-dk#cds-import-from-openapi) | | | | +| OData V2 | | | | +| OData V4 | | | | +| GraphQL2 | | | | > 1 Import API to CSN
> 2 Could be a good case for 3rd-party contribution
@@ -188,16 +188,16 @@ Following is an index of the features currently covered by CAP, with status and ### Database Support -| | CDS/deploy | Node.js | Java | -|-------------------------------------------------|:----------:|:------------------:|:----:| -| [SAP HANA](../guides/databases) | | | | -| [SAP HANA Cloud](../guides/databases-hana) | | | | -| [PostgreSQL](../guides/databases-postgres) | | | | -| [SQLite](../guides/databases-sqlite) 1 | | | | -| [H2](../java/persistence-services#h2) 1 | | | | -| [MongoDB](../guides/databases) out of the box | | | | -| Pluggable drivers architecture | | | | -| Out-of-the-box support for other databases? | | | | +| | CDS/deploy | Node.js | Java | +|----------------------------------------------------|:----------:|:-------:|:----:| +| [SAP HANA](../guides/databases) | | | | +| [SAP HANA Cloud](../guides/databases-hana) | | | | +| [PostgreSQL](../guides/databases-postgres) | | | | +| [SQLite](../guides/databases-sqlite) 1 | | | | +| [H2](../java/persistence-services#h2) 1 | | | | +| [MongoDB](../guides/databases) out of the box | | | | +| Pluggable drivers architecture | | | | +| Out-of-the-box support for other databases? | | | | > 1 To speed up development. Not for productive use!
@@ -243,20 +243,20 @@ Following is an index of the features currently covered by CAP, with status and ### Extensibility { .impl.internal} -| | | -|--------------------------------------------------------------------------------------------------|:----:| -| [Tenant-Specific Extensions](../guides/extensibility/) | | -| [Adding Extension Fields](../guides/extensibility/customization#about-extension-models) | | -| [Adding new Entities](../guides/extensibility/customization#about-extension-models) | | -| [Adding new Relationships](../guides/extensibility/customization#about-extension-models) | | -| [Adding/Overriding Annotations](../guides/extensibility/customization) | | -| Adding Events | | -| [Extension Namespaces](../guides/extensibility/customization) | | -| [Extension Templates](../guides/extensibility/customization#templates) | | -| Custom Governance Checks | | -| [Generic Input Validations](../guides/providing-services#input-validation) | | -| Declarative Constraints | | -| Execute Sandboxed Code | | -| Runtime API for In-App Extensibility | | -| [Key-User Extensibility (incl. UI)](../guides/extensibility/ui-flex) | | -| Propagating Extensions across (µ) Services | | +| | | +|------------------------------------------------------------------------------------------|:----:| +| [Tenant-Specific Extensions](../guides/extensibility/) | | +| [Adding Extension Fields](../guides/extensibility/customization#about-extension-models) | | +| [Adding new Entities](../guides/extensibility/customization#about-extension-models) | | +| [Adding new Relationships](../guides/extensibility/customization#about-extension-models) | | +| [Adding/Overriding Annotations](../guides/extensibility/customization) | | +| Adding Events | | +| [Extension Namespaces](../guides/extensibility/customization) | | +| [Extension Templates](../guides/extensibility/customization#templates) | | +| Custom Governance Checks | | +| [Generic Input Validations](../guides/providing-services#input-validation) | | +| Declarative Constraints | | +| Execute Sandboxed Code | | +| Runtime API for In-App Extensibility | | +| [Key-User Extensibility (incl. UI)](../guides/extensibility/ui-flex) | | +| Propagating Extensions across (µ) Services | | diff --git a/guides/using-services.md b/guides/using-services.md index 11698d5b8..5180b7a62 100644 --- a/guides/using-services.md +++ b/guides/using-services.md @@ -202,7 +202,7 @@ When importing the specification files, the `kind` is set according to the follo | OpenAPI | `rest` | | AsyncAPI | `odata` | -[Learn more about type mappings from OData to CDS and vice versa.](../node.js/cds-dk#special-type-mappings){.learn-more} +[Learn more about type mappings from OData to CDS and vice versa.](../node.js/cds-dk#odata-type-mappings){.learn-more} ::: tip Always use OData V4 (`odata`) when calling another CAP service. diff --git a/node.js/cds-dk.md b/node.js/cds-dk.md index 744014b46..cf8d26903 100644 --- a/node.js/cds-dk.md +++ b/node.js/cds-dk.md @@ -1,39 +1,32 @@ --- -label: CDS Design Time -synopsis: > - This guide is about consuming CDS design-time APIs programmatically. -# layout: node-js status: released --- # CDS Design Time APIs -{{$frontmatter?.synopsis}} +This guide is about programmatic CDS design-time APIs. - +[[toc]] - - +## Install `@sap/cds-dk` -## Import `@sap/cds-dk` +The design-time APIs are provided with package `@sap/cds-dk` which needs to be installed locally in your project: -The design-time APIs are provided with package `@sap/cds-dk` and can be used as follows: - -1. Install it locally: ```sh npm add @sap/cds-dk ``` -2. Import it in Node.js: +That given, you can use the APIs in your project like this: ```js const cds = require('@sap/cds-dk') +cds.import(...) ``` -## cds.import (file, options) → [csn](../cds/csn) { #import } +## cds.import() {.method} As an application developer, you have the option to convert OData specification (EDMX / XML), OpenAPI specification (JSON) or AsyncAPI specification (JSON) files to CSN from JavaScript API as an alternative to the `cds import` command. @@ -50,16 +43,14 @@ const csn = await cds.import(file, options) * `file` — Specify the path to a single input file to be converted for CSN. * `options` — `cds.import()` support the following `options`: - - #### options.keepNamespace _This option is only applicable for OData conversion._
-| Value | Description | -|------- |---------------------------------------------------| -| `true` | Keep the original namespace from the EDMX content.| -| `false`| Take the filename as namespace. | +| Value | Description | +|---------|----------------------------------------------------| +| `true` | Keep the original namespace from the EDMX content. | +| `false` | Take the filename as namespace. | > If the option is not defined, then the CSN is generated with the namespace defined as EDMX filename.
@@ -70,18 +61,21 @@ _This option is only applicable for OData conversion._
It accepts a list of namespaces whose attributes are to be retained in the CSN / CDS file. To include all the namespaces present in the EDMX pass "*". > For OData V2 EDMX attributes with the namespace "sap" & "m" are captured by default. +
-## cds.import.from.edmx (file, options) → [csn](../cds/csn) { #import-from-edmx } +## cds.import.from.edmx() {.method} This API can be used to convert the OData specification file (EDMX / XML) into CSN. The API signature looks like this: ```js const csn = await cds.import.from.edmx(ODATA_EDMX_file, options) ``` + +
-## cds.import.from.openapi (file) → [csn](../cds/csn) { #import-from-openapi } +## cds.import.from.openapi() {.method} This API can be used to convert the OpenAPI specification file (JSON) into CSN. The API signature looks like this: @@ -90,7 +84,7 @@ const csn = await cds.import.from.openapi(OpenAPI_JSON_file) ```
-## cds.import.from.asyncapi (file) → [csn](../cds/csn) { #import-from-asyncapi } +## cds.import.from.asyncapi() {.method} This API can be used to convert the AsyncAPI specification file (JSON) into CSN. The API signature looks like this: @@ -126,20 +120,21 @@ module.exports = async (srv) => { } ``` -#### Special Type Mappings + + +## OData Type Mappings The following mapping is used during the import of an external service API, see [Using Services](../guides/using-services#external-service-api). In addition, the [Mapping of CDS Types](../advanced/odata#type-mapping) shows import-related mappings. | OData | CDS Type | -| ------------------------------------------------------ | ---------------------------------------------------------------------------- | -| _Edm.Single_ | `cds.Double` + `@odata.Type: 'Edm.Single'` | -| _Edm.Byte_ | `cds.Integer` + `@odata.Type: 'Edm.Byte'` | -| _Edm.SByte_ | `cds.Integer` + `@odata.Type: 'Edm.SByte'` | -| _Edm.Stream_ | `cds.LargeBinary` + `@odata.Type: 'Edm.Stream'` | +|--------------------------------------------------------|------------------------------------------------------------------------------| +| _Edm.Single_ | `cds.Double` + `@odata.Type: 'Edm.Single'` | +| _Edm.Byte_ | `cds.Integer` + `@odata.Type: 'Edm.Byte'` | +| _Edm.SByte_ | `cds.Integer` + `@odata.Type: 'Edm.SByte'` | +| _Edm.Stream_ | `cds.LargeBinary` + `@odata.Type: 'Edm.Stream'` | | _Edm.DateTimeOffset
Precision : Microsecond_ | `cds.Timestamp` + `@odata.Type:'Edm.DateTimeOffset'` + `@odata.Precision:<>` | | _Edm.DateTimeOffset
Precision : Second_ | `cds.DateTime` + `@odata.Type:'Edm.DateTimeOffset'` + `@odata.Precision:0` | | _Edm.DateTime
Precision : Microsecond_ 1 | `cds.Timestamp` + `@odata.Type:'Edm.DateTime'` + `@odata.Precision:<>` | | _Edm.DateTime
Precision : Second_ 1 | `cds.DateTime` + `@odata.Type:'Edm.DateTime'` + `@odata.Precision:0` | 1 only OData V2 - From 00fe820dc33339259b3084131a9d93e39c547962 Mon Sep 17 00:00:00 2001 From: Daniel Hutzel Date: Wed, 27 Sep 2023 19:15:07 +0200 Subject: [PATCH 20/45] . --- menu.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/menu.md b/menu.md index 77fdb96b9..5b2f85b20 100644 --- a/menu.md +++ b/menu.md @@ -132,7 +132,7 @@ - [cds. ql ...](node.js/cds-ql) - [cds. tx()](node.js/cds-tx) - [cds. log()](node.js/cds-log) -- [cds .import()](node.js/cds-dk#import) +- [cds .import()](node.js/cds-dk#cds-import) - [cds. env](node.js/cds-env) - [cds. auth](node.js/authentication) - [cds. i18n](node.js/cds-i18n) From 829d3bad2a6ab1ed0c8340a196aebcbe094b9d22 Mon Sep 17 00:00:00 2001 From: Marc Becker Date: Thu, 28 Sep 2023 07:43:19 +0200 Subject: [PATCH 21/45] Adjust Cloud SDK minimum versions (#445) --- java/development/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/java/development/index.md b/java/development/index.md index 5834b9beb..485005edc 100644 --- a/java/development/index.md +++ b/java/development/index.md @@ -252,9 +252,9 @@ The CAP Java SDK uses various dependencies that are also used by the application | Java | 17 | 17 | | @sap/cds-dk | 6 | latest | | @sap/cds-compiler | 3 | latest | -| Spring Boot | 3.0 | 3.0 | +| Spring Boot | 3.0 | latest | | XSUAA | 3.0 | latest | -| SAP Cloud SDK | 4.13 | latest | +| SAP Cloud SDK | 4.24 | latest | | Java Logging | 3.7 | latest | ::: warning From 4ef9bad4b502b51f8ce6716c762312303c31884b Mon Sep 17 00:00:00 2001 From: Steffen Waldmann Date: Thu, 28 Sep 2023 08:37:12 +0200 Subject: [PATCH 22/45] Align code fences (#345) * Align code fences * consistent code fences --------- Co-authored-by: Rene Jeglinsky --- advanced/hybrid-testing.md | 12 ++++++------ guides/using-services.md | 2 +- java/development/index.md | 6 +++--- node.js/authentication.md | 6 +++--- node.js/cds-env.md | 6 +++--- node.js/cds-test.md | 2 ++ 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/advanced/hybrid-testing.md b/advanced/hybrid-testing.md index 37ed00a89..1d1c9236e 100644 --- a/advanced/hybrid-testing.md +++ b/advanced/hybrid-testing.md @@ -246,13 +246,13 @@ With `cds bind` you avoid storing credentials on your hard disk. If you need to For example, you can run the approuter from the `approuter` child directory: ::: code-group -```sh +```sh [Mac/Linux] cds bind --exec -- npm start --prefix approuter ``` -```cmd +```cmd [Windows] cds bind --exec -- npm start --prefix approuter ``` -```powershell +```powershell [Powershell] cds bind --exec '--' npm start --prefix approuter ``` ::: @@ -260,13 +260,13 @@ cds bind --exec '--' npm start --prefix approuter This works by building up a `VCAP_SERVICES` variable from the bindings in the chosen profiles (default: `hybrid`). You can run the following command to print the content of the generated `VCAP_SERVICES` variable: ::: code-group -```sh +```sh [Mac/Linux] cds bind --exec -- node -e 'console.log(process.env.VCAP_SERVICES)' ``` -```cmd +```cmd [Windows] cds bind --exec -- node -e 'console.log(process.env.VCAP_SERVICES)' ``` -```powershell +```powershell [Powershell] cds bind --exec '--' node -e 'console.log(process.env.VCAP_SERVICES)' ``` ::: diff --git a/guides/using-services.md b/guides/using-services.md index 5180b7a62..3688c543a 100644 --- a/guides/using-services.md +++ b/guides/using-services.md @@ -125,7 +125,7 @@ cds compile srv -s OrdersService -2 edmx > OrdersService.edmx cds compile srv -s OrdersService -2 edmx > OrdersService.edmx ``` -```powershell [Powershell] +```powershell [Powershell] cds compile srv -s OrdersService -2 edmx -o dest/ ``` ::: diff --git a/java/development/index.md b/java/development/index.md index 485005edc..78b1d9536 100644 --- a/java/development/index.md +++ b/java/development/index.md @@ -284,15 +284,15 @@ This section describes various options to create a CAP Java project from scratch Use the following command line to create a project from scratch with the CDS Maven archetype: ::: code-group -```sh +```sh [Mac/Linux] mvn archetype:generate -DarchetypeArtifactId=cds-services-archetype -DarchetypeGroupId=com.sap.cds -DarchetypeVersion=RELEASE ``` -```cmd +```cmd [Windows] mvn archetype:generate -DarchetypeArtifactId=cds-services-archetype -DarchetypeGroupId=com.sap.cds -DarchetypeVersion=RELEASE ``` -```powershell +```powershell [Powershell] mvn archetype:generate `-DarchetypeArtifactId=cds-services-archetype `-DarchetypeGroupId=com.sap.cds `-DarchetypeVersion=RELEASE ``` ::: diff --git a/node.js/authentication.md b/node.js/authentication.md index 0b9100681..f2a4a6d12 100644 --- a/node.js/authentication.md +++ b/node.js/authentication.md @@ -564,13 +564,13 @@ The resulting JWT token is sent to the application where it's used to enforce au 3. In your project folder run: ::: code-group - ```sh + ```sh [Mac/Linux] cds bind --exec -- npm start --prefix app ``` - ```cmd + ```cmd [Windows] cds bind --exec -- npm start --prefix app ``` - ```powershell + ```powershell [Powershell] cds bind --exec '--' npm start --prefix app ``` ::: diff --git a/node.js/cds-env.md b/node.js/cds-env.md index 3aff8b17e..aeb49622e 100644 --- a/node.js/cds-env.md +++ b/node.js/cds-env.md @@ -423,14 +423,14 @@ cds run --profile my-custom-profile or ::: code-group -```sh +```sh [Mac/Linux] CDS_ENV=my-custom-profile cds run ``` -```cmd +```cmd [Windows] set CDS_ENV=my-custom-profile cds run ``` -```powershell +```powershell [Powershell] $Env:CDS_ENV=my-custom-profile cds run ``` diff --git a/node.js/cds-test.md b/node.js/cds-test.md index 6a7a7d742..c47ff1175 100644 --- a/node.js/cds-test.md +++ b/node.js/cds-test.md @@ -54,8 +54,10 @@ To ensure `cds.env`, and hence all plugins, are loaded from the test's target fo ### Testing Service APIs + As `cds.test()` launches the server in the current process, you can access all services programmatically using the respective [Node.js Service APIs](core-services). Here's an example for that taken from [*cap/samples*](https://github.com/SAP-samples/cloud-cap-samples/blob/a8345122ea5e32f4316fe8faef9448b53bd097d4/test/consuming-services.test.js#L2): + ```js it('Allows testing programmatic APIs', async () => { const AdminService = await cds.connect.to('AdminService') From 9b7d4879b962238b3cde94af9d76670d2a3ed45f Mon Sep 17 00:00:00 2001 From: Johannes Vogel <31311694+johannes-vogel@users.noreply.github.com> Date: Thu, 28 Sep 2023 08:37:54 +0200 Subject: [PATCH 23/45] [chore] cds 7.3 will not set cloud sdk log level anymore (#424) [chore] cds 7.3will not set cloud sdk log level anymore --- guides/using-services.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/using-services.md b/guides/using-services.md index 3688c543a..483d7b360 100644 --- a/guides/using-services.md +++ b/guides/using-services.md @@ -389,7 +389,7 @@ First install the required packages: ```sh -npm add @sap-cloud-sdk/http-client@3.x @sap-cloud-sdk/util@3.x @sap-cloud-sdk/connectivity@3.x @sap-cloud-sdk/resilience@3.x +npm add @sap-cloud-sdk/http-client@3.x @sap-cloud-sdk/connectivity@3.x @sap-cloud-sdk/resilience@3.x ``` Then start the CAP application with the mocked remote service only: From b5b1b4f4b563a9812ec7152ecfb22ea109d78623 Mon Sep 17 00:00:00 2001 From: "Keckl, Matthias" <127967727+MatthiasAtSAP@users.noreply.github.com> Date: Thu, 28 Sep 2023 15:48:27 +0200 Subject: [PATCH 24/45] Format path in linter suggestions properly (#454) --- .github/etc/create-review.cjs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/etc/create-review.cjs b/.github/etc/create-review.cjs index 3505c528d..b727dd1e5 100644 --- a/.github/etc/create-review.cjs +++ b/.github/etc/create-review.cjs @@ -189,15 +189,18 @@ module.exports = async ({ github, require, exec, core }) => { continue } + // Github requires that no path starts with './', but cspell provides the paths exactly in this format + const properlyStructuredPath = path.replace(/^\.\//, '') + if (suggestions.length > 0) { // replace word with first suggestions and remove first "+" sign const suggestion = line.replace(word, suggestions[0]).replace('+', '') const commentBody = createCspellSuggestionText(suggestion, suggestions.slice(1)) - comments.push({ path, position, body: commentBody }) + comments.push({ path: properlyStructuredPath, position, body: commentBody }) } else { - comments.push({ path, position, body: createUnknownWordComment(word) }) + comments.push({ path: properlyStructuredPath, position, body: createUnknownWordComment(word) }) wordsWithoutSuggestions.push(word) } @@ -205,11 +208,11 @@ module.exports = async ({ github, require, exec, core }) => { spellingMistakesText += `* **${path}**${pointer} Unknown word "**${word}**"\n` } - if (wordsWithoutSuggestions.length > 0) { + if (wordsWithoutSuggestions.length > 0 && comments.length > 0) { spellingMistakesText += `\n${createWordsWithoutSuggestionsText(wordsWithoutSuggestions)}\n` } - if (matches.length > 0) { + if (matches.length > 0 && comments.length > 0) { spellingMistakesText += `${getSpellingCorrectionTip()}\n` } From e6b15d75df71048a668d1d70f6d40baeba96fd45 Mon Sep 17 00:00:00 2001 From: Steffen Waldmann Date: Fri, 29 Sep 2023 08:32:27 +0200 Subject: [PATCH 25/45] Clean up Node/Java scoping in Hello World (#453) Currently showing a lot of irrelevant Node content with the Java toggle enabled and vice versa. --- get-started/hello-world.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/get-started/hello-world.md b/get-started/hello-world.md index 251426b5b..e1dd3b13e 100644 --- a/get-started/hello-world.md +++ b/get-started/hello-world.md @@ -66,8 +66,6 @@ service say { ... for example, using [Node.js](../node.js/) express.js handlers style. -
- ::: code-group ```js [srv/world.js] @@ -88,7 +86,9 @@ module.exports = class say { ``` ::: -> That has limited flexibility, for example, you can register only one handler per event. { .impl .node} +> That has limited flexibility, for example, you can register only one handler per event. + +
@@ -126,21 +126,26 @@ public class HelloHandler implements EventHandler { ## Run it + ... for example, from your command line in the root directory of your "Hello World": -::: code-group -```sh [Node.js] +
+ +```sh cds watch ``` -```sh [Java] +
+ +
+ +```sh cd srv mvn cds:watch ``` -::: - +
## Consume it ... for example, from your browser:
From 355bb674e2238b58a8efa0ab5d2fe8f829a7420b Mon Sep 17 00:00:00 2001 From: Matthias Schur <107557548+MattSchur@users.noreply.github.com> Date: Fri, 29 Sep 2023 12:13:18 +0200 Subject: [PATCH 26/45] Fix "Extracting Filter Values" example (#456) --- java/query-introspection.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/java/query-introspection.md b/java/query-introspection.md index 508a8b59b..e2febc398 100644 --- a/java/query-introspection.md +++ b/java/query-introspection.md @@ -143,8 +143,8 @@ Examples: ```sql WHERE name = 'Sue' WHERE name = 'Bob' AND age = 50 -WHERE name = 'Alice' AND (age = 25 OR and age = 35) -WHERE name = 'Alice' AND age = 25 OR name = 'Alice' and age = 35 +WHERE name = 'Alice' AND (age = 25 OR age = 35) +WHERE name = 'Alice' AND age = 25 OR name = 'Alice' AND age = 35 ``` The first example above maps `name` to `Sue`. The second example maps `name` to 'Bob' and `age` to 50. In the third example only `name` is unambigously mapped to 'Alice' but a value for `age` can't be extracted. The fourth example is equivalent to the third. @@ -159,7 +159,7 @@ Map targetKeys = result.targetKeys(); Integer itemId = (Integer) targetKeys.get("ID"); // 1 ``` -To extract all filter values of the target entity including nonkey values, the `targetValues` method can be used: +To extract all filter values of the target entity including non-key values, the `targetValues` method can be used: ```java Map filterValues = result.targetValues(); From 19d5558ebaf4532390921b595ccc662e6f4b356d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20G=C3=B6rler?= Date: Fri, 29 Sep 2023 12:14:33 +0200 Subject: [PATCH 27/45] Java: Use List Values with IN (#451) * Update query-api.md * Apply suggestions from code review Co-authored-by: Matthias Schur <107557548+MattSchur@users.noreply.github.com> --------- Co-authored-by: Matthias Schur <107557548+MattSchur@users.noreply.github.com> --- java/query-api.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/java/query-api.md b/java/query-api.md index 7260b75d6..34ad33544 100644 --- a/java/query-api.md +++ b/java/query-api.md @@ -1277,18 +1277,25 @@ Combine multiple values with `CQL.list` to a list value (row value), which you c For example, the following query returns all sales after Q2/2012: ```java -import static com.sap.cds.ql.CQL.list; -import static com.sap.cds.ql.CQL.get; -import static com.sap.cds.ql.CQL.val; -import static com.sap.cds.ql.CQL.comparison; - -... +import static com.sap.cds.ql.CQL.*; CqnListValue props = list(get("year"), get("quarter")); CqnListValue vals = list(val(2012), val(2)); CqnSelect q = Select.from(SALES).where(comparison(props, GT, vals)); ``` +You can also compare multiple list values at once using an `IN` predicate - for example to efficiently filter by multiple key value sets: + +```java +import static com.sap.cds.ql.CQL.*; + +CqnListValue elements = list(get("AirlineID"), get("ConnectionID")); +CqnListValue lh454 = list(val("LH"), val(454)); +CqnListValue ba119 = list(val("BA"), val(119)); + +CqnSelect q = Select.from(FLIGHT_CONNECTION).where(in(elements, List.of(lh454, ba119))); +``` + #### Parameters {#expr-param} The [`param`](https://javadoc.io/doc/com.sap.cds/cds4j-api/latest/com/sap/cds/ql/CQL.html#param--) method can be statically imported from the helper class [CQL](https://javadoc.io/doc/com.sap.cds/cds4j-api/latest/com/sap/cds/ql/CQL.html). It provides an option to use a parameter marker in a query that is bound to an actual value only upon query execution. Using parameters you can execute a query multiple times with different parameter values. From 0747570689db084f18f45d47d3f26ee7e7a8dcaa Mon Sep 17 00:00:00 2001 From: Daniel Hutzel Date: Sat, 30 Sep 2023 04:29:05 +0200 Subject: [PATCH 28/45] Fixed typo in core-services.md --- node.js/core-services.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js/core-services.md b/node.js/core-services.md index a408bb395..90e59dfc9 100644 --- a/node.js/core-services.md +++ b/node.js/core-services.md @@ -936,7 +936,7 @@ await db.run (tx => { > 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 all 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. +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} From 5fa458b4f469efe60ede1f1e81c970e28ebaa42a Mon Sep 17 00:00:00 2001 From: Daniel Hutzel Date: Sat, 30 Sep 2023 14:32:16 +0200 Subject: [PATCH 29/45] Cosmetics --- get-started/in-a-nutshell.md | 14 +++++++------- guides/providing-services.md | 12 ++++++++---- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/get-started/in-a-nutshell.md b/get-started/in-a-nutshell.md index b570847fa..f84a86739 100644 --- a/get-started/in-a-nutshell.md +++ b/get-started/in-a-nutshell.md @@ -21,7 +21,7 @@ This guide is a step-by-step walkthrough to build a CAP application, using a min [[toc]] -## ⓪ Preliminaries +## Preliminaries 1. **Prerequisite:** The following steps assume you've installed Node.js, Visual Studio Code, and `@sap/cds-dk` as described in the [_Setup_ section of the _Jumpstart_ guide](jumpstart#setup). @@ -47,7 +47,7 @@ git clone https://github.com/sap-samples/cloud-cap-samples-java bookshop -## ① Jumpstart a Project { #start-a-project} +## 1. Jumpstart a Project { #start-a-project} 1. Create a new project using `cds init` @@ -108,7 +108,7 @@ So, let's go on adding some CDS model as follows... -## ② Capture Domain Models { #domain-models } +## 2. Capture Domain Models { #domain-models } Let's feed our project by adding a simple domain model. Start by creating a file named _db/schema.cds_ (also indicated in the code box's label) and copy the following definitions into it: @@ -196,7 +196,7 @@ cds db/schema.cds -2 sql -## ③ Providing Services { #defining-services} +## 3. Providing Services { #defining-services} @@ -318,7 +318,7 @@ cds srv/cat-service.cds -2 edmx Essentially, using a CLI, 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. -## ④ Using Databases {#databases} +## 4. Using Databases {#databases} @@ -450,7 +450,7 @@ cds deploy --to hana [Learn more about deploying to SAP HANA.](../guides/databases){.learn-more .impl .node} -## ⑤ Adding/Serving UIs {#adding-serving-uis} +## 5. Adding/Serving UIs {#adding-serving-uis} You can consume the provided services, for example, from UI frontends, using standard AJAX requests. Simply add an _index.html_ file into the _app/_ folder, to replace the generic index page. @@ -480,7 +480,7 @@ query options, such as `$select`, `$expand`, `$search`, and many more. [Learn more about **Serving OData Protocol**.](../advanced/odata){.learn-more} -## ⑥ Adding Custom Logic {#adding-custom-logic} +## 6. Adding Custom Logic {#adding-custom-logic} While the generic providers serve most CRUD requests out of the box, you can add custom code to deal with the specific domain logic of your application. diff --git a/guides/providing-services.md b/guides/providing-services.md index 4cada4153..af15b4e50 100644 --- a/guides/providing-services.md +++ b/guides/providing-services.md @@ -920,7 +920,9 @@ public class FooServiceImpl implements EventHandler {...} Within your custom implementations, you can register event handlers like that: -```js +::: code-group + +```js [Node.js] const cds = require('@sap/cds') module.exports = function (){ this.on ('submitOrder', (req)=>{...}) //> custom actions @@ -929,9 +931,7 @@ module.exports = function (){ this.after ('READ',`Books`, (each)=>{...}) } ``` -[Learn more about **adding event handlers in Node.js**.](../node.js/core-services#srv-on-before-after){.learn-more} - -```js +```Java @Component @ServiceName("BookshopService") public class BookshopServiceImpl implements EventHandler { @@ -942,6 +942,10 @@ public class BookshopServiceImpl implements EventHandler { } ``` +::: + +[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/provisioning-api#handlerclasses){.learn-more} From 3894949b808f4a393b6ffbe1109676d04b3b8ae1 Mon Sep 17 00:00:00 2001 From: Steffen Weinstock <79531202+stewsk@users.noreply.github.com> Date: Mon, 2 Oct 2023 10:41:48 +0200 Subject: [PATCH 30/45] Remove "beta" from Calculated Elements (#455) --- cds/cdl.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/cds/cdl.md b/cds/cdl.md index b5c162299..8f18785f0 100644 --- a/cds/cdl.md +++ b/cds/cdl.md @@ -255,12 +255,7 @@ entity ![Entity] { -### Calculated Elements (beta) {#calculated-elements} - -::: warning -This is a beta feature. Beta features aren't part of the officially delivered scope that SAP guarantees for future releases. -For more information, see [Important Disclaimers and Legal Information](https://help.sap.com/viewer/disclaimer). -::: +### Calculated Elements {#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. @@ -272,7 +267,7 @@ When reading a calculated element, the result of the expression is returned. Calculated elements with a value expression 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 (beta) +#### On-read ```cds entity Employees { @@ -326,7 +321,7 @@ A calculated element can be *used* in every location where an expression can occ For the Node.js runtime, only the new database services under the _@cap-js_ scope support this feature. ::: -#### On-write (beta) +#### 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. From ec7582752f486bfce8cbd90eaf8928741e027101 Mon Sep 17 00:00:00 2001 From: Andre Meyering Date: Mon, 2 Oct 2023 10:42:18 +0200 Subject: [PATCH 31/45] ci: Use @sap/cds-compiler v4 for snippet checker (#458) --- .github/cds-snippet-checker/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/cds-snippet-checker/package.json b/.github/cds-snippet-checker/package.json index 9bdd9ec63..3a171fd32 100644 --- a/.github/cds-snippet-checker/package.json +++ b/.github/cds-snippet-checker/package.json @@ -12,6 +12,6 @@ "check": "node check-cds-snippets.js" }, "dependencies": { - "@sap/cds-compiler": "^3.9.2" + "@sap/cds-compiler": "^4.2.0" } } From 33b446f9d317b2b84f7425e5a16c8b7b6f96f03f Mon Sep 17 00:00:00 2001 From: Steffen Weinstock <79531202+stewsk@users.noreply.github.com> Date: Mon, 2 Oct 2023 13:04:18 +0200 Subject: [PATCH 32/45] Expose assoc filter (#457) * capire: exposing associations in views * Publishing associations * Stable link anchor * improve a bit * Update cds/cdl.md Co-authored-by: Andre Meyering --------- Co-authored-by: Andre Meyering --- cds/cdl.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/cds/cdl.md b/cds/cdl.md index 8f18785f0..ae61611fc 100644 --- a/cds/cdl.md +++ b/cds/cdl.md @@ -751,6 +751,74 @@ GET /Teams?$expand=members($expand=user) to get all users of all teams. +### Publish Associations in Projections {#publish-associations} + +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. + +Example: +```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 Filter (beta) {#publish-associations-with-filter} + +::: warning +This is a beta feature. Beta features aren't part of the officially delivered scope that SAP guarantees for future releases. +For more information, see [Important Disclaimers and Legal Information](https://help.sap.com/viewer/disclaimer). +::: + +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`. + +Example: +```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 +to one, you should make this explicit in the filter by adding a `1:` before the condition: + +Example: +```cds +entity P_Employees as projection on Employees { + *, + addresses[1: kind='home'] as homeAddress // homeAddress is to-one +} +``` + +An association that has been published with a filter is read-only. It must not be +used to modify the target entity. + +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. + +Example: +```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 +}; +``` + + ## 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](../advanced/odata#annotations) guide. From 4c9e67d862008d3a9a38d1d38afaf8930cb93126 Mon Sep 17 00:00:00 2001 From: Matthias Schur <107557548+MattSchur@users.noreply.github.com> Date: Mon, 2 Oct 2023 21:39:28 +0200 Subject: [PATCH 33/45] `@cds.collate` annotation (#461) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * `@cds.collate` annotation * editing * Update java/persistence-services.md --------- Co-authored-by: René Jeglinsky --- java/persistence-services.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/java/persistence-services.md b/java/persistence-services.md index 99991dea6..e19dd34c8 100644 --- a/java/persistence-services.md +++ b/java/persistence-services.md @@ -18,14 +18,32 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ ## Database Support { #database-support} -The CAP Java SDK has built-in support for various databases. This section describes the different databases and any differences between them with respect to CAP features. There's out of the box support for SAP HANA with CAP currently as well as H2 and SQLite. However, it's important to note that H2 and SQLite aren't an enterprise grade database and are recommended for nonproductive use like local development or CI tests only. PostgreSQL is supported in addition, but has various limitations in comparison to SAP HANA, most notably in the area of schema evolution. +CAP Java has built-in support for various databases. This section describes the different databases and any differences between them with respect to CAP features. There's out of the box support for SAP HANA with CAP currently as well as H2 and SQLite. However, it's important to note that H2 and SQLite aren't an enterprise grade database and are recommended for nonproductive use like local development or CI tests only. PostgreSQL is supported in addition, but has various limitations in comparison to SAP HANA, most notably in the area of schema evolution. -### SAP HANA (Cloud) +Write operations through views are supported by the CAP runtime as described in [Resolvable Views](query-execution#updatable-views). Operations on views that cannot be resolved by the CAP runtime are passed through to the database. -SAP HANA is supported as the CAP standard database and recommended for productive use with needs for schema evolution and multitenancy. Some salient points to note with SAP HANA are: +### SAP HANA Cloud -1. Views are supported as described in [Resolvable Views](query-execution#updatable-views), else any operation on views are defaulted to SAP HANA, which has limitations as described in the [SAP HANA Cloud documentation](https://help.sap.com/docs/HANA_CLOUD_DATABASE/c1d3f60099654ecfb3fe36ac93c121bb/20d5fa9b75191014a33eee92692f1702.html#loio20d5fa9b75191014a33eee92692f1702__section_trx_ckh_qdb). -2. Shared locks are supported on SAP HANA Cloud only +SAP HANA Cloud is the CAP standard database recommended for productive use with needs for schema evolution and multitenancy. + +1. Write operations through views that can't be resolved by the CAP runtime are passed through to SAP HANA Cloud. Limitations are described in the [SAP HANA Cloud documentation](https://help.sap.com/docs/HANA_CLOUD_DATABASE/c1d3f60099654ecfb3fe36ac93c121bb/20d5fa9b75191014a33eee92692f1702.html#loio20d5fa9b75191014a33eee92692f1702__section_trx_ckh_qdb). + +2. [Shared locks](../java/query-execution#pessimistic-locking) are supported on SAP HANA Cloud only. + +3. When using `String` elements in locale-specific ordering relations (`>`, `<`, ... , `between`) a statement-wide collation is added that can have negative impact on the performance. If locale-specific ordering isn't required for specific `String` elements, annotate the element with `@cds.collate: false`. + +```cds +entity Books : cuid { + title : localized String(111); + descr : localized String(1111); + @cds.collate : false // [!code focus] + isbn : String(40); // does not require locale-specific sorting // [!code focus] +} +``` + +:::tip Disable Statement-Wide Collation +To disable statement-wide collation for all queries, set [`cds.sql.hana.ignoreLocale`](../java/development/properties#cds-sql-hana-ignoreLocale) to `true`. +::: ### PostgreSQL From a42d1edd026d9e3ba0de3723b8cedd5be248f6e3 Mon Sep 17 00:00:00 2001 From: Stefan Henke Date: Wed, 4 Oct 2023 11:31:33 +0200 Subject: [PATCH 34/45] Xsuaa support in cds-feature-identity (#431) * feature identity supports xsuaa * feature identity supports xsuaa * feature identity supports xsuaa * feature identity supports xsuaa * incorporate feedback * incorporate feedback * editing --------- Co-authored-by: Rene Jeglinsky --- java/security.md | 59 +++++++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 36 deletions(-) diff --git a/java/security.md b/java/security.md index 2cafdf6a1..f5e5d046b 100644 --- a/java/security.md +++ b/java/security.md @@ -35,59 +35,46 @@ Hence both, authentication and authorization, are essential for application secu * [Authentication](#authentication) describes how to configure authentication. * [Authorization](#auth) describes how to configure access control. -::: warning _❗ Warning_ +::: warning Without security configured, CDS services are exposed to public. Proper configuration of authentication __and__ authorization is required to secure your CAP application. ::: ## Authentication { #authentication} -User requests with invalid authentication need to be rejected as soon as possible, to limit the resource impact to a minimum. Ideally, authentication is one of the first steps when processing a request. This is one reason why it's not an integral part of the CAP runtime and needs to be configured on application framework level. In addition, CAP Java is based on a [modular architecture](architecture#modular_architecture) and allows flexible configuration of the authentication method. For productive scenarios, [XSUAA](#xsuaa) and [IAS](#ias) authentication is supported out of the box, but a [custom authentication](#custom-authentication) can be configured as well. For the local development and test scenario, there's a built-in [mock user](#mock-users) support. - -### Configure XSUAA Authentication { #xsuaa} - -Your application is secured by XSUAA-authentication **automatically**, if -1. Following dependencies are set: - * `xsuaa-spring-boot-starter` that brings Spring Security and [xsuaa library](https://github.com/SAP/cloud-security-xsuaa-integration) - * `cds-feature-xsuaa` -2. The application is bound to an [XSUAA service instance](../guides/authorization#xsuaa-configuration) -::: tip -CAP Java picks only a single XSUAA binding. If you have multiple bindings, choose a specific binding with property `cds.security.xsuaa.binding`. -Choose an appropriate XSUAA service plan to fit the requirements. For instance, if your service should be exposed as technical reuse service, make use of plan `broker`. -::: - -The individual dependencies can be explicitly added in the `pom.xml` file of your service. +User requests with invalid authentication need to be rejected as soon as possible, to limit the resource impact to a minimum. Ideally, authentication is one of the first steps when processing a request. This is one reason why it's not an integral part of the CAP runtime and needs to be configured on application framework level. In addition, CAP Java is based on a [modular architecture](architecture#modular_architecture) and allows flexible configuration of the authentication method. For productive scenarios, [XSUAA and IAS](#xsuaa-ias) authentication is supported out of the box, but a [custom authentication](#custom-authentication) can be configured as well. For the local development and test scenario, there's a built-in [mock user](#mock-users) support. -Recommended alternative is to use the `cds-starter-cloudfoundry` or the `cds-starter-k8s` starter bundle, which covers all required dependencies for XSUAA-authentication. +### Configure XSUAA and IAS Authentication { #xsuaa-ias} +To enable your application for XSUAA or IAS-authentication we recommend to use the `cds-starter-cloudfoundry` or the `cds-starter-k8s` starter bundle, which covers all required dependencies. -### Configure IAS Authentication { #ias} +:::details Individual Dependencies +These are the individual dependencies that can be explicitly added in the `pom.xml` file of your service: + * `com.sap.cloud.security:resourceserver-security-spring-boot-starter` that brings [spring-security library](https://github.com/SAP/cloud-security-services-integration-library/tree/main/spring-security) + * `org.springframework.boot:spring-boot-starter-security` + * `cds-feature-identity` -Your application is secured by IAS-authentication **automatically**, if -1. Following dependencies are set: - * `resourceserver-security-spring-boot-starter` that brings Spring Security and [Java security library](https://github.com/SAP/cloud-security-xsuaa-integration) - * `cds-feature-identity` -2. The application is bound to an [IAS service instance](https://help.sap.com/docs/IDENTITY_AUTHENTICATION) -::: warning -To enforce IAS authentication, make sure no XSUAA instance is bound to the CAP service at the same time. -::: -::: tip -To allow forwarding to remote services, JWT tokens issued by IAS service do not contain authorization information. In particular, no scopes are included. Closing this gap is up to you in your application. ::: -### Configure IAS and XSUAA Authentication (Hybrid) { #hybrid} +In addition, your application needs to be bound to corresponding service instances depending on your scenario. The following list describes which service needs to be bound depending on the tokens your applications should accept: + * only accept tokens issued by XSUAA --> bind your application to an [XSUAA service instance](../guides/authorization#xsuaa-configuration) + * only accept tokens issued by IAS --> bind your application to an [IAS service instance](https://help.sap.com/docs/IDENTITY_AUTHENTICATION) + * accept tokens issued by XSUAA and IAS --> bind your application to service instances of both types. -It is possible to support IAS-authentication and XSUAA-authentication at the same time (hybrid). In this case, the CAP application will accept tokens issued by IAS and XSUAA. +::: tip Specify Binding +CAP Java picks only a single binding of each type. If you have multiple XSUAA or IAS bindings, choose a specific binding with property `cds.security.xsuaa.binding`respectively `cds.security.identity.binding`. +Choose an appropriate XSUAA service plan to fit the requirements. For instance, if your service should be exposed as technical reuse service, make use of plan `broker`. +::: -Your application is secured by the hybrid mode **automatically**, if -1. You enabled IAS-authentication as described in [Configure IAS Authentication](#ias) -2. The application is additionally bound to an [XSUAA service instance](../guides/authorization#xsuaa-configuration) +### Transition from `cds-feature-xsuaa` to `cds-feature-identity`{ #transition-xsuaa-ias} +CAP also provides support for XSUAA-based authentication via the maven dependency `cds-feature-xsuaa` which is based on the [spring-xsuaa library](https://github.com/SAP/cloud-security-services-integration-library/tree/main/spring-xsuaa). As the spring-xsuaa library is deprecated, it is recommended to move to `cds-feature-identity`. -::: tip -In hybrid mode, the same constraints in regards to multiple XSUAA bindings applies as described in [Configure XSUAA Authentication](#xsuaa) +To do so, remove existing dependencies to `cds-feature-xsuaa` and `xsuaa-spring-boot-starter` and follow the description in [Configure XSUAA and IAS Authentication](#xsuaa-ias). +::: tip Backward Compatibility: Exclude Dependencies When Using Bundles +If you are using the `cds-starter-cloudfoundry` or the `cds-starter-k8s` starter bundle, make sure to **explicitly** exclude the mentioned dependencies using `...`. Otherwise, `cds-feature-xsuaa` will take priority over `cds-feature-identity` for backward compatibility. ::: ### Automatic Spring Boot Security Configuration { #spring-boot} -Only if **both, the library dependencies and an XSUAA resp. IAS service binding are in place**, the CAP Java SDK activates a Spring security configuration, which enforces authentication for all endpoints **automatically**: +Only if **both, the library dependencies and an XSUAA/IAS service binding are in place**, the CAP Java SDK activates a Spring security configuration, which enforces authentication for all endpoints **automatically**: * Protocol adapter endpoints (managed by CAP such as OData V4/V2 or custom protocol adapters) * Remaining custom endpoints (not managed by CAP such as custom REST controllers or Spring Actuators) From dca03acefa8597b502d179631751a1692c50fd90 Mon Sep 17 00:00:00 2001 From: Steffen Weinstock <79531202+stewsk@users.noreply.github.com> Date: Wed, 4 Oct 2023 11:33:25 +0200 Subject: [PATCH 35/45] Default for managed associations (#443) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Default for managed associations * don't say beta --------- Co-authored-by: René Jeglinsky --- cds/cdl.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cds/cdl.md b/cds/cdl.md index ae61611fc..955d5f426 100644 --- a/cds/cdl.md +++ b/cds/cdl.md @@ -600,6 +600,14 @@ key element `address_ID` being added automatically upon activation to a SQL data > For adding foreign key constraints on database level, see [Database Constraints.](../guides/databases#db-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 From 276032702d939531a0a4d837748e0c3cd8114fad Mon Sep 17 00:00:00 2001 From: "Keckl, Matthias" <127967727+MatthiasAtSAP@users.noreply.github.com> Date: Wed, 4 Oct 2023 11:40:18 +0200 Subject: [PATCH 36/45] Update color for cds commands (#464) Co-authored-by: Steffen Waldmann --- .vitepress/syntaxes/log.tmLanguage.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vitepress/syntaxes/log.tmLanguage.json b/.vitepress/syntaxes/log.tmLanguage.json index 9e8f207c7..7f4499bc5 100644 --- a/.vitepress/syntaxes/log.tmLanguage.json +++ b/.vitepress/syntaxes/log.tmLanguage.json @@ -48,7 +48,7 @@ "match": "^(?:\\[.*?\\]|\\$) (cds) (.*)$", "captures": { "1": { - "name": "constant.other.key" + "name": "entity.name.type" }, "2": { "name": "string" From 2904f9b322aa9f4a66df5ecd30f3b326cefdcd37 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:18:34 +0200 Subject: [PATCH 37/45] chore(deps): update dependency com.sap.cds:cds4j-api to v2.3.0 (#463) --- .vitepress/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 89cf2a359..e1265b3d2 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -22,7 +22,7 @@ const redirectLinks: Record = {} const latestVersions = { java_services: '2.2.0', - java_cds4j: '2.2.0' + java_cds4j: '2.3.0' } const localSearchOptions = { From d2604238d5d6bd5812e3427e57960449da8748ac Mon Sep 17 00:00:00 2001 From: BraunMatthias <59841349+BraunMatthias@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:32:29 +0200 Subject: [PATCH 38/45] Described pseudo role internal-user (#439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * java architecture update * review * described internal-user * Update guides/authorization.md Co-authored-by: René Jeglinsky * Update guides/authorization.md Co-authored-by: René Jeglinsky * Update guides/authorization.md Co-authored-by: René Jeglinsky * editing * stack to modular * editing * edit * fix link --------- Co-authored-by: René Jeglinsky --- guides/authorization.md | 21 +++++++++++++--- guides/databases-hana.md | 2 +- java/architecture.md | 52 ++++++++++++++++++++-------------------- menu.md | 2 +- 4 files changed, 46 insertions(+), 31 deletions(-) diff --git a/guides/authorization.md b/guides/authorization.md index d64c2d657..6cc40aab8 100644 --- a/guides/authorization.md +++ b/guides/authorization.md @@ -99,15 +99,30 @@ It's frequently required to define access rules that aren't based on an applicat The following predefined pseudo roles are currently supported by CAP: -* `authenticated-user` refers to (named or unnamed) users who have presented a valid authentication claim such as a logon token. -* `system-user` denotes an unnamed user used for technical communication. +* `authenticated-user` refers to named or unnamed users who have presented a valid authentication claim such as a logon token. +* [`system-user` denotes an unnamed user used for technical communication.](#system-user) +* [`internal-user` is dedicated to distinguish application internal communication.](#internal-user) * `any` refers to all users including anonymous ones (that means, public access without authentication). -The pseudo role `system-user` allows you to separate _internal_ access by technical users from _external_ access by business users. The technical user can come from a SaaS or the PaaS tenant. Such technical user requests typically run in a _privileged_ mode without any restrictions on an instance level. For example, an action that implements a data replication into another system needs to access all entities of subscribed SaaS tenants and can't be exposed to any business user. Note that `system-user` also implies `authenticated-user`. +#### system-user +The pseudo role `system-user` allows you to separate access by _technical_ users from access by _business_ users. Note that the technical user can come from a SaaS or the PaaS tenant. Such technical user requests typically run in a _privileged_ mode without any restrictions on an instance level. For example, an action that implements a data replication into another system needs to access all entities of subscribed SaaS tenants and can’t be exposed to any business user. Note that `system-user` also implies `authenticated-user`. + ::: tip For XSUAA or IAS authentication, the request user is attached with the pseudo role `system-user` if the presented JWT token has been issued with grant type `client_credentials` or `client_x509` for a trusted client application. ::: +#### internal-user +Pseudo-role `internal-user` allows to define application endpoints that can be accessed exclusively by the own PaaS tenant (technical communication). The advantage is that similar to `system-user` no technical CAP roles need to be defined to protect such internal endpoints. However, in contrast to `system-user`, the endpoints protected by this pseudo-role do not allow requests from any external technical clients. Hence is suitable for **technical intra-application communication**, see [Security > Application Zone](../guides/security/overview#application-zone). + +::: tip +For XSUAA or IAS authentication, the request user is attached with the pseudo role `internal-user` if the presented JWT token has been issued with grant type `client_credentials` or `client_x509` on basis of the **identical** XSUAA or IAS service instance. +::: + +::: 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. +::: + ### Mapping User Claims Depending on the configured [authentication](#prerequisite-authentication) strategy, CAP derives a *default set* of user claims containing the user's name, tenant and attributes: diff --git a/guides/databases-hana.md b/guides/databases-hana.md index 3983ea496..266306c22 100644 --- a/guides/databases-hana.md +++ b/guides/databases-hana.md @@ -57,7 +57,7 @@ For example, add a Maven runtime dependency to the `cds-feature-hana` feature: ::: tip -The [modules](../java/architecture#available-modules) `cds-starter-cloudfoundry` and `cds-starter-k8s` include `cds-feature-hana`. +The [modules](../java/architecture#standard-modules) `cds-starter-cloudfoundry` and `cds-starter-k8s` include `cds-feature-hana`. ::: diff --git a/java/architecture.md b/java/architecture.md index f2620b715..46f598f49 100644 --- a/java/architecture.md +++ b/java/architecture.md @@ -14,12 +14,12 @@ uacp: Used as link target from Help Portal at https://help.sap.com/products/BTP/ One of the key [CAP design principles](../about/#open-and-opinionated) is to be an opinionated but yet open framework. Giving a clear guidance for cutting-edge technologies on the one hand and still keeping the door wide open for custom choice on the other hand, demands a highly flexible CAP Java SDK. -The [modular architecture](#modular_architecture) reflects this requirement, allowing fine-grained [stack configuration](#stack_configuration) and custom extensions. +The [stack architecture](#modular_architecture) reflects this requirement, allowing fine-grained [stack configuration](#stack_configuration) and custom extensions. ## Stack Architecture { #modular_architecture} -### Architecture Overview +### Overview One of the basic design principle of the CAP Java SDK is to keep orthogonal functionality separated in independent components. The obvious advantage of this decoupling is that it makes concrete components exchangeable independently. Hence, it reduces the risk of expensive adaptions in custom code, which can be necessary due to new requirements with regards to the platform environment or used version of platform services. Hence, the application is [platform **and** service agnostic](../about/#agnostic-approach). @@ -39,7 +39,8 @@ The following diagram illustrates the modular stack architecture and highlights You can recognize five different areas of the stack, which comprise components according to different tasks: -* The [application framework](#application-framework) defines the runtime basis of your application typically including a web server. + +* The [application framework](#application-framework) defines the runtime basis of your application typically inclusing a web server. * [Protocol adapters](#protocol-adapters) map protocol-specific web events into [CQN](../cds/cqn) events for further processing. * The resulting CQN-events are passed to [service providers](#service-providers), which drive the processing of the event. * The [CQN execution engine](#cqn-execution-engine) is capable of translating [CQN](../cds/cqn) statements into native statements of a data sink such as a persistence service. @@ -59,7 +60,8 @@ As all other components in the different layers of the CAP Java SDK are decouple ### Protocol Adapters { #protocol-adapters} -The CAP runtime is based on an [event](../about/#events) driven approach. Generally, [Service](../about/#services) providers are the consumers of events, that means, they do the actual processing of events in [handlers](../guides/providing-services#event-handlers). During execution, services can send events to other service providers and consume the results. The native query language in CAP is [CQN](../cds/cqn), which is accepted by all services that deal with data query and manipulation. Inbound requests therefore need to be mapped to corresponding CQN events, which are sent to an accepting [Application Service](../about/#querying) afterwards. Mapping the ingress protocol to CQN essentially summarizes the task of protocol adapters depicted in the diagram. Most prominent example is the [OData V4](https://www.odata.org/documentation/) protocol adapter, which is fully supported by the CAP Java SDK. Further HTTP-based protocols can be added in future, but often applications require specific protocols, most notably [RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer) ones. Such application-specific protocols can easily be implemented by means of Spring RestControllers. + +The CAP runtime is based on an [event](../about/#events) driven approach. Generally, [Service](../about/#services) providers are the consumers of events, that means, they do the actual processing of events in [handlers](../guides/providing-services#event-handlers). During execution, services can send events to other service providers and consume the results. The native query language in CAP is [CQN](../cds/cqn), which is accepted by all services that deal with data query and manipulation. Inbound requests therefore need to be mapped to corresponding CQN events, which are sent to an accepting Application Service (see concept [details](../about/#querying)) afterwards. Mapping the ingress protocol to CQN essentially summarizes the task of protocol adapters depicted in the diagram. Most prominent example is the [OData V4](https://www.odata.org/documentation/) protocol adapter, which is fully supported by the CAP Java SDK. Further HTTP-based protocols can be added in future, but often applications require specific protocols, most notably [RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer) ones. Such application-specific protocols can easily be implemented by means of Spring RestControllers. The modular architecture allows to add custom protocol adapters in a convenient manner, which can be plugged into the stack at runtime. Note that different endpoints can be served by different protocol adapters at the same time. @@ -79,12 +81,12 @@ The CQN execution engine is responsible for processing the passed CQN events and ### Application Features { #application-features} The overall architecture of the CAP Java SDK allows additional components to be plugged in at runtime. This plugin mechanism makes the architecture open for future extensions and allows context-based configuration. It also enables you to override standard behavior with custom-defined logic in all different layers. Customer components or [extension modules](https://blogs.sap.com/2023/05/16/how-to-build-reusable-plugin-components-for-cap-java-applications/comment-page-1/#comment-674200) that are registered by the runtime can bring custom adapters, custom services or just custom handlers for existing services. -CAP Java makes use of [features](#feature-list) itself to provide optional functionality, examples are [SAP Event Mesh](./messaging-foundation) and [SAP Audit Log service](./auditlog) integration. +CAP Java makes use of [features](#standard-modules) itself to provide optional functionality, examples are [SAP Event Mesh](./messaging-foundation) and [Audit logging](./auditlog) integration. ## Stack Configuration { #stack_configuration} - As outlined in section [Modular Architecture](#modular_architecture), the CAP Java SDK is highly flexible. You can't only choose among modules prepared for different environments. You can also include optional or custom extensions. + As outlined in section [Modular Architecture](#modular_architecture), the CAP Java SDK is highly flexible. You can choose among modules prepared for different environments and in addition also include optional or custom extensions. Which set of modules is active at runtime is a matter of compile time and runtime configuration. At compile time, you can assemble modules from the different layers: @@ -95,7 +97,7 @@ CAP Java makes use of [features](#feature-list) itself to provide optional funct ### Module Configuration -All CAP Java SDK modules are built as [Maven](https://maven.apache.org/) artifacts and are available on [Apache Maven Central Repository](https://search.maven.org/search?q=com.sap.cds). They've group id `com.sap.cds`. +All CAP Java SDK modules are built as [Maven](https://maven.apache.org/) artifacts and are available on [Apache Maven Central Repository](https://search.maven.org/search?q=com.sap.cds). They've `groupId` `com.sap.cds`. Beside the Java libraries (Jars) reflecting the modularized functionality, the group also contains a "bill of materials" (BOM) pom named `cds-services-bom`, which is recommended especially for multi-project builds. It basically helps to control the dependency versions of the artifacts and should be declared in dependency management of the parent `pom`: ```xml @@ -116,8 +118,8 @@ Beside the Java libraries (Jars) reflecting the modularized functionality, the g ``` -::: tip -Importing `cds-services-bom` into the dependency management of your project ensures that all cds-module versions are in sync. +::: tip Keep Versions in Sync +Importing `cds-services-bom` into the DependencyManagement of your project ensures that versions of all CAP modules are in sync. ::: The actual Maven dependencies specified in your `pom` need to cover all libraries that are necessary to run the web application: An application framework, a protocol adapter, and the CAP Java SDK. @@ -152,22 +154,23 @@ The dependencies of a Spring Boot application with OData V4 endpoints could look ``` -::: tip + +::: tip API Modules w/o scope `dependency` Only API modules without dependency scope should be added (they gain `compile` scope by default) such as `cds-services-api` or `cds4j-api`. -All other dependencies should have a dedicated scope, like `runtime` or `test`, to prevent misuse. +All other dependencies should have a dedicated scope, like `runtime` or `test` to prevent misuse. ::: You are not obliged to choose one of the prepared application frameworks (identifiable by `artifactId` prefix `cds-framework`), instead you can define your own application context if required. Similarly, you're free to configure multiple adapters including custom implementations that map any specific web service protocol. -::: tip -It's highly recommended to configure `cds-framework-spring-boot` as application framework -as it provides you with a lot of out-of-the-box [integration with CAP](./development/#spring-boot-integration) -and enhanced features such as dependency injection and auto configuration. +::: tip Recommended Application Framework +We highly recommended to configure `cds-framework-spring-boot` as application framework. +It provides you with a lot of [integration with CAP](./development/#spring-boot-integration) out of the box, +as well as enhanced features, such as dependency injection and auto configuration. ::: -Additional application features you want to use are added as additional dependencies. For instance `cds-feature-mt` is required to make your application multitenancy-aware. +Additional application features you want to use are added as additional dependencies. The following is required to make your application multitenancy aware. ```xml @@ -180,35 +183,33 @@ Additional application features you want to use are added as additional dependen ``` - Choosing a feature by adding the Maven dependency *at compile time* enables the application to make use of the feature *at runtime*. If a chosen feature misses the required environment at runtime, the feature won't be activated. Together with the fact that all features have a built-in default implementation ready for local usage, you can run the application locally with the same set of dependencies as for productive mode. -For instance, the authentication feature `cds-feature-hana` requires a valid `hana`-binding in the environment. Hence, during local development without this binding, this feature gets deactivated and the stack falls back to default feature adapted for SQLite. +For instance, the authentication feature `cds-feature-hana` requires a valid `hana` binding in the environment. Hence, during local development without this binding, this feature gets deactivated and the stack falls back to default feature adapted for SQLite. -### CAP Java Standard Modules {#available-modules} +### CAP Java Standard Modules { #standard-modules } CAP Java comes with a rich set of prepared modules in all different layers of the stack: -#### Application Frameworks +**Application Frameworks**: * `cds-framework-spring`: Makes your application a Spring Boot application. * `cds-framework-plain`: Adds support to run as plain Java Servlet-based application. -#### Protocol adapters +**Protocol adapters**: * `cds-adapter-odata-v4`: Auto-exposes Application Services as OData V4 endpoints. * `cds-adapter-odata-v2`: Auto-exposes Application Services as OData V2 endpoints. * `cds-adapter-api`: Generic protocol adapter interface to be implemented by customer adapters. -#### Runtime (mandatory) +**Runtime (mandatory)**: * `cds-services-api`: Interface of the CAP Java SDK. Custom handler or adapter code needs to compile against. * `cds-services-impl`: Implementation of the CAP Java SDK. -#### Application features { #feature-list } - +**Application features**: * `cds-feature-cloudfoundry`: Makes your application aware of SAP BTP, Cloud Foundry environment. * `cds-feature-k8s`: [Service binding support for SAP BTP, Kyma Runtime](./development/#kubernetes-service-bindings). * `cds-feature-hana`: Makes your application aware of SAP HANA data sources. * `cds-feature-xsuaa`: Adds [XSUAA](https://github.com/SAP/cloud-security-xsuaa-integration)-based authentication to your application. * `cds-feature-identity`: Adds [Identity Services](https://github.com/SAP/cloud-security-xsuaa-integration) integration covering IAS to your application. -* `cds-feature-mt`: Makes your application multitenant-aware. +* `cds-feature-mt`: Makes your application multitenant aware. * `cds-feature-enterprise-messaging`: Connects your application to SAP Event Mesh. * `cds-feature-remote-odata`: Adds [Remote Service](remote-services#remote-services) support. @@ -226,7 +227,6 @@ To simplify the configuration on basis of Maven dependencies, the CAP Java SDK c Starter bundle `cds-starter-spring-boot` can be combined with any of the other bundles. - An example of a CAP application with OData V4 on Cloud Foundry environment: ```xml diff --git a/menu.md b/menu.md index 5b2f85b20..a7a96f18e 100644 --- a/menu.md +++ b/menu.md @@ -85,7 +85,7 @@ ### [Java](java/) - [Getting Started](java/getting-started) -- [Stack Architecture](java/architecture) +- [Modular Architecture](java/architecture) - [Services](java/consumption-api) - [Event Handlers](java/provisioning-api) - [Working with Data](java/data) From f6a18602640aae2f4d7056e72eb56d8bc67bec4e Mon Sep 17 00:00:00 2001 From: Marten Schiwek Date: Wed, 4 Oct 2023 14:33:28 +0200 Subject: [PATCH 39/45] Update fiori.md (#404) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update fiori.md More details about draft flow * Update advanced/fiori.md Co-authored-by: René Jeglinsky * Update advanced/fiori.md Co-authored-by: René Jeglinsky --------- Co-authored-by: René Jeglinsky --- advanced/fiori.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/advanced/fiori.md b/advanced/fiori.md index 9ecb3a89a..c730cb5fa 100644 --- a/advanced/fiori.md +++ b/advanced/fiori.md @@ -390,6 +390,8 @@ SAP Fiori supports edit sessions with draft states stored on the server, so user [Find a working end-to-end version in **cap/samples/fiori**.](https://github.com/sap-samples/cloud-cap-samples/tree/main/fiori){.learn-more} +[For details about the draft flow in SAP Fiori elements, see **SAP Fiori elements > Draft Handling**](https://ui5.sap.com/#/topic/ed9aa41c563a44b18701529c8327db4d){.learn-more} + ### Enabling Draft with `@odata.draft.enabled` @@ -405,6 +407,8 @@ annotate AdminService.Books with @odata.draft.enabled; You can't project from draft-enabled entities, as annotations are propagated. Either _enable_ the draft for the projection and not the original entity or _disable_ the draft on the projection using `@odata.draft.enabled: null`. ::: +### Difference between Compositions and Associations +Be aware that all compositions of the draft enabled entity are part of the same draft. Only those entities will get a `CREATE` button in SAP Fiori elements UIs as they are part of the draft. Associated entities on the other side can only be deleted or modified. Note that, for associations the changes are directly applied instead of being applied once changes are saved to the active version. ### Enabling Draft for [Localized Data](../guides/localized-data) {#draft-for-localized-data} From 320cc920dd0c0ce9554dad3bfa94cac94dba1aa1 Mon Sep 17 00:00:00 2001 From: Rene Jeglinsky Date: Wed, 4 Oct 2023 15:14:27 +0200 Subject: [PATCH 40/45] align offboarding --- about/features.md | 2 +- about/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/about/features.md b/about/features.md index c06354b6c..664148a6a 100644 --- a/about/features.md +++ b/about/features.md @@ -234,7 +234,7 @@ Following is an index of the features currently covered by CAP, with status and | [Deploy to/run on _SAP BTP, Cloud Foundry environment_](../guides/deployment/) | | | | Deploy to/run on _Kubernetes_1 | | | | [Deploy to/run on _Kyma_](../guides/deployment/deploy-to-kyma) | | | -| [SaaS on-/off-boarding](../guides/deployment/as-saas) | | | +| [SaaS on-/offboarding](../guides/deployment/as-saas) | | | | [Multitenancy](../guides/multitenancy/) | | | | Health checks | | | diff --git a/about/index.md b/about/index.md index 36c48198e..3833d94c2 100644 --- a/about/index.md +++ b/about/index.md @@ -65,7 +65,7 @@ Keeping pace with a rapidly changing world of cloud technologies and platforms i - Platform-specific deployment approaches and techniques - Platform-specific identity providers and authentication strategies -- On/Off-boarding of tenants in SaaS solutions and tenant isolation +- On-/Offboarding of tenants in SaaS solutions and tenant isolation - Synchronous protocols like *REST*, *OData*, or *GraphQL* - Asynchronous channels and brokers like *SAP Event Mesh*, *MQ*, or *Kafka* 1 - Different database technologies including *SQL* and *NoSQL* From 0c534deccc0d5ed930e4be0c0913ec61d1d932e5 Mon Sep 17 00:00:00 2001 From: Johannes Vogel <31311694+johannes-vogel@users.noreply.github.com> Date: Thu, 5 Oct 2023 09:56:48 +0200 Subject: [PATCH 41/45] mark req.diff as beta again (#468) was probably lost during shift to vitepress --- node.js/events.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node.js/events.md b/node.js/events.md index 345eba28b..97e21a5bd 100644 --- a/node.js/events.md +++ b/node.js/events.md @@ -429,7 +429,7 @@ In production, errors should never disclose any internal information that could Additionally, the OData protocol specifies which properties an error object may have. If a custom property shall reach the client, it must be prefixed with `@` in order to not be purged. -### req. diff() {.method} +### req. diff() (beta) {.method} [`req.diff`]: #req-diff Use this asynchronous method to calculate the difference between the data on the database and the passed data (defaults to `req.data`, if not passed). From 826f16ef88a35f1017fe643dd2a027d5678568cd Mon Sep 17 00:00:00 2001 From: Marcel Schwarz Date: Thu, 5 Oct 2023 09:58:35 +0200 Subject: [PATCH 42/45] Update SAP Cloud SDK doc links to v3 (#467) --- guides/using-services.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/using-services.md b/guides/using-services.md index 483d7b360..43f21669e 100644 --- a/guides/using-services.md +++ b/guides/using-services.md @@ -993,7 +993,7 @@ Since you don't want to use the destination for local testing, but only for prod } ``` -Additionally, you can provide [destination options](https://sap.github.io/cloud-sdk/api/v2/types/sap_cloud_sdk_connectivity.DestinationOptions.html) inside a `destinationOptions` object: +Additionally, you can provide [destination options](https://sap.github.io/cloud-sdk/api/v3/types/sap_cloud_sdk_connectivity.DestinationOptions.html) inside a `destinationOptions` object: ```jsonc "cds": { @@ -1648,7 +1648,7 @@ This list specifies the properties for application defined destinations. | `queries` | Map of URL parameters | | `forwardAuthToken` | [Forward auth token](#forward-auth-token) | -[Destination Type in SAP Cloud SDK for JavaScript](https://sap.github.io/cloud-sdk/api/v2/interfaces/sap_cloud_sdk_connectivity.Destination.html){.learn-more .impl .node} +[Destination Type in SAP Cloud SDK for JavaScript](https://sap.github.io/cloud-sdk/api/v3/interfaces/sap_cloud_sdk_connectivity.Destination.html){.learn-more .impl .node} [HttpDestination Type in SAP Cloud SDK for Java](https://help.sap.com/doc/82a32040212742019ce79dda40f789b9/1.0/en-US/index.html){.learn-more .impl .java} #### Authentication Types From 0ee4795a115766761fd2e64a2fe1b867557b9cbe Mon Sep 17 00:00:00 2001 From: Johannes Vogel <31311694+johannes-vogel@users.noreply.github.com> Date: Thu, 5 Oct 2023 10:01:11 +0200 Subject: [PATCH 43/45] update virtual model to match impl (#469) --- guides/databases-sqlite.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/databases-sqlite.md b/guides/databases-sqlite.md index 65ebbcf7b..d646e4cdb 100644 --- a/guides/databases-sqlite.md +++ b/guides/databases-sqlite.md @@ -703,8 +703,8 @@ For example, given that definition: ```cds entity Foo { - virtual foo : Integer; - bar : Integer; + foo : Integer; + virtual bar : Integer; } ``` From a71a7b55974d8cb523f82d35cf9135aa7d7b55f0 Mon Sep 17 00:00:00 2001 From: Christian Georgi Date: Thu, 5 Oct 2023 13:24:21 +0200 Subject: [PATCH 44/45] Remove obsolete config --- .vitepress/config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/.vitepress/config.ts b/.vitepress/config.ts index e1265b3d2..8b4df7b62 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -28,7 +28,6 @@ const latestVersions = { const localSearchOptions = { provider: 'local', options: { - exclude: (relativePath:string) => relativePath.includes('/customization-old'), miniSearch: { options: { tokenize: text => text.split( /[\n\r #%*,=/:;?[\]{}()&]+/u ), // simplified charset: removed [-_.@] and non-english chars (diacritics etc.) From 0b42c6ccc255e079bd65d6b04b9dc0d7437a082a Mon Sep 17 00:00:00 2001 From: sjvans <30337871+sjvans@users.noreply.github.com> Date: Thu, 5 Oct 2023 16:59:44 +0200 Subject: [PATCH 45/45] fix link to audit log service docs (#470) * fix link to audit log service docs * remove locale query option from links to sap help --- guides/data-privacy/audit-logging.md | 4 ++-- guides/data-privacy/index.md | 12 +++--------- guides/data-privacy/pdm.md | 2 +- guides/using-services.md | 2 +- java/persistence-services.md | 2 +- 5 files changed, 8 insertions(+), 14 deletions(-) diff --git a/guides/data-privacy/audit-logging.md b/guides/data-privacy/audit-logging.md index 0acf4ece9..fc61231b9 100644 --- a/guides/data-privacy/audit-logging.md +++ b/guides/data-privacy/audit-logging.md @@ -171,7 +171,7 @@ Here is what you need to do additionally, to integrate with SAP Audit Log Servi 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?locale=en-US). +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). @@ -227,7 +227,7 @@ Further, the service has pre-defined event payloads for the four event types: 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?locale=en-US), which maximizes performance by omitting any intermediate data structures. +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; diff --git a/guides/data-privacy/index.md b/guides/data-privacy/index.md index b94ff9463..5e752be8f 100644 --- a/guides/data-privacy/index.md +++ b/guides/data-privacy/index.md @@ -7,13 +7,7 @@ synopsis: > status: released --- - # Managing Data Privacy @@ -71,7 +65,7 @@ The first and frequently only task to do as an application developer is to ident The **Transparancy** obligation, requests to be able to report with whom data stored about an individual is shared and where that came from (e.g., [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/personal-data-manager?locale=en-US) 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. +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](audit-logging) {.learn-more} @@ -81,7 +75,7 @@ The [SAP Audit Log Service](https://help.sap.com/docs/personal-data-manager?loca 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?locale=en-US) allows you to inform individuals about the data you have stored regarding them. +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](pdm) {.learn-more} @@ -91,6 +85,6 @@ The [SAP Personal Data Manager](https://help.sap.com/docs/personal-data-manager? 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?locale=en-US) allows you to manage retention and residence rules to block or destroy personal data. +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. diff --git a/guides/data-privacy/pdm.md b/guides/data-privacy/pdm.md index ef90bbe24..7a4c44157 100644 --- a/guides/data-privacy/pdm.md +++ b/guides/data-privacy/pdm.md @@ -157,7 +157,7 @@ At this point, you are done with your application. Let's set up the SAP Personal 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?locale=en-US). +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). diff --git a/guides/using-services.md b/guides/using-services.md index 43f21669e..898e6dedd 100644 --- a/guides/using-services.md +++ b/guides/using-services.md @@ -1015,7 +1015,7 @@ Additionally, you can provide [destination options](https://sap.github.io/cloud- The `selectionStrategy` property controls how a [destination is resolved](#destination-resolution). -If you want to configure additional headers for the HTTP request to the system behind the destination, for example an Application Interface Register (AIR) header, you can specify such headers in the destination definition itself using the property [_URL.headers.\_](https://help.sap.com/docs/CP_CONNECTIVITY/cca91383641e40ffbe03bdc78f00f681/4e1d742a3d45472d83b411e141729795.html?locale=en-US&q=URL.headers). +If you want to configure additional headers for the HTTP request to the system behind the destination, for example an Application Interface Register (AIR) header, you can specify such headers in the destination definition itself using the property [_URL.headers.\_](https://help.sap.com/docs/CP_CONNECTIVITY/cca91383641e40ffbe03bdc78f00f681/4e1d742a3d45472d83b411e141729795.html?q=URL.headers). ##### Use Destinations with Java {.impl .java} diff --git a/java/persistence-services.md b/java/persistence-services.md index e19dd34c8..fe611af8a 100644 --- a/java/persistence-services.md +++ b/java/persistence-services.md @@ -107,7 +107,7 @@ cds: Supported pool types for single tenant scenarios are `hikari`, `tomcat`, and `dbcp2`. For a multitenant scenario `hikari`, `tomcat`, and `atomikos` are supported. The corresponding pool dependencies need to be available on the classpath. You can find an overview of the available pool properties in the respective documentation of the pool. For example, properties supported by Hikari can be found [here](https://github.com/brettwooldridge/HikariCP#gear-configuration-knobs-baby). -It is also possible to configure the database connection itself. For Hikari this can be achieved by using the `data-source-properties` section. Properties defined here are passed to the respective JDBC driver, which is responsible to establish the actual database connection. The following example sets such a [SAP HANA-specific configuration](https://help.sap.com/docs/SAP_HANA_PLATFORM/0eec0d68141541d1b07893a39944924e/109397c2206a4ab2a5386d494f4cf75e.html?locale=en-US): +It is also possible to configure the database connection itself. For Hikari this can be achieved by using the `data-source-properties` section. Properties defined here are passed to the respective JDBC driver, which is responsible to establish the actual database connection. The following example sets such a [SAP HANA-specific configuration](https://help.sap.com/docs/SAP_HANA_PLATFORM/0eec0d68141541d1b07893a39944924e/109397c2206a4ab2a5386d494f4cf75e.html): ```yaml cds: