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

RAP - Singleton Pattern

216

Das Singleton Pattern in RAP gibt es schon länger, doch wie genau funktioniert es und wie kannst du es auch für andere Use-Cases nutzen? Schauen wir uns dazu die Details in diesem Artikel an.

Werbung


In diesem Artikel schauen wir uns das Singleton Pattern an, wie es aufgebaut ist und wofür du es noch verwenden kannst. Dazu werden wir in ein Beispiel einsteigen.

 

Einleitung

Das ABAP RESTful  Application Programming Model ist das neue Modell in ABAP, um Cloud Ready und Clean Core Anwendungen zu erstellen. Mit RAP lassen sich neben Anwendungen, auch Schnittstellen für den internen und externen Gebrauch zur Verfügung stellen. Mit den neusten Features ist RAP sehr flexibel was den Aufbau und die Nutzung angeht, weshalb wir die Anwendungen in verschiedene Pattern aufteilen möchten.

 

Aufbau

Das "Singleton Pattern" wird so genannt, weil es uns genau eine Instanz zurückliefert, die wir dann bearbeiten können. Damit umgehen wir bestimmte Limitations, die zum Beispiel beim Arbeiten mit dem List Report auftreten können. Ein typisches Beispiel dafür ist auch die Business Configuration, die jeweils mit dem Singleton Pattern generiert wird.

 

Dazu die folgenden Merkmale zur Abgrenzung:

  • Root und Child über fixen Schlüssel verbunden
  • Es wird genau eine Instanz zurückgeliefert (Root Ebene)
  • List Report wird für die Bearbeitung übersprungen
  • Bearbeitung der Daten direkt in der Tabelle (Massenbearbeitung)
  • Consumption Layer Optional

 

 

Beispiel

Das erste Beispiel, welches dir in den Sinn kommen sollte, ist das Singleton Pattern, das verwendet wird, um eine Business Configuration zu generieren. Hintergrund ist dabei, dass wir direkt die Tabelle komplett editieren wollen und dabei nicht jeweils pro Zeile auf die Object Page springen wollen. Dies wird erreicht, indem wir direkt vom List Report über das Singleton auf die Object Page springen und dort im Edit-Modus ankommen. Dies hat den Vorteil, dass wir die Tabelle im Fullscreen pflegen können. Ein weiteres Beispiel, ähnlich dessen, ist auch ein Formular. Deshalb bauen wir uns im nächsten Beispiel ein Formular für die Dateneingabe auf, welches der User am Ende abschicken kann.

 

Modellierung

In diesem Kapitel generieren wir uns zuerst das Datenmodell bis hoch zum Service, um unserer Anwendung alle nötigen Daten zur Verfügung zu stellen.

 

Datenmodell

Das Datenmodell gestalten wir relativ einfach. Wir haben auf der obersten Ebene den Singleton. Hier haben wir zusätzlich administrative Felder, die wir für unser Draft-Handling benötigen. Hier können wir aber auch zusätzlich Daten des Eingabeformulars speichern. Diese kann der User später über das Formular eingeben. Direkt darunter befindet sich eine Eingabetabelle für Teilnehmer. Hier wollen wir einfach nur die Namen eingeben lassen und können über das Formular neue Einträge hinzufügen. Hier ist zum Beispiel nicht vorgesehen, eine Object Page anzulegen, da alles auf einer Seite passieren soll. Im Gegensatz zum Singleton verwenden wir hier auch noch eine zusätzliche User-ID, um die Daten der unterschiedlichen User über die verschiedenen Formulare abgrenzen zu können.

 

Im Datenmodell oben dienen die Tabellen lediglich dazu, Struktur zu geben. Hier wollen wir eigentlich keine Daten speichern, sondern diese sollen später über den Save-Button in einem gesonderten Prozess behandelt werden.

 

Core Data Service

Den Core Data Service modellieren wir dann auf der Tabelle, um die Struktur zu übernehmen. Den eigentlichen Select ziehen wir hier wieder aus der Language-Tabelle. Dies ist ein Standard, wenn es um das Singleton in der Business Configuration geht. So ist immer sichergestellt, dass wir genau einen Eintrag erhalten. Dadurch, dass wir aber noch die User-ID verwenden, sollten wir sicherstellen, dass hier auch immer unser aktueller User vorhanden ist. Eine weitere wichtige Eigenschaft ist der semantische Key: Diesen definieren wir auf dem Navigation Key als einheitliches Kriterium für die Navigation. Diesen benötigst du später, um in den Editor zu wechseln und über den Navigation Key zu navigieren, da wir nicht immer den aktuell angemeldeten User kennen.

@ObjectModel.semanticKey: [ 'NavigationKey' ]
define root view entity ZBS_R_SinEntityTP
  as select from    I_Language
    left outer join zbs_tst_single as _Single on 0 = 0
  composition of exact one to many ZBS_I_SinParticipant as _Participant
{
  key 1                             as NavigationKey,
  key $session.user                 as UserID,
      _Single.name                  as UserName,
      _Single.random_number         as RandomId,
      _Single.local_created_by      as LocalCreatedBy,
      _Single.local_last_changed_by as LocalLastChangedBy,
      _Single.local_last_changed    as LocalLastChangedAt,
      _Single.last_changed          as LastChangedAt,
      _Participant
}
where
  I_Language.Language = $session.system_language

 

Die Child-Entität der Teilnehmer modellieren wir dann gegen die zweite Tabelle und definieren die "to-parent" Association, damit diese beiden miteinander verbunden sind. Den Schlüssel des Singletons definieren wir lokal, ebenso wie die User-ID. Diese kommen soweit nicht aus dem Datenmodell bzw. der Tabelle unterhalb.

define view entity ZBS_I_SinParticipant
  as select from zbs_tst_sinpa
  association to parent ZBS_R_SinEntityTP as _MainForm on  $projection.NavigationKey = _MainForm.NavigationKey
                                                       and $projection.UserID        = _MainForm.UserID
{
  key uuid          as ParticipantKey,
      participant   as Participant,
      @Consumption.hidden: true
      1             as NavigationKey,
      @Consumption.hidden: true
      $session.user as UserID,
      _MainForm
}

 

Verhaltensdefinition

Die Verhaltensdefinition modellieren wir dann auf unserem Datenmodell. Dabei handelt es sich um eine Managed-Implementierung, die erst einmal so weit nicht vom Standard abweicht. Wir verwenden ebenfalls eine Draft Query, um den spezifischen Draft für den aktuell angemeldeten User zu erhalten. Dies ist wichtig, da wir nicht alle Datensätze zur ID (Singleton) zurückgeben wollen, sondern nur die Datensätze, die dem User gehören und die du auch in deinem Formular sehen sollst. Zusätzlich definieren wir den Unmanaged Save, da wir später beim Speichern der Daten die Informationen nicht in den Tabellen speichern wollen, sondern zum Beispiel für eine Weiterverarbeitung in einer zweiten Tabelle ablegen möchten.

define behavior for ZBS_R_SinEntityTP alias Singleton
draft table zbs_tst_singled query ZBS_I_SinSingletonDraft
lock master total etag LastChangedAt
authorization master ( instance )
with unmanaged save

 

Auf der Ebene der zweiten Entität definieren wir noch eine Determination, um die User-ID bei der Anlage zu befüllen. Grundsätzlich ist die Verbindung über den Core Data Service hergestellt, aber oft kommt das Feld im Draft ohne User-ID an. Deshalb müssen wir hier zusätzlich noch die Information befüllen, damit über die Draft-Query nur die passenden Datensätze zurückgegeben werden.

determination FillUserID on modify { create; }

 

Verhaltensimplementierung

In der Verhaltensimplementierung benötigen wir zwei Methoden, die wir implementieren müssen. Als Erstes definieren wir die Determination, die für uns den Usernamen per Draft aktualisiert. Hier reicht es, ein EML-Statement abzusetzen, um das Update der Daten anzustoßen. Wir übergeben die User-ID und setzen das Control-Flag, um den Datensatz nach Erstellung auf der Datenbank zu aktualisieren.

MODIFY ENTITIES OF ZBS_R_SinEntityTP IN LOCAL MODE
       ENTITY Participant
       UPDATE FROM VALUE #( FOR key IN keys
                            ( %tky            = key-%tky
                              UserID          = sy-uname
                              %control-UserID = if_abap_behv=>mk-on ) ).

 

Als Zweites müssen wir die Methode SAVE_MODIFIED implementieren, um unsere eigentliche Speicherlogik zu definieren. Dabei legen wir die Daten in einer neuen Tabelle ab und simulieren damit eine Weiterverarbeitung der Daten. Zuerst summieren wir alle Teilnehmer, um die Anzahl der eingegebenen Teilnehmer zu erhalten, und aktualisieren zum Abschluss dann die Daten auf der Datenbank, um zu sehen, dass der Speichervorgang funktioniert hat.

LOOP AT update-singleton INTO DATA(new_dataset).
  DATA(number_participants) = 0.
  LOOP AT create-participant TRANSPORTING NO FIELDS WHERE NavigationKey = new_dataset-NavigationKey.
    number_participants += 1.
  ENDLOOP.

  INSERT zbs_sin_result FROM @( VALUE #( uuid                   = xco_cp=>uuid( )->value
                                         created_by             = new_dataset-NavigationKey
                                         created_at             = utclong_current( )
                                         user_name              = new_dataset-UserName
                                         random_id              = new_dataset-RandomId
                                         number_of_participants = number_participants ) ).
ENDLOOP.

 

Fiori App

Um ein gutes Formular zu erhalten, müssen wir die Fiori-Anwendung generieren und ins System deployen, um sie am Ende auch testen zu können.

 

Generator

Standardmäßig verwenden wir hier wieder den Generator „from Template“ und generieren uns eine Fiori-Elements-Anwendung. Dabei verwenden wir in diesem Fall das „Form Entry Object Page“-Element und nicht den List Report, wie wir es sonst bisher getan haben. Hintergrund dazu ist, dass es sich hierbei um eine Object Page handelt und wir direkt nach dem Einstieg in die App auf der Object Page landen und nicht in einer Liste. Daher hält das Template einige Spezialitäten bereit.

 

Als Service verwenden wir hier unseren Singleton-Service und definieren zusätzlich eine Deployment- und Fiori-Launchpad-Konfiguration, da wir später nach dem Deployment die Anwendung auch im Launchpad testen wollen.

 

Navigation

Nach dem Generieren der Anwendung gehen wir in die Page Map. Dort löschen wir die Object Page für die Teilnehmer, sollte diese vorhanden sein bzw. durch die Generierung erzeugt worden sein. Dies hat den Hintergrund, dass wir keine Navigation wollen, wenn wir auf den Eintrag klicken. Ebenfalls sollen neue Datensätze direkt in der Tabelle angelegt werden, ohne eine entsprechende Navigation auszulösen. Hier sollst du am Ende in einem Formular arbeiten.

 

Ebenfalls navigieren wir in die Object Page des Singletons und prüfen, ob es hier Warnungen gibt. In unserem Fall zum Beispiel sehen wir eine Warnmeldung, die sich bis runter auf die Teilnehmertabelle zieht. Hier kannst du die Elemente aufklappen, bis du ganz unten angekommen bist.

 

Markiere dann die Zeile mit der Tabelle und schaue in den Eigenschaften, wo die Warnmeldung herkommt. In unserer App wurde zum Beispiel der Typ der Tabelle nicht definiert. Das System weiß nicht, ob es eine Responsive oder eine Grid Table ist. Hier definieren wir den Standard für die Responsive Table. Wenn wir bestätigen, sollte die Warnmeldung verschwinden.

 

Anpassung

Nun führen wir die wichtigste Anpassung durch, damit unsere Anwendung sauber funktioniert und die Navigation am Anfang auch durchgeführt wird. Dazu navigieren wir in den "webapp"-Folder und suchen dort nach der Datei "Component.js". Diese wurde automatisch mit dem Projekt generiert und mit erstem Content befüllt. Hier solltest du eine Funktion für "getStartupParameters" finden, die bereits mit dem "preferredMode" CREATE vorbelegt wurde. Diese Eigenschaft bedeutet, dass beim Starten der App automatisch der Modus CREATE aktiviert und ein neuer Datensatz erzeugt wird. Da wir immer nur einen Datensatz haben, würde es zu einer Fehlermeldung kommen, da der Datensatz bereits existiert und ein Create nicht mehr möglich ist.

getStartupParameters: function() {
    return Promise.resolve({
        preferredMode: ["create"]
    });
}

 

Dazu setzen wir den "preferredMode" auf EDIT. Das bedeutet, dass wir sofort im Edit Mode landen, wenn wir auf der Object Page angekommen sind. Zusätzlich müssen wir jetzt noch den eigentlichen Schlüssel mitgeben, für den wir in den Edit Mode gehen wollen. Hier kommt nun unser semantischer Schlüssel zum Einsatz, da wir die eigentliche User ID nicht kennen. Aber wir wissen, dass wir einen Datensatz erhalten, bei dem der NavigationKey auf 1 gesetzt wurde. Entsprechend setzen wir dieses Attribut für die Navigation.

getStartupParameters: function() {
    return Promise.resolve({
        preferredMode: ["edit"],
        NavigationKey: "1"
    });
}

 

Hinweis: Zusätzlich ist zu beachten, dass der NavigationKey auf dem semantischen Schlüssel basiert. Hier haben wir einiges ausprobiert und mit dem Debugger geschaut: Dieser muss immer einstellig sein, da es intern im JavaScript-Framework eine Prüfung gibt, die die Länge des angegebenen Schlüssels überprüft. Gibst du einen längeren Schlüssel an, weil deine Entität diesen hat, kann das Framework keine Zuordnung mehr durchführen und du landest auf einer leeren Seite.

 

Test

In unserem Test starten wir die Anwendung aus dem Launchpad heraus und landen direkt in der Eingabe, ohne eine weitere Taste drücken zu müssen. Wir geben die Informationen im oberen Bereich ein, wie den Organisator und eine zufällige Nummer, und können dann über den Create-Button über der Tabelle neue Einträge anlegen, die wir dann auch mit zwei Teilnehmern befüllen. Sind wir damit fertig, können wir über SAVE die Speicherlogik aufrufen und die Verarbeitung anstoßen.

 

Prüfen wir dann unsere Tabelle, die für die Dummy-Verarbeitung verantwortlich ist, finden wir dort den neuen Datensatz mit der Nummer, dem Namen und der Anzahl der Teilnehmer, die wir gespeichert haben. Damit hat der Prozess soweit funktioniert. Wie du bereits oben gesehen hast, ist das Formular nach dem Speichern erst einmal geschlossen und du kannst keine weitere Eingabe vornehmen. Hier reicht es, einmal Refresh zu drücken und die Anwendung neu zu laden, schon ist das Formular direkt wieder offen und kann für die nächste Eingabe genutzt werden.

 

Vollständiges Beispiel

Das vollständige Beispiel findest du bei uns im GitHub Repository und über den Commit findest du alle Änderungen. Innerhalb des Repository befindet sich das Beispiel im Paket ZBS_DEMO_RAP_PATTERN_SINGLETON. Die App wurde in das Fiori Paket deployt, um sie testen zu können.

 

Zusammenfassung

Das Singleton-Pattern hat die Eigenschaft, genau einen Datensatz zurückzugeben, um dir die Möglichkeit zu geben, an demselben Datensatz weiterzuarbeiten. Dies wird sehr oft für die Business Configuration verwendet, da hinter der Business Configuration auch ein Sperrobjekt steht und zum Beispiel auch ein Transportauftrag, der dann im Header gespeichert wird, während die Einträge gepflegt werden.

Eine zweite Möglichkeit, die wir in diesem Artikel gezeigt haben, ist eine Formulareingabe, die natürlich nicht nur für einen User funktionieren soll, sondern für unterschiedliche User. Dies ist möglich, indem wir noch ein User-Feld ergänzen und dann mit dem semantischen Schlüssel arbeiten. Zusätzlich ist unsere Anwendung auch noch draft-enabled. Das heißt: Wenn du die Daten eingibst, das Formular verlässt und später wieder aufrufst, sind bereits alle Daten vorbefüllt und du kannst genau dort weiterarbeiten, wo du aufgehört hast. Hier kannst du auch noch zusätzliche Aktionen implementieren, um das Formular per Klick auf wieder zu bereiningen in dem du die Felder im Draft löscht.

 

Fazit

Neben der Verwendung im Standard ist das Singleton Pattern vor allem für Formulare sehr gut verwendbar. Es bietet Vorteile wie eine zentrale Instanz zum Speichern, aber auch zum Sperren und zur Ablage von weiteren Informationen. Vor allem ist es aber ein Zwischenschritt, um auf die eigentliche Object Page für die Bearbeitung zu kommen.

 

Weitere Informationen:
SAP Help - Preferred Mode


Enthaltene Themen:
RAPBTPPatternSingleton
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.


RAP - Sales App Learnings

Kategorie - ABAP

Das Tutorial für die Sales App ist zu Ende und dieses Mal wurde eine große Bandbreite an Themen und Bereichen adressiert. In diesem Artikel schauen wir auf das Gelernte und wichtige Punkte.

08.09.2026

052: 10 Minutes from Start to Launchpad

Kategorie - YouTube

In dieser Folge schauen wir uns an, wie wir innerhalb von 10 Minuten eine RAP Anwendung erstellen können, diese generieren und bereitstellen und am Ende fertig im Launchpad aufrufen.

07.09.2026

RAP - Navigation (App to App)

Kategorie - ABAP

Wie kannst du eigentlich innerhalb deiner Fiori Anwendung auf eine andere Anwendung navigieren, ohne das der User die App manuell wechseln muss? Schauen wir uns die Navigation zwischen Fiori Apps an.

01.09.2026

ABAP in der Praxis - Setup App erstellen

Kategorie - ABAP

Keine SAP GUI mehr? Wie erstellen wir dann eigentlich eine statische Setup App und bleiben auf ABAP Seite trotzdem flexibel für die weitere Implementierung? Hier ein praktisches Beispiel.

28.08.2026

RAP - Nativer Datei-Upload

Kategorie - ABAP

Du willst eine Datei in deine RAP Application hochladen und im Anschluss verarbeiten? Die offizielle Lösung für Up- und Download lässt noch warten, mit einem kleinen Umweg ist es aber bereits Heute möglich.

18.08.2026