This is a test message to test the length of the message box.
Login
ABAP RAP Unmanaged
Erstellt von Software-Heroes

RAP - Unmanaged (Lokal)

213

In diesem Artikel ein Beispiel zur Implementierung eines Unmanaged RAP Objekts auf Basis einer lokal vorhandenen API.

Werbung


Im letzten Aritkel hatten wir beschrieben, wie das Unmanaged Szenario für das ABAP RESTful Programming Model in der Theorie funktioniert. In diesem Artikel schauen wir uns einmal eine Implementierung an, die auf Objekten basiert, die bereits im System implementiert wurden und uns als lokale APIs dienen.

 

Lokale API

Um die lokale API zu verstehen hier eine kurze Erklärung der Objekte und er Implementierungen die vorhanden sind. Es gibt bereits ein Datenmodell im System, welches auf der Tabelle ZBS_DMO_UN_DATA basiert und eine entsprechende DAO-Klasse (Data Access Object) besitzt.

 

Für die Klasse gibt es das Interface ZIF_BS_DEMO_RAP_DATA_HANDLER, welches die CRUDQ Methoden und Datentypen zur Verfügung stellt:

INTERFACE zif_bs_demo_rap_data_handler
  PUBLIC.

  TYPES:
    tt_r_key  TYPE RANGE OF zbs_dmo_un_data-gen_key,
    tt_r_text TYPE RANGE OF zbs_dmo_un_data-text,
    tt_r_date TYPE RANGE OF zbs_dmo_un_data-cdate,

    ts_data   TYPE zbs_dmo_un_data,
    tt_data   TYPE STANDARD TABLE OF ts_data WITH EMPTY KEY.

  METHODS:
    query
      IMPORTING it_r_key         TYPE tt_r_key  OPTIONAL
                it_r_text        TYPE tt_r_text OPTIONAL
                it_r_date        TYPE tt_r_date OPTIONAL
      RETURNING VALUE(rt_result) TYPE tt_data,

    read
      IMPORTING id_key           TYPE zbs_dmo_unmgnd-gen_key
      RETURNING VALUE(rs_result) TYPE ts_data,

    modify
      IMPORTING is_data          TYPE ts_data
      RETURNING VALUE(rd_result) TYPE abap_boolean,

    delete
      IMPORTING id_key           TYPE zbs_dmo_unmgnd-gen_key
      RETURNING VALUE(rd_result) TYPE abap_boolean.
ENDINTERFACE.

 

Dazu dann noch die folgende Implementierung der Klasse ZCL_BS_DEMO_RAP_DATA_HANDLER, welches die Implementierung und Logik zur Verfügung stellt:

CLASS zcl_bs_demo_rap_data_handler DEFINITION
  PUBLIC
  FINAL
  CREATE PRIVATE.

  PUBLIC SECTION.
    INTERFACES zif_bs_demo_rap_data_handler.

    CLASS-METHODS create_instance
      RETURNING VALUE(ro_result) TYPE REF TO zif_bs_demo_rap_data_handler.
ENDCLASS.


CLASS zcl_bs_demo_rap_data_handler IMPLEMENTATION.
  METHOD create_instance.
    ro_result = NEW zcl_bs_demo_rap_data_handler( ).
  ENDMETHOD.


  METHOD zif_bs_demo_rap_data_handler~delete.
    DELETE FROM zbs_dmo_un_data WHERE gen_key = @id_key.

    rd_result = xsdbool( sy-subrc = 0 ).
  ENDMETHOD.


  METHOD zif_bs_demo_rap_data_handler~modify.
    DATA(ls_data) = is_data.
    GET TIME STAMP FIELD ls_data-last_changed.

    IF ls_data-gen_key IS INITIAL.
      TRY.
          ls_data-gen_key = cl_system_uuid=>create_uuid_x16_static( ).

          IF ls_data-cdate IS INITIAL.
            ls_data-cdate = cl_abap_context_info=>get_system_date( ).
          ENDIF.

        CATCH cx_uuid_error.
          rd_result = abap_false.
          RETURN.
      ENDTRY.

      INSERT zbs_dmo_un_data FROM @ls_data.

    ELSE.
      UPDATE zbs_dmo_un_data FROM @ls_data.

    ENDIF.

    rd_result = xsdbool( sy-subrc = 0 ).
  ENDMETHOD.


  METHOD zif_bs_demo_rap_data_handler~query.
    SELECT FROM zbs_dmo_un_data
      FIELDS *
      WHERE gen_key IN @it_r_key
        AND text IN @it_r_text
        AND cdate IN @it_r_date
      INTO TABLE @rt_result.
  ENDMETHOD.


  METHOD zif_bs_demo_rap_data_handler~read.
    SELECT SINGLE FROM zbs_dmo_un_data
      FIELDS *
      WHERE gen_key = @id_key
      INTO @rs_result.
  ENDMETHOD.
ENDCLASS.

 

Dabei ergibt sich grob das folgende Bild, wenn wir auf die vorhandenen Objekte schauen:

 

 

Aufbau

Der Aufbau beschreibt den groben Aufbau der Objekte und Abhängigkeiten untereinander. Dabei werden wir allerdings nicht in die einzelnen Details eingehen, sondern verweisen hier auf die Grundlagen von RAP, wenn du die Objekte und Strukturen verstehen möchtest.

 

Tabelle

Der Aufbau unseres Unmanaged Objekts basiert auf einer "Dummy" Tabelle, die wir für die Modellierung verwenden. Diese Tabelle ist nicht 1:1 mit der datenhaltenden Tabelle vergleichbar, sondern umfasst nur die Felder, die wir für unser RAP Objekt benötigen:

@EndUserText.label : 'Unmanaged Data'
@AbapCatalog.tableCategory : #TRANSPARENT
define table zbs_dmo_unmgnd {
  key client  : abap.clnt not null;
  key gen_key : sysuuid_x16 not null;
  text        : abap.char(50);
  cdate       : abap.dats;
}

 

CDS Views

Entsprechend setzen wir auf die Tabelle einen Root View, der die Felder der Tabelle aufnimmt und zur Verfügung stellt. Auf diesen setzen wir den entsprechenden Projektions-View, der dann die Schnittstelle zur Anwendung zur Verfügung stellt:

@EndUserText.label: 'Unmanaged Consumption'
@AccessControl.authorizationCheck: #NOT_REQUIRED
@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DEMO_UNMANAGED_QUERY'
@UI: {
  headerInfo: {
    typeName: 'Unmanaged',
    typeNamePlural: 'Unmanaged',
    title: { value: 'Description' }
  }
}
define root view entity ZBS_C_DMOUnmanaged
  provider contract transactional_query
  as projection on ZBS_R_DMOUnmanaged
{
      @UI.facet      : [
        {
          id         : 'FacetData',
          label      : 'Data',
          type       : #FIELDGROUP_REFERENCE,
          targetQualifier: 'DATA'
        }
      ]

      @UI.lineItem: [{ position: 10, label: 'Key' }]
      @UI.fieldGroup: [{ position: 10, label: 'Key' }]
  key TableKey,
      @UI.selectionField: [{  position: 10 }]
      @UI.lineItem: [{ position: 20, label: 'Text' }]
      @UI.fieldGroup: [{ position: 20, label: 'Text', qualifier: 'DATA' }]
      Description,
      @UI.selectionField: [{  position: 20 }]
      @UI.lineItem: [{ position: 30, label: 'Created at' }]
      @UI.fieldGroup: [{ position: 20, label: 'Text', qualifier: 'DATA' }]
      CreationDate
}

 

In diesem Fall vermischen wir hier auch gleich die UI Annotationen mit in den View und legen nicht extra eine Metadaten Erweiterung an. Beide Szenarien sind soweit möglich und führen zum Ergebnis, dass unsere Anwendung eine konfigurierte Fiori Elements Oberfläche erhält.

 

Verhaltensdefinition

Unterschiede gibt es bei der Implementierung der Verhaltensdefinition, wie wir bereits im letzten Artikel gezeigt hatten. Die Projektion unterscheidet sich nicht vom Managed Ansatz, deshalb hier nur noch einmal die Definition auf unterster Ebene:

unmanaged implementation in class zbp_bs_demo_unmanaged unique;
strict ( 1 );

define behavior for ZBS_R_DMOUnmanaged alias Unmanaged
lock master
authorization master ( instance )
{
  create;
  update;
  delete;

  field ( readonly ) TableKey;

  mapping for zbs_dmo_unmgnd
    {
      TableKey     = gen_key;
      Description  = text;
      CreationDate = cdate;
    }
}

 

Der Schlüssel wird von der Anwendung automatisch vergeben, deshalb sollte dieser nur auf Anzeige in der Liste stehen und auf der Objektseite komplett ausgeblendet werden.

 

Weitere Objekte

Zum Abschluss legen wir nun noch die Service Definition und das Service Binding an, um einen UI OData Service zur Verfügung zu stellen und einen ersten Test mit der App durchführen zu können. Dabei werden wir für den Test ein OData Version 2 anlegen und konsumieren.

 

Test Preview

Starten wir nun die Fiori Elements Preview, dann erhalten wir die folgende App in der Vorschau, alle wichtigen Funktionen scheinen erst einmal verfügbar zu sein.

 

Beim Ausführen der Selektion über den Button "Go", werden allerdings keine Daten angezeigt. Auch das Anlegen von neuen Datensätzen funktioniert noch nicht.

 

Lesen

Das Lesen der Daten müssen wir nun selbst implementieren. Hier würde sich in der Verhaltensimplementierung, der Klasse ZBP_BS_DEMO_UMMANAGED, eigentlich die READ Methode anbieten, diese ist aber zum Lesen von Einzelsätzen da und nicht für die Query Operation. Dazu definieren wir im CDS der Projektion die folgende Annotation, um eine Leseklasse zu implementieren:

@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DEMO_UNMANAGED_QUERY'

 

Die Klasse wird immer aufgerufen, wenn über den OData Service die Entität angefragt wird und liefert entsprechende Daten zurück. Bereits im Artikel zur Custom Entity haben wir uns dieses Konzept angeschaut. Dazu die folgende Implementierung zur Klasse:

CLASS zcl_bs_demo_unmanaged_query DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES if_rap_query_provider.

  PRIVATE SECTION.
    METHODS get_data_from_request
      IMPORTING io_request       TYPE REF TO if_rap_query_request
      RETURNING VALUE(rt_result) TYPE zif_bs_demo_rap_data_handler=>tt_data
      RAISING   cx_rap_query_filter_no_range.
ENDCLASS.


CLASS zcl_bs_demo_unmanaged_query IMPLEMENTATION.
  METHOD if_rap_query_provider~select.
    DATA lt_output TYPE STANDARD TABLE OF ZBS_C_DMOUnmanaged.

    DATA(lt_database) = get_data_from_request( io_request ).
    lt_output = CORRESPONDING #( lt_database MAPPING TableKey = gen_key Description = text CreationDate = cdate ).

    IF io_request->is_data_requested( ).
      io_response->set_data( lt_output ).
    ENDIF.

    IF io_request->is_total_numb_of_rec_requested( ).
      io_response->set_total_number_of_records( lines( lt_output ) ).
    ENDIF.
  ENDMETHOD.


  METHOD get_data_from_request.
    DATA lt_r_key  TYPE zif_bs_demo_rap_data_handler=>tt_r_key.
    DATA lt_r_text TYPE zif_bs_demo_rap_data_handler=>tt_r_text.
    DATA lt_r_date TYPE zif_bs_demo_rap_data_handler=>tt_r_date.

    DATA(lt_filter) = io_request->get_filter( )->get_as_ranges( ).
    DATA(ld_offset) = io_request->get_paging( )->get_offset( ).
    DATA(ld_pagesize) = io_request->get_paging( )->get_page_size( ).

    LOOP AT lt_filter INTO DATA(ls_filter).
      CASE ls_filter-name.
        WHEN 'TABLEKEY'.
          lt_r_key = CORRESPONDING #( ls_filter-range ).
        WHEN 'DESCRIPTION'.
          lt_r_text = CORRESPONDING #( ls_filter-range ).
        WHEN 'CREATIONDATE'.
          lt_r_date = CORRESPONDING #( ls_filter-range ).
      ENDCASE.
    ENDLOOP.

    rt_result = zcl_bs_demo_rap_data_handler=>create_instance( )->query( it_r_key  = lt_r_key
                                                                         it_r_text = lt_r_text
                                                                         it_r_date = lt_r_date ).
  ENDMETHOD.
ENDCLASS.

 

Für die Implementierung packen wir die Anfrage (IO_REQUEST) aus und besorgen uns die Filter aus dem Frontend. Diese können wir dann an die API übergeben, um so unsere Daten zu erhalten. Im Anschluss müssen diese von der Datenbank auf den Core Data Service gemappt werden und im Anschluss übergeben wir die Daten an die Antwort (IO_RESPONSE). Wenn wir nun in der Anwendung auf den "GO"-Button klicken, erhalten wir das folgende Ergebnis, die Daten werden nun gelesen:

 

Puffer

Für die Verhaltensimplementierung wurden so weit alle Methoden implementiert, was aber fehlt ist der Puffer, der sich um die die Datensätze kümmert, bevor diese an die Speichersequenz übergeben werden. Diesen Puffer müssen wir selbst entwickeln, dazu gibt es allerdings auch eine Best-Practise für die Umsetzung. Wir implementieren den Puffer als lokale Klasse mit statischen Attributen, für unser Szenario würde die Klasse wie folgt aussehen:

CLASS lcl_data_buffer DEFINITION.
  PUBLIC SECTION.
    CLASS-DATA gt_create TYPE zif_bs_demo_rap_data_handler=>tt_data.
    CLASS-DATA gt_update TYPE zif_bs_demo_rap_data_handler=>tt_data.
    CLASS-DATA gt_delete TYPE zif_bs_demo_rap_data_handler=>tt_data.
ENDCLASS.

 

Grundsätzlich kann die Klasse auch über ein Attribut mit unterschiedlichen Merkmalen umgesetzt werden, hier sind der Ordnung und der Fantasie keine Grenzen gesetzt. Die Attribute werden zur Laufzeit die Daten übernehmen und der Speichersequenz zur Verfügung stellen.

 

Anlegen

Bevor wir die Daten anlegen, schauen wir uns einmal den Inhalt an, den wir bei der Anlage eines Datensatzes in die Methode bekommen:

 

Wir bekommen mit ENTITIES eine Tabelle übergeben, dessen Struktur die Daten enthält für die Anlage plus Zusatzinformationen, wie die %CID und die %CONTROL Struktur. Die CID ist der eindeutige Schlüssel innerhalb der Verarbeitung, sodass wir auch andere verknüpfte Datensätze zuordnen kann. In der CONTROL Struktur sind die entsprechenden Felder aktiv, die in diesem Prozessschritt bearbeitet werden können. Hierrüber könnten wir auch die Zuordnung ableiten. Die Implementierung der Create Methode sieht nun wie folgt aus:

METHOD create.
  INSERT LINES OF
         CORRESPONDING zif_bs_demo_rap_data_handler=>tt_data( entities MAPPING cdate = CreationDate text = Description )
         INTO TABLE lcl_data_buffer=>gt_create.
ENDMETHOD.


Im Grunde übernehmen wir die Daten nach dem Mapping in unsere Puffertabelle für die neuen Datensätze, mehr ist hier nicht zu beachten.

 

Aktualisieren

Für das Aktualisieren der Daten müssen wir etwas mehr Logik implementieren. Wir bekommen wieder unsere Entität übergeben, aber nur mit den Schlüsselfeldern und den geänderten Daten. Das heißt im nächsten Schritt müssen wir zuerst abgleichen, was sich eigentlich geändert hat. Die Implementierung würde nun wie folgt aussehen:

METHOD update.
  DATA(lo_data_handler) = zcl_bs_demo_rap_data_handler=>create_instance( ).

  LOOP AT entities INTO DATA(ls_entity).
    DATA(ls_original) = lo_data_handler->read( ls_entity-TableKey ).

    IF ls_entity-%control-Description = if_abap_behv=>mk-on.
      ls_original-text = ls_entity-Description.
    ENDIF.

    IF ls_entity-%control-CreationDate = if_abap_behv=>mk-on.
      ls_original-cdate = ls_entity-CreationDate.
    ENDIF.

    INSERT ls_original INTO TABLE lcl_data_buffer=>gt_update.
  ENDLOOP.
ENDMETHOD.

 

Zuerst einmal lesen wir über den Schlüssel den Originaldatensatz nach, dann prüfen wir die CONTROL Struktur, ob das Feld geändert wurde und übernehmen dann den neuen Inhalt in die Daten. Am Ende hängen wir den Datensatz an den Puffer für die Folgeverarbeitung.

 

Löschen

Der letzte Schritt, das Löschen, wird nun etwas anders implementiert, da wir nicht die ganze Entität übergeben bekommen, sondern nur den Schlüssel. Im Grunde müssen wir hier nur das Mapping anpassen und übergeben die Daten an den Puffer:

METHOD delete.
  INSERT LINES OF
         CORRESPONDING zif_bs_demo_rap_data_handler=>tt_data( keys MAPPING gen_key = TableKey )
         INTO TABLE lcl_data_buffer=>gt_delete.
ENDMETHOD.

 

 

Speichern

Zum Abschluss können wir nun die SAVE Methode implementieren. Hier geht es darum den Puffer zu lesen und entsprechende Aktionen auf die verschiedenen Einträge durchzuführen:

METHOD save.
  DATA(lo_data_handler) = zcl_bs_demo_rap_data_handler=>create_instance( ).

  LOOP AT lcl_data_buffer=>gt_create INTO DATA(ls_create).
    lo_data_handler->modify( ls_create ).
  ENDLOOP.

  LOOP AT lcl_data_buffer=>gt_update INTO DATA(ls_update).
    lo_data_handler->modify( ls_update ).
  ENDLOOP.

  LOOP AT lcl_data_buffer=>gt_delete INTO DATA(ls_delete).
    lo_data_handler->delete( ls_delete-gen_key ).
  ENDLOOP.

  CLEAR: lcl_data_buffer=>gt_create, lcl_data_buffer=>gt_update, lcl_data_buffer=>gt_delete.
ENDMETHOD.

 

Wir instanziieren den Daten Handler und verarbeiten alle Datensätze aus dem Puffer, in dem wir sie an unsere bestehende Logik übergeben. Zum Abschluss löschen wir den Puffer und sind damit fertig. Der Commit Work zur Persistierung der Datensätze wird durch das RAP Framework gesetzt, hier müssen wir uns nicht darum kümmern.

 

Komplettes Beispiel

Das vollständige Beispiel, alle Objekte und Änderungen kannst du über den Commit auf GitHub einsehen. Die hier gezeigten Beispiele sind nur Auszüge aus dem vollständigen Quellcode und den Objekten, die wir angelegt haben.

 

Fazit

In dieser ersten Anbindung einer lokalen API haben wir dir den Unmanaged Ansatz für ein RAP Objekt näher gebracht. Damit kannst du einen Großteil deines bestehenden Quellcodes leicht nach RAP migrieren und so auf den neuesten Technologien aufbauen, ohne alles überarbeiten zu müssen.


Enthaltene Themen:
RAPBTPUnmanagedLokal
Kommentare (0)



Und weiter ...

Bist du zufrieden mit dem Inhalt des Artikels? Wir posten jeden 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.


RAP - Popup Defaultwerte

Kategorie - ABAP

Wie kannst du im Popup einer Aktion in RAP dem User Defaultwerte zur Verfügung stellen? In diesem Artikel erweitern wir unsere Anwendung.

21.01.2025

RAP - Popup Pflichtfelder

Kategorie - ABAP

Wie kannst du eigentlich Pflichtfelder für ein Popup in RAP definieren? In diesem Artikel werden wir etwas genauer auf die Details eingehen.

14.01.2025

RAP - Deep Table Action

Kategorie - ABAP

Ist die Übergabe von Tabellen an Aktionen in RAP aktuell möglich? Dieser Artikel soll einen besseren Einblick in das Thema gewähren.

07.01.2025

ABAP Cloud - Programmiermodell

Kategorie - ABAP

Welches Programmiermodell kommt mit ABAP Cloud zum Einsatz und was können wir aus dem Vorgänger lernen? Mehr Details im Artikel.

03.01.2025

RAP - Side Effects

Kategorie - ABAP

Wie kannst du Teile der Fiori UI aktualisieren, ohne einen kompletten Refresh zu machen? Mit Side Effects ist das in ABAP und RAP ganz leicht möglich.

27.12.2024