Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get Started: Updates #662

Merged
merged 15 commits into from
Feb 19, 2024
208 changes: 175 additions & 33 deletions get-started/in-a-nutshell.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ git clone https://github.com/sap-samples/cloud-cap-samples-java bookshop
In order to start VSCode via the `code` CLI, users on macOS must first run a command (*Shell Command: Install 'code' command in PATH*) to add the VS Code executable to the `PATH` environment variable. Read VS Code's [macOS setup guide](https://code.visualstudio.com/docs/setup/mac) for help.
:::

> For Java development in VS Code you need to [install extensions](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack).

4. Run `cds watch` in an [*Integrated Terminal*](https://code.visualstudio.com/docs/terminal/basics)

::: code-group
Expand Down Expand Up @@ -192,7 +194,7 @@ _Find this source also in `cap/samples` [for Node.js](https://github.com/sap-sam
As soon as you save your file, the still running `cds watch` reacts immediately with new output like this:

```log
[cds] - connect to db { database: ':memory:' }
[cds] - connect to db > sqlite { database: ':memory:' }
/> successfully deployed to in-memory database.
```

Expand Down Expand Up @@ -283,20 +285,20 @@ service CatalogService @(path:'/browse') { // [!code focus]



### Served to OData out of the box
### Served to OData Out of the Box

<div class="impl node">

This time `cds watch` reacted with additional output like this:

```log
[cds] - serving AdminService { at: '/admin' }
[cds] - serving CatalogService { at: '/browse', impl: 'bookshop/srv/cat-service.js' }
[cds] - serving AdminService { at: '/odata/v4/admin' }
[cds] - serving CatalogService { at: '/browse' }

[cds] - server listening on { url: 'http://localhost:4004' }
```

As you can see, the two service definitions have been compiled and generic service providers have been constructed to serve requests on the listed endpoints _/admin_ and _/browse_.
As you can see, the two service definitions have been compiled and generic service providers have been constructed to serve requests on the listed endpoints _/odata/v4/admin_ and _/browse_.

</div>

Expand All @@ -311,9 +313,8 @@ c.s.c.services.impl.ServiceCatalogImpl : Registered service CatalogService

As you can see in the log output, the two service definitions have been compiled and generic service providers have been constructed to serve requests on the listed endpoints _/odata/v4/AdminService_ and _/odata/v4/browse_.

::: warning
Both services defined above contain security annotations that restrict access to certain endpoints. Please add the dependency to spring-boot-security-starter to the srv/pom.xml in order to activate mock user and authentication support:
:::
::: warning Add the dependency to spring-boot-security-starter
Both services defined above contain security annotations that restrict access to certain endpoints. Please add the dependency to spring-boot-security-starter to the _srv/pom.xml_ in order to activate mock user and authentication support:

<!-- TODO Notebooks: can't be automated yet as it requires insert in pom.xml -->
```xml
Expand All @@ -323,26 +324,32 @@ Both services defined above contain security annotations that restrict access to
</dependency>
```

:::

</div>

::: tip

CAP-based services are full-fledged OData services out of the box. Without adding any provider implementation code, they translate OData request into corresponding database requests, and return the results as OData responses.
::: tip CAP-based services are full-fledged OData services out of the box

Without adding any provider implementation code, they translate OData request into corresponding database requests, and return the results as OData responses.
:::

You can even use advanced query options, such as `$select`, `$expand`, `$search`, and many more. For example, try out this link:

- http://localhost:4004/browse/Books?$search=Brontë&$select=title,author&$expand=currency($select=code,name,symbol)&$orderby=title
| | |
| --- | ---|
| Node.js | http://localhost:4004/browse/Books?$search=Brontë&$select=title,author&$expand=currency($select=code,name,symbol)&$orderby=title |
| Java | http://localhost:8080/odata/v4/browse/Books?$search=Brontë&$select=title,author&$expand=currency($select=code,name,symbol)&$orderby=title |

[Learn more about **Serving OData Protocol**.](../advanced/odata){.learn-more}
[Learn more about **Generic Providers**.](../guides/providing-services){.learn-more}
[Learn more about **OData's Query Options**.](../advanced/odata){.learn-more}



### Generic *index.html* Pages

<!-- TODO: explain "Why" is there a generic index.html and from where is it served? Link zu cds.server-->
Open _<http://localhost:4004>_ in your browser and see the generic _index.html_ page:
Open _<http://localhost:4004>_ / _<http://localhost:8080>_ in your browser and see the generic _index.html_ page:

<div class="impl node">

Expand Down Expand Up @@ -402,10 +409,6 @@ ID,title,author_ID,stock
252,Eleonora,150,555
271,Catweazle,170,22
```
:::

::: code-group

```csvc [db/data/sap.capire.bookshop-Authors.csv]
ID,name
101,Emily Brontë
Expand Down Expand Up @@ -460,7 +463,7 @@ c.s.c.s.impl.persistence.CsvDataLoader : Filling sap.capire.bookshop.Books fro

Now that we've a connected, fully capable SQL database, filled with some initial data, we can send complex OData queries, served by the built-in generic providers:

- _[…/Books?$select=ID,title](http://localhost:4004/odata/v4/browse/Books?$select=ID,title)_ {.impl .node}
- _[…/Books?$select=ID,title](http://localhost:4004/browse/Books?$select=ID,title)_ {.impl .node}
renejeglinsky marked this conversation as resolved.
Show resolved Hide resolved
- _[…/Authors?$search=Bro](http://localhost:4004/odata/v4/admin/Authors?$search=Bro)_ {.impl .node}
- _[…/Authors?$expand=books($select=ID,title)](http://localhost:4004/odata/v4/admin/Authors?$expand=books($select=ID,title))_ {.impl .node}
- _[…/Books?$select=ID,title](http://localhost:8080/odata/v4/browse/Books?$select=ID,title)_ {.impl .java}
Expand All @@ -475,15 +478,30 @@ Now that we've a connected, fully capable SQL database, filled with some initial
[Learn more about **OData's Query Options**.](../advanced/odata){.learn-more}


<div class="impl node">
### Deploying Persistent Databases {.impl .node}

We can also use persistent instead of in-memory databases. For example, still with SQLite, add the following configuration:

> Add sqlite config from SQLite DB Guide
> cds init could update @cap.js/sqlite dependency to ^1.4
renejeglinsky marked this conversation as resolved.
Show resolved Hide resolved

::: code-group

### Deploying Persistent Databases
```json [package.json]
"cds": { "requires": {
"db": {
"kind": "sqlite",
"credentials": { "url": "db.sqlite" } // [!code focus]
}
}}
```

:::

We can also use persistent instead of in-memory databases. For example, still with SQLite:
Then deploy:

```sh
npm add sqlite3 -D
cds deploy --to sqlite:my.sqlite
cds deploy
```

The difference from the automatically provided in-memory database is that we now get a persistent database stored in the local file _./my.sqlite_. This is also recorded in the _package.json_.
Expand All @@ -494,14 +512,13 @@ To see what that did, use the `sqlite3` CLI with the newly created database:
sqlite3 my.sqlite .dump
sqlite3 my.sqlite .tables
```
[Learn how to install SQLite on Windows.](troubleshooting#how-do-i-install-sqlite-on-windows){.learn-more}

You could also deploy to a provisioned SAP HANA database using this variant:

```sh
cds deploy --to hana
```
</div>


[Learn more about deploying to SAP HANA.](../guides/databases){.learn-more .impl .node}

Expand Down Expand Up @@ -559,13 +576,15 @@ In Node.js, the easiest way to provide implementations for services is through e

In CAP Java, you can add custom handlers for your service as so called EventHandlers. As CAP Java integrates with Spring Boot, you need to provide your custom code in classes, annotated with `@Component`or `@Service`, for example. Use your favorite Java IDE to add a class like the following to the `srv/src/main/java/` folder of your application. {.impl .java}

```java
::: code-group
```java [srv/src/main/java/customer/bookshop/handlers/CatalogService.java]
@Component
@ServiceName(CatalogService_.CDS_NAME)
public class CatalogHandler implements EventHandler {
// your custom code will go here
}
```
:::

::: tip
Place the code in your package of choice and use your IDE to generate the needed `import` statements.
renejeglinsky marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -605,16 +624,54 @@ module.exports = function (){

Now that you have created the classes for your custom handlers it's time to add the actual logic. You can achieve this by adding methods annotated with CAP's `@Before`, `@On`, or `@After` to your new class. The annotation takes two arguments: the event that shall be handled and the entity name for which the event is handled.

```java
::: code-group
```java [srv/src/main/java/customer/bookshop/handlers/CatalogService.java]
@After(event = CqnService.EVENT_READ, entity = Books_.CDS_NAME)
public void addDiscountIfApplicable(List<Books> books) {
for (Books book : books) {
if (book.getStock() > 111) {
book.setTitle(book.getTitle() + " -- 11% discount!");
public void addDiscountIfApplicable(List<Books> books) {
for (Books book : books) {
if (book.getStock() != null && book.getStock() > 111) {
book.setTitle(book.getTitle() + " -- 11% discount!");
}
}
}
```
:::

:::details Code including imports
::: code-group
```java [srv/src/main/java/customer/bookshop/handlers/CatalogService.java]
package customer.bookshop.handlers;

import java.util.List;

import org.springframework.stereotype.Component;

import com.sap.cds.services.cds.CqnService;
import com.sap.cds.services.handler.EventHandler;
import com.sap.cds.services.handler.annotations.After;
import com.sap.cds.services.handler.annotations.ServiceName;

import cds.gen.catalogservice.Books;
import cds.gen.catalogservice.Books_;
import cds.gen.catalogservice.CatalogService_;

@Component
@ServiceName(CatalogService_.CDS_NAME)
public class CatalogHandler implements EventHandler {
@After(event = CqnService.EVENT_READ, entity = Books_.CDS_NAME)
public void addDiscountIfApplicable(List<Books> books) {
for (Books book : books) {
if (book.getStock() != null && book.getStock() > 111) {
book.setTitle(book.getTitle() + " -- 11% discount!");
}
}
}
}


```
:::


[Learn more about **event handlers** in the CAP Java documentation.](../java/provisioning-api#handlerclasses){.learn-more}

Expand Down Expand Up @@ -654,7 +711,8 @@ module.exports = async function (){

<div class="impl java">

```java
::: code-group
```java [srv/src/main/java/customer/bookshop/handlers/SubmitOrderHandler.java]
@Component
@ServiceName(CatalogService_.CDS_NAME)
public class SubmitOrderHandler implements EventHandler {
Expand All @@ -677,6 +735,53 @@ public class SubmitOrderHandler implements EventHandler {
}
}
```
:::

:::details Code including imports
::: code-group
```java [srv/src/main/java/customer/bookshop/handlers/CatalogService.java]
package customer.bookshop.handlers;

import org.springframework.stereotype.Component;

import com.sap.cds.ql.Select;
import com.sap.cds.ql.Update;
import com.sap.cds.services.handler.EventHandler;
import com.sap.cds.services.handler.annotations.On;
import com.sap.cds.services.handler.annotations.ServiceName;
import com.sap.cds.services.persistence.PersistenceService;

import cds.gen.catalogservice.Books;
import cds.gen.catalogservice.Books_;
import cds.gen.catalogservice.CatalogService_;
import cds.gen.catalogservice.SubmitOrderContext;

@Component
@ServiceName(CatalogService_.CDS_NAME)
public class SubmitOrderHandler implements EventHandler {

private final PersistenceService persistenceService;

public SubmitOrderHandler(PersistenceService persistenceService) {
this.persistenceService = persistenceService;
}

@On()
public void onSubmitOrder(SubmitOrderContext context) {
Select<Books_> byId = Select.from(cds.gen.catalogservice.Books_.class).byId(context.getBook());
Books book = persistenceService.run(byId).single().as(Books.class);
book.setStock(book.getStock() - context.getQuantity());

persistenceService.run(Update.entity(Books_.CDS_NAME).data(book));

context.setCompleted();
}
}

renejeglinsky marked this conversation as resolved.
Show resolved Hide resolved

```
:::

</div>

[Find this source also in **cap/samples**.](https://github.com/sap-samples/cloud-cap-samples/tree/main/bookshop/srv/cat-service.js){ .learn-more .impl .node target="_blank"}
Expand All @@ -690,7 +795,44 @@ public class SubmitOrderHandler implements EventHandler {

**Test this implementation**, [for example using the Vue.js app](#vue), and see how discounts are displayed in some book titles. {.impl .node}

Or submit orders until you see the error messages. {.impl .node}
### Sample HTTP Request

Or submit orders until you see the error messages. Create a file called _test.http_ and copy the request into it.

::: code-group
```http [Node.js]
###
# Submit Order

POST http://localhost:4004/browse/submitOrder
Content-Type: application/json
Authorization: Basic alice:

{

"book": 201,
"quantity": 2

}


```
```http [Java]
###
# Submit Order

POST http://localhost:8080/odata/v4/browse/submitOrder
Content-Type: application/json
Authorization: Basic authenticated:

{

"book": 201,
"quantity": 2

}
```
:::


## Summary and Next Steps
Expand Down
8 changes: 4 additions & 4 deletions guides/databases-sqlite.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,13 @@ As stated previously, `@cap-js/sqlite` uses an in-memory SQLite database by defa

```log
...
[cds] - connect to db > sqlite { url: ':memory:' } //[!code focus]
[cds] - connect to db > sqlite { url: ':memory:' } // [!code focus]
> init from db/init.js
> init from db/data/sap.capire.bookshop-Authors.csv
> init from db/data/sap.capire.bookshop-Books.csv
> init from db/data/sap.capire.bookshop-Books.texts.csv
> init from db/data/sap.capire.bookshop-Genres.csv
/> successfully deployed to in-memory database. //[!code focus]
/> successfully deployed to in-memory database. // [!code focus]
...
```

Expand Down Expand Up @@ -197,7 +197,7 @@ You can also use persistent SQLite databases. In this case, the schema is initia
{ "cds": { "requires": {
"db": {
"kind": "sqlite",
"credentials": { "url": "db.sqlite" } //[!code focus]
"credentials": { "url": "db.sqlite" } // [!code focus]
}
}}}
```
Expand Down Expand Up @@ -272,7 +272,7 @@ While drop-create is most appropriate for development, it isn't suitable for dat
"db": {
"kind": "sqlite",
"credentials": { "url": "db.sqlite" },
"schema_evolution": "auto" //[!code focus]
"schema_evolution": "auto" // [!code focus]
}
}}}
```
Expand Down
Loading