Skip to Content
Technical Articles

Introduction to OData and how to implement them in ABAP

Some years ago I created a small introduction into OData development for my colleagues at newFounders. Last week some developers at my current client told me this was still a nice document if you want to start with OData development, so I decided to share it with the community. I hope you enjoy it and learn something from it. I still have hopes to create a follow-up with covering the topics expanded and deep entitysets, batches and function imports, but only time will tell if this will happen.

Introduction  

OData is the current default way to communicate with an SAP backend, be it for an (SAPUI5) frontend or any other integration scenario. The goal of this document is to get an ABAP developer up and running with understanding and implementing OData services in an SAP ABAP-based backend system.  

OData services; background information and how to test them. 

I’ll try to explain what OData is by using calls on existing OData services, and expanding those calls with more options. I’ll start using a tool called Postman so we’ll need to get this tool as the first step. 

Warning: I use standard BP functionality, so only use this tutorial in a test-environment.

Postman 

Install Postman (https://www.getpostman.com/downloads/). Postman is a tool to test & execute HTTP(S) REST calls. As an alternative you can use Curl (http://curl.haxx.se/), but I will use Postman in this tutorial 

SAP ABAP backend.

We will use some demo-services and demo-data from SAP. If you find that the underlying tables are empty, you can fill them using transaction SEPM_DG. I assume you know the hostname and port number of your SAP installation. If not, try to find it in transaction SMICM –> Go To –> Services. 

Testing your environment.  

Start Postman, create a Collection to store your tests & calls in, and create your first OData call to test the connection to your backend. We’ll use a standard SAP OData service: http://YourSystemURL:Portnumber/sap/opu/odata/IWBEP/GWDEMO/?$format=xml . In Postman this looks like: 

  Postman%201

If you try this call now, you’ll get a logon error.  

Postman%20error

We have to add our credentials in the tab Authorization (type: Basic Auth): 

And if all’s correct, you’ll see the entity-types in the returned body after pressing <SEND> you’ll get results: 
Response%20Postman

 

Question: 
What would you have to do in order to get your response in a JSON format? 

OData background 

The format of an OData URL is shown below.
 OData%20call%20format

This URL points to a Service Document, which for OData, exposes two key things:
– The entity sets, functions and singletons that can be retrieved.
– A metadata document (which shows the packaging format of the data)  

To view the entity sets, open the link in a new browser tab. As you scroll through the page, you will see all the entity sets (or collections) listed.  
http://YourSystemURL:Portnumber/sap/opu/odata/IWBEP/GWDEMO/?$format=json  

The metadata document will show the individual fields, formats and navigation properties of all the collections. To view the metadata for any OData service, append $metadata at the end of the URL in your browser.
http://YourSystemURL:Portnumber/sap/opu/odata/IWBEP/GWDEMO/$metadata 

To see the data that is served by the OData service, just enter the collection you’d like to see: 
http://YourSystemURL:Portnumber/sap/opu/odata/IWBEP/GWDEMO/SalesOrderCollection?$format=json  

If you try this URI, you’ll see that it takes a long time to get all the results. Most of the time you just want to display a few of the items out of the collection. In order to get only a few items you can enhance the URI with some options like $top$skiptoken, $orderby, $filter
http://YourSystemURL:Portnumber/sap/opu/odata/IWBEP/GWDEMO/SalesOrderCollection?&$top=10&$skiptoken=5&$filter=BillingStatus%20ne%20’P’&$format=json  

In the above URI we want 10 Sales Orders that are not paid, skipping the first 5 entries. Note the “%20”; this is an encoded form for a space (“ ”). For more information: https://www.w3schools.com/tags/ref_urlencode.asp  

Still we get a lot of data; we can choose to only return a subset fields by using the option $select
http://YourSystemURL:Portnumber/sap/opu/odata/IWBEP/GWDEMO/SalesOrderCollection?&$top=10&$skiptoken=5&$filter=BillingStatus%20ne%20’P’&$select=SalesOrderID,Status,NetSum,Currency,CustomerName&$format=json  

But wouldn’t it be nice to get the ordered products in the line items as well? For that we have the option $expand
http://services.odata.org/V2/Northwind/Northwind.svc/Products?$format=json&$top=10&$select=ProductName,UnitPrice,Supplier&$expand=Supplier 
(Unfortunately, the SAP GWDemo OData service doesn’t support the $expand very well, so this is a different example) 

Luckily, there is another option to go from one Sales Order to the line items. That is to define the Sales Order with its key (like this: http://YourSystemURL:Portnumber/sap/opu/odata/IWBEP/GWDEMO/SalesOrderCollection(‘1244D0D392BB1EE5B49987627D705A45’) )  and use the other way to expand to the line items by using a path in the URI: 
http://YourSystemURL:Portnumber/sap/opu/odata/IWBEP/GWDEMO/SalesOrderCollection(‘1244D0D392BB1EE5B49987627D705A45’)/salesorderlineitems  

Please note that GUID’s are used in these last two URI’s. You will not have the same GUID’s in your system, so you should change your URI’s in order to get results.

Question: 
What could be the use of combining $top, $skip and $count ? 
Hint: Have a look at https://blogs.sap.com/2013/03/20/using-odatas-top-skip-and-count/  and https://blogs.sap.com/2017/12/06/display-countfilterorderbyinlinecounttop-and-skip-operations-using-odata-services/ 

Could you make an URI where you retrieve information on a sales order and its line-items, selecting only a few fields? Do you need to include the key in the selection to get the line-items? Why would you use a selection on fields? 

 

So far, we just retrieved information. Now we want to make some changes. You can use the standard HTTP operations GET, POST, PUT, PATCH, MERGE & DELETE. We’ll change the something simple, the ProductDescription of a Product. Get a list of some products:
http://YourSystemURL:Portnumber/sap/opu/odata/IWBEP/GWDEMO/ProductCollection?$top=20

Select a Product you want to change and get the details. The URI for the identification is given, for instance
http://YourSystemURL:Portnumber/sap/opu/odata/IWBEP/GWDEMO/ProductCollection(‘1244D0D392BB1EE5B49984552646FA45’) 
Use the body of the result as input for the update
   

Now search for the tag <ProductDescription> and change the description. Make sure you enter the body as XML, and set this in Postman as XML as well:   

Product%20DescriptionXML%20%28application/XML%29

Choose the PATCH operation and press SEND. 

Patch%20product

If all went well, you have changed the description of this product. Check with a GET operation. 

If you get an error referring to an x-csrf-token, change the GET request by adding the following in the header: x-csrf-token=fetch. In the response you’ll get a token back, use this value in the PATCH operation: 
x-csrf-token%3Dfetch

 

CSRF (Cross-site Request forgery) is the name of a way of session piggy-back riding that can be used for malicious means. In order to prevent this, a CSRF token is submitted by the SAP system so the browser can use this token in subsequent calls in a secure way. See for more information https://en.m.wikipedia.org/wiki/Cross-site_request_forgery and https://stackoverflow.com/questions/5207160/what-is-a-csrf-token-what-is-its-importance-and-how-does-it-work . 

 

Question: 
What would the difference be between the PUT and PATCH operations?
What is the result status of the PATCH call? Why?
What should you put in the body of a DELETE operation? 

 

For more information and exercises about OData please visit: 
https://developers.sap.com/tutorials/hcp-webide-odata-primer.html 
https://blogs.sap.com/2013/10/03/lets-talk-odata-shall-we/ 
https://sapyard.com/odata-and-sap-netweaver-gateway-part-i-introduction/ 
https://blogs.sap.com/2016/02/08/odata-everything-that-you-need-to-know-part-1/  and the subsequent blogposts in this series. 
https://www.odata.org/getting-started/basic-tutorial/  

And a very good reference concerning OData:                    https://www.odata.org/documentation/odata-version-2-0/uri-conventions/  

 

Creating a simple OData service in SAP.  

Introduction. 

As our focus will be on creating an OData service in SAP, we’ll use a quite straightforward data model: The Business Partner and use for its sub nodes address, telephone numbers etc. We’ll use the standard BAPI’s and function modules, so we don’t have to do too much ABAP.  

Most implementations of OData services in the SAP world use a separate Gateway system for serving OData and a separate system where the data actually resides (oftentimes an ECC-system).  

Question: What would be the advantage to have two different systems: one for storing the data, the other for serving the OData, instead of having only one system serving both purposes?   

Defining the OData service. 

In transaction ‘SEGW’ you can define your OData service. Let’s start by creating a read service (HTTP “GET” operation) for the Business Partner entity. Click on create and fill in the requested parameters. An empty OData project appears. 

SEGW%3A%20Create%20proejctSEGW%3A%20Data%20model 

What do we see in the tree?
First, the name of the OData service. In this case “Z_JW_BUSPARTNER”. 
Underneath we find the “Data Model” node. In the sub nodes of the “Data Model” node the data definition and the relations between them will be defined. Any functions will be seen here as well.  
The next node is the “Service Implementation”. In here the possible operations are defined for the entities in the “Data Model”.
In the “Runtime Artefacts” the (generated) classes will be summed.
In the “Service Maintenance” the details of the registration of service on the Gateway-system is displayed.  

To define the fields for the business partner we’ll import the BUT000 structure, but only use some fields. Right-click on the “Data Model” node and select “Import –> DDIC structure”.  

SEGW%3A%20import%20DDICSEGW%3A%20Import%20BUT000    

Fill in the fields and select the ones you want to use. As I will use my service only for organizations, I will initially only use the fields “Partner”, “NAME_ORG1”, “NAME_ORG”, “BU_SORT1” & “BU_SORT2”. The field “Partner” will be marked as key.  

Mark%20key
 

Question: Why do we need to mark at least one entry as key?

Now we have to specify what we can do with the fields. As we want to be able to have all fields available for CRUD, we mark them all for these operations. Note however, that we do not mark all fields.  

Mark%20CRUD

Question: which fields are not marked and why?  

After generating the first part of the OData service is created! Best practice is to leave the suggested names as they are, but you can change the names of the classes. Later onwe’ll use the classes with the “_DPC_EXT” and “_MPC_EXT” for implementing our own logic.  

Generate%20classes

After generating you should register the service in SICF. Select the entry under the node “Service Maintenance”. Just press the button “Register” 

Register

And we can test it immediately! 

Testing OData services in the SAP Gui.  

Testing the OData service is quite easy. SAP created a separate transaction for testing OData services, that you can reach by pressing the button with the monkey wrench:  

Test%20gateway

You’ll reach the following screen. The basic URI should be pre-filled.  

Test%20OData%20service%20in%20Gateway

By using the buttons “Entity Sets” and/or “Add URI Option” you can manipulate the URI.  

Question:
Try to get the metadata for your service, as well as the entries for the business partners. 

What is the return status of the call for the entries retrieving the business partners? 

Please note that only the last part of the URI is displayed in this SAP transaction, starting at the service root. 

Implementing code for the OData service to get a list of entities. 

You will have noticed that the service gives no results yet, but a “501” error (not implemented). And that is exactly what we are about to do. While generating the service, the system asked for names for classes. We will use the Data Provider class defined there. Standard it is the class ending with “_DPC_EXT”, you can find the name for the class under the “Service Implementation” node as well. There are five inherited methods that are related to the Business Partner entity.   

Service%20Implementation

The first operation to be published are the read-operation, and therefor the methods “BUSINESSPARTNERS_GET_ENTITY” and “BUSINESSPARTNERS_GET_ENTITYSET” will be re-implemented.  

Question: What is the difference between those two methods? 

As we used the data dictionary element “BUT000” as the source for our entity, we can directly select entries from that table into the entity. In most cases you will use a field list and a join in your selection or use a BAPI or function call.  

Warning: This solution might lead to performance problems in systems with many business partners.  

Implementation for the entity set 

METHOD businesspartners_get_entityset. "Redefinition 
    SELECT * FROM but000 INTO TABLE et_entityset. 
ENDMETHOD. 

 

With this single line of code, you have your OData running for retrieving all business partners!  

Now have a better look at the method you just re-implemented. As you see, there are many parameters in this method. Have a look at them in the debugger (set an external breakpoint); quite a few speak for themselves. The most important is the exporting parameter “ET_ENTITYSET”. In this parameter you will return your results, which will eventually be the result set of the OData service. 
The IS_PAGING parameter contains the $top & $skip options, the IT_FILTER_SELECT_OPTIONS; IT_KEY_TAB; IT_NAVIGATION_PATH IT_ORDER, IV_FILTER_STRING and IV_SEARCH_STRING parameters are for other options in the OData URI. A few of them are implemented by standard classes in SAP. See for more information this blog: https://blogs.sap.com/2017/12/06/display-countfilterorderbyinlinecounttop-and-skip-operations-using-odata-services/ . 

Implementing these standard functionalities will lead to the following method:  

 

METHOD businesspartners_get_entityset. "Redefinition 

    SELECT * FROM but000 INTO TABLE et_entityset. 

*** $inlinecount query option for all count entries. 
     IF io_tech_request_context->has_inlinecount( ) = abap_true. 
       DESCRIBE TABLE et_entityset LINES es_response_context-inlinecount. 
     ELSE. 
       CLEAR es_response_context-inlinecount. 
     ENDIF. 


*** The module for Filter conditions 
     CALL METHOD /iwbep/cl_mgw_data_util=>filtering 
       EXPORTING 
         it_select_options = it_filter_select_options 
       CHANGING 
         ct_data           = et_entityset. 

*** The module for $top and $skip Query Options 
     CALL METHOD /iwbep/cl_mgw_data_util=>paging 
       EXPORTING 
         is_paging = is_paging 
       CHANGING 
         ct_data   = et_entityset. 

*** The module for Orderby condition 
     CALL METHOD /iwbep/cl_mgw_data_util=>orderby 
       EXPORTING 
         it_order = it_order 
       CHANGING 
         ct_data  = et_entityset. 

ENDMETHOD. 

   

Wouldn’t it be nice if we offered a message if we couldn’t find any results? So, let us implement that.  In the methods for returning entities two exceptions are defined: one of type “/IWBEP/CX_MGW_BUSI_EXCEPTION” and one of type “/IWBEP/CX_MGW_TECH_EXCEPTION”. The first one is for problems with entities, like authorizations or no valid entries found, the second one is for technical problems, like unexpected input, operators or things like that. Such a message was displayed after the service was just generated, but not methods were implemented yet. This exception leads to an HTTP-error message (e.g. A 501-result).  

Furthermore, we can make use of a message container to store multiple messages. See https://help.sap.com/doc/saphelp_gateway20sp12/2.0/en-US/25/21aecce9db435daaea433071ff7d94/content.htm . 

The implementation of this service is completed by adding a check on the selection to see if anything was found:  

    IF sy-subrc NE 0. "No BP's found. 

      DATA(lo_message_container) =  me->mo_context->get_message_container( ). 

      lo_message_container->add_message( 
                iv_msg_type                = /iwbep/cl_cos_logger=>warning 
                iv_msg_id                  = 'SA' "SA(419) = No objects found 
                iv_msg_number              = 419 
                iv_add_to_response_header  = abap_true 
            ). 

     ENDIF. 

 

The message container is more versatile than just this method. Just explore and try things out.  

Feel free to test this service.  

Implementing the Read-service for a single entity.  

With the experience of implementing the service for getting an array of results for the result set, it is easy to implement the service for getting one entry. The method to be redefined here is the one called “BUSINESSPARTNERS_GET_ENTITY”. 
The service expects a valid key for the entity, in this case the key is a business partner number. This is transported to the method in parameter IT_KEY_TAB.  A simple solution could be the following coding:  

DATA(lv_bu_partner_raw) = CONV bu_partner(  
    |{ it_key_tab[ name = 'Partner' ]-value }| ). 

 DATA(lv_bu_partner) = CONV bu_partner(  
    |{ lv_bu_partner_raw  ALPHA = IN }| ). 

SELECT SINGLE * FROM but000 INTO CORRESPONDING FIELDS OF er_entity WHERE  
    partner = lv_bu_partner. 

 

Question: which fault-handling would you implement? How will you get the messages to the caller of the OData service? 

Implementing the Update / Create / Delete service.  

Getting information is one thing, but we would like to really do things with our entities. I’ll explain the Update service and leave it up you to implement the create and delete services as a practice. You’ll be able to guess which methods to redefine by now.  

The data which should be updated in the system is transferred via the parameter io_data_provider. You can retrieve the data by calling method read_entry_data( ). The update will take place using the HTTP “put” method. The key of the input is also given in the IT_KEY_TAB parameter, it is good practice to compare the value from the io_data_provider with the value in the IT_KEY_TAB parameter.   

It is also nice to give the updated value back to the user, via parameter ER_ENTITY. 

The simplified coding could look like this:  

  io_data_provider->read_entry_data( IMPORTING es_data = ls_entity ). 
  READ TABLE it_key_tab INTO DATA(ls_key_tab) INDEX 1. 

 * make sure the value matches with the OData payload 
  IF ls_key_tab-value EQ ls_entity-partner. 
      CALL FUNCTION 'BAPI_BUPA_CENTRAL_CHANGE' 
         EXPORTING         businesspartner           = ls_entity-partner 
           TABLES         return                    = lt_return. 
  ELSE. 

 * raise message: entity not found 
  ENDIF. 

  er_entity = ls_entity. 

 

Tip for easy testing: You’d like to update an existing entity; so first execute a GET for a single entity. Copy the response-result and use that as the starting point for your HTTP Request body. In the SAP GUI there is even a nice button for it:  And don’t forget to either add leading zeroes in your coding if you want to be forgiving, or put the correct key with leading zeroes once you call an update 

Question: What status code will you receive once you’ve made a successful update? Why would we get this particular status code? 

Now continue with implementing the create and delete services and test them. For creating Business Partners you could use “BAPI_BUPA_CREATE_FROM_DATA“. Use category ‘1’ for persons and ‘2’ for organizations.  

For deleting you can either just set the archiving marker, of you could re-use the coding of the SAP reports BUPA_PREPARE_DA (transaction BUPA_PRE_DA) and BUPA_TEST_DELETE (transaction BUPA_DEL).  

Question: Is it necessary to provide a body with details for the HTTP-request for the delete operation?   

Question: How will you know what the key is of the entity that was created with a HTTP-POST operation. 

Adding associations to your OData service 

Now we have our simple OData service running, it is time to enhance our service. We’ll add the possibility to add addresses.  For the sake of simplicity, I’ll only maintain telephone numbers in this example. For the implementation I’ve used the function modules: BAPI_BUPA_ADDRESSES_GET, BAPI_BUPA_ADDRESS_GETDETAIL, BAPI_BUPA_ADDRESS_ADD, BAPI_BUPA_ADDRESS_CHANGE and BAPI_BUPA_ADDRESS_REMOVE.  

In the OData service a second entity is created for the telephone numbers (Telephone & TelephoneSet). In this example only the minimum fields are maintained 

Association%20to%20Telephone

Now it is time to add the association between partner and telephones.  

Open the Data Model node of the OData service and select create on  Associations node. 

Create%20association

The association is from the Business Partner to the Telephone, and hence we name it BusinessPartnerGetTelephones. Any business partner can have zero to many telephone numbers, and that is used in the cardinality. If needed, generate the navigation properties as well. Navigation Property: A property of an Entry that represents a Link from the Entry to one or more related Entries. A Navigation Property is not a structural part of the Entry it belongs to.  

Create%20Association%201

Enter the “referential constraints” (the fields used for linking the two entities).  

Create%20Association%202

And accept the overview:  

Create%20Association%203

And the Association is created, including the Navigation Properties. 

Association%20and%20Navigation%20Properties

Question: How would the URI look like to test the Association?  

What is the difference between an URI with $expand and using an URI with an Association/Navigation path? (Hint see this article) 

What would the result be if you tested the URI in the previous two questions? Why? 

 

 After defining the services, the implementation still must be coded. Once this is completed, test your solution by creating a business partner, adding, changing, and deleting telephone numbers. It would be nice to test the navigation using both a Navigation Property and an $expand option
For instance: /BUSPARTNER_SRV/BusinessPartnerSet(’14’)/TelephoneSet  and /BUSPARTNER_SRV/BusinessPartnerSet(’14’)/?$expand=TelephoneSet . 

 

Conclusion

You have just created a basic OData service, and learned something about the theory behind OData. I hope you enjoyed it.

6 Comments
You must be Logged on to comment or reply to a post.