RAP - Deep Action in OData v4
In diesem Artikel schauen wir uns einmal Aktionen mit tiefen Strukturen an, wie wir sie erzeugen und Daten an einen API Endpunkt übergeben können.
Inhaltsverzeichnis
In diesem Szenario erweitern wir unsere komplexe Entität um eine zusätzliche Aktion. In diesem Fall wollen wir tiefe Daten (Kopf und Position) an die Aktion übergeben, um damit eigenen Code auszuführen und mit dem gesamten Datenpaket arbeiten zu können. Danke an Saptarshi für die Frage.
Einleitung
In bestimmten Situationen möchten wir nicht nur einzelne Daten in unser RAP Objekt bekommen, sondern tiefe Strukturen, in denen alle Daten bereits enthalten sind. So können wir die Daten auf Vollständigkeit prüfen, verschiedene SAP APIs im System triggern oder auch HTTP Schnittstellen ansprechen. Als Entwickler erhältst du damit die volle Kontrolle bei der Verarbeitung.
Voraussetzungen
Entsprechend gibt es auch Voraussetzungen, damit so eine Aktion funktioniert. Wir möchten die Aktion in einer Web API implementieren, um sie von außen aufrufen zu können. Mit tiefen Strukturen können wir allerdings nur in OData Version 4 arbeiten, dies ist die minimale Version für so eine Umsetzung. Bei OData v2 kommt es bei der Freigabe zu einem Fehler und dem Hinweis auf die Unterstützung.
Aufbau
Im ersten Schritt erweitern wir das bestehende RAP Objekt um eine zusätzliche Aktion.
Entitäten
Bevor wir allerdings die Aktion implementieren können, müssen wir die Strukturen dafür anlegen und die Felder definieren, die wir in unseren Service erhalten wollen. Legen wir dazu die abstrakte Entität an, die die Daten entgegennehmen soll.
@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;
}
Der DummyKey wird für die Verknüpfung der beiden Entitäten (Kopf und Position) benötigt. Über die Composition "_Position", definieren wir die Positionen, die mit übergeben werden. Die Entität für die Positionen sieht nun entsprechend so aus:
@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;
}
Entsprechend legen wir auch hier einen DummyKey an und definieren die Felder die wir auf Positionsebene benötigen. Über die Assoziation stellen wir die Beziehung zum Kopf bzw. den Rechnungen her. Zum Abschluss benötigen wir noch eine Verhaltensdefinition, um das Beziehungen untereinander zu definieren.
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;
}
Besonderheiten sind hier der Typ "abstract", der View benötigt auch den Zusatz "with hierarchy" um sauber zu funktionieren. Solltest du den Zusatz vergessen, erscheint beim Einbinden der Aktion der Fehler und weist darauf hin. Zum Abschluss unterdrücken wir noch den DummyKey, da wir diesen in den Daten nicht benötigen und anbieten wollen. Der Rest der Verhaltensdefinition entspricht dem Standard.
Verhaltensdefinition
Im nächsten Schritt können wir nun die Verhaltensdefinition erweitern und die neue Aktion anlegen. Fangen wir also im Interface an und definieren eine statische Aktion in der Entität "Invoice" auf oberster Ebene.
static action CreateInvoiceDocument deep parameter ZBS_S_RAPCreateInvoice;
Eine statische Aktion benötigen wir, da wir ohne Referenz zu einem Datensatz arbeiten wollen. Über den Zusatz DEEP geben wir an, dass es sich um einen tiefen Datentyp handelt. Über STRG + 1 können wir dann die Methode in der Verhaltensimplementierung anlegen lassen. Zum Abschluss erweitern wir die Consumption Sicht, sodass die Aktion in der API zur Verfügung steht.
use action CreateInvoiceDocument;
Web API
Um von außen auf die API zugreifen zu können, legen wir ein Service Binding vom Typ API und OData v4 an.
Nun müssen wir ein Communication Scenario anlegen und die entsprechende Konfiguration im ABAP Environment durchführen. Wie das Schritt für Schritt funktioniert, kannst du in diesem Artikel nachlesen.
Ausführung - ABAP
Starten wir den ersten Aufruf mit EML und probieren die Daten an die Aktion im System zu übergeben. Dazu legen wir eine interne Tabelle für die Aktion an, befüllen diese mit Daten und übergeben sie an das RAP Business Objekt.
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).
Damit ist der erste Test abgeschlossen und wir können den Weg über die API testen.
Ausführung - POSTMAN
Als zweiten Test wollen wir den Endpunkt im System per POSTMAN aufrufen, um so die API und die Übergabe zu testen. Dazu müssen wir zwei Schritte durchführen, einmal einen CSRF Token erstellen (GET) und einmal die Aktion auslösen (POST), wo wir dann auch die Daten übergeben.
Collection
Legen wir dazu in POSTMAN eine neue Collection (Ordner) an, unter dem Reiter "Authorization" stellen wir auf "Basic Authentication" um, damit erhalten wir die Möglichkeit uns per User und Passwort am Endpunkt anzumelden. Damit erhalten alle Requests unter der Collection diese Methode und wir müssen uns nicht mehr um die Anmeldung pro Anfrage kümmern.
Hinweis: Die Eintragung von User und Passwort dient lediglich zur Demonstration, im Normalfall werden solche Informationen in Environment Variablen abgelegt, wo sie auch verschlüsselt werden.
CSRF Token
Bevor wir die Aktion ausführen können, benötigen wir einen Token. Dazu legen wir unter unserer Collection einen neuen GET Request an und verwenden dabei den Endpunkt unseres Services, diese findest du zum Beispiel im Communication Arrangement im ABAP Environment, wo wir das Szenario eingerichtet haben.
Im Header geben wir noch das Feld "x-csrf-token" mit dem Wert "fetch" mit. Damit weiß der Endpunkt, dass wir einen neuen Token von ihm anfordern. Nach dem Ausführen findest du den Token im Antwort-Header im Feld "x-csrf-token".
Aktion
Nun möchten wir die eigentliche Aktion ausführen, dazu benötigen wir zuerst den Endpunkt unserer Aktion aus dem Service. Dieser setzt sich aus verschiedenen Bestandteilen zusammen:
<SERVICE-URL>/<ENTITY>/<NAMESPACE>.<ACTION>
Die Service URL haben wir bereits zuvor verwendet für den Token, es folgt dann die Entität, an der wir die Aktion definiert haben. Den Namespace findest du in den Metadaten des Service. Der hintere Teil der URL würde dann wie folgt aussehen:
/Invoice/com.sap.gateway.srvd_a2x.zbs_rapc_invoice.v0001.CreateInvoiceDocument
Wir erstellen nun einen POST Request und geben im Header den Token sowie den Content-Type mit.
Im Body geben wir unsere Daten mit, die wir an den Endpunkt übergeben wollen, als Format verwenden wir JSON. Hier darauf achten, dass die Zahlen auch wirklich im Zahlenformat übergeben werden. Die Daten werden als Objekt übergeben, wobei die Assoziation "_Position" aus deinem Array besteht, welches wiederum Objekte enthält.
Debugging
Damit wir prüfen können, ob der Aufruf klappt, möchten wir die Daten in der Implementierung debuggen. Dazu müssen wir einen Breakpoint für den technischen User setzen, da dieser den Code ausführt. In den ABAP Development Tools müsste du dazu in die "Properties" des ABAP Projekts gehen. Unter dem Punkt "ABAP Development -> Debug" findest du die Einstellungen, um einen anderen User zu debuggen. Hier kannst du über die Suche den Communication User (CC) finden, der Named-User wird nur für den API Zugriff benötigt.
Führen wir nun die Anfrage aus, dann öffnet sich der Debugger und über die Importvariablen können wir auf die Informationen zugreifen.
Nun kannst du die weitere Implementierung frei übernehmen und nach deinen Wünschen gestalten.
Vollständiges Beispiel
Alle durchgeführten Änderungen an den Objekten und die neuen Objekte findest du im Commit des GitHub Repository, falls du die einzelnen Schritte nachstellen möchtest oder dir noch Informationen fehlen.
Fazit
Die Implementierung einer Aktion mit tiefer Struktur kann sich etwas speziell anfühlen, wenn es um die Definition der abstrakten RAP Entität geht. Die eigentliche Implementierung und der Aufruf befindet sich dann allerdings im RAP Standard.