This is a test message to test the length of the message box.
Login
|
ABAP in der Praxis Objekt Generator
Erstellt von Software-Heroes

ABAP in der Praxis - Objekt Generator

177

In diesem Beispiel schauen wir uns an, wie wir mit der XCO Bibliothek einen wiederverwendbaren Generator erstellen, um uns für unsere Tutorials etwas Arbeit zu sparen und automatisiert DDIC Objekte zu generieren.

Werbung


In diesem Artikel gehen wir in die Praxis und erstellen uns einen Generator zur Anlage von neuen Objekten im DDIC Bereich. Dies soll uns in Zukunft bei Tutorials die manuelle Arbeit abnehmen.

 

Einleitung

Manchmal möchtest du etwas Neues probieren, bist aber eigentlich schon wieder bei der Anlage der Tabelle und der Erstellung ordentlicher Datenelemente genervt? Dann hilft dir ein Generator

 

Aufgabe

Die Aufgabe klingt erst einmal sehr einfach, wir benötigen einen Generator, der uns Datenelemente und Domänen anlegt und mit einer minimalen Konfiguration arbeitet. Damit wollen wir etwas Arbeit sparen, wenn wir beim nächsten Mal Modelle und Datenbanken anlegen wollen. Ist er wiederverwendbar gebaut, können wir damit auch in Schulungen Massendaten für jeden Teilnehmer generieren.

 

Hinweis: Im nächsten Abschnitt werden wir auf die Lösung eingehen, wenn du die Aufgabe erst einmal selbstständig machen möchtest, solltest du hier pausieren.

 

Lösung

Gehen wir Schritt für Schritt durch die verschiedenen Teile der Entwicklung.

 

Grundstruktur

Im ersten Schritt stellen wir eine saubere Grundstruktur der Objekte zur Verfügung. Wir wollen mit ABAP OO ein kleines Framework zur Verfügung stellen und dabei die Bestandteile der Testbarkeit und Wiederverwendbarkeit einhalten. Deshalb generieren wir neben der Klasse auch eine Factory und einen Injector, womit wir fast jede Entwicklung starten. Damit die Anlage einfach funktioniert, verwenden wir unsere IDE Actions, um uns die Klassen generieren zu lassen.

 

Nach der Formatierung der Objekte, können wir dann in die eigentliche Implementierung gehen.

 

Strukturen

Damit wir die Objekte anlegen können, sollten wir zuerst eine Konfiguration in Form von Strukturen und Tabellen anlegen. Hier möchten wir in den meisten Fällen eine Struktur, Tabelle oder Objekt in die Methode übergeben, um die Schnittstelle, klein, sauber und leicht erweiterbar zu halten. In diesem Fall legen wir eine Struktur an und beginnen mit den internen Objekten. Beginnen wir mit der Konfiguration für Domänen und Datenelemente.

TYPES: BEGIN OF domain,
         name           TYPE sxco_ad_object_name,
         description    TYPE if_xco_cp_gen_doma_s_form=>tv_short_description,
         base_type      TYPE string,
         length         TYPE i,
         decimals       TYPE i,
         case_sensitive TYPE abap_boolean,
       END OF domain.
TYPES domains TYPE STANDARD TABLE OF domain WITH EMPTY KEY.

TYPES: BEGIN OF data_element,
         name        TYPE sxco_ad_object_name,
         description TYPE if_xco_cp_gen_dtel_s_form=>tv_short_description,
         label       TYPE string,
         domain      TYPE sxco_ad_object_name,
       END OF data_element.
TYPES data_elements TYPE STANDARD TABLE OF data_element WITH EMPTY KEY.

 

Später wollen wir dann noch abstrakte Entitäten mit den Datenelementen anlegen können, deshalb definieren wir noch zusätzlich Konfigurationen für die Feldliste der Entität und die eigentliche Entität.

TYPES: BEGIN OF field,
         name         TYPE sxco_cds_field_name,
         data_element TYPE sxco_ad_object_name,
         currency     TYPE string,
         unit         TYPE string,
       END OF field.
TYPES fields TYPE STANDARD TABLE OF field WITH EMPTY KEY.

TYPES: BEGIN OF abstract_entity,
         name        TYPE sxco_cds_object_name,
         description TYPE if_xco_cp_gen_dtel_s_form=>tv_short_description,
         fields      TYPE fields,
       END OF abstract_entity.
TYPES abstract_entities TYPE STANDARD TABLE OF abstract_entity WITH EMPTY KEY.

 

Damit haben wir alle untergeordneten Typen definiert und können nun die eigentliche Konfigurationsstruktur definieren. Dazu wollen wir auch das Paket zur Anlage, den Transport und ein Präfix haben. Die ersten zwei Werte benötigen wir je nach System. Über die anderen Felder geben wir die Konfiguration in den Generator.

TYPES: BEGIN OF ddic_configuration,
         package           TYPE sxco_package,
         transport         TYPE sxco_transport,
         prefix            TYPE string,
         domains           TYPE domains,
         data_elements     TYPE data_elements,
         abstract_entities TYPE abstract_entities,
       END OF ddic_configuration.

 

Hinweis: Bereits mit dem Aufbau haben wir unsere Grundstruktur für die Anlage gelegt. Somit hat bei uns eigentlich jedes Datenelement immer eine Domäne, die den Typen und die technischen Details definiert.

 

Konfiguration

Da wir viele Teile der Konfiguration dem Aufrufer abnehmen wollen, können wir einige Informationen auch einfach ableiten. Über das XCO Framework stehen uns hier einige Methoden zur Verfügung. Zum Beispiel können wir über das aufrufende Objekt ableiten in welchem Paket wir unterwegs sind und ob es einen offenen Transportauftrag dazu gibt. Dazu könnten wir den Callstack abrufen und das aufrufende Objekt ermitteln. In diesem Fall halten wir es relativ einfach und übergeben über die Factory das Feld SY-REPID. Allerdings erhalten wir den Namen der Klasse als "ZMY_CLASS====CP" übergeben. Dafür schreiben wir eine einfache Funktion, die den Namen extrahiert.

calling_object = substring( val = calling_object
                            len = find( val = calling_object
                                        sub = '=' ) ).

 

Wir können uns nun die Klasse einlesen, zumindest gehen wir davon aus, dass wir von einer Klasse aus gerufen werden, und holen uns über das Interface IF_XCO_AR_OBJECT das Paket und dort den Namen. Grundsätzlich können wir auch über das Interface IF_XCO_CTS_CHANGEABLE an einen offenen Transport gelangen, wenn das Objekt einem zugeordnet ist.

xco_cp_abap=>class( CONV #( calling_object ) )->if_xco_ar_object~get_package( )->name.

 

Ist das Präfix befüllt, dann wollen wir alle Objekte mit diesem ergänzen, da diese meist im gleichen Namensraum sind. Spart bei der Eingabe Zeit und Aufwand und wir könnten damit auch verschiedene Objekte für verschiedene Teilnehmer generieren.

LOOP AT ddic_configuration-domains REFERENCE INTO DATA(domain).
  domain->name = ddic_configuration-prefix && domain->name.
ENDLOOP.

LOOP AT ddic_configuration-data_elements REFERENCE INTO DATA(data_element).
  data_element->name   = ddic_configuration-prefix && data_element->name.
  data_element->domain = ddic_configuration-prefix && data_element->domain.
ENDLOOP.

 

Operation

Damit wir Objekte generieren können, benötigen wir eine OPERATION vom XCO Framework. Da wir neue Objekte anlegen wollen, verwenden wir die Methode CREATE_PUT_OPERATION. Diese übergeben wir dann von Methode zu Methode, um die verschiedenen Typen anzulegen.

DATA(put_operation) = xco_cp_generation=>environment->dev_system( me->ddic_configuration-transport )->create_put_operation( ).

 

Bereits in einem älteren Artikel haben wir die Generierung von Artefakten mit XCO beschrieben. Im Artikel findest du weitere Informationen zu den Möglichkeiten und dem Framework.

 

Domänen

Was macht eigentlich eine Domäne aus und welche Typen benötigen wir? Dazu benötigen wir noch eine Struktur von Konstanten, um dem Nutzer die Möglichkeit zu geben unterstützte Typen anzugeben. Dabei verwenden die Typen, die wir in der Entwicklung normalerweise am häufigsten verwenden.

CONSTANTS:
  BEGIN OF domain_types,
    character     TYPE string VALUE `CHAR`,
    date          TYPE string VALUE `DATN`,
    time          TYPE string VALUE `TIMN`,
    integer       TYPE string VALUE `INT4`,
    integer_long  TYPE string VALUE `INT8`,
    timestamp     TYPE string VALUE `UTCLONG`,
    currency_code TYPE string VALUE `CUKY`,
    currency      TYPE string VALUE `CURR`,
    quantity      TYPE string VALUE `QUAN`,
    unit          TYPE string VALUE `UNIT`,
    decimals      TYPE string VALUE `DEC`,
    raw           TYPE string VALUE `RAW`,
    string        TYPE string VALUE `STRING`,
    short_string  TYPE string VALUE `SSTRING`,
  END OF domain_types.

 

Der eigentliche Typ der Struktur, ergibt sich aus den Anforderungen, die wir haben und den Pflichtfeldern des Standards. In diesem Fall benötigen wir immer eine Beschreibung, das Paket für die Anlage, einen Namen und den Typen. Über die Operation fügen wir eine neue Domäne ein, setzen das Zielpaket und lassen uns die Spezifikation geben. Über die Spezifikation können wir dann die Eigenschaften, wie Beschreibung, den Typen und Case-Sensitive setzen.

LOOP AT ddic_configuration-domains INTO DATA(domain).
  DATA(specification) = operation->for-doma->add_object( domain-name
    )->set_package( ddic_configuration-package
    )->create_form_specification( ).

  specification->set_short_description( domain-description ).

  CASE domain-base_type.
    WHEN zif_gen_objects=>domain_types-character.
      format = xco_cp_abap_dictionary=>built_in_type->char( CONV #( domain-length ) ).
    WHEN zif_gen_objects=>domain_types-date.
      format = xco_cp_abap_dictionary=>built_in_type->datn.
    WHEN zif_gen_objects=>domain_types-integer.
      format = xco_cp_abap_dictionary=>built_in_type->int4.
    WHEN OTHERS.
      CONTINUE.
  ENDCASE.

  specification->set_format( format ).
  specification->output_characteristics->set_case_sensitive( domain-case_sensitive ).
ENDLOOP.

 

Datenelemente

Die Datenelemente sehen dann recht ähnlich aus, wie die Domänen. Hier haben wir Pflichtfelder wie die Texte, die wir befüllen sollten. Damit bleibt die Logik hier recht überschaubar.

LOOP AT ddic_configuration-data_elements INTO DATA(data_element).
  DATA(specification) = operation->for-dtel->add_object( data_element-name
    )->set_package( ddic_configuration-package
    )->create_form_specification( ).

  specification->set_short_description( data_element-description ).
  specification->set_data_type( xco_cp_abap_dictionary=>domain( data_element-domain ) ).

  DATA(label) = COND #( WHEN data_element-label IS INITIAL
                        THEN data_element-name
                        ELSE data_element-label ).

  specification->field_label-short->set_text( CONV #( label ) ).
  specification->field_label-medium->set_text( CONV #( label ) ).
  specification->field_label-long->set_text( CONV #( label ) ).
  specification->field_label-heading->set_text( CONV #( label ) ).
ENDLOOP.

 

Abstrakte Entitäten

Kommen wir zur ersten CDS Entität, die durchaus etwas anspruchsvoller sein können. Der Anfang ist erst einmal gleich, wir definieren eine Spezifikation und setzen die Beschreibung. Dann haben wir aber unterschiedliche Typen, die wir anlegen können. Daher benötigen wir über die Spezifikation unseren Typen, um diesen weiter auszuprägen.

DATA(abstract_entity) = specification->add_abstract_entity( ).

 

Dann übernehmen wir unsere Felder in die Entität. Da wir für jedes Feld ein Datenelement definieren wollen, können wir hier eine einfache Logik verwenden. Die komplexere Typbestimmung bleibt damit in der Domäne. Zuerst fügen wir über ADD_FIELD das Feld hinzu und setzen über SET_TYPE dann den Typen als Datenelement. Der schwierige Teil dabei ist, den passenden Beispielcode zu finden, wie das Feld hinzugefügt wird und auch der Typ. Im ersten Schritt hatten wir es über eine Expression versucht, die aber hinter jedem Datensatz dann ein Komma macht, was bei einer abstrakten Entität zu Fehlern führt.

DATA(cds_field) = abstract_entity->add_field( xco_cp_ddl=>field( field-name )
  )->set_type( xco_cp_abap_dictionary=>data_element( field-data_element ) ).

 

Was fehlt nun eigentlich noch? Die Entität wird uns einen Fehler schmeißen, wenn wir ein AMOUNT oder QUANTITY Feld verwenden und die Einheiten nicht miteinander verlinken. Daher müssen wir im Nachgang Annotationen an den Feldern ergänzen. Dabei werden die Annotationen ohne das @ angegeben und der Wert über den Builder zusammengefügt. Diese Recherche hat auch etwas Zeit gekostet, da die Verwendung nicht gleich offensichtlich war und die ABAP Docs keine Informationen enthielten, wie die Methode und die Werte verwendet werden.

IF field-currency IS NOT INITIAL.
  cds_field->add_annotation( 'Semantics.amount.currencyCode' )->value->build( )->add_string( field-currency ).
ENDIF.

IF field-unit IS NOT INITIAL.
  cds_field->add_annotation( 'Semantics.quantity.unitOfMeasure' )->value->build( )->add_string( field-unit ).
ENDIF.

 

Generierung

Am Ende nicht vergessen auch die Objekte zu generieren. Hier generieren und aktivieren sie in einem Schritt und legen sie im System an. Du kannst sie aber auch erst einmal in einem inaktiven Zustand anlegen lassen, wenn du sowieso noch nacharbeiten hast oder dich um die Fehler kümmern willst.

RETURN put_operation->execute( ).

 

Möglichkeiten

Um noch weitere Optimierungen in den Code zu bekommen, können wir noch einige Dinge optimieren, die dem Verwender weitere Eingaben erleichtern:

  • Defaults - Automatische Defaults für Label, Beschreibungen und Grundtypen, um die Eingaben zu minimieren.
  • Grundtypen - Verwendung von Grundtypen und Datenelemente aus dem Standard (SPRAS, ABAP_BOOLEAN)
  • Fehlerbehandlung - Das Objekt kann nicht angelegt werden, dafür wird ein Protokoll zurückgegeben. Es gibt das Objekt schon oder Pflichteingaben fehlen?
  • JSON - Aktuell bezieht der Generator eine Struktur für die Generierung, allerdings ist es auch recht einfach möglich ein JSON im System oder aus einem Git Repository als Konfiguration zu lesen und zu verwenden.

 

Test

Nachdem wir nun die Logik definiert haben, sollten wir auch einmal die Logik testen und ein paar Objekte anlegen. Dazu verwenden wir einen Unit Test, was aber in diesem Fall nicht Best Practice ist, da ein Test keine Objekte im System ändern sollte bzw. dann auch die entsprechende Einstufung benötigt. Befüllen wir als unsere Struktur für die Konfiguration.

DATA(abstract_entities) = VALUE zif_gen_objects=>abstract_entities(
    ( name        = 'ZGEN_S_TestGeneratedStructure'
      description = 'Automatic generated'
      fields      = VALUE #( ( name = 'KeyField' data_element = 'KEY_FIELD' )
                             ( name = 'SalesVolume' data_element = 'AMOUNT' currency = 'SalesCurrency' )
                             ( name = 'SalesCurrency' data_element = 'CURRENCY' ) ) ) ).

DATA(data_elements) = VALUE zif_gen_objects=>data_elements(
    ( name = 'KEY_FIELD' domain = 'KEY_FIELD' description = 'Key' )
    ( name = 'AMOUNT' domain = 'AMOUNT' description = 'Amount' )
    ( name = 'CURRENCY' domain = 'CURRENCY' description = 'Currency' ) ).

DATA(domains) = VALUE zif_gen_objects=>domains(
    ( name = 'KEY_FIELD' base_type = zif_gen_objects=>domain_types-character length = 7 )
    ( name = 'AMOUNT' base_type = zif_gen_objects=>domain_types-currency )
    ( name = 'CURRENCY' base_type = zif_gen_objects=>domain_types-currency_code ) ).

DATA(config) = VALUE zif_gen_objects=>ddic_configuration( prefix            = 'ZGEN_DEMO_'
                                                          domains           = domains
                                                          data_elements     = data_elements
                                                          abstract_entities = abstract_entities ).

 

Dann erzeugen wir uns über die Factory eine Instanz, übergeben die Daten und lassen uns die Objekte im System erzeugen.

DATA(generator) = zcl_gen_objects_factory=>create_generator( sy-repid ).
DATA(result) = generator->generate_ddic( config ).

 

Nachdem die Logik hoffentlich ohne größere Fehler durchlaufen ist, sollten wir nach einem Refresh im aktuellen Paket unsere Objekte finden.

 

Im Core Data Service wurden die Felder wie gewünscht definiert, die Beziehung zur Währung ist eingetragen und alle Objekte wurden aktiviert. Damit ist der Test für uns erfolgreich.

define abstract entity ZGEN_S_TestGeneratedStructure
{
  KeyField      : zgen_demo_key_field;
  @Semantics.amount.currencyCode: 'SalesCurrency'
  SalesVolume   : zgen_demo_amount;
  SalesCurrency : zgen_demo_currency;
}

 

Vollständiges Beispiel

Die aktuelle Version des Generators findest du in diesem GitHub Repository. Das Repository ist auch Grundlage für den nächsten Artikel, wo wir die ersten Objekte per Konfiguration anlegen wollen, um so einen schnellen Einstieg zu erhalten.

 

Fazit

Die Arbeit mit der XCO Bibliothek ist in einigen Fällen nicht wirklich einfach und wenn wir die API noch nicht so gut kennen, dauert die Suche nach Beispielen und der richtigen Methode schon einmal etwas länger. Am Ende erhalten wir einen ABAP Cloud kompatiblen Generator, der uns in Zukunft etwas Arbeit abnehmen wird.


Enthaltene Themen:
TippABAP in der PraxisXCOGenerator
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 - Logging Performance

Kategorie - ABAP

Wie sieht es eigentlich mit der Performance des BAL Logs in der ABAP Cloud Welt aus? Schauen wir uns dazu drei Lösungen an und messen die Performance in verschiedenen Szenarien.

19.12.2025

ABAP in der Praxis - Fiori Daten fehlerhaft

Kategorie - ABAP

In diesem kleinen Praxisbeispiel schauen wir uns einen Fehlerfall in Fiori an. Hier werden die Daten im UI falsch angezeigt, obwohl alles sonst richtig zu sein scheint. Die Spur führt uns durch den RAP Stack in eine andere Richtung.

10.10.2025

ABAP Tipp - Handling von Funktionsbausteinen

Kategorie - ABAP

Wie gehst du eigentlich mit Funktionsbausteinen und der Fehlerbehandlung innerhalb von ABAP um? In diesem kleinen Tipp schauen wir uns die Behandlung auch im Rahmen von RFC an.

26.08.2025

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