4. Replicating Data - SAP-samples/teched2022-AD265 GitHub Wiki
Customers
Enable Replication for In the incidents list, the application shall display (remote) customer data together with (application-local) incident data. This raises a performance issue: when showing potentially hundreds of incidents, shall the app reach out to the remote system at all? Or just for single records, for all records at once, or for a chunk of records?
We use a different approach by replicating remote data on demand.
The scenario will look like this:
- The user enters a new incident and selects the customer through a value help. This value help shows only remote customer data.
- As soon as the incident record is created, the customer data is written to a local replica table.
- Further requests for the incident's customer are served from this replica table.
- Replicated records will be updated if a remote customer changes.
Start by adding a persistent table for the replicas. This can be done with just one line in srv/mashup.cds
:
annotate s4.simple.Customers with @cds.persistence: { table,skip:false };
The annotation @cds.persistence: {table,skip:false}
turns the view above into a table with the same signature (ID
and name
columns). See the documentation for more on annotations that influence persistence.
Replicate Data On Demand
Now there is code needed to replicate the customer record whenever an incident is created.
In file srv/incidents-service.js
, add this code (to the outer function):
const db = await cds.connect.to('db') // our primary database
const { Customers } = db.entities('s4.simple') // CDS definition of the Customers entity
this.after (['CREATE','UPDATE'], 'Incidents', async (data) => {
const { customer_ID: ID } = data
if (ID) {
let replicated = await db.exists (Customers,ID)
if (!replicated) {
console.log ('>> Updating customer', ID)
let customer = await S4bupa.read (Customers,ID)
await INSERT(customer) .into (Customers)
}
}
})
Now create an incident in the UI. Don't forget to select a customer through the value help.
In the log, you can see the >> Updating customer
line, confirming that replication happens.
Test without UI
With the REST client for VS Code, you can conveniently test the same flow without the UI.
Create a file tests.http
and this content:
###
# @name IncidentsCreate
POST http://localhost:4004/incidents/Incidents
Content-Type: application/json
{
"title": "New incident",
"customer_ID": "Z100001"
}
###
@id = {{IncidentsCreate.response.body.$.ID}}
POST http://localhost:4004/incidents/Incidents(ID={{id}},IsActiveEntity=false)/draftActivate
Content-Type: application/json
-
Click
Send Request
above thePOST .../Incidents
line. This will create the record in a draft tate. -
Click
Send Request
above thePOST .../draftActivate
line. This corresponds to theSave
ction in the UI.This second request is needed for all changes to entities managed by SAP Fiori's draft mechanism.
You should see the same >> Updating customer
server log.
Event-based Replication
We haven't discussed yet how to update the cache table holding the Customers
data. We'll use events to inform our application whenever the remote BusinessPartner has changed. Let's see what you need to do.
Add Events to Imported APIs
First, as synchronous and asynchronous APIs from SAP S/4HANA sources are not comprised in the imported API definition (the edmx file), we have to add event defintitions manually. For the business partner model, the event information can be found at https://api.sap.com/event/CE_BUSINESSPARTNEREVENTS/resource
In file srv/external/index.cds
, add this:
extend service S4 {
event BusinessPartner.Changed @(topic: 'sap.s4.beh.businesspartner.v1.BusinessPartner.Changed.v1') {
BusinessPartner: S4.A_BusinessPartner:BusinessPartner;
}
}
This allows CAP's support for events and messaging to kick in, which automatically subscribes to message brokers and emits events behind the scenes.
Also, the event name BusinessPartner.Changed
is semantically closer to the domain and easier to read than the underlying technical event sap.s4.beh.businesspartner.v1.BusinessPartner.Changed.v1
.
React to Events
So, the piece to close the loop is code to consume events in the application.
In srv/incidents-service.js
, add this event handler (into the body of the outer function):
// update cache if BusinessPartner has changed
S4bupa.on('BusinessPartner.Changed', async ({ event, data }) => {
console.log('<< received', event, data)
const { BusinessPartner: ID } = data
const customer = await S4bupa.read (Customers, ID)
let exists = await db.exists (Customers,ID)
if (exists)
await UPDATE (Customers, ID) .with (customer)
else
await INSERT.into (Customers) .entries (customer)
})
Emitting Events from Mocked Services
But who is the event emitter? Usually it's the remote data source, i.e. the SAP S4/HANA system. For local runs, it would be great if something could emit events when testing. Luckily, you can add a simple event emitter in a new file srv/external/API_BUSINESS_PARTNER.js
:
module.exports = function () {
const { A_BusinessPartner } = this.entities;
this.after('UPDATE', A_BusinessPartner, async data => {
const event = { BusinessPartner: data.BusinessPartner }
console.log('>> BusinessPartner.Changed', event)
await this.emit('BusinessPartner.Changed', event);
})
}
This means whenever you change data through the API_BUSINESS_PARTNER
mock service, a local event is emitted.
Also note how the event name BusinessPartner.Changed
matches to the event definition from the CDS code above.
Put it all together
Before starting the application again, it's time to turn the current in-memory database into a persistent one. This way, data is not reset after each restart, which is useful if you added data manually.
So, kill cds watch
, then execute:
cds deploy --with-mocks --to sqlite
Start the application with mocks:
cds watch
The application runs as before. In the log, however, you no longer see a database deployment, but a line like:
...
[cds] - connect to db > sqlite { url: 'db.sqlite', database: 'db.sqlite' }
...
This also means that after changes to the data model (new fields, entities etc.), you need to execute the
cds deploy ...
command again. Keep this in mind in case you see errors like table/view not found.
In your file tests.http
, first execute the 2 requests to create an incident again (see section above).
Now change customer Z100001
with an HTTP request. Add this request:
###
PUT http://localhost:4004/api-business-partner/A_BusinessPartner/Z100001
Authorization: Basic carol:
Content-Type: application/json
{
"BusinessPartnerFullName": "Albus Percival Wulfric Brian Dumbledore"
}
After clicking Send Request
above the PUT ...
line, you should see both the event being emitted as well as received:
>> BusinessPartner.Changed { BusinessPartner: 'Z100001' }
<< received BusinessPartner.Changed { BusinessPartner: 'Z100001' }
The UI also reflects the changed data:
Note that we can't test the event roundtrip in the
cds watch --profile sandbox
mode, as the sandbox system of SAP API Business Hub does not support modifications. You would need to use a dedicated SAP S/4HANA system here. See this tutorial for how to register your own SAP S/4HANA system.
Summary
In the next exercise, you will learn how to consolidate the current code into an integration package and how to use this package.