RAP - Deep Action in OData v4
In this article we will look at actions with deep structures, how we can create them and pass data to an API endpoint.
Table of contents
In this scenario, we extend our complex entity with an additional action. In this case, we want to pass deep data (header and position) to the action in order to be able to execute our own code and work with the entire data package. Thanks to Saptarshi for the question.
Introduction
In certain situations, we don't just want to get individual data into our RAP object, but rather deep structures that already contain all the data. This allows us to check the data for completeness, trigger various SAP APIs in the system, or even address HTTP interfaces. As a developer, this gives you full control over processing.
Requirements
Accordingly, there are also requirements for such an action to work. We want to implement the action in a web API so that we can call it from outside. However, we can only work with deep structures in OData version 4, which is the minimum version for such an implementation. With OData v2, an error occurs when it is released and a note is made about support.
Structure
In the first step we extend the existing RAP object with an additional action.
Entities
Before we can implement the action, however, we need to create the structures for it and define the fields that we want to receive in our service. To do this, let's create the abstract entity that should receive the data.
@EndUserText.label: 'Create Invoice'
define root abstract entity ZBS_S_RAPCreateInvoice
{
key DummyKey : abap.char(1);
Document : abap.char(8);
Partner : abap.char(10);
_Position : composition [0..*] of ZBS_S_RAPCreatePosition;
}
The DummyKey is needed to link the two entities (head and position). Using the composition "_Position", we define the positions that are passed along. The entity for the positions now looks like this:
@EndUserText.label: 'Create Position'
define abstract entity ZBS_S_RAPCreatePosition
{
key DummyKey : abap.char(1);
Material : abap.char(5);
@Semantics.quantity.unitOfMeasure : 'Unit'
Quantity : abap.quan(10,0);
Unit : abap.unit(3);
@Semantics.amount.currencyCode : 'Currency'
Price : abap.curr(15,2);
Currency : abap.cuky;
_DummyAssociation : association to parent ZBS_S_RAPCreateInvoice on $projection.DummyKey = _DummyAssociation.DummyKey;
}
Accordingly, we also create a dummy key here and define the fields that we need at position level. We use the association to establish the relationship to the header or the invoices. Finally, we need a behavior definition to define the relationships between each other.
abstract;
strict( 2 );
with hierarchy;
define behavior for ZBS_S_RAPCreateInvoice alias Invoice
{
field ( suppress ) DummyKey;
association _Position;
}
define behavior for ZBS_S_RAPCreatePosition alias Position
{
field ( suppress ) DummyKey;
}
Special features here are the type "abstract", the view also needs the addition "with hierarchy" to work properly. If you forget the addition, an error will appear when you include the action and point it out. Finally, we suppress the dummy key, as we do not need it in the data and do not want to provide it. The rest of the behavior definition corresponds to the standard.
Behavior definition
In the next step, we can now extend the behavior definition and create the new action. So let's start in the interface and define a static action in the "Invoice" entity at the top level.
static action CreateInvoiceDocument deep parameter ZBS_S_RAPCreateInvoice;
We need a static action because we want to work without a reference to a data set. We use the DEEP addition to indicate that this is a deep data type. We can then use CTRL + 1 to create the method in the behavior implementation. Finally, we extend the consumption view so that the action is available in the API.
use action CreateInvoiceDocument;
Web API
To access the API from outside, we create a service binding of type API and OData v4.
Now we need to create a communication scenario and carry out the corresponding configuration in the ABAP environment. You can read how this works step by step in this article.
Execution - ABAP
Let's start the first call with EML and try to pass the data to the action in the system. To do this, we create an internal table for the action, fill it with data and pass it to the RAP business object.
DATA lt_document TYPE TABLE FOR ACTION IMPORT ZBS_R_RAPCInvoice~CreateInvoiceDocument.
lt_document = VALUE #( ( %cid = to_upper( cl_uuid_factory=>create_system_uuid( )->create_uuid_x16( ) )
%param = VALUE #( Document = 'TEST'
Partner = '1000000004'
_position = VALUE #(
Unit = 'ST'
Currency = 'EUR'
( Material = 'F0001' Quantity = '2' Price = '13.12' )
( Material = 'H0001' Quantity = '1' Price = '28.54' ) ) ) ) ).
MODIFY ENTITIES OF ZBS_R_RAPCInvoice
ENTITY Invoice
EXECUTE CreateInvoiceDocument FROM lt_document
FAILED DATA(ls_failed_deep)
REPORTED DATA(ls_reported_deep).
This completes the first test and we can test the path via the API.
Execution - POSTMAN
As a second test, we want to call the endpoint in the system via POSTMAN in order to test the API and the transfer. To do this, we have to perform two steps: create a CSRF token (GET) and trigger the action (POST), where we then transfer the data.
Collection
To do this, we create a new collection (folder) in POSTMAN and under the "Authorization" tab we switch to "Basic Authentication" so that we can log in to the endpoint using a user and password. This means that all requests under the collection receive this method and we no longer have to worry about logging in for each request.
Hint: The entry of user and password is for demonstration purposes only; normally such information is stored in environment variables, where it is also encrypted.
CSRF Token
Before we can execute the action, we need a token. To do this, we create a new GET request under our collection and use the endpoint of our service. You can find this, for example, in the Communication Arrangement in the ABAP Environment, where we set up the scenario.
In the header we also include the field "x-csrf-token" with the value "fetch". This tells the endpoint that we are requesting a new token from it. After executing, you will find the token in the response header in the field "x-csrf-token".
Action
Now we want to execute the actual action. To do this, we first need the endpoint of our action from the service. This consists of various components:
<SERVICE-URL>/<ENTITY>/<NAMESPACE>.<ACTION>
We have already used the service URL for the token, followed by the entity where we defined the action. You can find the namespace in the service metadata. The last part of the URL would then look like this:
/Invoice/com.sap.gateway.srvd_a2x.zbs_rapc_invoice.v0001.CreateInvoiceDocument
We now create a POST request and specify the token and the content type in the header.
In the body we enter the data we want to pass to the endpoint, using JSON as the format. Make sure that the numbers are actually passed in numeric format. The data is passed as an object, with the association "_Position" consisting of your array, which in turn contains objects.
Debugging
So that we can check whether the call works, we want to debug the data in the implementation. To do this, we need to set a breakpoint for the technical user, as this is the user who executes the code. In the ABAP Development Tools, you would need to go to the "Properties" of the ABAP project. Under "ABAP Development -> Debug" you will find the settings for debugging another user. Here you can use the search function to find the Communication User (CC); the Named User is only needed for API access.
If we now execute the request, the debugger opens and we can access the information via the import variables.
Now you can freely take over the further implementation and design it according to your wishes.
Full example
All changes made to the objects and the new objects can be found in the commit of the GitHub repository if you want to recreate the individual steps or are still missing information.
Conclusion
Implementing a deep-structure action can feel a bit special when it comes to defining the abstract RAP entity. However, the actual implementation and invocation is then in the RAP standard.