The main audiences of this blog are:
- Experienced AppGyver citizen developers who are keen to learn how to implement OData in SAP backend using CDS technology and RAP framework.
- Experienced ABAPers who are also AppGyver beginners and are keen to learn how RAP generated OData is integrated in Appgyver.
- Experienced in neither but are interested in the topic.
BTP ABAP Environment is chosen as backend because:
- As common as consuming from S4, it’s also common scenario to use BTP ABAP environment as backend
- No need for S4(on-prem or cloud) to do this hands-on and everything is done in BTP front to back.
Prerequisite:
- BTP non-trial subscription
- ABAP Environment instance & AppGyver service in BTP.
1. Create OData data source and Service
1.1 Create Ztable and populate with data
The detailed steps of 1.1 are also in this tutorial.
Access your BTP ABAP environment from ABAP Development Tools (ADT onwards) and create Ztable of your choice. Next, populate the table with data by creating a program(class or function module). In this blog, I created table ZUSER_DATA which stores sample user information.
1.2 CDS basic view, Projection view and Service definition/binding
The detailed steps of 1.2 are also in this tutorial.
Create CDS basic view ZUSER based on the Ztable. Date fields are modified to simple DD.MM.YYYY format. Without this, AppGyver will receive the date in Unix Epoch format and conversion on frontend is required. This is not ideal for low code/no code because we want to keep as much logic in the backend as possible.
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'User info'
define root view entity ZUSER as select from zuser_data as user
{
key user.bname as userID,
concat(
concat(substring( cast(user.date_from as abap.char(10)), 7, 2 ), '.'),
concat(substring( cast(user.date_from as abap.char(10)), 5, 2 ),
concat('.', substring( cast(user.date_from as abap.char(10)), 1, 4 )))
) as date_from,
concat(
concat(substring( cast(user.date_to as abap.char(10)), 7, 2 ), '.'),
concat(substring( cast(user.date_to as abap.char(10)), 5, 2 ),
concat('.', substring( cast(user.date_to as abap.char(10)), 1, 4 )))
) as date_to,
user.name_first as name_first,
user.name_last as name_last,
/*-- Admin data --*/
@Semantics.user.createdBy: true
created_by,
@Semantics.systemDateTime.createdAt: true
created_at,
@Semantics.user.lastChangedBy: true
last_changed_by,
@Semantics.systemDateTime.lastChangedAt: true
last_changed_at
}
Create a projection view projecting the basic view. UI annotations are only needed for Fiori element app, which is not relevant for this blog so they can be removed upon your choice.
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'Projection view for user info'
@UI: {
headerInfo: { typeName: 'User', typeNamePlural: 'Users', title: { type: #STANDARD, value: 'userID' } } }
@Search.searchable: true
define root view entity ZC_USER
provider contract transactional_query
as projection on ZUSER
{
@UI.facet: [ { id: 'User',
purpose: #STANDARD,
type: #IDENTIFICATION_REFERENCE,
label: 'User info',
position: 10 } ]
@UI: {
lineItem: [ { position: 10, importance: #HIGH } ],
identification: [ { position: 10, label: 'User ID'} ],
selectionField: [ { position: 10 } ] }
key userID as userID,
@UI: {
lineItem: [ { position: 20, importance: #HIGH } ],
identification: [ { position: 20, label: 'Valid From' } ],
selectionField: [ { position: 20 } ] }
date_from as date_from,
@UI: {
lineItem: [ { position: 30, importance: #HIGH } ],
identification: [ { position: 30, label: 'Valid to' } ],
selectionField: [ { position: 30 } ] }
@Search.defaultSearchElement: true
date_to as date_to,
@UI: {
lineItem: [ { position: 40, importance: #HIGH } ],
identification: [ { position: 40, label: 'First name' } ],
selectionField: [ { position: 40 } ] }
name_first as name_first,
@UI: {
lineItem: [ { position: 50, importance: #HIGH } ],
identification: [ { position: 50, label: 'Last name' } ],
selectionField: [ { position: 50 } ] }
name_last as name_last,
@UI.hidden: true
last_changed_at as LastChangedAt
}
Finally create service definition from the projection view and create service binding from the service definition. After publishing the service, access the Service URL + Entity set to see if the data stored in your table is displayed. So in my case, the URL is “https://yourBTPaccount/sap/opu/odata/sap/ZSRVBIN_USER/C_USER”.
@EndUserText.label: 'Service definition for ZC_USER'
define service ZSRVDEF_USER {
expose ZC_USER as C_USER;
}
2. Add behaviors to CDS views
The detailed steps of 2 are also in this tutorial.
Defining behavior to the basic view and projection view will allow the OData to perform CRUD operation performing direct update of source database table. Create New Behavior Definition from basic view ZUSER. It’s important to set the source database table(ZUSER_DATA) as the persistent table and mapping of the fields. The implementation class zbp_user is implemented and activated but it’s in the default state and no logic is added.
managed implementation in class zbp_user unique;
strict;
define behavior for ZUSER alias USER
persistent table ZUSER_DATA
lock master
authorization master ( instance )
etag master last_changed_at
//late numbering
{
// administrative fields (read only)
field ( numbering : managed, readonly ) userID;
// administrative fields (read only)
field ( readonly ) last_changed_at, last_changed_by, created_at, created_by;
// mandatory fields
field ( mandatory : create ) date_from, date_to, name_first, name_last;
create;
update;
delete;
mapping for ZUSER_DATA
{
userID = bname;
date_from = date_from;
date_to = date_to;
name_first = name_first;
name_last = name_last;
}
}
Create New Behavior Definition for projection view ZC_USER as well.
projection;
strict;
define behavior for ZC_USER alias C_USER
use etag
{
use create;
use update;
use delete;
}
3. Communication configuration
The detailed steps of 3.1 are also in this tutorial step 9~11.
The detailed steps of 3.2 are also in this tutorial.
3.1 Create a communication scenario
Right click your package and choose New > Other ABAP Repository Object > Communication Management > Communication Scenario. I named mine ZCOMMU_USER. Go to the Inbound tab and add inbound service, which is in the format of “your_service_binding_IWSG”.
Finally, publish the scenario locally.
3.2 Communication arrangement
Go to ABAP environment Dashboard by accessing ADT and right click the project, click properties, under ABAP Development access the System URL. Alternatively, you can go to BTP subaccount and click on the ABAP environment instance.
Navigate to “Maintain Communication User” under “Communication Management” tab and create a user with strong password. “Propose password” can be used for this purpose. Save the user.
Navigate back to the dashboard and go to Communication System under the same tab. Give a system ID name for the BTP ABAP Environment. Copy the system URL from ADT, remove “https://” and set it in the host name.
Last step is to add the user created in the previous step as the communication user for this system ID. Finally, we can save this communication system.
Navigate to “Maintain Communication Arrangement” under the same tab and click New. Choose the communication Scenario created in the step 2. Enter the communication system and user created in the previous steps and save. The Service URL at the bottom will be used later in BTP destination.
3.3 Configure BTP destination
Go to your BTP subaccount and navigate to Destination under Connectivity on the left side pane. Click on New Destination. Setup the connection as below. Make sure to set the user and password created in step 3.2 and add HTML5.DynamicDestination = true” and “WebIDEEnabled = true” as Additional Properties. Make sure to set the service URL from the communication arrangement as well. Save the connection and click on Check Connection. It should return “Connection to “XXXX” established. Response returned: “200: OK”.
4. AppGyver setup
4.1 Receive service in AppGyver
Create a new project in AppGyver and go to AUTH tab and set as BTP authentication.
Go to DATA tab and Click “ADD INTEGRATION”. Click “BTP DESTINATION” and all available services configured in BTP destination are displayed. Choose the one configured in step 3.3 and the screen should change and display Data entities. If it does not, most likely the service is not reachable or AppGyver could not comprehend the service URL as service metadata(if you followed the steps so far, your AppGyver should proceed and display the data entities).
Click “Install integration” button on the top right and “Enable Data Entity” button below it. You can view the data you’ve set in the source table in step 1.1 by clicking “Browse Data” button.
Good job! Now we can use the OData on AppGyver and it’s time to be creative 🙂
4.2 Create list app to display data
Go to the first page of the app(Empty page by default) and switch to variable screen. Add your OData entity as data variable. When you click the + button, it is already on the dropdown list added for you. The fetched data is stored in data variable “your entity name1”. In my case, it’s “C_USER1”.
Switch back to canvas view and drop down the UI component of your choice. For example, with “Line item” component it will look something like this. Make sure to set data variable C_USER1 in Repeat with property. In the primary label, I combined first and last name and valid dates.
Alternatively you could use other components such as “Card” from the marketplace.
Next step..
My next blog will go through the steps to implement AppGyver Create/Update/Delete process to handle RAP generated OData.
However, it seems that Create/Update are not supported yet by AppGyver BTP authentication for handling SAP OData service(haven’t tested Delete). There might be some configuration I’ve missed to make them work but I’ve reported this issue and still waiting for the analysis.
https://appgyver.canny.io/bug-reports/p/btp-authentication-csrf-token-error-with-create-record