This is a test message to test the length of the message box.
Login
ABAP Tipp Generische Query Implementierung
Erstellt von Software-Heroes

ABAP Tipp - Generische Query Implementierung

206

Die immer wieder gleiche Implementierung in Query Klassen für Custom Entities in RAP? Es wird mal Zeit für eine wiederverwendebare Komponente.

Werbung


In diesem Artikel schauen wir uns eine Implementierung an, um über die Custom Entity eine wiederverwendbare Abfrage zu starten und einfach an Daten aus einem OData zu kommen, egal auf welchem System die Daten liegen.

 

Einleitung

Über eine Custom Entity besteht eine einfache Möglichkeit in einem System eine Wertehilfe für ein anderes System zu erstellen. Dabei werden zur Laufzeit die Daten aus einem Remote System gelesen und dem Anwender zur Verfügung gestellt. Damit lassen sich sehr einfach Remote Szenarien umsetzen. allerdings ist die Implementierung des Queries recht aufwändig, vor allem wenn du es immer wieder machen musst.

 

Query

Die Query Klasse wird im Core Data Service über die Annotation "@ObjectModel.query.implementedBy: 'ABAP:xxx'" definiert und zur Laufzeit wird im SADL Framework die Implementierung aufgerufen. Dabei müssen wir die Methode SELECT des Interfaces IF_RAP_QUERY_PROVIDER implementieren.

 

Neben der eigentlichen Methode müssen von dem Objekt IO_REQUEST einige Methoden (GET_SORT_ELEMENTS, GET_PAGING) aufgerufen werden, da es sonst zu einem Dump kommt. Weitere Infos dazu findest du im Deep Dive zu Custom Entities.

 

Konzept

In diesem Artikel planen wir eine generische Implementierung der Abfrage in verschiedenen Konstellationen, um die Funktionen bei weiteren Implementierungen und Services einfach wiederverwenden zu können. Damit solltest du dann bei den weiteren Projekten an der Implementierung sparen.

 

Architektur

Für die Architektur verwenden wir das Factory Pattern, um das Thema Entkopplung und Testbarkeit mit abzudecken. Zusätzlich benötigen wir noch eine Ausnahmeklasse, wenn bestimmte Schritte nicht umgesetzt werden können oder es zu Abbrüchen in der Abfrage kommt.

 

Da so eine Komponente normalerweise übergreifend entwickelt wird und wir uns in ABAP Cloud mit dem Konzept der Software Komponenten beschäftigen müssen, sollten wir das Thema Entkopplung und Verwendung über SWC berücksichtigen.

 

Konfiguration

Damit wir eine Konfiguration für den Zugriff haben, definieren wir uns diese als Struktur im Interface. Der Vorteil einer Struktur ist, dass wir immer nur einmal Daten durch die Klasse schleusen müssen, ohne jeden einzelnen Parameter zu definieren. Damit ist die Schnittstelle jederzeit erweiterbar, ohne dass wir viel Code anpassen müssen. 

TYPES property TYPE STANDARD TABLE OF if_com_arrangement_factory=>ty_query_param_prop WITH EMPTY KEY.

TYPES:
  BEGIN OF arrangement,
    comm_scenario  TYPE if_com_management=>ty_cscn_id,
    service_id     TYPE if_com_management=>ty_cscn_outb_srv_id,
    comm_system_id TYPE if_com_management=>ty_cs_id,
    property       TYPE property,
  END OF arrangement.

TYPES: BEGIN OF configuration,
         arrangement       TYPE arrangement,
         cloud_destination TYPE string,
         consumption_model TYPE /iwbep/if_cp_runtime_types=>ty_proxy_model_id,
         service_root      TYPE string,
         client            TYPE string,
         language          TYPE sy-langu,
         protocol          TYPE protocol_intern,
       END OF configuration.

 

Grundsätzlich kannst du dich für ein Objekt (Klasse/Interface) oder eine Struktur entscheiden. Hier solltest du einfach für dich entscheiden, benötigst du auch noch so etwas wie eine Validierung oder zusätzliche Methoden. Im einfachen Fall reicht auch eine Struktur, um die Daten zu transportieren.

 

Methoden

Bei der Implementierung entscheiden wir uns für drei Methoden, die komplette Verarbeitung der Daten mit Request und Response, die Bearbeitung per Request und die manuelle Übergabe aller Werte. So sind wir später am flexibelsten und können steuern, welche Bestandteile wir an die Komponente abgeben. In jedem Fall müssen wir die Daten per CHANGING Parameter weitergeben, damit die Information durch unseren Code geschleust wird.

METHODS read_odata_by_values
  IMPORTING setting       TYPE setting_by_value
  CHANGING  business_data TYPE ANY TABLE
            !count        TYPE count
  RAISING   zcx_bs_demo_provider_error.

METHODS read_odata_by_request
  IMPORTING setting       TYPE setting_by_request
  CHANGING  business_data TYPE ANY TABLE
            !count        TYPE count
  RAISING   zcx_bs_demo_provider_error.

METHODS read_odata_with_response
  IMPORTING setting       TYPE setting_with_response
  CHANGING  business_data TYPE ANY TABLE
  RAISING   zcx_bs_demo_provider_error.

 

Implementierung

In diesem Kapitel schauen wir uns nun die Implementierung der verschiedenen Szenarien an und welche Methode welche Aufgabe hat.

 

Hierarchie

Im Grunde rufen sich die Methoden gegenseitig in einer Hierarchie auf und verfolgen spezifische Aufgaben. Je nachdem wieviel du von den folgenden Schritten abgeben möchtest, startest du mit einer anderen Methode:

  • READ_ODATA_WITH_RESPONSE - Ruft die nächste Methode auf und befüllt das Ergebnis direkt in den Repsonse.
  • READ_ODATA_BY_REQUEST - Entpackt das Request Objekt, löscht nicht vorhandene Felder und ergänzt benötigte Felder. Definiert außerdem die Pagination für die Anfrage.
  • READ_ODATA_BY_VALUES - Führt die finale Abfrage durch, es wird der Client erzeugt und der HTTP Request vorbereitet. Das Ergebnis wird dann in die Variablen geschrieben.

 

Destination

Im ersten Schritt benötigen wir eine Destination für die Abfrage. Über die Konfiguration hat der User aktuell die Möglichkeit eine Cloud Destination (Destination Service) oder ein Communication Arrangement mitzugeben.

IF configuration-arrangement IS NOT INITIAL.
  result = cl_http_destination_provider=>create_by_comm_arrangement(
      comm_scenario  = configuration-arrangement-comm_scenario
      service_id     = configuration-arrangement-service_id
      comm_system_id = determine_communication_system( ) ).

ELSEIF configuration-cloud_destination IS NOT INITIAL.
  result = cl_http_destination_provider=>create_by_cloud_destination(
      i_name       = configuration-cloud_destination
      i_authn_mode = if_a4c_cp_service=>service_specific ).

ENDIF.

 

Beim Communication Arrangement können wir theoretisch über die "Additional Properties" mehrere Communication Systems hinterlegen. Dazu implementieren wir noch eine Ermittlung des richtigen Systems über die Standard API.

DATA(query) = VALUE if_com_arrangement_factory=>ty_query(
    cscn_id_range = VALUE #( ( sign = 'I' option = 'EQ' low = configuration-arrangement-comm_scenario ) )
    ca_property   = configuration-arrangement-property ).

DATA(arrangement_factory) = cl_com_arrangement_factory=>create_instance( ).
arrangement_factory->query_ca( EXPORTING is_query           = query
                               IMPORTING et_com_arrangement = DATA(systems) ).

RETURN systems[ 1 ]->get_comm_system_id( ).

 

Client

Im nächsten Schritt erzeugen wir uns den passenden Client für unsere Abfrage. Über die Konfiguration erzeugen wir uns das passende Objekt für OData v2 oder v4. Über die Konstanten könnten wir auch noch andere Clients implementieren, wenn wir sie bräuchten.

DATA(http_client) = cl_web_http_client_manager=>create_by_http_destination( destination ).

CASE configuration-protocol.
  WHEN zif_bs_demo_service_prov=>protocol-odata_v2.
    result = /iwbep/cl_cp_factory_remote=>create_v2_remote_proxy(
        is_proxy_model_key       = VALUE #( repository_id       = 'DEFAULT'
                                            proxy_model_id      = configuration-consumption_model
                                            proxy_model_version = '0001' )
        io_http_client           = http_client
        iv_relative_service_root = configuration-service_root ).

  WHEN zif_bs_demo_service_prov=>protocol-odata_v4.
    result = /iwbep/cl_cp_factory_remote=>create_v4_remote_proxy(
        is_proxy_model_key       = VALUE #( repository_id       = 'DEFAULT'
                                            proxy_model_id      = configuration-consumption_model
                                            proxy_model_version = '0001' )
        io_http_client           = http_client
        iv_relative_service_root = configuration-service_root ).

ENDCASE.

 

Wir sollten nun alle Objekte haben, um mit der Implementierung der Methoden zu beginnen.

 

READ_ODATA_WITH_RESPONSE

Die Methode ruft die Folgemethode READ_ODATA_BY_REQUEST auf und übergibt das Ergebnis dann direkt an den Response. Damit nimmt uns die Methode die Zuweisung ab, da wir zuvor prüfen müssen, ob die Daten auch vom Request angefordert wurden.

IF setting-request->is_total_numb_of_rec_requested( ).
  setting-response->set_total_number_of_records( local_count ).
ENDIF.

IF setting-request->is_data_requested( ).
  setting-response->set_data( business_data ).
ENDIF.

 

READ_ODATA_BY_REQUEST

Im ersten Schritt zerlegen wir den Request und holen uns alle Informationen für unsere Abfrage, dazu befüllen wir die Struktur für den nächsten Schritt.

DATA(local_setting) = CORRESPONDING zif_bs_demo_service_prov=>setting_by_value( setting ).
local_setting-filter_condition   = setting-request->get_filter( )->get_as_ranges( ).
local_setting-requested_elements = setting-request->get_requested_elements( ).
local_setting-sort_order         = setting-request->get_sort_elements( ).
local_setting-is_data_requested  = setting-request->is_data_requested( ).
local_setting-is_count_requested = setting-request->is_total_numb_of_rec_requested( ).

 

Bevor wir die Folgemethode READ_ODATA_BY_VALUES aufrufen, passen wir noch die entsprechenden Felder an, die in der Anfrage nicht mehr benötigt werden. Dazu ergänzen wir noch Felder, die wir vielleicht für andere Ableitungen benötigt.

LOOP AT setting-delete_fields REFERENCE INTO DATA(field_for_deletion).
  DELETE local_setting-filter_condition WHERE name = field_for_deletion->*.
  DELETE local_setting-requested_elements WHERE table_line = field_for_deletion->*.
  DELETE local_setting-sort_order WHERE element_name = field_for_deletion->*.
ENDLOOP.

LOOP AT setting-read_fields REFERENCE INTO DATA(field_to_read).
  INSERT field_to_read->* INTO TABLE local_setting-requested_elements.
ENDLOOP.

 

Unsere Custom Entity hält vielleicht auch virtuelle Felder oder Felder, die auf einem anderen Weg ermittelt werden. Wenn der Anwender in der App die Felder filtert, einblendet oder sortiert, werden sie in den Request aufgenommen. Wenn wir den Request gegen unseren OData Service durchführen, würde diese Felder zu einem Fehler führen.

 

READ_ODATA_BY_VALUES

Die letzte Methode baut den eigentlichen HTTP Request auf und ruft unsere Daten ab. Dazu erzeugen wir uns eine Destination und einen OData Request.

DATA(odata_client) = create_client( ).
DATA(odata_request) = odata_client->create_resource_for_entity_set( setting-entity_name )->create_request_for_read( ).

 

Im nächsten Schritt bereiten wir die Anfrage vor und ergänzen den Filter, die Elemente, weitere Optionen und Einschränkungen. Bei den zusätzlichen Optionen setzen wir den Mandanten und die Sprache als zusätzliche Option, wenn das in den Einstellungen gewünscht ist.

set_filter_for_request( odata_request = odata_request
                        setting       = setting ).
set_elements_for_request( odata_request = odata_request
                          setting       = setting ).
set_options_for_request( odata_request = odata_request
                         setting       = setting ).
set_query_options_for_request( odata_request ).

 

Sind wir damit fertig, können wir die Anfrage ausführen und weisen das Ergebnis den Variablen zu.

DATA(odata_response) = odata_request->execute( ).
IF setting-is_data_requested = abap_true.
  odata_response->get_business_data( IMPORTING et_business_data = business_data ).
ENDIF.
IF setting-is_count_requested = abap_true.
  count = odata_response->get_count( ).
ENDIF.

 

Damit ist die Grundlage für die Verwendung gelegt und in einem der nächsten Artikel wird die Klasse zum Einsatz kommen.

 

Komplettes Beispiel

Alle Objekte findest du bei uns im GitHub Repository im Paket ZBS_DEMO_RAP_UTILITY. Der oben gezeigte Quellcode zeigt immer nur Ausschnitte der Implementierung. Grundsätzlich kannst du andere Anforderungen haben und solltest die Implementierung entsprechend für dich ändern. Zu 99% reicht uns die aktuelle Implementierung für Leseszenarien.

 

Fazit

Eine generische Implementierung zum Lesen von Remote Daten zu haben, spart viel Zeit und Entwicklungsarbeit. Die Besonderheit im Zusammenhang mit Software Komponenten solltest du allerdings beachten.


Enthaltene Themen:
TippCustom EntityQuery
Kommentare (0)



Und weiter ...

Bist du zufrieden mit dem Inhalt des Artikels? Wir posten jeden Dienstag und Freitag neuen Content im Bereich ABAP und unregelmäßig in allen anderen Bereichen. Schaue bei unseren Tools und Apps vorbei, diese stellen wir kostenlos zur Verfügung.


ABAP Tipp - Generische Datentypen

Kategorie - ABAP

Was unterscheidet eigentlich CLIKE von CSEQUENCE? Generische Typen können manchmal etwas undurchsichtig sein und als ABAP Entwickler wählen wir vielleicht den Typen zu generisch.

12.08.2025

Recycling-Heroes (Erklärt)

Kategorie - ABAP

Was haben die Recycling-Heroes mit moderner ABAP Entwicklung und ABAP Cloud zu tun? In diesem Artikel geben wir Einblicke in die Idee.

15.07.2025

ABAP Tipp - Ranges und Select-Options

Kategorie - ABAP

Ranges und Select-Options in ABAP sind sehr ähnlich und doch gibt es feine Unterschiede bei der Nutzung im ABAP OO Kontext. Hier schauen wir uns die moderne Verwendung an.

09.05.2025

ABAP in Praxis - String Verarbeitung

Kategorie - ABAP

In diesem praktischen Beispiel schauen wir uns die String Verarbeitung zur Ermittlung der CDS Namen in CamelCase an und wie du das mit ABAP umsetzen kannst.

15.10.2024

ABAP in der Praxis - Test Driven Development

Kategorie - ABAP

Wie funktioniert eigentlich TDD in der Praxis und gibt es einfache Beispiele zum Lernen in ABAP? In dieser Übung gehen wir auf den praktischen Teil ein.

24.09.2024