MDD ist seit einiger Zeit ein heißes Thema. Mit der Einführung leistungsstarker Tools wie EMF und oAW ist es möglich geworden, die Vorteile der modellgetriebenen Entwicklung tatsächlich zu nutzen, ohne zu viel Zeit und Geld für die Tools aufzuwenden. MDD hat bei bestimmten Projekttypen längst die Gewinnschwelle zwischen intellektuellem Aufwand und Entwicklungsbeschleunigung erreicht. Es ist nicht die Lösung für alle Probleme, aber es ist eine wirklich leistungsstarke Technik mit einer Reihe stabiler und bewährter Tools.

SAP R/3-Systeme sind gut etabliert – meist als ERP- oder CRM-Systeme anerkannt, aber tatsächlich zu viel mehr fähig. Was viele außerhalb der R/3-Welt nicht wissen, ist, dass jedes R/3-System mit einer vollwertigen Entwicklungsumgebung ausgestattet ist. Es gibt eine fast schon traditionelle Kluft zwischen „R/3-Entwicklern der alten Schule“ und „modernen“ Nicht-R/3-Entwicklern, und dies könnte stark dazu beitragen, dass es bisher keinen wirklichen Kontakt zwischen der Welt von MDD und R/3 zu geben scheint – zumindest nicht im Sinne von „Toolkits zur Steigerung der Entwicklerproduktivität“. SAP selbst verlässt sich stark auf Modellierungs- und Generierungstechniken, aber nur wenig davon ist als Tool für den durchschnittlichen Entwickler verfügbar. Warum ist es so schwierig, das „Beste aus beiden Welten“ zu bekommen – liegt es nur an fehlendem Interesse und fehlenden Informationen oder gibt es ernstere Probleme zu lösen? Begleiten Sie mich und sehen Sie, ob und wie modellbasierte Entwicklungspraktiken auf die SAP R/3-Entwicklung angewendet werden können und welche Probleme dabei gelöst werden müssen .

Dieser Artikel basiert auf den Inhalten meines Vortrags bei der Special Interest Group Model Driven Software Engineering (SIG-MDSE) im Mai 2008.

In einem durchschnittlichen MDD-Projekt in einer Java-Umgebung verwenden wir generative Technologien, um bestimmte Artefakte automatisch zu erstellen. Dabei handelt es sich in der Regel um Quellcode-Dateien für Klassen und Schnittstellen, Steuerungs- und Beschreibungsdateien (z. B. XML-Dateien zur Steuerung von Ant-Builds oder zur Steuerung der Serialisierung) oder einige sprachabhängige Eigenschaftendateien, die alle in Ordnern angeordnet sind, die die Paketstruktur darstellen. Mit anderen Worten: Das Ergebnis der Generierungsschritte sind meist Textdateien, und der Name, die Erweiterung und der Inhalt der Datei müssen berücksichtigt werden, wenn festgelegt wird, was mit der Datei geschehen soll. Die Rolle der Datei mit dem Namen MANIFEST.MF kann beispielsweise nur anhand ihres Namens bestimmt werden, aber Sie müssen sich den Inhalt von FooBar.java ansehen, um herauszufinden, ob es darin eine Klasse FooBar oder eine Schnittstelle FooBar gibt. Für die Codegenerierung ist dies ein Vorteil – Sie können dasselbe Toolset verwenden, um alle beteiligten Dateien zu generieren.

In einem SAP R/3-System sieht das anders aus. Die Matrix hat uns bereits gelehrt, dass es keinen Löffel gibt, und in ähnlicher Weise gibt es in einem R/3-System keine Dateien. Klassen und Schnittstellen werden in Datenbanktabellen definiert und beschrieben, Attribute und Methoden werden in Tabellen gespeichert, die auf diese Tabellen verweisen, und Methodenparameter – Sie verstehen schon. Sogar der Quellcode der Methoden wird in Datenbanktabellen gespeichert. Sehen Sie sich die folgenden Screenshots an, um einen Eindruck davon zu bekommen, wie eine Klasse in der R/3-Entwicklungsumgebung definiert und implementiert wird.

ABAP-Klassendefinition und Methoden-Implementierung

ABAP-Klassendefinition und Methoden-Implementierung

Als zusätzliche kleine Komplikation gibt es Teile von ABAP, die keine Objektorientierung unterstützen – für die bildschirmbasierte Interaktion benötigen Sie beispielsweise immer noch prozedurale Programme mit einer anderen internen Struktur. Noch schlimmer: Es gibt viele Artefakte, die überhaupt keine Ähnlichkeit mit der Programmierung haben, aber dennoch sehr häufig generiert werden müssen: Datenstrukturen (sowohl für den vorübergehenden Gebrauch als auch für die dauerhafte Speicherung) und die Elemente, aus denen die Strukturen bestehen, Dialogelemente, sprachabhängige Texte, Sperren und Protokollierungsobjekte usw. Viele dieser Artefakttypen haben eine komplexe innere Struktur und verweisen auf andere Objekte. Sie sind für die Entwicklung unerlässlich. Diese Objekte können nicht ausgelassen werden, da sie ebenso Teil der Anwendung sind wie eine Klasse oder eine Schnittstelle.

Sie haben vielleicht bemerkt, dass wir gerade auf eine weitere Komplikation gestoßen sind – die Verwaltung von Low-Level-Abhängigkeiten. Wenn Sie es nicht bemerkt haben, brauchen Sie sich nicht zu schämen, denn dies ist bei „traditionellen“ MDD-Projekten kein Problem. Nehmen Sie das folgende UML-Diagramm als Beispiel:

UML-Beispiel (Java)

UML-Beispiel (Java)

In einer Java-Umgebung müssen wir drei Textdateien erstellen und sie im entsprechenden Verzeichnis ablegen. Der Java-Compiler kümmert sich dann um den Rest. Wir können die Dateien in beliebiger Reihenfolge erstellen, da die Kompilierung erst nach Abschluss der Erstellung beginnt. Sie haben es vielleicht schon erraten: In einem R/3-System ist das anders. Zunächst müssen wir zwischen der Erstellung und der Aktivierung eines Objekts unterscheiden. Ich möchte Sie nicht zu sehr in die Interna der R/3-IDE einführen – vorerst reicht es zu wissen, dass Objekte in einem „inaktiven“ Zustand erstellt und gespeichert werden können, aber erst verwendet werden können, wenn sie aktiv sind. Inaktive Objekte können unvollständig sein und sogar Syntaxfehler enthalten, die vor der Aktivierung natürlich entfernt werden müssen. Sobald ein aktives Objekt geändert wird, wird die aktive Version beibehalten und eine zusätzliche inaktive Version erstellt. Wenn diese Version aktiviert wird, ersetzt sie die vorherige aktive Version.

Beim Erstellen von Artefakten, die auf andere Objekte verweisen, gibt es zwei wichtige Regeln:

  • Objekte, die in der Definition verwendet werden, müssen beim Speichern der Definition in einem aktiven Zustand vorliegen. (Beispiel: Datentypen von Parametern in einer Methodendefinition.)
  • Objekte, die in einer Implementierung verwendet werden, müssen in einem aktiven Zustand vorliegen, wenn das verweisende Objekt aktiviert wird. (Beispiel: Datentypen, die in einer Methodenimplementierung verwendet werden.)

Wenden wir diese Regeln nun an und nehmen das folgende UML-Diagramm als Beispiel:

UML-Beispiel (ABAP)

UML-Beispiel (ABAP)

Wie Sie sehen können, implementiert CL_MY_SECOND_CLASS das Interface IF_MY_INTERFACE. Diese Beziehung ist Teil der Definition der Klasse, sodass IF_MY_INTERFACE vorhanden sein muss, bevor wir die Klasse überhaupt speichern können. (Für einen menschlichen Entwickler trifft das eigentlich nicht zu – Sie können natürlich Interface-Implementierungen zu vorhandenen Klassen hinzufügen. Für einen Programmgenerator gilt das nicht – dazu kommen wir später.) IF_MY_INTERFACE definiert eine Methode mit einem Parameter, der als Referenz auf CL_MY_FIRST_CLASS typisiert ist. Auch hier muss CL_MY_FIRST_CLASS vorhanden sein, bevor wir die Schnittstelle speichern können. Dies führt zu der folgenden obligatorischen Generierungsreihenfolge:

  1. CL_MY_FIRST_CLASS
  2. IF_MY_INTERFACE
  3. CL_MY_SECOND_CLASS

Wenn wir uns nicht an diese Reihenfolge halten, schlägt die Generierung fehl – und wir haben bisher noch keine einzige Codezeile generiert …

Bei kleinen Zielanwendungen ist es theoretisch möglich, die Reihenfolge der Generierung manuell festzulegen, aber bei größeren Anwendungen mit einer variierenden Anzahl und Struktur von Objekten ist dies keine Option mehr. Andererseits ist es nicht ratsam, den Generator oder die Vorlagen mit Anweisungen über die Reihenfolge, in der die Objekte generiert werden müssen, zu „verunreinigen“. Eine praktikable Lösung ist die Durchführung einer zweistufigen Generierung: Zunächst wird ein transientes Zielmodell generiert, das die zu generierenden Objekte in einer Struktur enthält, die dem R/3-System so genau wie gewünscht entspricht. Der Inhalt dieses Zwischenmodells kann in beliebiger Reihenfolge generiert werden, und aus dem Inhalt können viele intrinsische Low-Level-Abhängigkeiten bereits abgeleitet werden. Andere Abhängigkeiten können bei Bedarf während der Modell-zu-Modell-Transformation hinzugefügt werden. In einem zweiten Schritt kann der Inhalt des Zwischenmodells als Abhängigkeitsstruktur angeordnet werden (die dann eine Reihe von DAGs sein sollte – sonst gibt es Probleme…) und der Reihe nach (in einem Tiefendurchlauf) verarbeitet werden.

Dieser Ansatz beseitigt auch das oben beschriebene Problem mit dem Artefakttyp auf elegante Weise – es ist möglich, all diese Strukturen und Objekttypen im Metamodell des Zwischenmodells zu kombinieren und sie in den Generierungsprozess einzubeziehen. Es ist auch eine gute Idee, dieses Zwischenmodell in einer Datei zu speichern – es ist viel einfacher und schneller, den Generierungsprozess zu debuggen, wenn man sich die Ergebnisse direkt ansehen kann. Jetzt können wir auch unsere geliebten Standard-MDD-Tools verwenden, um die Aufgabe auszuführen, obwohl wir für die Quellcodegenerierung eine leichte Abweichung benötigen: Zuerst generieren wir ein Zwischenmodell (z. B. mit Xtend), das alles außer dem Quellcode enthält, dann generieren wir den Quellcode in separate Textdateien (z. B. mit Xpand) und erst dann führen wir die beiden zusammen, indem wir den Methodencode in mehrzeilige String-Eigenschaften des Zwischenmodells einfügen. So könnte ein solches Zwischenmodell aussehen:

Zwischenmodell für Repository-Objekte

Zwischenmodell für Repository-Objekte

Übrigens haben wir gerade ein wesentliches Problem ausgelassen – woher bekommen wir die Namen unserer Artefakte? Auch dies ist kein Problem für Java-Projekte, vor allem, weil Sie so viele Klassen mit den Namen Foo oder Bar haben können, wie Sie möchten, solange sie sich in verschiedenen Paketen befinden. Das R/3-System enthält auch etwas, das „Paket“ genannt wird, aber im Gegensatz zu den meisten anderen Entwicklungsumgebungen bieten Pakete in ABAP keine separaten Namensräume. Das zentrale Objekt-Repository der Entwicklungsumgebung ist eine flache Datenbank aller Objekte, die erfordert, dass Objektnamen innerhalb des gesamten Systems eindeutig sind – es können nicht zwei Klassen CL_FOO gleichzeitig existieren. Mit einer maximalen Länge von 30 Zeichen für den Klassennamen und nur 16 Zeichen für Tabellennamen wird die Erfindung der Artefaktnamen zu einem echten Problem – aber eines, das für jedes Projekt separat gelöst werden muss.

Kehren wir zu unserem ausgefallenen Zwischenmodell zurück. Jetzt haben wir ein ziemlich genaues Bild davon, was wir erstellen wollen, und wissen sogar, in welcher Reihenfolge wir es erstellen müssen. Was fehlt, ist eine konsistente Methode, um unsere Träume tatsächlich wahr werden zu lassen. Es gibt praktisch keine offiziellen Schnittstellen, die Schreibzugriff auf die R/3-Entwicklungsumgebung ermöglichen – selbst die Schnittstellen zum Lesen sind nur in homöopathischen Dosen vorhanden. Dafür gibt es eine ganze Reihe sehr guter Gründe, aber unterm Strich müssen wir diese Tatsache einfach akzeptieren. Es gibt keine offizielle Unterstützung für die Generierung der meisten Objekttypen, und die wenigen vorhandenen Schnittstellen sind an einigen Stellen sogar defekt.

Jetzt ist es möglich, eigene Schnittstellen zu erstellen. Das ist mühsam, sogar gefährlich (man kann das gesamte System ruinieren, wenn man nicht weiß, wo man herumstochern), aber es ist möglich. Ich habe es getan, und es gibt einen guten Grund, warum ich diese Schnittstellen nicht online stelle (abgesehen von den IP-Problemen) – wenn etwas schief geht und ein System ausfällt, steht mein Name auf dem Programm, das es kaputt gemacht hat. Aber im Ernst – um eine Reihe von Schnittstellen aktiv zu warten und zu unterstützen, wären umfangreiche Tests (sprich: Arbeitskräfte) und eine Landschaft aus mehreren Systemen mit mehreren R/3-Versionen (sprich: finanzielle Mittel) erforderlich, und ich habe weder das eine noch das andere. Ich prüfe derzeit eine zweite Möglichkeit: die Umwandlung des Zwischenmodells in eine Datei, die SAPlink importieren kann. Aufgrund der Struktur von SAPlink-Dateien ist dies eine ziemliche Herausforderung, und ich habe noch nichts Vorzeigbares.

Bitte schreiben Sie mir, wenn Sie an diesem Thema interessiert sind – ich würde gerne einige Ideen besprechen und würde mich über jeden Kommentar.