BTP - Connect on-premise (OData)
In this article we look at how we can connect and call an on-premise OData interface to the ABAP environment.
Table of contents
The ABAP Environment is a development environment that hardly has any data of its own and has to read the data from the other systems. In this article we will develop an interface via RAP On-Premise and make it available as an OData v2 API. We will then consume the data in the BTP.
UPDATE (12/15/2023):
- A new Consumption Model (v2) has now been made available, the update of the article can be found via the link.
- Changes were made in the generation of the model, other points in the article are still up to date.
Structure On-Premise
As a first step, let's take a look at the on-premises interface. To do this, we define a table with the names of partners, which we also use in our partner app in the RAP series. The name becomes the key and we define additional information such as industry and description.
@EndUserText.label : 'Company Names'
@AbapCatalog.tableCategory : #TRANSPARENT
define table zbs_dmo_cname {
key client : abap.clnt not null;
key name : abap.char(60) not null;
branch : abap.char(50);
description : abap.char(255);
}
In the next step we generate the RAP stack on the table, which means we create the core data services and the behavior implementation. The behavior definition looks like this:
managed implementation in class zbp_demo_company_name unique;
strict;
define behavior for ZBS_I_DmoCName alias Name
persistent table zbs_dmo_cname
lock master
authorization master ( instance )
{
create;
update;
delete;
mapping for zbs_dmo_cname
{
CompanyName = name;
Branch = branch;
CompanyDescription = description;
}
}
On the stack we generate an OData v2 Web API to make the data available. The Core Data Services do not need annotations because we only need an interface and no output:
Here is an overview of how to set up a simple RAP application. You can find out how the on-premise interface is structured and everything else that belongs to the interface in the repository.
Data
We also fill the table with data, we do this using an executable class and a static method to make it reusable. You can also find the class in the on-premise repository:
CLASS zcl_bs_demo_rebuild_intf_data DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
CLASS-METHODS:
rebuild_data.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_bs_demo_rebuild_intf_data IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
zcl_bs_demo_rebuild_intf_data=>rebuild_data( ).
ENDMETHOD.
METHOD rebuild_data.
DATA:
lt_names TYPE SORTED TABLE OF zbs_dmo_cname WITH UNIQUE KEY name.
lt_names = VALUE #(
( name = 'SAP'
branch = 'Software'
description = 'SAP SE is a German multinational software company based in Walldorf, Baden-Württemberg. It develops enterprise software to manage business operations and customer relations.' )
( name = 'Microsoft'
branch = 'Software'
description = 'Microsoft Corporation is an American multinational technology corporation producing computer software, consumer electronics, personal computers, and related services.' )
( name = 'BMW'
branch = 'Automotive Industry'
description = 'Bayerische Motoren Werke AG, abbreviated as BMW, is a German multinational manufacturer of luxury vehicles and motorcycles headquartered in Munich, Bavaria.' )
( name = 'Nestle'
branch = 'Food'
description = 'Nestlé S.A. is a Swiss multinational food and drink processing conglomerate corporation headquartered in Vevey, Vaud, Switzerland.' )
( name = 'Amazon'
branch = 'Online Trade'
description = 'Amazon.com, Inc. is an American multinational technology company focusing on e-commerce, cloud computing, online advertising, digital streaming, and artificial intelligence.' )
).
INSERT zbs_dmo_cname FROM TABLE lt_names.
COMMIT WORK.
ENDMETHOD.
ENDCLASS.
The content of the table then looks like this (Data Preview in Eclipse):
Metadata
Now that we have completed the interface and the endpoint can be called on-premises, we now need the metadata of the interface for the ABAP environment. The easiest way is via the URL that you can call up via the service binding:
Your browser will be called up and the service will be displayed. You will see the first information about the OData service as XML:
Now you only have to add "$metadata" at the end of the URL, then the meta file of the service will be displayed, which you can then simply right-click and "Save as" save as an XML file:
We now need the XML file for the following steps in the ABAP environment.
Consumption Model
The on-premise development is now complete and we can start connecting to the ABAP environment. To do this, we create a new package ZBS_DEMO_RAP_INTERFACE in our RAP project. Then you can right-click on the package and search for "Consumption Model" via "New -> Other Repository Object". We now want to create this type of object in order to automatically receive all objects for access.
The new component should already be assigned to the right package, besides the name and the description, we still have to adapt the type of RFC to OData:
Now we specify the path to the metadata file and specify a prefix that is used for all objects. Since "Z" is automatically set as the first character, we only need the following delimitation:
In the next step you select the entities of the service that you also want to use in this system. Since we only developed our service with one entity, only this one entity is suggested:
At the end you get a summary of all objects that are to be generated, you can check the objects and the names used again (in our case the service consumption model, as well as a CDS view and a behavior definition for each entity):
Finally, you will find yourself in the generated Service Consumption Model and get an overview of the generated objects. In the left list you can see your generated entities. If you select an entry, the relevant information will be loaded on the right-hand side, such as the CDS view or the behavior definition. Code snippets are shown in the lower part of the right side to access the data:
Read
After we have connected the interface to the system, we can carry out the first test call using a class. To do this, we can reuse much of the example code from the Service Consumption Model window and build the following test method:
DATA:
lt_business_data TYPE TABLE OF zbs_rap_companynames.
TRY.
DATA(lo_client_proxy) = cl_web_odata_client_factory=>create_v2_remote_proxy(
EXPORTING
iv_service_definition_name = 'ZBS_DEMO_RAP_ONPREM_ODATA'
io_http_client = get_client( )
iv_relative_service_root = '/sap/opu/odata/sap/ZBS_API_COMPANY_NAMES_O2' ).
DATA(lo_request) = lo_client_proxy->create_resource_for_entity_set( 'COMPANYNAMES' )->create_request_for_read( ).
lo_request->set_top( 50 )->set_skip( 0 ).
DATA(lo_response) = lo_request->execute( ).
lo_response->get_business_data( IMPORTING et_business_data = lt_business_data ).
out->write( 'Data on-premise found:' ).
out->write( lt_business_data ).
CATCH cx_root INTO DATA(lo_error).
out->write( lo_error->get_text( ) ).
ENDTRY.
First, the HTTP client is generated via the destination (get_client method) and the HTTP proxy for OData v2 is generated from this. The name of the service and part of the URL are transferred here. This information is required later for the call. We then create a read request for our entity and set a TOP/SKIP value to read only part of the data, where we have less than 50 records. We then execute the query and get the result returned. The abstract entity that we use for data transfer was automatically created for the return. As a result, we get the data from on-premise:
The client can be generated via a communication arrangement or a destination in the BTP in the destination service. In this example we use the Destination Service, which points to our on-premise system with the OData.
You can find the complete example of the test class from the ABAP Environment here:
CLASS zcl_bs_demo_read_odata DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
CONSTANTS:
c_destination TYPE string VALUE `<destination-service-id>`.
METHODS:
get_client
RETURNING VALUE(ro_result) TYPE REF TO if_web_http_client
RAISING
cx_http_dest_provider_error
cx_web_http_client_error.
ENDCLASS.
CLASS zcl_bs_demo_read_odata IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA:
lt_business_data TYPE TABLE OF zbs_rap_companynames.
TRY.
DATA(lo_client_proxy) = cl_web_odata_client_factory=>create_v2_remote_proxy(
EXPORTING
iv_service_definition_name = 'ZBS_DEMO_RAP_ONPREM_ODATA'
io_http_client = get_client( )
iv_relative_service_root = '/sap/opu/odata/sap/ZBS_API_COMPANY_NAMES_O2' ).
DATA(lo_request) = lo_client_proxy->create_resource_for_entity_set( 'COMPANYNAMES' )->create_request_for_read( ).
lo_request->set_top( 50 )->set_skip( 0 ).
DATA(lo_response) = lo_request->execute( ).
lo_response->get_business_data( IMPORTING et_business_data = lt_business_data ).
out->write( 'Data on-premise found:' ).
out->write( lt_business_data ).
CATCH cx_root INTO DATA(lo_error).
out->write( lo_error->get_text( ) ).
ENDTRY.
ENDMETHOD.
METHOD get_client.
DATA(lo_destination) = cl_http_destination_provider=>create_by_cloud_destination(
i_name = c_destination
i_authn_mode = if_a4c_cp_service=>service_specific
).
ro_result = cl_web_http_client_manager=>create_by_http_destination( lo_destination ).
ENDMETHOD.
ENDCLASS.
Conclusion
Thanks to the use of RAP also on-premise, interfaces for the BTP can be made available very easily. If no RAP is available yet, this can also be done in the classic way via the SEGW.