generated from SAP/repository-template
-
Notifications
You must be signed in to change notification settings - Fork 75
/
using-services.md
1658 lines (1198 loc) · 66.8 KB
/
using-services.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
index: 22
synopsis: >
Learn how to use uniform APIs to consume local or remote services.
redirect_from:
- guides/consuming-services
# layout: cookbook
status: released
impl-variants: true
# uacp: Used as link target from Help Portal at
---
<script setup>
import { h } from 'vue'
const Y = () => h('span', { class: 'y', title: 'Supported' }, ['✓'] )
const X = () => h('span', { class: 'x', title: 'Not supported' }, ['✗'] )
</script>
<style scoped>
.y { color: var(--vp-c-green); font-weight:900; }
.x { color: var(--vp-c-red); font-weight:900; }
</style>
# Consuming Services
[[toc]]
## Introduction
If you want to use data from other services or you want to split your application into multiple microservices, you need a connection between those services. We call them **remote services**. As everything in CAP is a service, remote services are modeled the same way as internal services — using CDS.
CAP supports service consumption with dedicated APIs to [import](#import-api) service definitions, [query](#execute-queries) remote services, [mash up](#building-mashups) services, and [work locally](#local-mocking) as much as possible.
<!--
While requests that are part of your application are translated into data base requests, requests to remote services are translated to OData requests, or in future possible for other protocols.
CAP allows you to model your own projections on remote services to decouple from the remote service's interface and to adapt it to your needs. With a few lines of code, you can expose remote services through your services and build mash-ups.
Connection in productive use works through SAP BTP Destination services or by specifying the required credentials through environment variables.
-->
### Feature Overview
For outbound remote service consumption, the following features are supported:
+ OData V2
+ OData V4
+ [Querying API](#querying-api-features)
+ [Projections on remote services](#supported-projection-features)
### Tutorials and Examples
Most snippets in this guide are from the [Build an Application End-to-End using CAP, Node.js, and VS Code](https://developers.sap.com/mission.btp-application-cap-e2e.html) tutorial, in particular [Consume Remote Services from SAP S/4HANA Cloud Using CAP](https://developers.sap.com/mission.btp-consume-external-service-cap.html).
| Example | Description |
| ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| [Consume Remote Services from SAP S/4HANA Cloud Using CAP](https://developers.sap.com/mission.btp-consume-external-service-cap.html) | End-to-end Tutorial, Node.js, SAP S/4HANA Cloud, SAP Business Accelerator Hub |
| [Capire Bookshop (Fiori)](https://github.com/sap-samples/cloud-cap-samples/tree/main/fiori) | Example, Node.js, CAP-to-CAP |
| [Example Application (Node.js)](https://github.com/SAP-samples/cloud-cap-risk-management/tree/ext-service-s4hc-suppliers-ui) | Complete application from the end-to-end Tutorial |
| [Example Application (Java)](https://github.com/SAP-samples/cloud-cap-risk-management/tree/ext-service-s4hc-suppliers-ui-java) | Complete application from the end-to-end Tutorial |
### Define Scenario
Before you start your implementation, you should define your scenario. Answering the following questions gets you started:
+ What services (remote/CAP) are involved?
+ How do they interact?
+ What needs to be displayed on the UI?
You have all your answers and know your scenario, go on reading about [external service APIs](#external-service-api), getting an API definition from [the SAP Business Accelerator Hub](#from-api-hub) or [from a CAP project](#from-cap-service), and [importing an API definition](#import-api) to your project.
#### Sample Scenario from End-to-End Tutorial
<!-- Bookshop, SFlight, Incidents Mgmt, Risk Mgmt, Orders Mgmt. etc. -> we might want to cut down on our sample scenarios -->
The risk management use case of the previously mentioned [tutorial](https://developers.sap.com/mission.btp-application-cap-e2e.html) shows you one possible scenario:
![A graphic showing the flow for one possible scenario. A user can either view risks or view the suppliers. The suppliers master data is already available from a system and is consumed in an application that enables the user to add the risks. From the maintained risks the user can get information about the supplier connected to a risk. From the supplier view, it's also possible to get details about a risk that is associated with a supplier. The user can block/unblock suppliers from the risk view.](./assets/using-services/risk-mgmt.drawio.svg){style="width: 500px"}
::: info _User Story_
A company wants to ensure that goods are only sourced from suppliers with acceptable risks. There shall be a software system, that allows a clerk to maintain risks for suppliers and their mitigations. The system shall block the supplier used if risks can't be mitigated.
:::
The application is an extension for SAP S/4HANA. It deals with _risks_ and _mitigations_ that are local entities in the application and _suppliers_ that are stored in SAP S/4HANA Cloud. The application helps to reduce risks associated with suppliers by automatically blocking suppliers with a high risk using a [remote API Call](#execute-queries).
##### Integrate
The user picks a supplier from the list. That list is coming [from the remote system and is exposed by the CAP application](#expose-remote-services). Then the user does a risk assessment. Additional supplier data, like name and blocked status, should be displayed on the UI as well, by [integrating the remote supplier service into the local risk service](#integrate-remote-into-local-services).
##### Extend
It should be also possible to search for suppliers and show the associated risks by extending the remote supplier service [with the local risk service](#extend-a-remote-by-a-local-service) and its risks.
## ① Get and Import an External Service API { #external-service-api }
To communicate to remote services, CAP needs to know their definitions. Having the definitions in your project allows you to mock them during design time.
These definitions are usually made available by the service provider. As they aren't defined within your application but imported from outside, they're called *external* service APIs in CAP. Service APIs can be provided in different formats. Currently, *EDMX* files for OData V2 and V4 are supported.
### From SAP Business Accelerator Hub { #from-api-hub}
The [SAP Business Accelerator Hub](https://api.sap.com/) provides many relevant APIs from SAP. You can download API specifications in different formats. If available, use the EDMX format. The EDMX format describes OData interfaces.
To download the [Business Partner API (A2X) from SAP S/4HANA Cloud](https://api.sap.com/api/API_BUSINESS_PARTNER/overview), go to section **API Resources**, select **API Specification**, and download the **EDMX** file.
[Get more details in the end-to-end tutorial.](https://developers.sap.com/tutorials/btp-app-ext-service-add-consumption.html#07f89fdd-82b2-4987-aa86-070f1d836156){.learn-more}
### For a Remote CAP Service { #from-cap-service}
We recommend using EDMX as exchange format. Export a service API to EDMX:
<!-- TODO: Should we mention this here? -->
<!-- ::: warning
The export-import cycle is the way to go for now. It is under investigation to improve this procedure.
::: -->
::: code-group
```sh [Mac/Linux]
cds compile srv -s OrdersService -2 edmx > OrdersService.edmx
```
```cmd [Windows]
cds compile srv -s OrdersService -2 edmx > OrdersService.edmx
```
```powershell [Powershell]
cds compile srv -s OrdersService -2 edmx -o dest/
```
:::
[You can try it with the orders sample in cap/samples.](https://github.com/SAP-samples/cloud-cap-samples/tree/master/orders){.learn-more}
By default, CAP works with OData V4 and the EDMX export is in this protocol version as well. The `cds compile` command offers options for other OData versions and flavors, call `cds help compile` for more information.
::: warning
**Don't just copy the CDS file for a remote CAP service**, for example from a different application. There are issues to use them to call remote services:<br>
- The effective service API depends on the used protocol.<br>
- CDS files often use includes, which can't be resolved anymore.<br>
- CAP creates unneeded database tables and views for all entities in the file.<br>
:::
### Import API Definition { #import-api}
Import the API to your project using `cds import`.
```sh
cds import <input_file> --as cds
```
> `<input_file>` can be an EDMX (OData V2, OData V4), OpenAPI or AsyncAPI file.
| Option | Description |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--as cds` | The import creates a CDS file (for example _API_BUSINESS_PARTNER.cds_) instead of a CSN file. |
This adds the API in CDS format to the _srv/external_ folder and also copies the input file into that folder.
<div class="impl node">
Further, it adds the API as an external service to your _package.json_. You use this declaration later to connect to the remote service [using a destination](#use-destinations-with-node-js).
```json
"cds": {
"requires": {
"API_BUSINESS_PARTNER": {
"kind": "odata-v2",
"model": "srv/external/API_BUSINESS_PARTNER"
}
}
}
```
</div>
Alternatively, you can set the options and flags for `cds import` in your _.cdsrc.json_:
```json
{
"import": {
"as": "cds",
"force": true,
"include_namespaces": "sap,c4c"
}
}
```
Now run `cds import <filename>`
- `--as` only supports these formats: "csn","cds", and "json"
- `--force` is applicable only in combination with `--as` option. By default the `--force` flag is set to false.
> If set to true, existing CSN/CDS files from previous imports are overwritten.
When importing the specification files, the `kind` is set according to the following mapping:
|Imported Format | Used `kind` |
|---------|---------|
| OData V2 | `odata-v2` |
| OData V4 | `odata` (alias for `odata-v4`) |
| OpenAPI | `rest` |
| AsyncAPI | `odata` |
[Learn more about type mappings from OData to CDS and vice versa.](../node.js/cds-dk#special-type-mappings){.learn-more}
::: tip
Always use OData V4 (`odata`) when calling another CAP service.
:::
<div class="impl java">
You need to configure remote services in Spring Boot's _application.yaml_:
```yaml
spring:
config.activate.on-profile: cloud
cds:
remote.services:
API_BUSINESS_PARTNER:
destination:
type: "odata-v2"
```
To work with remote services, add the following dependency to your Maven project:
```xml
<dependency>
<groupId>com.sap.cds</groupId>
<artifactId>cds-feature-remote-odata</artifactId>
<scope>runtime</scope>
</dependency>
```
[Learn about all `cds.remote.services` configuration possibilities.](../java/development/properties#cds-remote-services){.learn-more}
</div>
## ② Local Mocking {#local-mocking}
When developing your application, you can mock the remote service.
### Add Mock Data
As for any other CAP service, you can add mocking data.
The CSV file needs to be added to the _srv/external/data_ folder. {.impl .node}
The CSV file needs to be added to the _db/data_ folder. {.impl .java}
::: code-group
```csv [API_BUSINESS_PARTNER-A_BusinessPartner.csv]
BusinessPartner;BusinessPartnerFullName;BusinessPartnerIsBlocked
1004155;Williams Electric Drives;false
1004161;Smith Batteries Ltd;false
1004100;Johnson Automotive Supplies;true
```
:::
For Java, make sure to add the `--with-mocks` option to the `cds deploy` command used to generate the `schema.sql` in `srv/pom.xml`. This ensures that tables for the mocked remote entities are created in the database.{.impl .java}
[Find this source in the end-to-end tutorial](https://github.com/SAP-samples/cloud-cap-risk-management/blob/ext-service-s4hc-suppliers-ui-java/srv/external/data/API_BUSINESS_PARTNER-A_BusinessPartner.csv){.learn-more}
[Get more details in the end-to-end tutorial.](https://developers.sap.com/tutorials/btp-app-ext-service-add-consumption.html#12ff20a2-e988-465f-a508-f527c7fc0c29){.learn-more}
### Run Local with Mocks
Start your project with the imported service definition.
<div class="impl node">
```sh
cds watch
```
The service is automatically mocked, as you can see in the log output on server start.
```log{17}
...
[cds] - model loaded from 8 file(s):
...
./srv/external/API_BUSINESS_PARTNER.cds
...
[cds] - connect using bindings from: { registry: '~/.cds-services.json' }
[cds] - connect to db > sqlite { database: ':memory:' }
> filling sap.ui.riskmanagement.Mitigations from ./db/data/sap.ui.riskmanagement-Mitigations.csv
> filling sap.ui.riskmanagement.Risks from ./db/data/sap.ui.riskmanagement-Risks.csv
> filling API_BUSINESS_PARTNER.A_BusinessPartner from ./srv/external/data/API_BUSINESS_PARTNER-A_BusinessPartner.csv
/> successfully deployed to sqlite in-memory db
[cds] - serving RiskService { at: '/service/risk', impl: './srv/risk-service.js' }
[cds] - mocking API_BUSINESS_PARTNER { at: '/api-business-partner' } // [!code focus]
[cds] - launched in: 1.104s
[cds] - server listening on { url: 'http://localhost:4004' }
[ terminate with ^C ]
```
</div>
<div class="impl java">
```sh
mvn spring-boot:run
```
</div>
### Mock Associations
You can't get data from associations of a mocked service out of the box.
The associations of imported services lack information how to look up the associated records. This missing relation is expressed with an empty key definition at the end of the association declaration in the CDS model (`{ }`).
::: code-group
```cds{9} [srv/external/API_BUSINESS_PARTNER.cds]
entity API_BUSINESS_PARTNER.A_BusinessPartner {
key BusinessPartner : LargeString;
BusinessPartnerFullName : LargeString;
BusinessPartnerType : LargeString;
...
to_BusinessPartnerAddress :
Association to many API_BUSINESS_PARTNER.A_BusinessPartnerAddress { }; // [!code focus]
};
entity API_BUSINESS_PARTNER.A_BusinessPartnerAddress {
key BusinessPartner : String(10);
key AddressID : String(10);
...
};
```
:::
To mock an association, you have to modify [the imported file](#import-api). Before doing any modifications, create a local copy and add it to your source code management system.
<!-- TODO: Ellipsis not ideal here, not copiable -->
```sh
cp srv/external/API_BUSINESS_PARTNER.cds srv/external/API_BUSINESS_PARTNER-orig.cds
git add srv/external/API_BUSINESS_PARTNER-orig.cds
...
```
Import the CDS file again, just using a different name:
```sh
cds import ~/Downloads/API_BUSINESS_PARTNER.edmx --keep-namespace \
--as cds --out srv/external/API_BUSINESS_PARTNER-new.cds
```
Add an `on` condition to express the relation:
<!-- cds-mode: ignore -->
::: code-group
```cds [srv/external/API_BUSINESS_PARTNER-new.cds]
entity API_BUSINESS_PARTNER.A_BusinessPartner {
...
to_BusinessPartnerAddress :
Association to many API_BUSINESS_PARTNER.A_BusinessPartnerAddress
on to_BusinessPartnerAddress.BusinessPartner = BusinessPartner;
};
```
:::
Don't add any keys or remove empty keys, which would change it to a managed association. Added fields aren't known in the service and lead to runtime errors.
Use a 3-way merge tool to take over your modifications, check it and overwrite the previous unmodified file with the newly imported file:
```sh
git merge-file API_BUSINESS_PARTNER.cds \
API_BUSINESS_PARTNER-orig.cds \
API_BUSINESS_PARTNER-new.cds
mv API_BUSINESS_PARTNER-new.cds API_BUSINESS_PARTNER-orig.cds
```
To prevent accidental loss of modifications, the `cds import --as cds` command refuses to overwrite modified files based on a "checksum" that is included in the file.
### Mock Remote Service as OData Service (Node.js) {.impl .node}
As shown previously you can run one process including a mocked external service. However, this mock doesn't behave like a real external service. The communication happens in-process and doesn't use HTTP or OData. For a more realistic testing, let the mocked service run in a separate process.
First install the required packages:
<!-- TODO: No fixed major version numbers? -->
```sh
npm add @sap-cloud-sdk/http-client@3.x @sap-cloud-sdk/util@3.x @sap-cloud-sdk/connectivity@3.x @sap-cloud-sdk/resilience@3.x
```
Then start the CAP application with the mocked remote service only:
```sh
cds mock API_BUSINESS_PARTNER
```
If the startup is completed, run `cds watch` in the same project from a **different** terminal:
```sh
cds watch
```
CAP tracks locally running services. The mocked service `API_BUSINESS_PARTNER` is registered in file _~/.cds-services.json_. `cds watch` searches for running services in that file and connects to them.
Node.js only supports *OData V4* protocol and so does the mocked service. There might still be some differences to the real remote service if it uses a different protocol, but it's much closer to it than using only one instance. In the console output, you can also easily see how the communication between the two processes happens.
### Mock Remote Service as OData Service (Java) {.impl .java}
You configure CAP to do OData and HTTP requests for a mocked service instead of doing it in-process. Configure a new Spring Boot profile (for example `mocked`):
_application.yaml_:
```yaml
spring:
config.activate.on-profile: mocked
cds:
application.services:
- name: API_BUSINESS_PARTNER-mocked
model: API_BUSINESS_PARTNER
serve.path: API_BUSINESS_PARTNER
remote.services:
API_BUSINESS_PARTNER:
destination:
name: "s4-business-partner-api-mocked"
```
The profile exposes the mocked service as OData service and defines a destination to access the service. The destination just points to the CAP application itself. You need to implement some Java code for this:
::: code-group
```java [DestinationConfiguration.java]
@EventListener
void applicationReady(ApplicationReadyEvent ready) {
int port = Integer.valueOf(environment.getProperty("local.server.port"));
DefaultHttpDestination mockDestination = DefaultHttpDestination
.builder("http://localhost:" + port)
.name("s4-business-partner-api-mocked").build();
DefaultDestinationLoader loader = new DefaultDestinationLoader();
loader.registerDestination(mockDestination);
DestinationAccessor.prependDestinationLoader(loader);
}
```
:::
Now, you just need to run the application with the new profile:
```sh
mvn spring-boot:run -Dspring-boot.run.profiles=default,mocked
```
When sending a request to your CAP application, for example the `Suppliers` entity, it is transformed to the request for the mocked remote service and requested from itself as a OData request. Therefore, you'll see two HTTP requests in your CAP application's log.
For example:
[http://localhost:8080/service/risk/Suppliers](http://localhost:8080/service/risk/Suppliers)
```log
2021-09-21 15:18:44.870 DEBUG 34645 — [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/service/risk/Suppliers", parameters={}
...
2021-09-21 15:18:45.292 DEBUG 34645 — [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : GET "/API_BUSINESS_PARTNER/A_BusinessPartner?$select=BusinessPartner,BusinessPartnerFullName,BusinessPartnerIsBlocked&$top=1000&$skip=0&$orderby=BusinessPartner%20asc&sap-language=de&sap-valid-at=2021-09-21T13:18:45.211722Z", parameters={masked}
...
2021-09-21 15:18:45.474 DEBUG 34645 — [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed 200 OK
2021-09-21 15:18:45.519 DEBUG 34645 — [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 200 OK
```
[Try out the example application.](https://github.com/SAP-samples/cloud-cap-risk-management/tree/ext-service-s4hc-suppliers-ui-java){.learn-more}
## ③ Execute Queries {#execute-queries}
You can send requests to remote services using CAP's powerful querying API.
### Execute Queries with Node.js{.impl .node}
Connect to the service before sending a request, as usual in CAP:
```js
const bupa = await cds.connect.to('API_BUSINESS_PARTNER');
```
Then execute your queries using the [Querying API](../node.js/core-services#srv-run-query):
```js
const { A_BusinessPartner } = bupa.entities;
const result = await bupa.run(SELECT(A_BusinessPartner).limit(100));
```
We recommend limiting the result set and avoid the download of large data sets in a single request. You can `limit` the result as in the example: `.limit(100)`.
Many features of the querying API are supported for OData services. For example, you can resolve associations like this:
```js
const { A_BusinessPartner } = bupa.entities;
const result = await bupa.run(SELECT.from(A_BusinessPartner, bp => {
bp('BusinessPartner'),
bp.to_BusinessPartnerAddress(addresses => {
addresses('*')
})
}).limit(100));
```
[Learn more about querying API examples.](https://github.com/SAP-samples/cloud-cap-risk-management/blob/ext-service-s4hc-suppliers-ui/test/odata-examples.js){.learn-more}
[Learn more about supported querying API features.](#querying-api-features){.learn-more}
### Execute Queries with Java {.impl .java}
You can use dependency injection to get access to the remote service:
```java
@Autowired
@Qualifier(ApiBusinessPartner_.CDS_NAME)
CqnService bupa;
```
Then execute your queries using the [Querying API](../java/query-execution):
```java
CqnSelect select = Select.from(ABusinessPartner_.class).limit(100);
List<ABusinessPartner> businessPartner = bupa.run(select).listOf(ABusinessPartner.class);
```
[Learn more about querying API examples.](https://github.com/SAP-samples/cloud-cap-risk-management/blob/ext-service-s4hc-suppliers-ui/test/odata-examples.js){.learn-more}
[Learn more about supported querying API features.](#querying-api-features){.learn-more}
### Model Projections
External service definitions, like [generated CDS or CSN files during import](#import-api), can be used as any other CDS definition, but they **don't** generate database tables and views unless they are mocked.
It's best practice to use your own "interface" to the external service and define the relevant fields in a projection in your namespace. Your implementation is then independent of the remote service implementation and you request only the information that you require.
```cds
using { API_BUSINESS_PARTNER as bupa } from '../srv/external/API_BUSINESS_PARTNER';
entity Suppliers as projection on bupa.A_BusinessPartner {
key BusinessPartner as ID,
BusinessPartnerFullName as fullName,
BusinessPartnerIsBlocked as isBlocked,
}
```
As the example shows, you can use field aliases as well.
[Learn more about supported features for projections.](#supported-projection-features){.learn-more}
### Execute Queries on Projections to a Remote Service{.impl .node}
Connect to the service before sending a request, as usual in CAP:
```js
const bupa = await cds.connect.to('API_BUSINESS_PARTNER');
```
Then execute your queries:
```js
const suppliers = await bupa.run(SELECT(Suppliers).where({ID}));
```
CAP resolves projections and does the required mapping, similar to databases.
A brief explanation, based on the previous query, what CAP does:
+ Resolves the `Suppliers` projection to the external service interface `API_BUSINESS_PARTNER.A_Business_Partner`.
+ The **where** condition for field `ID` will be mapped to the `BusinessPartner` field of `A_BusinessPartner`.
+ The result is mapped back to the `Suppliers` projection, so that values for the `BusinessPartner` field are mapped back to `ID`.
This makes it convenient to work with external services.
### Building Custom Requests with Node.js{.impl .node}
If you can't use the querying API, you can craft your own HTTP requests using `send`:
<!-- TODO: What is 'A_BusinessPartner' here? -->
```js
bupa.send({
method: 'PATCH',
path: A_BusinessPartner,
data: {
BusinessPartner: 1004155,
BusinessPartnerIsBlocked: true
}
})
```
[Learn more about the `send` API.](../node.js/core-services#srv-send-request){.learn-more}
### Building Custom Requests with Java {.impl .java}
For Java, you can use the `HttpClient` API to implement your custom requests. The API is enhanced by the SAP Cloud SDK to support destinations.
[Learn more about using the HttpClient Accessor.](https://sap.github.io/cloud-sdk/docs/java/features/connectivity/sdk-connectivity-http-client){.learn-more}
[Learn more about using destinations.](#use-destinations-with-java){.learn-more}
## ④ Integrate and Extend {#integrate-and-extend}
By creating projections on remote service entities and using associations, you can create services that combine data from your local service and remote services.
What you need to do depends on [the scenarios](#sample-scenario-from-end-to-end-tutorial) and how your remote services should be integrated into, as well as extended by your local services.
### Expose Remote Services
To expose a remote service entity, you add a projection on it to your CAP service:
```cds
using { API_BUSINESS_PARTNER as bupa } from '../srv/external/API_BUSINESS_PARTNER';
extend service RiskService with {
entity BusinessPartners as projection on bupa.A_BusinessPartner;
}
```
CAP automatically tries to delegate queries to database entities, which don't exist as you're pointing to an external service. That behavior would produce an error like this:
```xml
<error xmlns="https://docs.oasis-open.org/odata/ns/metadata">
<code>500</code>
<message>SQLITE_ERROR: no such table: RiskService_BusinessPartners in: SELECT BusinessPartner, Customer, Supplier, AcademicTitle, AuthorizationGroup, BusinessPartnerCategory, BusinessPartnerFullName, BusinessPartnerGrouping, BusinessPartnerName, BusinessPartnerUUID, CorrespondenceLanguage, CreatedByUser, CreationDate, (...) FROM RiskService_BusinessPartner ALIAS_1 ORDER BY BusinessPartner COLLATE NOCASE ASC LIMIT 11</message>
</error>
```
To avoid this error, you need to handle projections. Write a handler function to delegate a query to the remote service and run the incoming query on the external service.
<div class="impl node">
```js
module.exports = cds.service.impl(async function() {
const bupa = await cds.connect.to('API_BUSINESS_PARTNER');
this.on('READ', 'BusinessPartners', req => {
return bupa.run(req.query);
});
});
```
[Get more details in the end-to-end tutorial.](https://developers.sap.com/tutorials/btp-app-ext-service-add-consumption.html#0a5ed8cc-d0fa-4a52-bb56-9c864cd66e71){.learn-more}
</div>
<div class="impl java">
```java
@Component
@ServiceName(RiskService_.CDS_NAME)
public class RiskServiceHandler implements EventHandler {
@Autowired
@Qualifier(ApiBusinessPartner_.CDS_NAME)
CqnService bupa;
@On(entity = BusinessPartners.CDS_NAME)
Result readSuppliers(CdsReadEventContext context) {
return bupa.run(context.getCqn());
}
}
```
</div>
::: warning
If you receive `404` errors, check if the request contains fields that don't exist in the service and start with the name of an association. `cds import` adds an empty keys declaration (`{ }`) to each association. Without this declaration, foreign keys for associations are generated in the runtime model, that don't exist in the real service. To solve this problem, you need to reimport the external service definition using `cds import`.
:::
This works when accessing the entity directly. Additional work is required to support [navigation](#handle-navigations-across-local-and-remote-entities) and [expands](#handle-expands-across-local-and-remote-entities) from or to a remote entity.
Instead of exposing the remote service's entity unchanged, you can [model your own projection](#model-projections). For example, you can define a subset of fields and change their names.
::: tip
CAP does the magic that maps the incoming query, according to your projections, to the remote service and maps back the result.
:::
```cds
using { API_BUSINESS_PARTNER as bupa } from '../srv/external/API_BUSINESS_PARTNER';
extend service RiskService with {
entity Suppliers as projection on bupa.A_BusinessPartner {
key BusinessPartner as ID,
BusinessPartnerFullName as fullName,
BusinessPartnerIsBlocked as isBlocked
}
}
```
```js
module.exports = cds.service.impl(async function() {
const bupa = await cds.connect.to('API_BUSINESS_PARTNER');
this.on('READ', 'Suppliers', req => {
return bupa.run(req.query);
});
});
```
[Learn more about queries on projections to remote services.](#execute-queries-on-projections-to-a-remote-service){.learn-more}
### Expose Remote Services with Associations
It's possible to expose associations of a remote service entity. You can adjust the [projection for the association target](#model-projections) and change the name of the association:
```cds
using { API_BUSINESS_PARTNER as bupa } from '../srv/external/API_BUSINESS_PARTNER';
extend service RiskService with {
entity Suppliers as projection on bupa.A_BusinessPartner {
key BusinessPartner as ID,
BusinessPartnerFullName as fullName,
BusinessPartnerIsBlocked as isBlocked,
to_BusinessPartnerAddress as addresses: redirected to SupplierAddresses
}
entity SupplierAddresses as projection on bupa.A_BusinessPartnerAddress {
BusinessPartner as bupaID,
AddressID as ID,
CityName as city,
StreetName as street,
County as county
}
}
```
As long as the association is only resolved using expands (for example `.../risk/Suppliers?$expand=addresses`), a handler for the __source entity__ is sufficient:
```js
this.on('READ', 'Suppliers', req => {
return bupa.run(req.query);
});
```
If you need to resolve the association using navigation or request it independently from the source entity, add a handler for the __target entity__ as well:
```js
this.on('READ', 'SupplierAddresses', req => {
return bupa.run(req.query);
});
```
As usual, you can put two handlers into one handler matching both entities:
```js
this.on('READ', ['Suppliers', 'SupplierAddresses'], req => {
return bupa.run(req.query);
});
```
### Mashing up with Remote Services
You can combine local and remote services using associations. These associations need manual handling, because of their different data sources.
#### Integrate Remote into Local Services
Use managed associations from local entities to remote entities:
```cds
@path: 'service/risk'
service RiskService {
entity Risks : managed {
key ID : UUID @(Core.Computed : true);
title : String(100);
prio : String(5);
supplier : Association to Suppliers;
}
entity Suppliers as projection on BusinessPartner.A_BusinessPartner {
key BusinessPartner as ID,
BusinessPartnerFullName as fullName,
BusinessPartnerIsBlocked as isBlocked,
};
}
```
#### Extend a Remote by a Local Service { #extend-a-remote-by-a-local-service}
You can augment a projection with a new association, if the required fields for the on condition are present in the remote service. The use of managed associations isn't possible, because this requires to create new fields in the remote service.
<!--Does it matter if it's managed or unmanaged? In other section we say, that you shouldn't make it a managed assoc b/c that would lead to runtime errors. -->
```cds
entity Suppliers as projection on bupa.A_BusinessPartner {
key BusinessPartner as ID,
BusinessPartnerFullName as fullName,
BusinessPartnerIsBlocked as isBlocked,
risks : Association to many Risks on risks.supplier.ID = ID,
};
```
### Handle Mashups with Remote Services { #building-mashups}
Depending on how the service is accessed, you need to support direct requests, navigation, or expands. CAP resolves those three request types only for service entities that are served from the database. When crossing the boundary between database and remote sourced entities, you need to take care of those requests.
The list of [required implementations for mashups](#required-implementations-for-mashups) explains the different combinations.
#### Handle Expands Across Local and Remote Entities
Expands add data from associated entities to the response. For example, for a risk, you want to display the suppliers name instead of just the technical ID. But this property is part of the (remote) supplier and not part of the (local) risk.
[Get more details in the end-to-end tutorial.](https://developers.sap.com/tutorials/btp-app-ext-service-consume-ui.html#7d36d433-2b88-407c-a6cc-d6a05dcc8547){.learn-more}
To handle expands, you need to add a handler for the main entity:
1. Check if a relevant `$expand` column is present.
2. Remove the `$expand` column from the request.
3. Get the data for the request.
4. Execute a new request for the expand.
5. Add the expand data to the returned data from the request.
Example of a CQN request with an expand:
```json
{
"from": { "ref": [ "RiskService.Suppliers" ] },
"columns": [
{ "ref": [ "ID" ] },
{ "ref": [ "fullName" ] },
{ "ref": [ "isBlocked" ] },
{ "ref": [ "risks" ] },
{ "expand": [
{ "ref": [ "ID" ] },
{ "ref": [ "title" ] },
{ "ref": [ "descr" ] },
{ "ref": [ "supplier_ID" ] }
] }
]
}
```
[See an example how to handle expands in Node.js.](https://github.com/SAP-samples/cloud-cap-risk-management/blob/ext-service-s4hc-suppliers-ui/srv/risk-service.js){.impl .node .learn-more}
[See an example how to handle expands in Java.](https://github.com/SAP-samples/cloud-cap-risk-management/blob/ext-service-s4hc-suppliers-ui-java/srv/src/main/java/com/sap/cap/riskmanagement/handler/RiskServiceHandler.java){.impl .java .learn-more}
Expands across local and remote can cause stability and performance issues. For a list of items, you need to collect all IDs and send it to the database or the remote system. This can become long and may exceed the limits of a URL string in case of OData. Do you really need expands for a list of items?
```http
GET /service/risk/Risks?$expand=supplier
```
Or is it sufficient for single items?
```http
GET /service/risk/Risks(545A3CF9-84CF-46C8-93DC-E29F0F2BC6BE)/?$expand=supplier
```
::: warning Keep performance in mind
Consider to reject expands if it's requested on a list of items.
:::
#### Handle Navigations Across Local and Remote Entities
Navigations allow to address items via an association from a different entity:
```http
GET /service/risks/Risks(20466922-7d57-4e76-b14c-e53fd97dcb11)/supplier
```
<!-- I Thought we remove all Notes examples?-->
The CQN consists of a `from` condition with 2 values for `ref`. The first `ref` selects the record of the source entity of the navigation. The second `ref` selects the name of the association, to navigate to the target entity.
```json
{
"from": {
"ref": [ {
"id": "RiskService.Risks",
"where": [
{ "ref": [ "ID" ] },
"=",
{ "val": "20466922-7d57-4e76-b14c-e53fd97dcb11" }
]},
"supplier"
]
},
"columns": [
{ "ref": [ "ID" ] },
{ "ref": [ "fullName" ] },
{ "ref": [ "isBlocked" ] }
],
"one": true
}
```
To handle navigations, you need to check in your code if the `from.ref` object contains 2 elements. Be aware, that for navigations the handler of the **target** entity is called.
If the association's on condition equals the key of the source entity, you can directly select the target entity using the key's value. You find the value in the `where` block of the first `from.ref` entry.
Otherwise, you need to select the source item using that `where` block and take the required fields for the associations on condition from that result.
[See an example how to handle navigations in Node.js.](https://github.com/SAP-samples/cloud-cap-risk-management/blob/ext-service-s4hc-suppliers-ui/srv/risk-service.js){.learn-more .impl .node}
[See an example how to handle navigations in Java.](https://github.com/SAP-samples/cloud-cap-risk-management/blob/ext-service-s4hc-suppliers-ui-java/srv/src/main/java/com/sap/cap/riskmanagement/handler/RiskServiceHandler.java){.learn-more .impl .java}
### Limitations and Feature Matrix
#### Required Implementations for Mashups { #required-implementations-for-mashups}
You need additional logic, if remote entities are in the game. The following table shows what is required. "Local" is a database entity or a projection on a database entity.
| **Request** | **Example** | **Implementation** |
| --------------------------------------------------------------------- | ---------------------------------------- | ----------------------------------------------------------------- |
| Local (including navigations and expands) | `/service/risks/Risks` | Handled by CAP |
| Local: Expand remote | `/service/risks/Risks?$expand=supplier` | Delegate query w/o expand to local service and implement expand. |
| Local: Navigate to remote | `/service/risks(...)/supplier` | Implement navigation and delegate query target to remote service. |
| Remote (including navigations and expands to the same remote service) | `/service/risks/Suppliers` | Delegate query to remote service |
| Remote: Expand local | `/service/risks/Suppliers?$expand=risks` | Delegate query w/o expand to remote service and implement expand. |
| Remote: Navigate to local | `/service/Suppliers(...)/risks` | Implement navigation, delegate query for target to local service |
#### Transient Access vs. Replication
::: tip
The _Integrate and Extend_ chapter shows only techniques for transient access.
:::
The following matrix can help you to find the best approach for your scenario:
| **Feature** | **Transient Access** | **Replication** |
|-------------------------------------------------------|-----------------------|-----------------------------------|
| Filtering on local **or** remote fields <sup>1</sup> | Possible | Possible |
| Filtering on local **and** remote fields <sup>2</sup> | Not possible | Possible |
| Relationship: Uni-/Bidirectional associations | Possible | Possible |
| Relationship: Flatten | Not possible | Possible |
| Evaluate user permissions in remote system | Possible | Requires workarounds <sup>3</sup> |
| Data freshness | Live data | Outdated until replicated |
| Performance | Degraded <sup>4</sup> | Best |
<br>
> <sup>1</sup> It's **not required** to filter both, on local and remote fields, in the same request. <br>
> <sup>2</sup> It's **required** to filter both, on local and remote fields, in the same request. <br>
> <sup>3</sup> Because replicated data is accessed, the user permission checks of the remote system aren't evaluated. <br>
> <sup>4</sup> Depends on the connectivity and performance of the remote system. <br>
## ⑤ Connect and Deploy {#connect-and-deploy}
<!--
### Connect to Business Services on SAP BTP
TODO: Token exchange flow -->
### Using Destinations { #using-destinations}
Destinations contain the necessary information to connect to a remote system. They're basically an advanced URL, that can carry additional metadata like, for example, the authentication information.
You can choose to use [SAP BTP destinations](#btp-destinations) or [application defined destinations](#app-defined-destinations).
#### Use SAP BTP Destinations { #btp-destinations}
CAP leverages the destination capabilities of the SAP Cloud SDK.
##### Create Destinations on SAP BTP
Create a destination using one or more of the following options.
- **Register a system in your global account:** You can check here how to [Register an SAP System](https://help.sap.com/products/BTP/65de2977205c403bbc107264b8eccf4b/2ffdaff0f1454acdb046876045321c91.html) in your SAP BTP global account and which systems are supported for registration. Once the system is registered and assigned to your subaccount, you can create a service instance. A destination is automatically created along with the service instance.
<!-- TODO: risk management link -->
- **Connect to an on-premise system:** With SAP BTP [Cloud Connector](https://help.sap.com/docs/CP_CONNECTIVITY/cca91383641e40ffbe03bdc78f00f681/e6c7616abb5710148cfcf3e75d96d596.html), you can create a connection from your cloud application to an on-premise system.
- **Manually create destinations:** You can create destinations manually in your SAP BTP subaccount. See section [destinations](https://help.sap.com/docs/CP_CONNECTIVITY/cca91383641e40ffbe03bdc78f00f681/5eba6234a0e143fdacd8535f44c315c5.html) in the SAP BTP documentation.
- **Create a destination to your application:** If you need a destination to your application, for example, to call it from a different application, then you can automatically create it in the MTA deployment.
##### Use Destinations with Node.js
In your _package.json_, a configuration for the `API_BUSINESS_PARTNER` looks like this:
```json
"cds": {
"requires": {
"API_BUSINESS_PARTNER": {
"kind": "odata",
"model": "srv/external/API_BUSINESS_PARTNER"
}
}
}
```
If you've imported the external service definition using `cds import`, an entry for the service in the _package.json_ has been created already. Here you specify the name of the destination in the `credentials` block.
In many cases, you also need to specify the `path` prefix to the service, which is added to the destination's URL. For services listed on the SAP Business Accelerator Hub, you can find the path in the linked service documentation.
Since you don't want to use the destination for local testing, but only for production, you can profile it by wrapping it into a `[production]` block:
```json
"cds": {
"requires": {
"API_BUSINESS_PARTNER": {
"kind": "odata",
"model": "srv/external/API_BUSINESS_PARTNER",
"[production]": {
"credentials": {
"destination": "S4HANA",
"path": "/sap/opu/odata/sap/API_BUSINESS_PARTNER"
}
}
}
}
}
```
Additionally, you can provide [destination options](https://sap.github.io/cloud-sdk/api/v2/types/sap_cloud_sdk_connectivity.DestinationOptions.html) inside a `destinationOptions` object:
```jsonc
"cds": {
"requires": {