RAP - Custom Entity Wertehilfe (Deep Dive)
Mit der Custom Entity hast du in RAP die meisten Freiheiten in der Entwicklung von ABAP Cloud Anwendungen, doch wie sieht es mit potentiellen Fehlern aus?
Inhaltsverzeichnis
In diesem Artikel wollen wir eine Wertehilfe mit einer Custom Entity implementieren. Dabei werden wir Schritt für Schritt auf die unterschiedlichen Fallstricke eingehen und was du bei der Entwicklung beachten solltest.
Einleitung
Die Custom Entity haben wir bereits in einem älteren Artikel beschrieben und die Funktionsweise geschildert. Die Custom Entity stellt eine Struktur zur Verfügung und implementiert die Logik über eine ABAP Klasse. Die Entität selbst enthält keine Daten, sondern muss immer erst durch das SADL Framework aufgerufen werden. Deshalb kommen Custom Entitys vor allem in RAP Anwendungen zum Einsatz.
In diesem Beispiel wollen wir unsere einfache RAP Anwendung um eine Suchhilfe für die Städte erweitern und gehen noch einmal alles Schritt für Schritt durch.
Anlage
Im ersten Schritt beginnen wir mit der Anlage des Core Data Services und der ABAP Klasse.
Klasse
Da bei der Anlage der Custom Entity, auch die Klasse in der Annotation geprüft wird, implementieren wir die leere Klasse zuerst. Über das Kontextmenü im "Project Explorer" können wir den Wizard starten.
Dazu legen wir die Klasse an und verwenden dazu das Interface IF_RAP_QUERY_PROVIDER, welches die Custom Entity erwartet.
Die Klasse sieht nun wie folgt aus, hier wurde aber auch der ABAP Cleaner einmal ausgeführt:
CLASS zcl_bs_demo_city_query DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_rap_query_provider.
ENDCLASS.
CLASS zcl_bs_demo_city_query IMPLEMENTATION.
METHOD if_rap_query_provider~select.
ENDMETHOD.
ENDCLASS.
Core Data Service
Im nächsten Schritt legen wir den Core Data Service über das Kontextmenü an. Dabei verwenden wir das CDS Template "defineCustomEntityWithParameters" als Vorlage:
Hast du bei der Anlage das falsche Template gewählt, musst du das Objekt nicht löschen, sondern kannst ein anderes CDS Template wählen. Wir definieren zwei Felder, einmal die Stadt, die wir suchen und einmal einen kurzen Schlüssel, über den wir ebenfalls suchen können. Da wir eingebaute Datentypen verwenden, setzen wir noch das Label und die QuickInfo für den Anwender, damit die Texte in der UI erscheinen.
@EndUserText.label: 'City Value Help'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DEMO_CITY_QUERY'
define custom entity ZBS_I_RAPCityVH
{
@EndUserText.label: 'City'
@EndUserText.quickInfo: 'Name of the City'
key City : abap.char(60);
@EndUserText.label: 'City (Short)'
@EndUserText.quickInfo: 'Short name of the City'
CityShort : abap.char(10);
}
Implementierung
Wir haben nun die Grundelemente angelegt, die wir für die Suchhilfe benötigen. In diesem Abschnitt implementieren wir die nötigen Logiken, um Daten in der UI zu sehen.
Einbindung
Binden wir dazu die Suchhilfe an das Feld im Projection View ZBS_C_RAPPartner, dazu verwenden wir die Annotation "Consumption.valueHelpDefinition":
@Consumption.valueHelpDefinition: [{ entity: { name: 'ZBS_I_RAPCityVH', element: 'City' } }]
City,
Da auch Wertehilfen nach Außen freigegeben werden müssen, solltest du den View in deine Servicedefinition mit aufnehmen. Im Preview funktioniert es meist auch ohne Freigabe, spätestens nach dem Deployment wird der fehlende Zugriff auffallen.
@EndUserText.label: 'Simple Partner Service'
define service ZBS_SIMPLE_PARTNER {
expose ZBS_C_RAPPartner as Partner;
expose ZBS_I_RAPCityVH;
}
Query Klasse
Erweitern wir nun die Query Klasse um die Logik, die wir brauchen um die Wertehilfe einfach verwenden zu können. Dazu benötigen wir eine interne Tabelle mit dem Datentyp der Custom Entity. Diese befüllen wir mit den entsprechenden Daten.
DATA lt_values TYPE STANDARD TABLE OF ZBS_I_RAPCityVH WITH EMPTY KEY.
lt_values = VALUE #( ( City = 'Walldorf' CityShort = 'DE' )
( City = 'Redmond' CityShort = 'US' )
( City = 'Menlo Park' CityShort = 'US' )
( City = 'Hangzhou' CityShort = 'CN' )
( City = 'Munich' CityShort = 'DE' )
( City = 'Vevey' CityShort = 'CH' )
( City = 'Sankt Petersburg' CityShort = 'RU' )
( City = 'Seattle' CityShort = 'US' )
( City = 'Wolfsburg' CityShort = 'DE' )
( City = 'Cologne' CityShort = 'DE' ) ).
Um die Daten zurückzugeben, müssen wir noch die passenden Methoden des REPSONSE aufrufen. Hier ist es wichtig, die Methoden nur aufzurufen, wenn es vom Framework angefordert wird, da es sonst zu einem Fehler kommt. Daher fragen wir zuerst, ob die Felder angefragt wurden und rufen dann die Methode auf. Wichtig ist auch, die beiden Methoden müssen immer implementiert werden und sollten zum Beispiel nicht in einem TRY/CATCH sein, wo sie bei einem Fehler vielleicht nicht aufgerufen werden.
IF io_request->is_data_requested( ).
io_response->set_data( lt_values ).
ENDIF.
IF io_request->is_total_numb_of_rec_requested( ).
io_response->set_total_number_of_records( lines( lt_values ) ).
ENDIF.
Rufen wir jetzt die Wertehilfe auf, erhalten wir leider kein Ergebnis, die Liste bleibt leer.
Das Problem finden wir in den ABAP Development Tools über den "Feed Reader". Im Gateway Log tauchen dazu zwei Fehlermeldungen auf.
Interessant ist hier der "Backend Error", da wir hier weiterführende Informationen zu unserem Problem finden. Wir haben die Methode GET_SORT_ELEMENTS nicht aufgerufen. Dies wird aber weder im Interface noch an der Methode beschrieben.
Neben dieser Methode müssen wir auch die Methode GET_PAGING aufrufen, für die wir einen ähnlichen Fehler erhalten. Der Aufruf der Methoden muss innerhalb des Methodenaufrufs implementiert werden.
io_request->get_sort_elements( ).
io_request->get_paging( ).
Führen wir noch einmal die Suchhilfe aus, werden die Daten angezeigt und wir können Einträge für den Filter übernehmen. Damit ist ein sehr einfache Suchhilfe ohne viel Zusatz und als "Festwerthilfe" definiert.
Erweiterung
Die Suchhilfe funktioniert, allerdings keine zusätzlichen Funktionen wie Filterung, Sortierung oder Paging, welches von außen an uns übergeben wird. In diesem Abschnitt werden wir auch noch diese grundsätzlichen Funktionen implementieren.
Classic ABAP
Im klassischen ABAP gibt es die Hilfeklasse /IWBEP/CL_MGW_DATA_UTIL, diese besitzt so weit alle Methoden um Daten zu filtern. Diese ist allerdings in ABAP Cloud und im ABAP Environment nicht verfügbar.
Kopie
Wenn du die Klasse zuvor verwendest, musst du allerdings auch die RAP Objekte und Strukturen zuerst in die Gateway Strukturen konvertieren. Damit wir auch in der Cloud mit dieser Funktion arbeiten können, kopieren wir die Gateway-Klasse und führen ein Refactoring durch. Schauen wir uns dazu die Methode ORDERBY an.
DATA: lt_otab TYPE abap_sortorder_tab,
ls_oline TYPE abap_sortorder.
DATA: ls_order LIKE LINE OF it_order.
LOOP AT it_order INTO ls_order.
ls_oline-name = ls_order-property.
TRANSLATE ls_oline-name TO UPPER CASE.
IF ls_order-order = gcs_sorting_order-descending.
ls_oline-descending = 'X'.
ENDIF.
APPEND ls_oline TO lt_otab.
CLEAR ls_oline.
ENDLOOP.
SORT ct_data BY (lt_otab).
Nach dem Vergleich der Zielstruktur und der Verwendung von Inline-Deklaration, sowie Modern ABAP, verkleinert sich die Logik auf zwei Zeilen:
DATA(lt_sort) = CORRESPONDING abap_sortorder_tab( it_sort MAPPING name = element_name ).
SORT ct_data BY (lt_sort).
Die vollständige Logik der Klasse findest du im Commit weiter unten oder direkt im GitHub Repository auf dem aktuellsten Stand.
Request
Nun haben wir drei Methoden und ein Request Objekt, dazu erstellen wir noch eine Methode, die die Arbeit übernimmt, alle nötigen Schritte durchzuführen und die Methoden in der richtigen Reihenfolge aufzurufen. Dazu lesen wir zuerst alle nötigen Einschränkungen aus dem REQUEST Objekt, wie die Sortierung, die Pagination und die Filter
DATA(lt_sort) = io_request->get_sort_elements( ).
DATA(ld_offset) = io_request->get_paging( )->get_offset( ).
DATA(ld_page_size) = io_request->get_paging( )->get_page_size( ).
TRY.
DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ).
CATCH cx_rap_query_filter_no_range.
CLEAR lt_filter.
ENDTRY.
Dann rufen wir die Methoden in der richtigen Reihenfolge auf. Die Pagination sollte zum Schluss erfolgen, da wir zuerst einmal die richtige Reihenfolge und den Filter benötigen, bevor wir dann einen Ausschnitt der Daten erzeugen können.
filter_data( EXPORTING it_filter = lt_filter
CHANGING ct_data = ct_data ).
order_data( EXPORTING it_sort = lt_sort
CHANGING ct_data = ct_data ).
page_data( EXPORTING id_offset = ld_offset
id_page_size = ld_page_size
CHANGING ct_data = ct_data ).
Als letzten Schritt müssen wir noch die Logik in unserer Query Implementierung aufrufen. Dazu merken wir uns zuerst die Anzahl aller vorhandenen Datensätze, bevor wird die Daten verändern.
DATA(ld_all_entries) = lines( lt_values ).
NEW zcl_bs_demo_adjust_data( )->adjust_via_request( EXPORTING io_request = io_request
CHANGING ct_data = lt_values ).
Ergebnis
Damit haben wir die Grundfunktionen der Suchhilfe implementiert und können die Klasse wiederverwenden. Hier einmal eine kurze Demo der Funktion FILTER und SORT in der Suchhilfe.
Zusammenfassung
Hier noch einmal eine kurze Zusammenfassung des Artikels und der wichtigsten Learnings aus der Implementierung.
- Schnelle Umsetzung - Die Umsetzung einer einfachen Suchhilfe ohne viel Zusatz ist mit zwei Objekten und zwei Anpassungen durchgeführt.
- Service - Aufnahme der Entität in die Servicedefinition, da sonst später im Test die Suchhilfe nicht aufgerufen werden kann.
- Versteckte Pflichtfelder - Nicht ganz so durchschaubar sind die eigentlichen "Pflichten" bei der Implementierung einer Custom Entity Query. Hier solltest du darauf achten bei Request und Response die entsprechenden Methoden zu rufen.
- Zusatzfunktionen - Die Implementierung wie Filterung, Sortieren und Paging bedeutet viel manueller Aufwand in der Entwicklung, bei Core Data Services wird bereits alles geschenkt.
Beispiel
Alle Anpassungen findest du als Commit in unserem GitHub Repository und kannst dir dort die gemachten Änderungen und neuen Objekte noch einmal im Detail anschauen.
Fazit
In diesem Artikel hast du gelernt die Custom Entity auch für eigene Suchhilfen zu verwenden und welche Fallstricke bei der Implementierung auf dich warten können. Das sollte dich allerdings nicht davor abschrecken, das Objekt zu verwenden, da es viele Flexibilitäten gewährt.