This is a test message to test the length of the message box.
Login
ABAP Cloud Übergreifende Komponenten
Erstellt von Software-Heroes

ABAP Cloud - Übergreifende Komponenten

111

Welche Punkte gibt es zu beachten, wenn du übergreifende Komponenten in ABAP Cloud entwickeln möchtest? Hier schauen wir auf verschiedene Beispiele.

Werbung


In diesem Artikel schauen wir uns die Arbeit über Software Komponenten hinweg an und welche Herausforderungen die Nutzung dieser Komponenten mit sich bringt.

 

Einleitung

Auf jedem System gibt es normalerweise eine handvoll Komponenten, die in der Entwicklung wiederverwendet werden. Solche zentralen Komponenten werden in den meisten Fällen zentral zur Verfügung gestellt und auch gewartet. Solche Komponenten sind zum Beispiel eine Fassade für das Application Log, Templates und Services für den Mailversand, Parametertabellen oder verschiedene Konfigurationen. Im Bereich ABAP Cloud verwenden wir verschiedene Software Komponenten, um die Bestandteile unserer Anwendungen zu trennen.

Wieso wir viele TIER-1 Software Komponenten (SWC) verwenden, haben wir dir in diesem Artikel zusammengefasst. Dabei geht es um die bewusste Trennung von Code und DDIC Elementen, um mehr Struktur und eine saubere Kapselung ins System zu bringen. Am Ende machen wir uns darüber Gedanken, welche Objekte wir für andere Freigeben.

 

Szenario

In diesem Fall nutzen wir zwei Klassen und ein Interface. Eine Klasse ist eine Re-Use Komponente und die anderen Klasse implementiert einige Methoden und hat ein Interface, wo auch Typen definiert sind, die wir in unserem Szeanrio testen. Vom Aufbau her sehen die Artefakte wie folgt aus.

 

 

Freigabe

Da wir mit einer wiederverwendbaren Komponente arbeiten und diese in einer anderen Software Komponente ist, müssen wir einen C1 Contract definieren und damit einen Stabilitätsvertrag eingehen. Das Objekt wird damit unsere eigene freigegebene API im System. Wollen wir die Klasse ohne Freigabe nutzen, erhalten wir die folgende Fehlermeldung.

 

Im nächsten Schritt sollten wir also noch unsere C1 Freigabe auf der Klasse definieren.

 

Project Explorer

Wenn das Objekt im Project Explorer wählst und per Rechts-Klick das Kontextmenü öffnest, findest du bei passenden Objekten eine Aktion im unteren Bereich.

 

Properties

Ist das Objekt geöffnet, kannst du in den "Properties" View wechseln und findest dort unter "API State" die entsprechenden Contract Informationen. Über den "+" Button kannst du dann in den Standardflow zur Freigabe einsteigen.

 

Flow

Im Anschluss läufst du durch den Standard für die Freigabe der Objekte. Wichtig ist der C1 Contract für die Nutzung des Objekts und das die Einstellung "Use in Cloud Development" gesetzt ist, damit wir das Objekt innerhalb von ABAP Cloud verwenden können.

 

Zum Abschluss wird der Eintrag in den Transport übernomen, wo einige Tabelleneinträge für die Freigabe transportiert werden. Zusammengefasst wird das Ganze unter dem Objekttyp "APIS".

 

Datentyp

Wie sieht es nun mit der allgemeinen Nutzung von Datentypen bei übergreifenden Komponenten aus? In diesem Fall gehen wir davon aus, dass der Typ außerhalb der Re-Use Komponente in der aufrufenden SWC liegt.

 

Vorbereitung

Dazu legen wir einen Datentyp im Interface ZIF_BS_DEMO_SWC_USE an, den wir in unserer übergreifenden Komponente befüllen lassen wollen. Der Typ besteht aus unterschiedlichen Feldern mit unterschiedlichen Datentypen.

TYPES charlike TYPE c LENGTH 25.
TYPES packed   TYPE p LENGTH 15 DECIMALS 2.

TYPES: BEGIN OF dummy,
         number    TYPE i,
         char      TYPE charlike,
         string    TYPE string,
         packed    TYPE packed,
         timestamp TYPE utclong,
       END OF dummy.

TYPES dummys TYPE STANDARD TABLE OF dummy WITH EMPTY KEY.

 

Referenz

In diesem Szenario geben wir den Datentyp als Referenz in die Methode. Dazu definieren wir unsere Re-Use-Methode wie folgt:

METHODS table_reference
  IMPORTING !data TYPE REF TO data.

 

Bei der Methodenimplementierung benötigen wir zuerst ein Feldsymbol um die Referenz zuweisen zu können. Im nöchsten Schritt würden wir dann die Daten dem Feldsymbol zuweisen, um sie zurückzugeben.

METHOD table_reference.
  FIELD-SYMBOLS <table> TYPE STANDARD TABLE.

  ASSIGN data->* TO <table>.

  DATA(parts) = get_default_parts( ).
  <table> = CORRESPONDING #( parts ).
ENDMETHOD.

 

Allerdings funktioniert diese Methode nicht, da bereits beim ASSIGN ein SY-SUBRC von 4 gesetzt wird. Wir haben aktuell keinen Zugriff auf den Typen aus dem Interface.

 

Changing

Im nächsten Versuch verwenden wir einen Changing Parameter, um die Tabelle von außen in die Methode zu geben und dort zu befüllen.

METHODS table_changing
  CHANGING !data TYPE ANY TABLE.

 

Die Methodenimplementierung ist recht einfach gehalten, wir erzeugen nur unsere Dummy Daten und weisen sie per CORRESPONDING dem Changing Parameter zu.

METHOD table_changing.
  DATA(parts) = get_default_parts( ).
  data = CORRESPONDING #( parts ).
ENDMETHOD.

 

Diese Variante funktioniert so weit auch und die entsprechend befüllten Felder kommen beim Aufrufer an. Im Beispiel findest du auch das gleiche Beispiel für das Thema EXPORTING, auch dieses Szenario funktioniert.

 

Dynamisch

Im letzten Beispiel übergeben wir den Typen dynamisch als String und erwarten von der Routine die Daten als erzeugte Referenz von dem Typen zurück.

METHODS table_with_type
  IMPORTING type_name   TYPE string
  RETURNING VALUE(data) TYPE REF TO data.

 

Bei der Implementierung erzeugen wir dynamisch unsere Daten Referenz über den mitgelieferten Typen, weisen die Referenz dem neuen Feldsymbol zu und übergeben die Daten.

METHOD table_with_type.
  CREATE DATA data TYPE (type_name).
  ASSIGN data->* TO FIELD-SYMBOL(<data>).

  DATA(parts) = get_default_parts( ).
  <data> = CORRESPONDING #( parts ).
ENDMETHOD.

 

In diesem Fall erhalten wir direkt einen Dump, da der CREATE nicht funktioniert. Die Meldung deutet allerdings darauf hin, dass der Typ unbekannt ist. Bei der dynamischen Zuweiseung, kann erst einmal nicht der Typ geprüft werden und damit ist die Meldung nicht so genau.

 

Lösung

Grundsätzlich kannst du auch das Interface ZIF_BS_DEMO_SWC_USE mit einer C1 Freigabe ausstatten, allerdings wäre das Interface dann im gesamten System freigegeben und könnte auch von anderen genutzt werden, was widerrum zu Abhängigkeiten führen könnte. Der sauberste Weg wäre daher die Anlage einer "Software Component Relation". Dort geben wir unsere Komponenten für die SWC der übergreifenden Komponenten frei (Access Permission).

Führen wir nun noch einmal alle Methoden unserer Klasse aus, dann funktionieren alle Verfahren. Aktuell sind die Software Component Relations allerdings nur im ABAP Environment und der S/4HANA Cloud Public Edition verfügbar und werden sehr wahrscheinlich mit 2025 verfügbar sein.

 

Application Log

Im nächsten Beispiel schauen wir uns das Anlegen eines Application Logs an. In den meisten Fällen verwenden wir eine Fassade für das Application Log, da die Übernahme der Meldungen auch mit den neuen Schnittstellen noch relativ viel Code benötigt, ebenso wie die Initialisierung.

 

Szenario

Dazu definieren wir eine Methode in unserer übergreifenden Komponente die eine Tabelle von Nachrichten entgegen nimmt und diese dann in einem Application Log speichert.

METHODS save_messages
  IMPORTING !messages TYPE messages
            !header   TYPE REF TO if_bali_header_setter OPTIONAL.

 

Die Implementierung der Methode ist recht lang, dazu eine kurze Erklärung. Wir erzeugen ein Application Log Objekt und im Anschluss einen entsprechenden Header, wenn keiner übergeben wurde. Dann übernehmen wir den Header in unser Log Objekt, speichern die Meldung und rufen zum Abschluss den SAVE auf, um die Meldungen ins Log zu persistieren.

METHOD save_messages.
  DATA(application_log) = cl_bali_log=>create( ).

  DATA(local_header) = header.
  IF local_header IS INITIAL.
    local_header = cl_bali_header_setter=>create( object      = 'ZBS_APPL_REUSE'
                                                  subobject   = 'TEST'
                                                  external_id = CONV #( xco_cp=>uuid( )->value ) ).
  ENDIF.

  local_header->set_expiry( expiry_date       = CONV d( cl_abap_context_info=>get_system_date( ) + 7 )
                            keep_until_expiry = abap_true ).
  application_log->set_header( local_header ).

  LOOP AT messages INTO DATA(message).
    application_log->add_item( cl_bali_message_setter=>create_from_bapiret2( message ) ).
  ENDLOOP.

  cl_bali_log_db=>get_instance( )->save_log( application_log ).
ENDMETHOD.

 

Das verwendete Application Log Objekt "ZBS_APPL_REUSE" liegt in diesem Fall in unserer aufrufenden Komponente, also bei der Anwendung die die übergreifende Komponente nutzt. Zur Einfachheit haben wir das Objekt hartcodiert hinterlegt.

 

Fehler

Führen wir nun die Methode aus, kommt es bei der Anlage des Header in der Methode zu einem Fehler und unsere Routine bricht ab. Beim Debuggen wirst du feststellen, dass der Standard hier Prüfungen vornimmt, ob unser Application Log Object freigegeben ist. Damit kann unsere Komponente den Header nicht anlegen und damit das Log auch nicht speichern.

 

Lösung

In diesem Beispiel gibt es zwei Lösungen die uns zum Ziel bringen können:

  1. Anlage des Headers (CL_BALI_HEADER_SETTER) in der aufrufenden Komponente und Übergabe an die Speichern Methode.
  2. Nutzung der Software Component Relations, um alle Objekte für die übergreifenden Komponenten freizugeben.

 

Anpassung

Möchtest du freigegebene APIs anpassen, ist dies grundsätzlich möglich, du wirst aber auch mit einer Sicherheitsmeldung beim Editieren begrüßt.

 

Daher solltest du die Standardregeln für das Ändern von Artefakten beachten

 

Vollständiges Beispiel

Hier findest du noch einmal alle gezeigten Resourcen aus dem Artikel, auf eine Ablage in GitHub haben wir dieses Mal verzichtet.

 

Interface

Das Interface stellt einen Typen zur Verfügung.

INTERFACE zif_bs_demo_swc_use
  PUBLIC.

  TYPES charlike TYPE c LENGTH 25.
  TYPES packed   TYPE p LENGTH 15 DECIMALS 2.

  TYPES: BEGIN OF dummy,
           number    TYPE i,
           char      TYPE charlike,
           string    TYPE string,
           packed    TYPE packed,
           timestamp TYPE utclong,
         END OF dummy.

  TYPES dummys TYPE STANDARD TABLE OF dummy WITH EMPTY KEY.
ENDINTERFACE.

 

Klassen

Die Klasse soll die übergreifende Komponente konsumieren.

CLASS zcl_bs_demo_swc_use DEFINITION
  PUBLIC FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES if_oo_adt_classrun.
    INTERFACES zif_bs_demo_swc_use.

  PRIVATE SECTION.
    METHODS call_method
      IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.

    METHODS call_with_changing
      IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.

    METHODS call_with_reference
      IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.

    METHODS call_for_reference
      IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.

    METHODS call_with_exporting
      IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.

    METHODS call_application_log
      IMPORTING !out TYPE REF TO if_oo_adt_classrun_out.
ENDCLASS.


CLASS zcl_bs_demo_swc_use IMPLEMENTATION.
  METHOD if_oo_adt_classrun~main.
    call_method( out ).
    call_with_changing( out ).
    call_with_reference( out ).
    call_for_reference( out ).
    call_with_exporting( out ).
    call_application_log( out ).
  ENDMETHOD.


  METHOD call_method.
    DATA(dummy_data) = VALUE zif_bs_demo_swc_use=>dummys(
        ( number = 1 char = 'one' string = `First field` packed = '1.11' timestamp = utclong_current( ) )
        ( number = 2 char = 'two' string = `Second field` packed = '2.22' timestamp = utclong_current( ) ) ).

    out->write( dummy_data ).
  ENDMETHOD.


  METHOD call_with_changing.
    DATA changed_data TYPE zif_bs_demo_swc_use=>dummys.

    DATA(reuse) = NEW zcl_bs_demo_swc_reuse( ).

    reuse->table_changing( CHANGING data = changed_data ).

    out->write( changed_data ).
  ENDMETHOD.


  METHOD call_with_reference.
    DATA changed_data TYPE zif_bs_demo_swc_use=>dummys.

    DATA(reuse) = NEW zcl_bs_demo_swc_reuse( ).

    reuse->table_reference( data = REF #( changed_data ) ).

    out->write( changed_data ).
  ENDMETHOD.


  METHOD call_for_reference.
    DATA(reuse) = NEW zcl_bs_demo_swc_reuse( ).

    DATA(reference) = reuse->table_with_type( type_name = 'ZIF_BS_DEMO_SWC_USE=>DUMMYS' ).

    out->write( reference->* ).
  ENDMETHOD.


  METHOD call_with_exporting.
    DATA changed_data TYPE zif_bs_demo_swc_use=>dummys.

    DATA(reuse) = NEW zcl_bs_demo_swc_reuse( ).

    reuse->table_exporting( IMPORTING data = changed_data ).

    out->write( changed_data ).
  ENDMETHOD.


  METHOD call_application_log.
    DATA(messages) = VALUE zcl_bs_demo_swc_reuse=>messages( id = 'ZDUMMY'
                                                            ( type = 'S' number = '001' )
                                                            ( type = 'W' number = '002' ) ).

*    DATA(header) = cl_bali_header_setter=>create( object      = 'ZBS_APPL_REUSE'
*                                                  subobject   = 'TEST'
*                                                  external_id = CONV #( xco_cp=>uuid( )->value ) ).

    DATA(reuse) = NEW zcl_bs_demo_swc_reuse( ).

    reuse->save_messages( messages = messages
*                          header   = header
                          ).

    out->write( messages ).
  ENDMETHOD.
ENDCLASS.

 

Die Klasse stellt unsere übergreifende Komponente dar.

CLASS zcl_bs_demo_swc_reuse DEFINITION
  PUBLIC FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    TYPES messages TYPE STANDARD TABLE OF bapiret2 WITH EMPTY KEY.

    METHODS table_changing
      CHANGING !data TYPE ANY TABLE.

    METHODS table_reference
      IMPORTING !data TYPE REF TO data.

    METHODS table_with_type
      IMPORTING type_name   TYPE string
      RETURNING VALUE(data) TYPE REF TO data.

    METHODS table_exporting
      EXPORTING !data TYPE ANY TABLE.

    METHODS save_messages
      IMPORTING !messages TYPE messages
                !header   TYPE REF TO if_bali_header_setter OPTIONAL.

  PRIVATE SECTION.
    TYPES: BEGIN OF part,
             number    TYPE i,
             string    TYPE string,
             timestamp TYPE utclong,
           END OF part.

    TYPES parts TYPE STANDARD TABLE OF part WITH EMPTY KEY.

    METHODS get_default_parts
      RETURNING VALUE(result) TYPE parts.
ENDCLASS.


CLASS zcl_bs_demo_swc_reuse IMPLEMENTATION.
  METHOD table_changing.
    DATA(parts) = get_default_parts( ).
    data = CORRESPONDING #( parts ).
  ENDMETHOD.


  METHOD table_reference.
    FIELD-SYMBOLS <table> TYPE STANDARD TABLE.

    ASSIGN data->* TO <table>.

    DATA(parts) = get_default_parts( ).
    <table> = CORRESPONDING #( parts ).
  ENDMETHOD.


  METHOD table_with_type.
    CREATE DATA data TYPE (type_name).
    ASSIGN data->* TO FIELD-SYMBOL(<data>).

    DATA(parts) = get_default_parts( ).
    <data> = CORRESPONDING #( parts ).
  ENDMETHOD.


  METHOD table_exporting.
    DATA(parts) = get_default_parts( ).
    data = CORRESPONDING #( parts ).
  ENDMETHOD.


  METHOD get_default_parts.
    RETURN VALUE parts( ( number = 13 string = `Thirteen` timestamp = utclong_current( ) )
                        ( number = 20 string = `Twenty`  ) ).
  ENDMETHOD.


  METHOD save_messages.
    DATA(application_log) = cl_bali_log=>create( ).

    DATA(local_header) = header.
    IF local_header IS INITIAL.
      local_header = cl_bali_header_setter=>create( object      = 'ZBS_APPL_REUSE'
                                                    subobject   = 'TEST'
                                                    external_id = CONV #( xco_cp=>uuid( )->value ) ).
    ENDIF.

    local_header->set_expiry( expiry_date       = CONV d( cl_abap_context_info=>get_system_date( ) + 7 )
                              keep_until_expiry = abap_true ).
    application_log->set_header( local_header ).

    LOOP AT messages INTO DATA(message).
      application_log->add_item( cl_bali_message_setter=>create_from_bapiret2( message ) ).
    ENDLOOP.

    cl_bali_log_db=>get_instance( )->save_log( application_log ).
  ENDMETHOD.
ENDCLASS.

 

Fazit

Die Entwicklung von übergreifenden Komponenten funktioniert in der neuen Welt weiterhin, ist aber mit gewissen Risiken und Hindernissen verbunden, wenn es um eine saubere Implementierung in allen Fällen geht. Anhand der Beispiele sollten du aber viele Probleme aus dem Weg räumen können.


Enthaltene Themen:
ABAP CloudABAPÜbergreifendKomponente
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.


ABAP Cloud - XML erstellen

Kategorie - ABAP

Wie kannst du eigentlich in ABAP Cloud ein XML außerhalb von Transformationen erstellen? In diesem Artikel bauen wir ein XML im Detail nach.

28.03.2025

ABAP Cloud - Transport der Software-Komponente

Kategorie - ABAP

Wie sieht es eigentlich mit dem Transport von Software Komponenten im ABAP Cloud aus? Benötigst du die Komponente On-Premise auch im Test- und Produktivsystem?

11.03.2025

ABAP Cloud - CRV Update & TIER-3

Kategorie - ABAP

Wie kannst du in ABAP Cloud die richtige API für dein Szenario finden und was machst du eigentlich mit TIER-3 in deiner Entwicklung? Mehr Informationen hier.

25.02.2025

ABAP Cloud - XML lesen

Kategorie - ABAP

Wie kannst du in ABAP Cloud relativ einfach XML Daten lesen und verarbeiten? Dazu schauen wir uns ein Beispiel an und gehen das Schritt für Schritt durch.

21.02.2025

ABAP Cloud - Clean Core (Szenarien)

Kategorie - ABAP

Lass uns in diesem Artikel noch einmal die Clean Core Architektur mit ABAP Cloud anschauen, wo diese eingesetzt wird und wo du deine Anwendungen bauen kannst.

10.01.2025