diff --git a/exercises/01/readme.md b/exercises/01/readme.md index 7a0ae1f..0f3e928 100644 --- a/exercises/01/readme.md +++ b/exercises/01/readme.md @@ -6,7 +6,7 @@ Successfully completing this exercise relies on the hardware and software [prere ## Steps -After completing these steps you'll have a working local environment for development of CAP based projects with Node.js. +After completing these steps you'll have a working local environment for development of CAP based projects with Node.js, using the CAP "development kit" package `@sap/cds-dk`. ### 1. Install the CDS command line tool @@ -43,7 +43,7 @@ user-agent = "npm/6.4.1 node/v10.15.3 linux x64" ; "npm config ls -l" to show all defaults. ``` -:point_right: Next, explore the information about the `@sap/cds-dk` (the dk stands for development kit)package, including its dependencies, with: +:point_right: Next, explore the information about the `@sap/cds-dk` package, including its dependencies, with: ```sh user@host:~ @@ -59,15 +59,14 @@ user@host:~ => npm install --global @sap/cds-dk ``` -This should eventually produce output similar to this: +This produces a fair amount of output as it works, but should eventually end with a couple of lines similar to this: ```sh -/Users/d056949/.nvm/versions/node/v10.15.3/bin/cds -> /Users/d056949/.nvm/versions/node/v10.15.3/lib/node_modules/@sap/cds-dk/bin/cds.js -+ @sap/cds-dk@1.1.4 -updated 1 package in 27.076s ++ @sap/cds-dk@1.4.2 +added 471 packages from 381 contributors in 33.398s ``` -Here you can see that the version of the `@sap/cds-dk` package installed is 3.18.4. It may be that the version of `@sap/cds-dk` that is installed when you do this exercise will be different (newer). +Here you can see that the version of the `@sap/cds-dk` package just installed is 1.4.2. It may be that the version of `@sap/cds-dk` that is installed when you do this exercise will be different (newer). ### 2. Install the CDS extension for VS Code @@ -77,7 +76,7 @@ Extensions can be installed directly in VS Code from the extension marketplace, :point_right: Go to the [Cloud section of the SAP Development Tools website](https://tools.hana.ondemand.com/#cloud) and find the "CDS Language Support for Visual Studio Code" section. Follow the instructions there to download and subsequently install the extension. -To install the downloaded extension in VS Code have a look at the screenshot below: +To install the downloaded extension in VS Code have a look at the screenshot below: ![CDS Extension installation in VS Code](vscode-extension-import.png) @@ -85,17 +84,17 @@ When successfully installed, you should see the extension thus (again, the versi ![CDS Language Support extension installed in VS Code](vscode-extension.png) -> **For macOS users only:** To be able to open VS Code from the command line (which you will be doing in a subsequent exercise) you need to add the installation path of VS Code to the environment variable PATH. There's an option to do this via the `Command Palette` in VS Code. +> **For macOS users only:** To be able to open VS Code from the command line (which you will be doing in a subsequent exercise) you need to add the installation path of VS Code to the environment variable PATH. There's an option to do this via the `Command Palette` in VS Code. > Open the `Command Palette` with ⇧⌘P. You can also open it via the menu bar: View -> Command Palette. > ![Command Palette navigation in the menu bar](command-palette.png) -> Search for `code` and press Enter. +> Search for `code` and press Enter. > ![search for code in the command palette](install-code-path.png) -> A success message for the process should then appear at the bottom right of the screen. +> A success message for the process should then appear at the bottom right of the screen. > ![message that the 'code' command was successfully installed](sucessfully-installed.png) diff --git a/exercises/01/run.bash b/exercises/01/run.bash new file mode 100755 index 0000000..5fe4e16 --- /dev/null +++ b/exercises/01/run.bash @@ -0,0 +1,14 @@ +#!/bin/bash + +# https://github.com/SAP-samples/cloud-cap-nodejs-codejam/tree/master/exercises/01 + +echo EXERCISE 01 + +echo 1. Install the CDS command line tool +npm set @sap:registry=https://npm.sap.com +npm install --global @sap/cds-dk + +echo 2. Install the CDS extension for VS Code \(NOP\) + +echo 3. Verify your development environment \(NOP\) + diff --git a/exercises/02/default-shell-windows.png b/exercises/02/default-shell-windows.png index f74311c..1d94d1d 100644 Binary files a/exercises/02/default-shell-windows.png and b/exercises/02/default-shell-windows.png differ diff --git a/exercises/02/initialized-project-in-vscode.png b/exercises/02/initialized-project-in-vscode.png index e43c694..f22e32a 100644 Binary files a/exercises/02/initialized-project-in-vscode.png and b/exercises/02/initialized-project-in-vscode.png differ diff --git a/exercises/02/integrated-terminal-in-view.png b/exercises/02/integrated-terminal-in-view.png index 884e1c6..dd46d3b 100644 Binary files a/exercises/02/integrated-terminal-in-view.png and b/exercises/02/integrated-terminal-in-view.png differ diff --git a/exercises/02/readme.md b/exercises/02/readme.md index 1200a52..fe28011 100644 --- a/exercises/02/readme.md +++ b/exercises/02/readme.md @@ -18,9 +18,7 @@ After completing these steps you'll be familiar with how you can use the `cds` c For any new CAP based project you start by indirectly creating a directory containing various basic files. This can be achieved with the CDS command line tool `cds` which you installed in [exercise 01](../01/). -The `cds` tool should be available in your executable path, having been installed globally as part of the Node.js `@sap/cds` package. - -:point_right: First, explore the `cds` command line tool by executing it with no parameters; you will see what options are available: +The `cds` tool should be available in your executable path, having been installed globally as part of the Node.js `@sap/cds-dk` package. ```sh user@host:~ @@ -36,12 +34,13 @@ COMMANDS i | init jump-start cds-based projects c | compile process models selectively - m | import add models from external sources + d | deploy e.g. to databases or cloud s | serve run servers locally + w | watch restart server on file changes + m | import add models from external sources r | repl read-eval-event loop e | env get/set cds configuration b | build prepare for deployment - d | deploy e.g. to databases or cloud v | version get detailed version information ? | help get detailed usage information @@ -59,34 +58,25 @@ user@host:~ => cds init --help ``` -Amongst other things, you should see a `--modules` option to specify a list of modules to be created when the project is initialized, and also a `--verbose` option. The options `--mta`, `--db-technology` and `--insecure` are related to deployment to Cloud Foundry and access management in that context. `--skip-sample-models` avoids the creation of sample CDS source files which you will build step by step in this CodeJam yourself. - -:point_right: Use all of these options to initialize a new project directory called `bookshop` thus: +:point_right: Use some of these options to initialize a new project directory called `bookshop` thus: ```sh user@host:~ -=> cds init --modules db,srv --mta --insecure --db-technology hana --verbose --skip-sample-models bookshop +=> cds init bookshop --add hana,mta ``` You should see output that looks similar to this: ``` -Initializing project in folder bookshop. -Copying templates for type db to db ... -Creating mta file /private/tmp/bookshop/mta.yaml ... -Copying templates for type srv to srv ... -Creating mta file /private/tmp/bookshop/mta.yaml ... -Updating npm dependencies in /private/tmp/bookshop/package.json ... -Running npm install... -npm notice created a lockfile as package-lock.json. You should commit this file. -added 120 packages from 178 contributors and audited 220 packages in 6.978s -found 0 vulnerabilities +[cds] - creating new project in current folder +> applying template 'hana'... +> applying template 'mta'... +done. -Done. -Learn about first steps at https://cap.cloud.sap/docs/get-started/in-a-nutshell +Find samples on https://github.com/SAP-samples/cloud-cap-samples +Learn about next steps at https://cap.cloud.sap/docs/get-started ``` - ### 2. Open the project in VS Code Now that the project has been initialized, it's time to explore it. The VS Code IDE is a comfortable environment in which to do so, so at this point you will open up the newly created `bookshop` directory in it. @@ -111,19 +101,22 @@ The skeleton project that has been initialized is visible in VS Code. This is wh Briefly, the directories and contents can be described thus: -| Directory | Contents | +| Directory or File | Description | | -------------- | -------- | -| `.vscode` | VS Code specific files for launch configurations (useful for debugging, which we will cover in [exercise 08](../08/)) | -| `db` | Where the data models (in CDS) are specified. | -| `node_modules` | This is the place where NPM packages (modules) are to be found in a Node.js based project | -| `srv` | Where the service definitions (in CDS) are specified. | -| `mta.yaml` | This is the central descriptor file for the project. It defines all modules (microservices) and backing services (like databases). This information will be used to build the .mtar archive during design time and to deploy & provision the apps and services during deploy time. | +| `.vscode/` | VS Code specific files for launch configurations (useful for debugging, which we will cover in [exercise 08](../08/)). This directory may or may not be visible in VS Code, depending on version and settings. | +| `app/` | Where any UI components live, in case you're building and serving a full stack app. | +| `db/` | Where the data models (in CDS) are specified. | +| `srv/` | Where the service definitions (in CDS) are specified. | +| `.eslintrc` | Configuration for linting of the JavaScript with [ESLint](https://eslint.org/). | +| `.gitignore` | Specification of what content should be ignored when using `git` for source code control, specifically for CAP-based projects. | +| `mta.yaml` | This is the central descriptor file for the project. It defines all modules (microservices) and backing services (like databases). This information will be used to build the .mtar archive during design time and to deploy & provision the apps and services during deploy time. | +| `package.json` | The standard package descriptor file for Node.js based (NPM) packages and projects. | +| README.md | A small 'Getting Started' guide for this project. | -Besides the directories there are also a number of files, including the project's `package.json` (present in any Node.js based project). ### 4. Create a simple data model and service definition -:point_right: Create a new file called `data-model.cds` in the `db/` directory of the recently created project, copy the following lines to the file and save it: +:point_right: Create a new file called `schema.cds` in the `db/` directory of the recently created project, copy the following lines to the file and save it: ```cds: namespace my.bookshop; @@ -137,10 +130,10 @@ entity Books { > You **may** wish to use VS Code's "auto save" feature, then again you may not. Just in case you do, you can turn it on via the **File -> Auto Save** menu option. -:point_right: Create a new file called `cat-service.cds` in the `srv/` directory of the recently created project, copy the following lines to the file and save it: +:point_right: Create a new file called `service.cds` in the `srv/` directory of the recently created project, copy the following lines to the file and save it: ```cds: -using my.bookshop as my from '../db/data-model'; +using my.bookshop as my from '../db/schema'; service CatalogService { entity Books as projection on my.Books; @@ -151,14 +144,16 @@ You have now created a simple data model as well as a service definition for you ### 5. Examine the data model and service definition -The key files in this project as far as the business domain is concerned are the `db/data-model.cds` and the `srv/cat-service.cds` files that you just added. +The key files in this project as far as the business domain is concerned are the `db/schema.cds` and the `srv/service.cds` files that you just added. :point_right: Have a brief look at the content of each of these files to get a basic understanding of what's there. Note the use of the `namespace` and how it is defined in the data model and referenced in the service definition. Note also the how the different parts of each file are syntax highlighted. -### 6. Start up the service +### 6. Install the dependencies + +The `package.json` file that was created when you initialized the project directory contains a list of NPM packages upon which the project is dependent. Before we can start the service up, these dependencies must be installed. Now is a good time to do that. -Now you're going to start up the service in the skeleton project. **VS Code has an integrated terminal which you can and should use for this and subsequent command line activities**. +> VS Code has an integrated terminal which you can and should use for this and subsequent command line activities. :point_right: Open the integrated terminal in VS Code. Do this by opening the Command Palette and searching for 'integrated terminal'. You may wish to use the keyboard shortcut for this - note there is a keyboard shortcut for toggling the integrated terminal in and out of view as well. @@ -171,24 +166,47 @@ This should open up the terminal at the bottom of VS Code like this: > **Windows users:** Please make sure to select `cmd` as your default shell before you continue: ![default shell](default-shell-windows.png) -:point_right: In the integrated terminal, use the `cds` command line tool with the `serve` command to start serving. Specify `all`, like this, so that `cds` will look for appropriate configuration to serve: +:point_right: In the integrated terminal, making sure first that you're in the project directory itself (where `package.json` is to be found), install the dependencies like this: + +```sh +user@host:~/bookshop +=> npm install +``` + +You should see output that ends something like this: + +```sh +npm notice created a lockfile as package-lock.json. You should commit this file. +added 129 packages from 181 contributors and audited 258 packages in 4.762s +found 0 vulnerabilities +``` + +Great. Now your fledgling service can already be started up! + +### 7. Start up the service + +:point_right: In the same integrated terminal, use the following command to start up the service: + ```sh user@host:~/bookshop -=> cds serve all +=> npm start ``` You should see output similar to this: ``` -[cds] - connect to datasource - hana:db,srv +> bookshop@1.0.0 start /tmp/codejam/bookshop +> npx cds run + +[cds] - connect to datasource - hana:undefined [cds] - serving CatalogService at /catalog [cds] - service definitions loaded from: - srv/cat-service.cds - db/data-model.cds + srv/service.cds + db/schema.cds -[cds] - launched in: 533.555ms +[cds] - launched in: 583.297ms [cds] - server listening on http://localhost:4004 ... [ terminate with ^C ] ``` @@ -196,7 +214,7 @@ You should see output similar to this: The OData service is now running, and available via [http://localhost:4004](http://localhost:4004). -### 7. Explore the OData service +### 8. Explore the OData service While we have no data in the OData service (we don't even have a persistence layer yet!) we can ask the OData service for the two well-known documents: the service document and the metadata document. @@ -218,7 +236,7 @@ With a single command, you've initialized a basic OData service project and with ## Questions -1. Why is there an focus on "Contracts First" (As all you need to run a service is a service definition) - what advantages does that bring? +1. Why is there a focus on "Contracts First" (As all you need to run a service is a service definition) - what advantages does that bring? 2. What is the difference between the data model and the service definition? Why do we need both? @@ -228,4 +246,7 @@ With a single command, you've initialized a basic OData service project and with 4. What happened to the `cds` process when you accessed the entityset? Can you think of reasons why this happened? - \ No newline at end of file + + +5. What actually happens when you run `npm start`, and why? + diff --git a/exercises/02/run.bash b/exercises/02/run.bash new file mode 100755 index 0000000..a70806d --- /dev/null +++ b/exercises/02/run.bash @@ -0,0 +1,40 @@ +#!/bin/bash + +# https://github.com/SAP-samples/cloud-cap-nodejs-codejam/tree/master/exercises/02 + +echo EXERCISE 02 + +echo 1. Initialize a new CAP project +cds init bookshop --add hana,mta && cd bookshop + +echo 2. Open the project in VS Code \(NOP\) + +echo 3. Explore the initialized project structure \(NOP\) + +echo 4. Create a simple data model and service definition +cat < db/schema.cds +namespace my.bookshop; + +entity Books { + key ID : Integer; + title : String; + stock : Integer; +} +EOSCHEMA + +cat < srv/service.cds +using my.bookshop as my from '../db/schema'; + +service CatalogService { + entity Books as projection on my.Books; +} +EOSERVICE + +echo 5. Examine the data model and service definition \(NOP\) + +echo 6. Install the dependencies +npm install + +echo 7. Start up the service \(NOP\) + +echo 8. Explore the OData service \(NOP\) diff --git a/exercises/03/readme.md b/exercises/03/readme.md index ba82d96..0322122 100644 --- a/exercises/03/readme.md +++ b/exercises/03/readme.md @@ -7,12 +7,38 @@ In this exercise you'll enhance your basic bookshop project by adding to the dat After completing these steps you'll have a slightly more complex OData service, with a second entity that is related to the first. It will also be backed by an actual persistence layer, provided by SQLite. +### 1. Use `cds watch` to have the service restart on changes -### 1. Add a new Authors entity to the model +During the course of this CodeJam you'll be making many changes and additions to CDS and JavaScript sources. Rather than restart the service manually each time, you can use the `watch` command with the `cds` command line tool, that will restart the server on file changes. + +:point_right: Do this now, by entering `cds watch` in the integrated terminal (if you still have the service running, terminate it first with Ctrl-C): + +```sh +user@host:~/bookshop +=> cds watch +``` + +The `watch` command uses the NPM [nodemon](https://www.npmjs.com/package/nodemon) package, which you can see from the output that it produces, which should look something like this: + +``` +[cds] - running nodemon... +--exec cds run --with-mocks --in-memory? +--ext cds,csn,csv,ts,mjs,cjs,js,json,properties,edmx,xml + +[cds] - connect to datasource - hana:undefined +[cds] - serving CatalogService at /catalog +[cds] - launched in: 555.266ms +[cds] - server listening on http://localhost:4004 ... +[ terminate with ^C ] +``` + +You can now proceed with saving changes to your project and have the service automatically restarted. Nice! + +### 2. Add a new Authors entity to the model Currently the data model is extremely simple. In this step you'll add a second entity `Authors`. -:point_right: Open the `db/data-model.cds` file in VS Code and add a new entity definition, after the `Books` entity, thus: +:point_right: Open the `db/schema.cds` file in VS Code and add a new entity definition, after the `Books` entity, thus: ```cds entity Authors { @@ -21,30 +47,23 @@ entity Authors { } ``` -This is deliberately very simple at this point. Don't forget to save the file. - -:point_right: In the integrated terminal, start (or restart) the service like this: - -```sh -user@host:~/bookshop -=> cds serve all -``` +This is deliberately very simple at this point. Don't forget to save the file ... at which point your service should restart automatically thanks to `cds watch`. -:point_right: Open up (or refresh) the [service metadata document](http://localhost:4004/catalog/$metadata) and check for the entity definition you've just added. +:point_right: Open up (or refresh) the [service metadata document](http://localhost:4004/catalog/$metadata) and check for the Authors entity definition you've just added. You're right. It's not there. -### 2. Expose the Authors entity in the service +### 3. Expose the Authors entity in the service While there is now a second entity definition in the data model, it is not exposed in the existing service. In this step, you'll remedy that. -:point_right: Open up the `srv/cat-service.cds` file and add a second entity to the `CatalogService` definition. +:point_right: Open up the `srv/service.cds` file and add a second entity to the `CatalogService` definition. -This is what the contents of `srv/cat-service.cds` should look like after you've added the new entity and removed the annotation: +This is what the contents of `srv/service.cds` should look like after you've added the new entity and removed the annotation: ```cds -using my.bookshop as my from '../db/data-model'; +using my.bookshop as my from '../db/schema'; service CatalogService { entity Books as projection on my.Books; @@ -52,18 +71,18 @@ service CatalogService { } ``` -:point_right: Restart the service and check the metadata document once again. The definition of the Authors entity should now be present in the metadata, and will look something like this: +:point_right: After the service restarts, check the metadata document once again. The definition of the Authors entity should now be present in the metadata, and will look something like this: ![Books and Authors entities in the metadata document](books-authors-metadata-document.png) This is nice, but there's something fundamental that's missing and preventing this data model from being useful. -### 3. Add a relationship between the Books and Authors entities +### 4. Add a relationship between the Books and Authors entities -The `Books` and `Authors` entities are standalone and currently are not related to each other. This is not ideal, so in this step you'll fix that by adding a relationship in the form of an [association](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/9ead8e4701d04848a6fdc84356723a52.html). +The `Books` and `Authors` entities are standalone and currently are not related to each other. This is not ideal, so in this step you'll fix that by adding a relationship in the form of an [association](https://cap.cloud.sap/docs/cds/cdl#associations). -:point_right: Return to the `db/data-model.cds` file and add an association from the `Books` entity to the `Authors` entity, bearing in mind the simplified assumption that a book has a single author. The association should describe a new `author` property in the `Books` entity like this: +:point_right: Return to the `db/schema.cds` file and add an association from the `Books` entity to the `Authors` entity, bearing in mind the simplified assumption that a book has a single author. The association should describe a new `author` property in the `Books` entity like this: ```cds entity Books { @@ -94,71 +113,29 @@ Note that this is a 'to-many' relationship. Don't forget to save the file. -:point_right: Restart the service and check the [metadata document](http://localhost:4004/catalog/$metadata) again. There should now be OData navigation properties defined between the two entities, like this: +:point_right: After the service has restarted, check the [metadata document](http://localhost:4004/catalog/$metadata) again. There should now be OData navigation properties defined between the two entities, like this: ![navigation properties](navigation-properties.png) -### 4. Deploy the service to a persistence layer +### 5. Deploy the service to a persistence layer -As it stands, the OData service has no storage. We can actually simulate storage with [service provider](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/b9c34890348b4f2184e07a6731bce50b.html) logic in JavaScript but that's not a path we want to explore right now (we'll look at it in [exercise 08](../08/)). Instead, we'll use a real database in the form of [SQLite](https://sqlite.org) and deploy the data model and service definition to it. +As it stands, the OData service has no storage. We can actually simulate storage with [service provider](https://cap.cloud.sap/docs/guides/service-impl) logic in JavaScript but that's not a path we want to explore right now (we'll look at it in [exercise 08](../08/)). Instead, we'll use a real database in the form of [SQLite](https://sqlite.org) and deploy the data model and service definition to it. -:point_right: Update the database definition in the project's top-level `package.json` file to include a SQLite DB for local testing. This will fix the issue you encountered before when the Node.js `cds` process crashed. Currently, you'll see a section that describes the persistence layer configuration: - -```json -"cds": { - "requires": { - "db": { - "kind": "hana", - "model": [ - "db", - "srv" - ] - } - }, - "odata": { - "version": "v4" - } -} +As we want to use a local SQLite database (SQLite was defined in the [prerequisites](../../prerequisites.md) for this CodeJam), we need to install a client library to allow the CAP engine to communicate with this DB. -``` - -> Ensure you select the top level `package.json` file - there's also one in the `db/` directory but that's not the one you want. - -To prepare the app for a multiple databases, change the content of the "cds" section to this (not forgetting the comma at the end): - -```json -"cds": { - "requires": { - "db": { - "kind": "sqlite", - "model": [ - "db", - "srv" - ], - "credentials": { - "database": "bookshop.db" - }, - "[production]": { - "kind": "hana" - } - } - }, - "odata": { - "version": "v4" - } -}, -``` +:point_right: Do that now, i.e. install the `sqlite3` package for this purpose: -:point_right: As we want to use a local SQLite database, we need to install a client library to allow the CAP engine to communicate with this DB. Install the `sqlite3` package for this purpose: -``` +```sh user@host:~/bookshop => npm install -D sqlite3 ``` > The use of the `-D` (or `--save-dev`) parameter signifies that the `sqlite3` package is a dependency for development purposes only. Have a look at what gets added to `package.json` at this point to see the two different types of package dependencies. -:point_right: Explore the `cds deploy` command like this: +Now it's time to make that deployment to the persistence layer. + +:point_right: Explore the options for the deploy command like this: ```sh user@host:~/bookshop @@ -172,6 +149,8 @@ SYNOPSIS according configuration from package.json or .cdsrc.json in key cds.requires.db. Same for the database. + Supported databases: sqlite, hana + [...] ``` @@ -181,14 +160,19 @@ Use this command to deploy the data model and service definition to a new SQLite ``` user@host:~/bookshop -=> cds deploy +=> cds deploy --to sqlite:bookshop.db ``` -This should complete fairly quietly. +This should complete fairly quietly, something like this: + +``` +/> successfully deployed to ./bookshop.db + > updated ./package.json +``` -### 5. Explore the new database +### 6. Explore the new database -At this point you should have a new file `bookshop.db` in the project folder. +At this point you should have a new file `bookshop.db` in the project directory. :point_right: Have a look inside it with the `sqlite3` command line utility; use the `.tables` command to see what has been created: @@ -204,10 +188,10 @@ sqlite> .quit user@host:~/bookshop ``` -> The `sqlite3` command line utility is not directly related to the `sqlite3` NPM package you just installed; it came from the installation of SQLite itself as described in the [prerequisites](../../prerequisites.md). +> The `sqlite3` command line utility is not directly related to the `sqlite3` NPM package you just installed; it came from the installation of SQLite itself. -### 6. Dig into the link between the CDS definitions and the artefacts in the database +### 7. Dig into the link between the CDS definitions and the artefacts in the database Looking at the tables in the `bookshop.db` database we see that there are two pairs of names; one pair prefixed with `CatalogService` and the other pair prefixed with `my_bookshop`. If you guessed that the `CatalogService`-prefixed artefacts relate to the service definition and the `my_bookshop`-prefixed artefacts relate to the data model, you are correct. @@ -279,7 +263,7 @@ CREATE VIEW CatalogService_Books AS SELECT FROM my_bookshop_Books AS "BOOKS_$0"; ``` -Observe that compiling the service definition will automatically produce DDL for the entities in the data model, as the service refers to them. Observe also that the service artefacts are views, whereas the data model artefacts are tables. +Observe that compiling the service definition will automatically produce DDL for the entities in the data model too, as the service refers to them. Observe also that the service artefacts are views, whereas the data model artefacts are tables. ## Summary @@ -296,4 +280,4 @@ You now have a fully functional, albeit simple, OData service backed by a persis 3. Why might you use the `cds compile` command at all? - \ No newline at end of file + diff --git a/exercises/03/run.bash b/exercises/03/run.bash new file mode 100755 index 0000000..188f49a --- /dev/null +++ b/exercises/03/run.bash @@ -0,0 +1,60 @@ +#!/bin/bash + +# https://github.com/SAP-samples/cloud-cap-nodejs-codejam/tree/master/exercises/03 + +echo EXERCISE 03 + +cd bookshop + +echo 1. Use cds watch to have the service restart on changes \(NOP\) + +echo 2. Add a new Authors entity to the model +cat < db/schema.cds +namespace my.bookshop; + +entity Books { + key ID : Integer; + title : String; + stock : Integer; +} +entity Authors { + key ID : Integer; + name : String; +} +EOSCHEMA + +echo 3. Expose the Authors entity in the service +cat < srv/service.cds +using my.bookshop as my from '../db/schema'; + +service CatalogService { + entity Books as projection on my.Books; + entity Authors as projection on my.Authors; +} +EOSERVICE + +echo 4. Add a relationship between the Books and Authors entities +cat < db/schema.cds +namespace my.bookshop; + +entity Books { + key ID : Integer; + title : String; + stock : Integer; + author : Association to Authors; +} +entity Authors { + key ID : Integer; + name : String; + books : Association to many Books on books.author = \$self; +} +EORELATIONS + +echo 5. Deploy the service to a persistence layer +npm install -D sqlite3 +cds deploy --to sqlite:bookshop.db + +echo 6. Explore the new database \(NOP\) + +echo 7. Dig into the link between the CDS definitions and the artefacts in the database \(NOP\) + diff --git a/exercises/04/readme.md b/exercises/04/readme.md index 7353b90..d6be294 100644 --- a/exercises/04/readme.md +++ b/exercises/04/readme.md @@ -25,7 +25,10 @@ Your directory structure should then look something like this (the screenshot al ### 2. Redeploy to the persistence layer -:point_right: The CSV files are discovered and used during a `cds deploy`, so deploy again thus: +The CSV files are discovered and used during a `cds deploy`, so a new deployment is required. While the `cds watch` will restart the service, it won't do a deploy for us, so we'll do that manually now. + +:point_right: Deploy again, this time noting that you don't have to specify the `--to` option: + ```sh user@host:~/bookshop @@ -35,18 +38,18 @@ user@host:~/bookshop During deployment this time you should see extra messages: ``` - > filling my.bookshop.Books from db/csv/my.bookshop-Books.csv > filling my.bookshop.Authors from db/csv/my.bookshop-Authors.csv + > filling my.bookshop.Books from db/csv/my.bookshop-Books.csv /> successfully deployed to ./bookshop.db ``` ### 3. Restart the service -:point_right: Restart the service thus: +:point_right: Now the data's been loaded, you should fire up `cds watch` again: ```sh user@host:~/bookshop -=> cds serve all +=> cds watch ``` Now the [Books](http://localhost:4004/catalog/Books) and [Authors](http://localhost:4004/catalog/Authors) entitysets in the OData service will show data in response to OData Query and Read operations. @@ -80,3 +83,5 @@ Your OData service now has sample data that you can access via OData operations. 2. Where do you think the format of the CSV file names has come from? + +3. How come we didn't have to specify the details (in `--to`) during the deployment in this exercise? diff --git a/exercises/04/run.bash b/exercises/04/run.bash new file mode 100755 index 0000000..be06e3c --- /dev/null +++ b/exercises/04/run.bash @@ -0,0 +1,36 @@ +#!/bin/bash + +# https://github.com/SAP-samples/cloud-cap-nodejs-codejam/tree/master/exercises/04 + +echo EXERCISE 04 + +cd bookshop + +echo 1. Bring sample CSV files in the project +mkdir db/csv +cat < db/csv/my.bookshop-Authors.csv +ID,NAME +42,Douglas Adams +101,Emily Brontë +107,Charlote Brontë +150,Edgar Allen Poe +170,Richard Carpenter +EOAUTHORS + +cat < db/csv/my.bookshop-Books.csv +ID,TITLE,AUTHOR_ID,STOCK +421,The Hitch Hiker's Guide To The Galaxy,42,1000 +427,"Life, The Universe And Everything",42,95 +201,Wuthering Heights,101,12 +207,Jane Eyre,107,11 +251,The Raven,150,333 +252,Eleonora,150,555 +271,Catweazle,170,22 +EOBOOKS + +echo 2. Redeploy to the persistence layer +cds deploy + +echo 3. Restart the service \(NOP\) + +echo 4. Try out some OData Query operations \(NOP\) diff --git a/exercises/05/readme.md b/exercises/05/readme.md index e4c97a2..8d05dc8 100644 --- a/exercises/05/readme.md +++ b/exercises/05/readme.md @@ -1,6 +1,6 @@ # Exercise 05 - Adding a further entity, using generic features -In this exercise you'll add a further entity to the data model and expose it through the service. In defining this entity you'll make use of some [generic features](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/454731d38a1e49c3aa5b182e5209bd20.html) available for all CAP projects. +In this exercise you'll add a further entity to the data model and expose it through the service. In defining this entity you'll make use of some [generic features](https://cap.cloud.sap/docs/cds/common) available for all CAP projects. ## Steps @@ -12,7 +12,7 @@ At the end of these steps you'll have a third entity `Orders`, and will have per If this is a bookshop service, we need to be able to place orders. So you should now add a third entity to the data model, for those orders. -:point_right: Open the `db/data-model.cds` file and first of all add this third entity (not forgetting to save the file when you're done): +:point_right: Open the `db/schema.cds` file and first of all add this third entity (not forgetting to save the file when you're done): ```cds entity Orders { @@ -22,9 +22,9 @@ entity Orders { } ``` -We're not quite done with this entity, but for now, have a look at the fruits of your labor by adding a new entry to the service definition for this entity, redeploying and then restarting the service. +We're not quite done with this entity, but for now, you're about to have a first look at the fruits of your labor by adding a new entry to the service definition for this entity. -:point_right: Add the entry to the `CatalogService` service definition in the `srv/cat-service.cds` file: +:point_right: Add the entry to the `CatalogService` service definition in the `srv/service.cds` file: ``` service CatalogService { @@ -34,9 +34,19 @@ service CatalogService { } ``` -Observe that the CDS Language Service extension picks up the new `Orders` entity straight away (as long as you've saved the `db/data-model.cds` file) and offers it as a suggestion in the code completion feature. +Observe that the CDS Language Service extension picks up the new `Orders` entity straight away (as long as you've saved the `db/schema.cds` file) and offers it as a suggestion in the code completion feature. -:point_right: Now redeploy to have the data model and service definition changes reflected in the persistence layer (note that the CSV data will be used again to seed the tables): +:point_right: Noting that your service has been automatically restarted (by `cds watch`) already, take a look at the new `Orders` entity: . + +You should see an error both in the response returned, and in the service log output, that looks something like this: + +``` +SQLITE_ERROR: no such table: CatalogService_Orders +``` + +That's because we still need to deploy the changes to the persistence layer, to have a new table and view created there for the `Orders` entity. + +:point_right: Do this now (note that the CSV data will be used again to seed the tables): ```sh user@host:~/bookshop @@ -48,21 +58,13 @@ user@host:~/bookshop => ``` -:point_right: Once you've redeployed, restart the service: +> If you want to make sure that the new table and view are there now, you can check with `sqlite3 bookshop.db .tables`. + +:point_right: Once you've redeployed, restart `cds watch`: ```sh user@host:~/bookshop -=> cds serve all - -[cds] - connect to datasource - sqlite:bookshop.db -[cds] - serving CatalogService at /catalog -[cds] - service definitions loaded from: - - srv/cat-service.cds - db/data-model.cds - -[cds] - server listens at http://localhost:4004 ... (terminate with ^C) -[cds] - launched in: 821.718ms +=> cds watch ``` The `Orders` entity is now available in the service (but there is [no data](http://localhost:4004/catalog/Orders) as yet). @@ -72,9 +74,9 @@ The `Orders` entity is now available in the service (but there is [no data](http When a new order comes in we want to capture the date and time. If we were running in an authenticated environment (in this CodeJam we're not, but CAP supports it) we also want to capture the user associated with the creation. Similarly we want to capture modification information. -We can use some [common CDS definitions](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/454731d38a1e49c3aa5b182e5209bd20.html) that are available to us, built into `@sap/cds` itself. These definitions can be found in the file `@sap/cds/common.cds` in the `node_modules/` directory. +We can use some [common CDS definitions](https://cap.cloud.sap/docs/guides/domain-models#use-common-reuse-types) that are available to us, built into `@sap/cds` itself. These definitions can be found in the file `@sap/cds/common.cds` in the `node_modules/` directory. -:point_right: Use the Explorer view in VS Code to open up the directories under `node_modules/` in the project, to find the `common.cds` file and open it up. In particular, find and examine the `managed` [aspect](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/40582e7bbeca4311b0b165c8b9745094.html), as well as the abstract entity `cuid`. +:point_right: Use the Explorer view in VS Code to open up the directories under `node_modules/` in the project, to find the `common.cds` file and open it up. In particular, find and examine the `managed` [aspect](https://cap.cloud.sap/docs/cds/common#aspect-managed), as well as the abstract entity `cuid`. ![looking at common.cds](common-cds.png) @@ -87,7 +89,7 @@ You will now enhance the `Orders` entity using some of the common features made - adding creation and modification information - adding a country property referring to the `Country` type -:point_right: First, import the common features to the data model file by adding the following line to `db/data-model.cds`, on the line below the `namespace` declaration: +:point_right: First, import the common features to the data model file by adding the following line to `db/schema.cds`, on the line below the `namespace` declaration: ```cds using { cuid, managed, Country } from '@sap/cds/common'; @@ -130,31 +132,40 @@ entity Orders : cuid, managed { Note the difference in capitalization here. The property name is `country` which is described by the type `Country`. -### 4. Redeploy and restart the service +### 4. Restart the service manually and check the output -In the same way as you've done previously, it's now time to redeploy and then restart the service. +While the `cds watch` is useful, it supresses various messages to keep the noise down. But there's something in those suppressed messages that you should pay attention to. -:point_right: This time, try it all in a single line, like this: +:point_right: Terminate any running `cds watch` process, and run `cds deploy && npm start` manually: ```sh user@host:~/bookshop -=> cds deploy && cds serve all - > filling my.bookshop.Books from db/csv/my.bookshop-Books.csv +=> cds deploy && npm start > filling my.bookshop.Authors from db/csv/my.bookshop-Authors.csv -/> successfully deployed database to bookshop.db + > filling my.bookshop.Books from db/csv/my.bookshop-Books.csv +/> successfully deployed to ./bookshop -[cds] - connect to datasource - sqlite:bookshop.db +> bookshop@1.0.0 start /tmp/codejam/bookshop +> npx cds run + +[cds] - connect to datasource - sqlite:bookshop [cds] - serving CatalogService at /catalog [cds] - service definitions loaded from: - srv/cat-service.cds - db/data-model.cds + srv/service.cds + db/schema.cds node_modules/@sap/cds/common.cds -[cds] - server listening on http://localhost:4004 ... (terminate with ^C) -[cds] - launched in: 722.087ms +[cds] - launched in: 875.646ms +[cds] - server listening on http://localhost:4004 ... +[ terminate with ^C ] ``` +> If your operating system command line doesn't support `&&` then just run the two commands one after the other. + +Notice the extra line in the output of the "service definitions loaded from" message. It shows us that not only are definitions being loaded from what we've defined explicitly (i.e. our `srv/service.cds` and `db/schema.cds` files) but also, implicitly, from `node_modules/@sap/cds/common.cds` because of our reference to it in `db/schema.cds` in the `using` statement. + + ### 5. Examine what the Orders entity looks like now @@ -268,11 +279,8 @@ At this point you have a meaningful OData service with data and against which yo ## Questions -1. Did you notice an extra line in the output of `cds serve all` after the addition of the reference to `@sap/cds/common`? - - -2. We added a field `country` described by the type `Country`. What exactly is this type, and what does it bring about in the resulting service's metadata? +1. We added a field `country` described by the type `Country`. What exactly is this type, and what does it bring about in the resulting service's metadata? -3. Are there any issues with the way we have set up the service definition right now? +2. Are there any issues with the way we have set up the service definition right now? diff --git a/exercises/05/run.bash b/exercises/05/run.bash new file mode 100755 index 0000000..d5b5d4a --- /dev/null +++ b/exercises/05/run.bash @@ -0,0 +1,101 @@ +#!/bin/bash + +# https://github.com/SAP-samples/cloud-cap-nodejs-codejam/tree/master/exercises/05 + +echo EXERCISE 05 + +cd bookshop + + +echo 1. Add a new entity Orders + +cat < db/schema.cds +namespace my.bookshop; + +entity Books { + key ID : Integer; + title : String; + stock : Integer; + author : Association to Authors; +} +entity Authors { + key ID : Integer; + name : String; + books : Association to many Books on books.author = \$self; +} +entity Orders { + key ID : UUID; + book : Association to Books; + quantity : Integer; +} +EOORDERS + +cat < srv/service.cds +using my.bookshop as my from '../db/schema'; + +service CatalogService { + entity Books as projection on my.Books; + entity Authors as projection on my.Authors; + entity Orders as projection on my.Orders; +} +EOORDERSSERVICE + +cds deploy + + +echo 2. Explore generic CDS features \(NOP\) + + +echo 3. Enhance the Orders entity + +cat < db/schema.cds +namespace my.bookshop; +using { cuid, managed, Country } from '@sap/cds/common'; + +entity Books { + key ID : Integer; + title : String; + stock : Integer; + author : Association to Authors; +} +entity Authors { + key ID : Integer; + name : String; + books : Association to many Books on books.author = \$self; +} +entity Orders : cuid, managed { + key ID : UUID; + book : Association to Books; + quantity : Integer; + country : Country; +} +EOORDERSMOD + + +echo 4. Restart the service manually and check the output +cds deploy + + +echo 5. Examine what the Orders entity looks like now \(NOP\) + + +echo 6. Create some entries in the Orders entity + +cds run > run.log 2>&1 & +CDSPID=$! +sleep 2 + +curl \ + -d '{"book_ID":201,"quantity":5}' \ + -H 'Content-Type: application/json' \ + http://localhost:4004/catalog/Orders + +curl \ + -d '{"ID": "527ef85a-aef2-464b-89f6-6a3ce64f2e14", "book_ID":427,"quantity":9}' \ + -H 'Content-Type: application/json' \ + http://localhost:4004/catalog/Orders + +kill $CDSPID + + +echo 7. Examine the data in the Orders entityset \(NOP\) diff --git a/exercises/06/readme.md b/exercises/06/readme.md index d2034cd..e0381d8 100644 --- a/exercises/06/readme.md +++ b/exercises/06/readme.md @@ -80,14 +80,7 @@ service CatalogService { What does this do, precisely? Let's find out. -:point_right: First save the file then redeploy & restart the service, like you did in [exercise 05](../05/): - -```sh -user@host:~/bookshop -=> cds deploy && cds serve all -``` - -:point_right: Now examine the OData service's [metadata](http://localhost:4004/catalog/$metadata), and you should find annotations that look like this: +:point_right: After saving the file, start up `cds watch` to get back into the auto restart mode. The, examine the OData service's [metadata](http://localhost:4004/catalog/$metadata). You should find annotations that look like this: ![readonly annotations](readonly-annotations.png) @@ -158,7 +151,9 @@ In a similar way to how we restricted access to the `Books` and `Authors` entity As you might have guessed, this is achieved via the `@insertonly` annotation shortcut. -:point_right: In the `CatalogService` service definition in `srv/cat-service.cds`, annotate the `Orders` entity with `@insertonly` so it looks like this: +:point_right: Before making this edit, switch back (if you haven't already) to using `cds watch` so that restarts will be automatic after changes. + +:point_right: In the `CatalogService` service definition in `srv/service.cds`, annotate the `Orders` entity with `@insertonly` so it looks like this: ```cds service CatalogService { @@ -168,8 +163,6 @@ service CatalogService { } ``` -:point_right: Redeploy and restart the service (run `cds deploy && cds serve all` in the terminal). - :point_right: Now create a couple of orders using the Postman collection from [exercise 05](../05/) - there should be a couple of POST requests against the `Orders` entityset (refer to the step in exercise 05 for the command line invocations if you wish). ![Postman request collection](../05/postman-collection.png) diff --git a/exercises/06/run.bash b/exercises/06/run.bash new file mode 100755 index 0000000..312cba2 --- /dev/null +++ b/exercises/06/run.bash @@ -0,0 +1,104 @@ +#!/bin/bash + +# https://github.com/SAP-samples/cloud-cap-nodejs-codejam/tree/master/exercises/06 + +echo EXERCISE 06 + +cd bookshop + + +echo 1. Import a collection of HTTP requests into Postman \(NOP\) + + +echo 2. Test the existing write access to Books and Authors + +cat < srv/service.cds +using my.bookshop as my from '../db/schema'; + +service CatalogService { + entity Books as projection on my.Books; + entity Authors as projection on my.Authors; + entity Orders as projection on my.Orders; +} +EOSERVICE + + +cds run > run.log 2>&1 & +CDSPID=$! +sleep 2 + +curl -X DELETE 'http://localhost:4004/catalog/Books(44138)' +curl -X DELETE 'http://localhost:4004/catalog/Authors(162)' + +curl \ + -d '{"ID": 162, "name": "Iain M Banks"}' \ + -H 'Content-Type: application/json' \ + http://localhost:4004/catalog/Authors + +curl \ + -d '{"ID": 44138, "title": "Consider Phlebas", "stock": 541, "author_ID": 162 }' \ + -H 'Content-Type: application/json' \ + http://localhost:4004/catalog/Books + +kill $CDSPID + + +echo 3. Restrict access to the Books and Authors entities +cat < srv/service.cds +using my.bookshop as my from '../db/schema'; + +service CatalogService { + @readonly entity Books as projection on my.Books; + @readonly entity Authors as projection on my.Authors; + entity Orders as projection on my.Orders; +} +EOSERVICEREADONLY + + +echo 4. Attempt to modify the Books and Authors entitysets + +cds run > run.log 2>&1 & +CDSPID=$! +sleep 2 + +curl \ + -d '{"ID": 47110, "title": "The Player of Games", "stock": 405, "author_ID": 162 }' \ + -H 'Content-Type: application/json' \ + http://localhost:4004/catalog/Books + +curl \ + -X DELETE \ + 'http://localhost:4004/catalog/Books(251)' + +kill $CDSPID + + +echo 5. Restrict access to the Orders entityset + +cat < srv/service.cds +using my.bookshop as my from '../db/schema'; + +service CatalogService { + @readonly entity Books as projection on my.Books; + @readonly entity Authors as projection on my.Authors; + @insertonly entity Orders as projection on my.Orders; +} +EOSERVICEINSERTONLY + +cds run > run.log 2>&1 & +CDSPID=$! +sleep 2 + +curl \ + -d '{"book_ID":201,"quantity":5}' \ + -H 'Content-Type: application/json' \ + http://localhost:4004/catalog/Orders + +curl \ + -d '{"ID": "527ef85a-aef2-464b-89f6-6a3ce64f2e14", "book_ID":427,"quantity":9}' \ + -H 'Content-Type: application/json' \ + http://localhost:4004/catalog/Orders + +kill $CDSPID + + diff --git a/exercises/07/readme.md b/exercises/07/readme.md index 8e7eb62..a10af3d 100644 --- a/exercises/07/readme.md +++ b/exercises/07/readme.md @@ -14,7 +14,7 @@ At the end of these steps, you'll have two OData services both exposing differen Service definitions can live alongside each other in the same CDS file. -:point_right: In `srv/cat-service.cds`, add a second service definition thus: +:point_right: In `srv/service.cds`, add a second service definition thus: ```cds service Stats { @@ -31,7 +31,15 @@ service Stats { Here the `Stats` service exposes the Orders entity in a read-only fashion as in the `CatalogService`, but uses the `excluding` clause to omit specific properties. These properties are not of interest to the analysis UI so are explicitly left out. Note that it also exposes the information as an entity called `OrderInfo`. -:point_right: Now redeploy and start serving the services (`cds deploy && cds serve all`) and check the root document at [http://localhost:4004/](http://localhost:4004/). You should see something like this: +:point_right: Effect a deployment to the persistence layer so that the relevant artefact will be created: + +```sh +user@host:~/bookshop +=> cds deploy +``` + + +:point_right: Start the service up again via `cds watch` and check the root document at [http://localhost:4004/](http://localhost:4004/). You should see something like this: ![two services](two-services.png) @@ -89,7 +97,7 @@ For Windows users: curl ^ -d "{\"book_ID\": 252, \"quantity\": 7}" ^ -H "Content-Type: application/json" ^ - http://localhost:4004/catalog/Authors + http://localhost:4004/catalog/Orders ``` Order 42 copies of The Hitch Hiker's Guide To The Galaxy (obviously!): @@ -106,7 +114,7 @@ For Windows users: curl ^ -d "{\"book_ID\": 421, \"quantity\": 42}" ^ -H "Content-Type: application/json" ^ - http://localhost:4004/catalog/Authors + http://localhost:4004/catalog/Orders ``` Now it's time to take a look at what the service will show us for these orders. We know we can't look at the `Orders` entityset as it has a `@insertonly` annotation shortcut based restriction, so we turn to our new service `Stats`. @@ -129,4 +137,7 @@ It's easy to explore building different views on the same underlying data model, 2. What did the order creation HTTP requests look like - which service was used, and why? - \ No newline at end of file + + +3. What was the artefact created in the persistence layer in Step 1? + diff --git a/exercises/08/handlers-dir.png b/exercises/08/handlers-dir.png index 03f6e9d..f7dd931 100644 Binary files a/exercises/08/handlers-dir.png and b/exercises/08/handlers-dir.png differ diff --git a/exercises/08/readme.md b/exercises/08/readme.md index 3d04ecb..ff7b942 100644 --- a/exercises/08/readme.md +++ b/exercises/08/readme.md @@ -1,6 +1,6 @@ # Exercise 08 - Adding custom logic, and debugging -In this exercise you'll learn how to add custom processing of specific OData operations on your services. It's done by adding [custom implementation logic](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/68af515a26d944c38d81fd92ad33681e.html) (in JavaScript) via well-defined hooks into the service API. +In this exercise you'll learn how to add custom processing of specific OData operations on your services. It's done by adding [custom implementation logic](https://cap.cloud.sap/docs/guides/service-impl) (in JavaScript) via well-defined hooks into the service API. Along the way you'll also learn how to use debugging features in VS Code, with the [launch configuration](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations) provided by the `cds init` command that you used in an earlier exercise. @@ -18,14 +18,14 @@ Custom logic for a given service definition is provided in a JavaScript file tha This custom logic file is normally placed in the same directory as the service definition file (i.e. side-by-side with it). -:point_right: Create a new file `cat-service.js` in the `srv/` directory. You should end up with something like this: +:point_right: Create a new file `service.js` in the `srv/` directory. You should end up with something like this: ![handlers directory](handlers-dir.png) ### 2. Add some basic custom logic code -:point_right: In the new file `cat-service.js`, add the following code: +:point_right: In the new file `service.js`, add the following code: ```javascript module.exports = srv => { @@ -40,39 +40,22 @@ You can see that this custom logic handler file is in the form of a module, whic ### 3. Run the service -:point_right: In the same way you've started the service in previous exercises, simply start the service now: +Following the automatic service restart, you should see a few interesting new lines in the output: -```sh -user@host:~/bookshop -=> cds serve all ``` - -You should see a few interesting lines in the output, highlighted here: - -``` -user@host:~/bookshop -=> cds serve all - -[cds] - connect to datasource - sqlite:bookshop.db +[cds] - connect to datasource - sqlite:bookshop +[cds] - serving CatalogService at /catalog - with impl: srv/service.js +[cds] - serving Stats at /stats - with impl: srv/service.js Service name: CatalogService Service name: Stats -[cds] - serving CatalogService at /catalog - impl: cat-service.js -[cds] - serving Stats at /stats - impl: cat-service.js -[cds] - service definitions loaded from: - - srv/cat-service.cds - db/data-model.cds - node_modules/@sap/cds/common.cds - -[cds] - server listens at http://localhost:4004 ... (terminate with ^C) -[cds] - launched in: 1125.468ms +[cds] - launched in: 928.482ms +[cds] - server listening on http://localhost:4004 ... +[ terminate with ^C ] ``` -The first two ("serving \ at \ ...") that we've seen before now have extra information showing that there's a JavaScript implementation that complements the service definition. +The two lines containing "serving \ at \ ..." that we've seen before now have extra information showing that there's a JavaScript implementation that complements the service definition. -Note that as the relationship between the service definition and the handler is at file level, the new `cat-service.js` file is deemed a handler for both services (`CatalogService` and `Stats`) in that service definition file. - -In fact, we can see that two lines from the call to `console.log` confirm that - the function defined in the module is called twice - once for each service (the first time `srv.name` is "CatalogService", and the second time it's "Stats"). +Note that as the relationship between the service definition and the handler is at file level, the new `service.js` file is deemed a handler for both services (`CatalogService` and `Stats`) in that service definition file. In fact, we can see that two lines from the call to `console.log` confirm that - the function defined in the module is called twice - once for each service (the first time `srv.name` is "CatalogService", and the second time it's "Stats"). ### 4. Set a breakpoint and launch in debug mode @@ -114,14 +97,14 @@ srv.path ![debug control buttons](debug-buttons.png) -:point_right: When you've finished exploring, switch back to the integrated terminal and terminate the service. +:point_right: When you've finished exploring, use the "Stop" debug control button to terminate the service. ### 5. Add custom logic -At this point we're confident enough to start adding custom logic, by [registering custom handlers](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/94c7b69cc4584a1a9dfd9cb2da295d5e.html). The custom logic should cause a discount message ("5% off!") to appear with the titles of books that are highly stocked (and therefore are those which we want to discount in order to get rid of). +At this point we're confident enough to start adding custom logic, by registering custom handlers. The custom logic should cause a discount message ("5% off!") to appear with the titles of books that are highly stocked (and therefore are those which we want to discount in order to get rid of). -:point_right: Add the following code directly after the call to `console.log` in the `cat-service.js` file. As you do, notice in the code the comments that the custom logic is implemented in two different ways, using two different programming styles - you only need one of them. Comment out (or delete) one of them, leaving the one you prefer: +:point_right: Add the following code directly after the call to `console.log` in the `service.js` file. As you do, notice in the code the comments that the custom logic is implemented in two different ways, using two different programming styles - you only need one of them. Comment out (or delete) one of them, leaving the one you prefer: ```js if (srv.name === 'CatalogService') { @@ -159,7 +142,7 @@ At this point we're confident enough to start adding custom logic, by [registeri ## Summary -You have added custom logic and learned how to debug a service in VS Code. The options available for adding custom logic are rich and plentiful - we recommend you look further into the [documentation](https://help.sap.com/viewer/65de2977205c403bbc107264b8eccf4b/Cloud/en-US/94c7b69cc4584a1a9dfd9cb2da295d5e.html) for more information. +You have added custom logic and learned how to debug a service in VS Code. The options available for adding custom logic are rich and plentiful - we recommend you look further into the [documentation](https://cap.cloud.sap/docs/guides/service-impl) for more information. ## Questions @@ -167,8 +150,5 @@ You have added custom logic and learned how to debug a service in VS Code. The o 1. What other hooks do you think might be useful in customizing a service? -2. What is the command used in the launch configuration for starting the service in debug mode - is it `cds serve all`? - - -3. How many times is the function (that is supplied to the `after` hook) called? +2. How many times is the function (that is supplied to the `after` hook) called? diff --git a/exercises/09/empty-table.png b/exercises/09/empty-table.png index a6f1211..c6f0591 100644 Binary files a/exercises/09/empty-table.png and b/exercises/09/empty-table.png differ diff --git a/exercises/09/readme.md b/exercises/09/readme.md index 58e5ac9..2958376 100644 --- a/exercises/09/readme.md +++ b/exercises/09/readme.md @@ -10,16 +10,13 @@ Following these steps, you'll build a simple Fiori app that sits in a local Fior ### 1. Introduce a basic HTML page to be served for the UI -Following the "convention over configuation" theme, the Node.js flavored CAP model will also automatically serve static resources (such as UI artefacts) from a directory called `app/`. +Following the "convention over configuation" theme, the Node.js flavored CAP model will also automatically serve static resources (such as UI artefacts) from a directory called `app/`. If there isn't anything that can be sensibly served in the `app/` directory it will serve the "Welcome to cds.services" landing page we've seen already: ![the "Welcome to cds.services" landing page](../07/two-services.png) -:point_right: Create an `app/` directory, at the same level as the `db/` and `srv/` directories. This directory that will contain the app files. - - -:point_right: Create the `webapp/` directory as a child of the `app/` and create an `index.html` file within it, containing the following. +:point_right: Create the `webapp/` directory as a child of the existing `app/` directory, and create an `index.html` file within it, containing the following: ```html @@ -36,73 +33,16 @@ If there isn't anything that can be sensibly served in the `app/` directory it w ``` -:point_right: Restart the service (with `cds serve all`) and go to the URL [http://localhost:4004/webapp](http://localhost:4004/webapp). Here, while the the page itself looks empty, there is the page title "Bookshop" in the browser tab, that shows us that the HTML we entered has been loaded: +:point_right: Restart `cds watch` and go to the URL [http://localhost:4004/webapp](http://localhost:4004/webapp). Here, while the the page itself looks empty, there is the page title "Bookshop" in the browser tab, that shows us that the HTML we entered has been loaded: ![title in browser tab](title-in-browser-tab.png) -### 2. Add a new module to the project descriptor - -This new `app/` directory contains all UI files and represents a new module in the context of what we're eventually going to deploy to the SAP Cloud Platform. - -For this to work and be included in what we eventually deploy and run, we must add information to the main deployment descriptor file `mta.yaml` that holds information relating to this. - -:point_right: Replace the content of your `mta.yaml` file with the following lines. (Basically only the `bookshop-ui` module was added, but the formatting of `yaml` files is very unforgiving, so just copy / paste the entire content from here and replace everything, to keep things simple!) -``` -_schema-version: 2.0.0 -ID: bookshop -version: 1.0.0 -modules: - - name: bookshop-db - type: hdb - path: db - parameters: - memory: 256M - disk-quota: 256M - requires: - - name: bookshop-db-hdi-container - - name: bookshop-srv - type: nodejs - path: srv - parameters: - memory: 512M - disk-quota: 256M - provides: - - name: srv_api - properties: - url: ${default-url} - requires: - - name: bookshop-db-hdi-container - - name: bookshop-ui - type: nodejs - path: app - parameters: - memory: 256M - disk-quota: 256M - requires: - - name: srv_api - group: destinations - properties: - forwardAuthToken: true - strictSSL: true - name: srv_api - url: ~{url} -resources: - - name: bookshop-db-hdi-container - type: com.sap.xs.hdi-container - properties: - hdi-container-name: ${service-name} - parameters: - service: hana - -``` - -This snippet not only describes the runtime environment of the new module, it also injects the URL of the deployed `srv` module during deploy time. - +### 2. Add a Fiori sandbox environment to the UI index page -### 3. Add a Fiori sandbox environment to the UI index page +Let's now add a bit more to the contents of the `index.html` file. To create a sandbox Fiori launchpad we'll need the UI5 runtime as well as artefacts from the `test-resources` area of the toolkit. -Let's now get back to the HTML in the `index.html` file. To create a sandbox Fiori launchpad we'll need the UI5 runtime as well as artefacts from the `test-resources` area of the toolkit. +> While you have `cds watch` running, you may notice that it's not looking out for changes to HTML files, but that doesn't actually matter, as with HTML changes, the server doesn't need to be restarted. So you can make these following changes and simply switch over to the browser to refresh. :point_right: Add these `script` elements between the `title` element and the end of the `head` element in `index.html`: @@ -144,7 +84,7 @@ Reloading the browser tab should now show the beginnings of something recognizab ![an empty Fiori launchpad](empty-fiori-launchpad.png) -### 4. Introduce a basic UI app to the Fiori launchpad +### 3. Introduce a basic UI app to the Fiori launchpad Now we have the launchpad as a container for our app, let's introduce it gradually. @@ -174,7 +114,7 @@ Reloading the index page in the browser should show something like this: ![Fiori launchpad with tile](launchpad-with-tile.png) -### 5. Create the app artefacts +### 4. Create the app artefacts As we can see from the configuration we've just added, we're suggesting the app is a Component-based app (where the component name is "bookshop") and is to be found at (relative) URL `/webapp`. Let's flesh that out in terms of directories and files now. @@ -289,28 +229,24 @@ This is a modern UI5 component definition that points to a JSON configuration fi ``` -Now you can open the "Browse Books" app and see all data - or better: see all (empty) lines. The reason for this, is that the Fiori Elements app does not yet know which properties to display. +Now you can open the "Browse Books" app and see the beginnings of a list report. ![empty table](empty-table.png) -### 6. Create a CDS index file -This is the point where you can introduce an `index.cds` file which controls which services are exposed. +### 5. Create a CDS index file with annotations -:point_right: Create a file `index.cds` in the `srv/` directory, and add the following single line as the initial content: +This is the point where you can introduce an `index.cds` file which controls which services are exposed, and also which can contain annotations to drive the Fiori elements based app. + +:point_right: Create a file `index.cds` in the `srv/` directory, and initially add this single line, which brings in the service definitions defined in `service.cds`: ```cds -using from './cat-service'; +using from './service'; ``` -> At this point you can actually reload the UI; while you will see some semblance of an app when you select the tile in the launchpad, the app's display will be mostly empty. - +Now let's look at important content that will help us join together in our minds the two complementary worlds of CAP and Fiori. This content is to be added next to `index.cds` and controls what gets served to Fiori frontends, via annotations that form a rich layer of metadata over the top of the service. -### 7. Add annotations for the service - -Now let's look at important content that will help us join together in our minds the two complementary worlds of CAP and Fiori. This content is to be added to `index.cds` and controls what gets served to Fiori frontends, via annotations that form a rich layer of metadata over the top of the service. - -:point_right: Below the initial line (`using from ...`) that you added to `index.cds` in the previous step, add the following content: +:point_right: Below the initial `using from ...` line, add the following content: ```cds annotate CatalogService.Books with @( @@ -348,12 +284,8 @@ annotate CatalogService.Authors with { > You may see some warnings that there are no texts for the internationalization (i18n) identifiers. We'll fix this shortly, you can ignore the warnings for now. -The final thing to do in this step is to redeploy because we have added CDS artefacts. - -:point_right: Do this now, with `cds deploy`, before restarting the service with `cds serve all`. - -### 8. Test the app +### 6. Test the app The app should be ready to invoke. Reload the Fiori launchpad and select the tile. It should open up into a nice List Report style Fiori Elements app - all driven from the service's annotations: @@ -362,7 +294,7 @@ The app should be ready to invoke. Reload the Fiori launchpad and select the til Well done! -### 9. Add base internationalization texts +### 7. Add base internationalization texts Just to round things off, add some i18n texts - they're referred to in various annotation sections, and it will make the app look a little more polished. @@ -377,7 +309,7 @@ Book=Book Books=Books ``` -:point_right: Redeploy and restart the service (`cds deploy && cds serve all`) and reload the app. You should see the static texts as specified in the `i18n.properties` file, such as "Author Name" rather than "AuthorName". +:point_right: After the `cds watch` mechanism has restarted the server, refresh the app, and you should see the static texts as specified in the `i18n.properties` file, such as "Author Name" rather than "AuthorName". ## Summary @@ -389,5 +321,3 @@ While this was a little intense as far as creation of artefacts was concerned, w 1. Why do we put the internationalization file in the `srv/` directory (rather than the `app/` directory)? -2. Which variable have you accessed in the `mta.yaml` descriptor and where has it been defined? - \ No newline at end of file diff --git a/exercises/10/readme.md b/exercises/10/readme.md index f9f538c..f087141 100644 --- a/exercises/10/readme.md +++ b/exercises/10/readme.md @@ -1,12 +1,12 @@ # Exercise 10 - Deploy the app to Cloud Foundry -In this exercise you'll make your project cloud-ready. In a cloud deployment, all modules will run as independent (but linked) applications. Therefore you need make add a couple of files to define the individual applications. +In this exercise you'll make your project cloud-ready. In a cloud deployment, all modules will run as independent (but linked) applications. The modules, and the relationships between them, are described in the `mta.yaml` file in the project's root. There's one already there that's been generated because of the use of `--add mta` when the project was initialized in [exercise 02](../exercises/02/). In this exercise you'll make some modifications and additions to that file, and add a couple more resources too. ## Steps -At the end of these steps, your project will be deployed to SAP Cloud Platform Cloud Foundry Environment. +At the end of these steps, your project will be cloud ready, built and deployed to SAP Cloud Platform Cloud Foundry Environment. ### 1. Sign in to Cloud Foundry @@ -19,28 +19,68 @@ user@host:~/bookshop ### 2. Tailor the HDI container declaration to the trial landscape -In a [previous exercise](../09) you may have seen that your `mta.yaml` deployment descriptor file referenced the "hana" service for the HDI container. We need to ensure that the correct HDI container will be used in the trial landscape. +We need to ensure that the correct HDI container will be used in the trial landscape. To do this, we need to make sure that the appropriate parameter is specified in the corresponding resource in `mta.yaml`. -:point_right: Replace the `resources` section in the `mta.yaml` file with this: -``` +:point_right: Edit the `mta.yaml` file, and to the `parameters` section (which should be at the end of the file), add a line "`service: hanatrial`". Here's what the section should look like after the edit: + +```yaml +############## RESOURCES ################################## resources: - - name: bookshop-db-hdi-container - type: com.sap.xs.hdi-container - properties: - hdi-container-name: ${service-name} - parameters: - service: hanatrial + ##### Services extracted from CAP configuration #### + ##### 'service-plan' can be configured via 'cds.requires..vcap.plan' + - name: bookshop-db + type: com.sap.xs.hdi-container + + parameters: + service: hanatrial + properties: + hdi-service-name: ${service-name} # required for Java case +############################################################ ``` -> Take care to get the whitespace and indentation right! +> Take care to get the whitespace and indentation right, and remember that there are no tabs allowed in YAML files! This step is only necessary when you want to deploy the project to the trial landscape. -### 3. Add a module descriptor file for the srv module -You might have noticed that there is no module descriptor for the `srv` module defined. For local development, such a descriptor is not needed as CAP knows how to parse those files. For the deployment to Cloud Foundry, on the other hand, a descriptor is required, to define the module dependencies and start commands. +### 3. Add a module definition to `mta.yaml` for the UI + +The `mta.yaml` file contains module definitions for the service and the database, but not (yet) for the Fiori-based UI. You'll do that now in this step. + +:point_right: Edit `mta.yaml` and add the following module definition, directly below the `modules:` line, and before the server module definition (`bookshop-srv`) which starts with a "#### SERVER MODULE ####" style comment line: + +```yaml +modules: + ############## UI MODULE ########################## + - name: bookshop-ui + type: nodejs + path: app + parameters: + memory: 256M + disk-quota: 256M + requires: + - name: srv-binding + group: destinations + properties: + forwardAuthToken: true + strictSSL: true + name: srv-binding + url: ~{srv-url} + + ############## SERVER MODULE ########################## + - name: bookshop-srv + type: nodejs + [...] +``` + +> The `bookshop-ui` module is shown here in context so you can see where to insert it - make sure you only add the lines for this module, and again, make sure you get the whitespace right. + + +### 4. Add a module descriptor file for the srv module -:point_right: Create a new descriptor file `package.json` in the **`srv/` directory**, and add the following content representing the server module, to make it cloud-ready: +You might have noticed that there is no descriptor for the `srv` module defined. For local development, such a descriptor is not needed as CAP knows how to parse those files. For the deployment to Cloud Foundry, on the other hand, a descriptor is required, to define the module dependencies and start commands. + +:point_right: Create a new `package.json` file in the `srv/` directory, and add the following content representing the server module, to make it cloud-ready: ```json { @@ -68,13 +108,11 @@ You might have noticed that there is no module descriptor for the `srv` module d } ``` +### 5. Add the app router configuration +Similar to the `srv` module, we need to add a descriptor file for the `app` module as well. We will embed the UI source files into an app router, to be able to connect to the `srv` module and to forward requests to it. -### 4. Add the app router configuration - -Similar to the `srv` module, we need to add a module descriptor for the `app` module as well. We will embed the UI source files into an app router, to be able to connect to the `srv` module and to forward requests to it. - -:point_right: Create a new `package.json` file in the **`app/` directory** with the following content, to start this module as an independent app router application within Cloud Foundry: +:point_right: Create a new `package.json` file in the `app/` directory with the following content, to start this module as an independent app router application within Cloud Foundry: ```json { @@ -103,7 +141,7 @@ Similar to the `srv` module, we need to add a module descriptor for the `app` mo "localDir": "webapp/" }, { "source": "^(.*)$", - "destination": "srv_api" + "destination": "srv-binding" }] } @@ -112,7 +150,46 @@ Similar to the `srv` module, we need to add a module descriptor for the `app` mo This file not only defines the welcome page, but also defines which requests are forwarded to which Cloud Foundry application. -### 5. Add npm scripts to trigger the deployment +### 6. Specify HANA as a database in the configuration + +We're about to deploy to HANA in the form of an HDI container on the trial landscape of SAP Cloud Platform Cloud Foundry. This means we need to ensure we have the right configuration for the database, so that things get built correctly. This can be done in the `package.json` file. + +:point_right: In the root level `package.json` file, find the `cds -> requires -> db` node and add another section for `[production]`, like this: + +```cds +"[production]": { + "kind": "hana" +}, +``` + +' +so it then looks like this: + +```cds + "cds": { + "requires": { + "db": { + "kind": "sqlite", + "model": [ + "db/", + "srv/", + "app/" + ], + "[production]": { + "kind": "hana" + }, + "credentials": { + "database": "db.db" + } + } + } + }, +``` + +The "hana" value of "kind" will now be used in place of "sqlite" when the "production" profile is used. See the [Runtime Configuration for Node.js](https://cap.cloud.sap/docs/advanced/config) section of the CAP documentation for more details. + + +### 7. Add npm scripts to trigger the deployment So far, the `package.json` file in your project root only defines scripts for local project execution. @@ -123,24 +200,36 @@ So far, the `package.json` file in your project root only defines scripts for lo "build:mta": "cds build/all && mbt build -p=cf" ``` -You might have noticed, that the `mbt` command isn't a typical shell command. This is actually a command from another Node module. This tool allows you to package your project into a deployable archive, which you'll need in order to get the app onto the SAP Cloud Platform. +You might have noticed, that the `mbt` command isn't a typical shell command. This is actually a command from another Node.js package. `mbt` allows you to package your project into a deployable archive, which you'll need in order to get the app onto the SAP Cloud Platform. -:point_right: Install this module in your project to allow its usage in the npm scripts, like this: +:point_right: Install this package locally in your project to allow its usage in the npm scripts, like this: ``` user@host:~/bookshop => npm install mbt ``` -### 6. Build the project +### 8. Build the project We're almost there. To make our project ready for deployment, we need to package it into a single archive which can be used a delivery unit. -:point_right: Trigger the build process with the following command. +:point_right: Trigger the build process with the following command, specifying "production" for the `NODE_ENV` environment variable: + ``` -user@host:~/bookshop -=> npm run build:mta +NODE_ENV=production npm run build:mta ``` -### 7. Deploy the archive + +If you're running Windows then you'll need to set the environment variable first with `set` and then run the command, like this: + +``` +set NODE_ENV production +npm run build:mta +``` + +> If you want to run the two commands (`cds build/all` and `mbt build -p=cf`) manually, one after the other, to see what they do, note that you'll have to use `npx` to run the `mbt` command, like this: `npx mbt build -p=cf`. In fact, the latest version of `mbt` has the value `cf` as the default for the `-p` ('platform') flag, so you don't actually need to specify this explicitly. + + +### 9. Deploy the archive + Now you should see a new directory `mta_archives/` which contains an archive file named with the `ID` and `version` we defined in the `mta.yaml` descriptor. One command is all it takes to deploy your project to the cloud. :point_right: Execute the following command to trigger the deployment process: @@ -151,9 +240,9 @@ user@host:~/bookshop ``` -### 8. Check the apps and services in Cloud Foundry +### 10. Check the apps and services in Cloud Foundry -You can and should check the status of what you've deployed to your trial Cloud Foundry environment on the SAP Cloud Platform. +You can and should check the status of what you've deployed to your trial Cloud Foundry environment on the SAP Cloud Platform. Use the following commands do to this: