REST Services in ABAP

Im Rahmen der SAPUI5 Entwicklung wird auf das REST basierte OData Protokoll gesetzt. Dass es auch möglich ist REST Services in ABAP nativ umzusetzen, ist unserer Erfahrung nach nicht so bekannt. Du wolltest schon immer wissen wie das eigentlich geht? Dann bist du hier richtig, denn wir wollen heute versuchen die OData Funktionalitäten CRUDQ (CREATE, READ, UPDATE, DELETE, QUERY) mit einem nativen REST Service zu implementieren – los geht’s!

Von Andreas Fischer & Paul Holst

 

REST steht erst einmal für Representational State Transfer und hat den Anspruch mit einer einheitlichen Schnittstelle über bereits vorhandene WWW Architektur eine vereinfachte Mashine-2-Mashine-Communication zu ermöglichen. REST selbst ist dabei weder Protokoll noch Standard und wird auch dadurch einzigartig, dass über die angesprochenen URIs keine Methodeninformationen übergeben werden (müssen), sondern nur eine Ressource. SAP liefert bereits die benötigten Grundstrukturen mit, die einfach implementiert werden können um REST Services zu erstellen.

Ziel unseres REST-Service ist es mithilfe von HTTP-Requests die CRUDQ Funktionalitäten für Salesorder Header und die zugehörigen Items anzuwenden.

 

REST Services in ABAP: Klassenmodell

Grundsätzlich besteht unser REST-Service aus zwei zusammenhängenden Klassen. Auf der einen Seite haben wir die Application-Class (ZCL_PH_REST_EPM_APP) auf der anderen Seite die Ressource-Class (ZCL_PH_REST_EPM_RES), welche wir für diesen hier als lokale Objekte illustrativ angelegt haben. Die Resource Klasse regelt dabei die zur Verfügung zu stellenden Datenquellen und DB-Operations, welche wiederum aus der Application-Klasse aufgerufen werden.

Man kann sich das Ganze auch etwas bildlicher vorstellen: Tun wir mal so, als sei der der Ablauf unseres REST Service eine Pizzabestellung. Statt umständlich loszufahren und an der Theke eine Pizza zu ordern und diese wieder mit nach Hause zu nehmen, ist es auch möglich den Online Weg zu nehmen. Als Kunde sind wir der Client, der eine Anfrage losschickt. Das tun wir zum Beispiel über das Portal, welches unser Lieferdienst (in unserem Beispiel der Server) zur Verfügung stellt. Unsere ZCL_PH_REST_EPM_APP Klasse fungiert (hat nichts mit Pizza Fungi zu tun) genau wie dieses Portal als Schablone für die Bestellung einer definierten Ressource. Danach wird diese Anfrage entgegengenommen und vom Personal des Ladens ZCL_PH_REST_EPM_RES bearbeitet, bis schließlich das Ergebnis, unsere Pizza bzw. unsere angeforderten Daten zu uns zurückgeschickt werden.

Die Application-Klasse konstruiert also den Rahmen und legt fest über welche URI welche Handler Klasse angesprochen werden soll. Die Handler Klassen sind hierbei als lokale Klassen der Resource Klasse implementiert.

 

Application Klasse: Registrierung von URIs und Handler Klassen

Die Application-Klasse erbt von der von SAP zur Verfügung gestellten Klasse CL_REST_HTTP_HANDLER. Diese Klasse ermöglicht es uns die Erstellung eines REST-Services für HTTP-Anwendungen (wie man dem Namen unschwer entnehmen kann).

Zunächst haben wir die Tabelle (mt_Ressource_metadata) auf der Basis des Strukturtypen TS_RESOURCE_METADATA angelegt. Die Tabelle dient zum einen zur eindeutigen Definition / Bestimmung der URI und zum anderen der Angabe der genutzten lokalen Handler-Klasse. Im CONSTRUCTOR beginnt die Initialisierung der angesprochenen Tabelle.

Der erste Datensatz wird beispielsweise für unsere GET Methode benötigt. Der Platzhalter {header_key} im Template dient uns – wie wir später sehen – zum Anlegen der eindeutigen URI gemäß des Angesprochenen Node_Key aus dem Sales Order Header bzw. der DB Tabelle snwd_so. Die spezifische Handler Klasse wird in der Resource Metadata dem jeweiligen URI Template zugeordnet. Der CONSTRUCTOR kann dabei natürlich durch beliebig vielen URI-Patterns erweitert werden. Möchtest du beispielsweise statt der der Header-Daten die ITEMS einer Sales-Order ansprechen, so müsstest du hier ein neues Template mit dazugehöriger Handler-Klasse definieren.

 

In der redefinierten Methode if_rest_application~get_root_handler wird unsere Ressource-Klasse angesprochen.

 

Resource-Klasse

In der Resource-Class spielt die eigentliche Musik. Wie wir sehen, erbt die Klasse von der SAP-integrierten abstrakten Klasse CL_REST_RESOURCE. Abstrakte Klassen lassen sich nicht instanziieren und dienen somit als Strukturelement für die jeweiligen Unterklassen. CL_REST_RESOURCE stellt u.a. die Basismethoden fürs GET, POST, PUT und DELETE zur Verfügung, welche innerhalb unserer Unterklasse ZCL_PH_REST_EPM_RES gemäß unseres Verwendungsbeispiels redefiniert werden.

Helper Klasse

Die LCL_HELPER Klasse beinhaltet die Definition der beiden Funktionen, welche die Daten zur Übertragung in JSON (de-)serialisieren. Diesen geben wir zur Implementierung im Wesentlichen Parameter mit.

Handler

Die Handler Klassen handeln den eigentlichen Teil der Datenbankzugriffe ab. Dabei ist die LCL_BASE_HANDLER Klasse der Grundstein für die Item- bzw. Header Handler Klassen.

Base Handler

LCL_BASE_HANDLER Klasse bildet den Rahmen für die Request Parameter des REST Interfaces und nimmt diese auf. Des Weiteren instanziiert sie die ein Objekt der LCL_HELPER Klasse zwecks JSON Serialisierung und Deserialisierung und behandelt potenzielle Exceptions.

Der LCL_BASE_HANDLER bildet die Basis für unsere expliziten Handlerklassen LCL_Header_Handler und LCL_ITEM_HANDLER.

Bevor wir in unsere expliziten Handler-Klassen einsteigen, ist es noch wichtig zu erwähnen, dass die Datenbank typischen CRUDQ Operationen mit den http-Methoden GET, POST, PUT, DELETE zugeordnet werden.

Die Methode get_uri_attributes im LCL_BASE_HANDLER liest die Attribute der URIs und macht diese für die weitere Verwendung innerhalb der Struktur verfügbar. Dies ist elementar, wenn wir über einen Schlüssel einen eindeutigen Datensatz ansprechen, wie es beim READ und DELETE der Fall ist.

Die Methode get_url_param dient zur Identifikation von Schlüsselwörtern innerhalb der URI zum Filtern gewünschter Ergebnisse und wird vorrangig für die QUERY-Anwendungen benötigt. Die Ergebnisse werden in der Struktur rs_url_param gespeichert und können so nach Belieben in den expliziten Handler-Klassen verarbeitet werden.

Für die Query der Handler reichen diese Methoden natürlich noch nicht aus. Wir müssen ebenfalls identifizieren ob und wenn ja welche Operatoren einem Filter zugeordnet sind. Z.B. benötigen wir dies, wenn wir alle Salesorder-Header haben wollen, die in einer bestimmten Zeile einen Wert überschreiten oder unterschreiten.

Natürlich gibt es noch diverse weitere Operatoren, die ihr ja sicherlich alle kennt. Ansonsten schaut einfach nochmal hier rein:

https://help.sap.com/doc/abapdocu_750_index_htm/7.50/en-US/abenregex_syntax_specials.htm

 

Andere  Handler

Die lokalen Klasse LCL_HEADER_HANDLER und LCL_ITEM_HANDLER erben vom LCL_BASE_HANDLER und redefinieren die Datenbanktypischen CRUDQ Methoden für die tatsächliche Anwendung.

Bei den GET-Operationen der Methoden READ und QUERY arbeiten wir mit einfachen SELECT–Anweisung. Die mr_helper-Klasse serialisiert im Folgenden den zurückgegebenen Datensatz. Bei den anderen drei Methoden folgt am Anfang eine Deserialisierung der Response, gefolgt von den Anweisungen INSERT (POST), UPDATE (PUT) und DELETE (DELETE). Am Ende steht wieder die Serialisierung der Daten.

 

Interface / Function Import

Dass über REST nicht nur einfache CRUDQ Funktionalität sondern auch komplette Function-Imports möglich sind hast du vielleicht schon einmal gehört – jetzt wollen wir dir zum Schluss noch zeigen wie du das auch in die Tat umsetzen kannst. Für unsere Übung haben wir die Änderung des Lifecycle Status Feldes im Sales Order Header im Visier.

Zunächst erstellen wir uns in der Resource-Klasse ein lokales Interface.

Dieses Interface erfüllt einen ähnlichen Zweck wie unser BASE Handler bei den CRUDQ Anweisungen und nimmt unseren Request generell entgegen…

…um sie dann auf Basis des Templates im Constructor() der APPLICATION  Klasse zur passenden Funktion zu leiten die wir für diesen Zweck implementiert haben:

 

SICF

Um den Service auch von außen erreichbar zu machen musst du ihn im SAP erst einmal registrieren. Dafür startest du die Transaktion SICF (System Internet Communication Framework) und rufst die Übersicht “Pflege der Services” auf. Dort legen wir unter default_host/sap/bc/rest unseren Service an, damit dieser als HTTP Endpunkt zur Verfügung steht.

Das sieht dann folgendermaßen aus:

 

Output & Unit Tests

Als Beispiel ist im Folgenden einmal das Resultat unserer GET-Methode aufgeführt.

In der oberen Abbildung siehst du einen Ausschnitt der erwähnten Datenbank Tabelle snwd_so. Der Node_Key ist Bestandteil der DB-Tabelle und eine von SAP erstellte GUID und dient dir zur eindeutigen Identifikation von Datensätzen. Deshalb verwendest du ihn hier auch als Paramter für die URI. In der folgenden Abbildung siehst du das Resultat unserer GET-Methode, zum Aufruf des gewünschten Datensatzes im Browser, in JSON:

Um alle Anwendungsfälle auch richtig und vor allem persistent durchtesten zu können solltest du noch entsprechende Unit Tests erstellen – so stellst du sicher, dass auch potenzielle Änderungen in den Handlern keine bösen Überraschungen bergen. Dafür kannst du in der APPLICATION Klasse eine lokale Testklasse anlegen und in dieser die Testmethoden definieren und implementieren. Am Beispiel der GET Methode oben könnte eine Implementierung etwa so aussehen:

 

Und damit sind wir auch durch! Wir hoffen, dass wir dir einen guten Einblick verschaffen konnten wie du REST Services in ABAP erstellen kannst. Obwohl CRUDQ und Function Imports mit diesem Ansatz maßgeschneidert und recht komfortabel möglich sind, ist native REST Services in ABAP allerdings keine vollständige Alternative zum klassischen OData Ansatz. Eine Serialisierung in atom bzw. xml ist hier nämlich nicht möglich.

Wenn du noch Fragen zum Thema hast schreib uns einfach – wir freuen uns über eure Nachrichten.

Über den Autor

Andreas Fischer

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

27 − 23 =