You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- Find and open [_Business Partner (A2X)_](https://api.sap.com/api/API_BUSINESS_PARTNER/overview)
474
474
- Switch to the *API Specification* subtab.
475
-
- Click the download icon next to *OData EDMX* to download the `API_BUSINESS_PARTNER.edmx`file to your local machine.
475
+
- Click the download icon next to *OData EDMX* to download the `.edmx` file.
476
476
:::
477
477
478
478
2. Import that to the current project:
@@ -801,6 +801,8 @@ There are different options to provide initial data, test data, and mock data:
801
801
802
802
In all cases, the `.csv` files are placed next to the `.cds` files, and hence they are automatically detected and loaded into the in-memory database.
803
803
804
+
For Java, make sure to add the `--with-mocks` option to the `cds deploy`command used to generate the `schema.sql`in`srv/pom.xml`. This ensures that tables forthe mocked remote entities are createdin the database.
805
+
804
806
[Learn more about *Adding Initial Data*](../databases/initial-data) {.learn-more}
805
807
806
808
> [!tip] Mocking for Inner-Loop Development
@@ -811,33 +813,40 @@ In all cases, the `.csv` files are placed next to the `.cds` files, and hence th
811
813
812
814
### Required Custom Code
813
815
814
-
When it comes to real non-mocked integration with external services, custom code is required to handle the actual data integration. We can see that by starting two separate processes, one for the S/4 service, and one for the Xtravels app as follows from within the `cap/samples/xtravels` project folder:
815
-
816
-
1. Start the a mocked S/4 service in terminal 1:
816
+
When it comes to real non-mocked integration with external services, custom code is required to handle the actual data integration. For a more realistic test, lets use `cds mock` to run the mocked services in separate processes. Run these commands **in separate terminals**:
817
817
818
818
```shell
819
819
cds mock apis/capire/s4.cds
820
820
```
821
-
822
-
2. Start the Xtravels app in terminal 2:
823
-
821
+
```shell
822
+
cds mock apis/capire/xflights.cds
823
+
```
824
824
```shell
825
825
cds watch
826
826
```
827
+
828
+
In the log output of the xtravels app server we now see that it connects to the two other services instead of mocking them:
829
+
830
+
```zsh
831
+
[cds] - connect to S4BusinessPartnerService > odata {
[cds] - connect to sap.capire.flights.data > hcql {
837
+
url: 'http://localhost:54475/hcql/data'
838
+
}
832
839
```
833
840
834
-
Note that in the output there is no mention of a mocked `S4BusinessPartnerService` anymore. This is because the CAP runtime now detected that a service with that name is served by a different process within my local binding environment now, so we don't mock it in-process any longer.
841
+
This is because the CAP runtime now detected that services with that name are served by different processes within our local binding environment now, so we don't mock them in-process any longer.
842
+
843
+
When we open the Fiori UI in the browser again, we see the data from the S/4 service is missing now, as we have not yet implemented the required custom code for the actual data integration, the same applies to the flight data from _xflights_:
835
844
836
-
When we open the Fiori UI in the browser again, we see the data from the S/4 service is missing now, as we have not yet implemented the required custom code for the actual data integration:
845
+

837
846
838
-

847
+

839
848
840
-
In addition, when we look into the log output of the Xtravels app, we see something like that, which indicates that the Fiori client is desparately trying to fetch the missing customer data in _$batch_ requests, with one GET request per row, 30 in total, corresponding to the default page size. If we'd scroll the list in the UI this would repeat like crazy.
849
+
In addition, when we again look into the log output, we see some bulk requests like shown below, which indicates that the Fiori client is desparately trying to fetch the missing customer data. If we'd scroll the list in the UI this would repeat like crazy.
841
850
842
851
<span style="font-size:63%">
843
852
@@ -887,94 +896,160 @@ So, let's go on and fill this gap with required custom code...
887
896
888
897
### CAP-level Data Federation
889
898
890
-
Displaying external data in lists UIs commonly requires fast access to that data. Relying on live calls to remote services per row is clearly not an option, as that would lead to poor performance, excessive load on on server, and lacking all resilience. Instead, we need to replicate the required data from remote services into local replica tables, which can then be accessed fast and reliably by UIs using good old SQL JOINs.
899
+
Displaying external data in lists commonly requires fast access to that data. Relying on live calls to remote services per row is clearly not an option, as that would lead to poor performance, excessive load on server, and a nightmare regarding resilience. Instead, we somehow need to ensure that all required data is available locally, so that it can be accessed fast and reliably by UIs, using good old SQL JOINs.
900
+
901
+
In the xtravels app we accomplished that with a simple, yet quite effective data replication solution, which automatically replicates data for all [consumption views](#consumption-views) tagged with the `@federated` annotation, for example:
902
+
903
+
```cds :line-numbers=4
904
+
@federated entity Customers as projection on S4.A_BusinessPartner { ... }
905
+
```
906
+
907
+
If a remote service is detected, these entities are turned into tables to serve as local persistence for replicated data (line 9 in the code below).
> By tagging entities with `@federated` we stay _intentional_ about **_what_** we want to achieve, and avoid any premature assumptions about **_how_** things are actually implemented. => This allows CAP runtimes – or your own _generic_ solutions, as in this case – to choose the best possible implementation strategies for the given environment and use case, which may differ between development, testing, and production environments, or might need to evolve over time.
912
+
891
913
892
-
We did implement a simple, yet quite effective, solution in `srv/data-federation.js` in the Xtravels app as shown below:
914
+
Here's the complete code, placed infile `srv/data-federation.js`:
This is a generic solution which automatically replicates all entities annotated with `@federated`, as we did in the [consumption views](#consumption-views) before, for example:
959
+
Let's have a closer look at this code, which handles these main tasks:
928
960
929
-
```cds :line-numbers=4
930
-
@federated entity Customers as projection on S4.A_BusinessPartner { ... }
931
-
```
961
+
1. **Prepare Persistence** – When the model is `loaded`, before it's deployed to the database, we collect all to be `@federated` entities, check whether their respective services are remote, and if so, turn them into tables forlocal replicas (line 11).
932
962
933
-
These entities are detected, and, unless mocked, turned into tables to serve as local persistence for replicated data (line 9 in the code above).
963
+
2. **Setup Replication** – Later when all services are `served`, we connect to each remote one (line 20), register a handler for replication (line 21), and schedule it to be invoked every three seconds (line 22).
964
+
965
+
3. **Replicate Data** – Finally, the `replicate` handler implements a simple polling-based data federation strategy, based on `modifiedAt` timestamps (lines 28-32), with the actual call to remote happening on line 29.
966
+
967
+
> [!tip] CAP-level Querying -> agnostic to databases & protocols
968
+
> We work with **database-agnostic** and **protocol-agnostic** [CQL queries](../../cds/cql) both for interacting with the local database as well as for querying remote services. In effect, we got a fully generic solution for replication, i.e., it works for**_any_** remote service that supports OData, or HCQL.
934
969
935
-
> [!tip] Stay Intentional -> What, not how
936
-
> By default, consumption views on top of remote entities are virtual and do not have local persistence. By setting the `@cds.persistence.table` annotation to `true`, we instruct the CAP database layer to create actual database tables for these entities, which can then be used to store replicated data locally.
937
-
>
938
-
> Yet, you as an application developer stay intentional about **_what_** you want to achieve (-> `@federated` tags), and **avoid any eager assumptions** about **_how_** this is actually implemented. => This allows CAP runtimes – or you own _generic_ framework plugins, as with the code above – to choose the best possible implementation strategy for the given environment and use case, which may differ between development, testing, and production environments.
939
970
940
-
The actual replication is accomplished by the highlighted lines 16 and 24-26, which implement a simple polling-based data federation mechanism, based on `modifiedAt` timestamps. Let's break that down line by line:
971
+
#### Test Drive
941
972
942
-
```js :line-numbers=16
943
-
const remote = await cds.connect.to (each.remote) // done once
973
+
Let's see the outcome in action: to activate the above data federation code, edit `srv/server.js` file and uncomment the single line of code in there like this:
For all received rows, `UPSERT` automatically inserts new records or updates existing ones in the local replica table.
964
1024
965
-
Noteworthy:
1025
+
Finally, open the Fiori UI in the browser again, and see that customer data from S/4 as well as flight data from xflights is now displayed properly, thanks to the data federation implemented above.
966
1026
967
-
- `SELECT` and `UPSERT` commands are part of CAP Node.js' [`cds.ql`](../../node.js/cds-ql) and and create [_CQL_](../../cds/cql) query objects. When `await`ed as in line 24 and 26, they are executed against the local database, translated to database-native SQL under the hood.
1027
+

968
1028
969
-
- `remote.read` on the other hand executes queries against the connected remote service, translating them to the connected service's remote protocol, like OData or HCQL. It's actually a convenience shorthand for:

1030
+
1031
+
1032
+
1033
+
### Delegating Requests
1034
+
1035
+
Value helps are common use cases where delegation of requests is needed, which we implemented like this in `srv/travel-service.js` for the `Customers` entity:
The event handler intercepts all `READ` requests to the `Customers` entity, and simply delegates the incoming query as-is to the connected S/4 service (line 26).
1045
+
1046
+
Noteworthy: The incoming request refers to:
973
1047
974
-
- In both/all cases we work with database-agnostic and protocol-agnostic queries constructed using [`cds.ql`](../../node.js/cds-ql), while CAP runtimes handle the protocol and dialect translations under the hood.
1048
+
- the `TravelService.Customers` entity, which is a view on
1049
+
- the `Customers` entity, which in turn is a consumption view on
1050
+
- the remote `A_BusinessPartner` entity.
975
1051
976
-
> [!tip] Agnostic Querying, ... <i>as if they were local</i>
977
-
> Both, queries executed against local databases, as well as queries executed against remote services using, are **database-agnostic** and **protocol-agnostic**, respectively. CAP runtimes handle all necessary translations under the hood, allowing us to write code that works regardless of where the data actually resides – be it with mocked data, replicated data, or real remote data. We can even switch between different integration and federation strategies later on.
1052
+
The CAP runtime is aware of that and automatically translates the query into a query to the underlying entity, known to the remote service. Thereby, all select clauses, and where clauses, are fully preserved, translated, and delegated.
978
1053
979
1054
980
1055
### Requests to Remote
@@ -983,32 +1058,35 @@ In addition to replication, there are also use cases where we need to send reque
Another example is delegating value help requests to remote services, which we implemented like this in `srv/travel-service.js` for `Customers` -> S/4 Business Partners:
Service bindings configure connectivity to remote services. They are injected respective connection points configure in CAP through `cds.requires.<service-name>` configurations, which are defined like this:
This intercepts all `READ` requests to the `Customers` entity, and simply delegates the original request's query to the connected S/4 service. This ensures we support all kind of selectclauses, and where clauses, as they are fully preserved. The CAP runtime handles the necessary protocol translations under the hood, including mapping all projections to entities known to the remote service.
1008
-
1009
-
1010
-
## Service Bindings
1011
1088
1089
+
They are added to consuming applications' _package.json_ files, either manually, or automatically when using `cds import` as we saw earlier.
1012
1090
1013
1091
### CAP Node.js
1014
1092
@@ -1093,6 +1171,9 @@ cds watch
1093
1171
1094
1172
Open UI → flights data displayed
1095
1173
1174
+
> [!tip] Decoupled Inner-Loop Development
1175
+
> CAP runtimes automatically mock imported service APIs during development, allowing us to develop and test integrated applications in fast inner loops, without the need to connect to real remote services. This decouples inner-loop development from external dependencies, speeding up development and increasing resilience.
0 commit comments