MDA-konforme Generierung

Generation MDA

Wolfgang Fahl, Markus Guske, Oliver Pospisil

In diesem Artikel wird gezeigt, wie durch die Kombination von MDA-konformer Softwareentwicklung und dem Einsatz von Generierungstechnologie die Entwicklung von Softwaresystemen erleichtert und beschleunigt werden kann. Gerd Wüthrich hat bereits im Artikel Der Klassen-Klon [1] einen Ansatz vorgestellt, wie mit Hilfe von XML-codierten Informationen und der XSLT-Transformation Java-Code generiert werden kann. Dabei wurden bereits die Vorteile, die durch den Einsatz von Generatoren entstehen, dargestellt. Den Nutzen von MDA-konformer Softwareentwicklung hat Philip Stube in seinem Artikel Einführung in die Model Driven Architecture (MDA) ausführlich erklärt [2].

Warum ist Plattformunabhängigkeit wichtig?

Die Veteranen unter den Softwareentwicklern haben es in ihrem Berufsleben meistens schon mehrfach erlebt. Aufgrund der immer schnelleren technischen Weiterentwicklung in der Computerindustrie wird versucht, die Möglichkeiten der neuen Hardware und der neuen Entwicklungsmethoden durch Anpassung der bestehenden Softwaresysteme auszunutzen. Oftmals geht diese Anpassung mit einem vollständigen Wechsel der Entwicklungs-Plattform einher.

Die fachlichen Anforderungen haben sich im Gegensatz zum technischen Wechsel oftmals gar nicht oder wenig verändert. Dennoch müssen sie in den meisten Fällen von Hand neu implementiert werden, weil die fachlichen und technischen Bestandteile eines Systems nicht getrennt wurden. Durch eine neue und zunächst ungewohnte Programmiersprache kommt die Entwicklungsmannschaft nicht in der gewünschten Geschwindigkeit voran. Wie viel einfacher wäre es, wenn sich der Fokus der Entwicklung nur auf die neue Systemarchitektur konzentrieren könnte? Die Antwort hierauf ist der Einsatz von Generatoren.

Ob nun die Computerhardware von einem Hersteller zum anderen geändert werden soll, das Betriebssystem gewechselt wird, das Datenbanksystem, das Netzwerksystem oder die Programmiersprache – in den meisten Fällen ist die zugehörige Software sofort betroffen und muss angepasst werden. Plattformunabhängigkeit zu erreichen bedeutet zunächst einmal, die plattformspezifischen Teile einer Software unabhängig vom eigentlichen fachlichen Inhalt der Software zu entwickeln.

Dies führt bereits zu einer „besseren“ Software, denn die Schnittstellen der Software zu den plattformspezifischen Funktionen sind sauber vom fachlichen Inhalt getrennt und schon damit wird die Software besser wartbar. Es besteht nun die Möglichkeit, die Plattform zu ändern und den daraufhin notwendigen Aufwand zur Änderung der Software zu minimieren. Dies ist sogar für die Änderung der Programmiersprache möglich, wenn die Software programmiersprachenunabhängig geplant und spezifiziert wird.

UML als plattformunabhängige Planungsgrundlage

Die Unified Modelling Language (UML) bietet die wesentlichen Elemente, um eine Softwarelösung plattformunabhängig zu planen und spezifizieren. In UML können zunächst einmal Packages, Klassen, Interfaces, Vererbung, Methoden und Exceptions festgelegt werden. Für die in der Praxis anzutreffenden Elemente der UML gibt es in allen gängigen objektorientierten Sprachen wie z.B. Java eine direkte Unterstützung.

Eine Gegenüberstellung soll dies praxisnah erläutern:

UML-Konzept Korrespondierendes Java-Konzept
UML-Package Java-Package
UML-Klasse Java-Klasse
UML-Interface Java-Interface
UML-Vererbung Java-Vererbung
UML-Methode Java-Methode
UML-Exception Java-Exception
UML-Attribut Java-Attribut, sowie Getter und Setter Methoden
1:1-Beziehungen Java-Attribut
Zu n-Beziehungen z.B. Java-Collection-Interface

Die UML-Konzepte Package, Klasse, Interface, Vererbung, Methode und Exception können 1 zu 1 in Java umgesetzt werden.

Für die UML-Elemente „Attribut“ und „Beziehung“ muss bei der Umsetzung in eine Programmiersprache etwas mehr Aufwand getrieben werden. Zu einem Attribut wird in der Regel ein Attribut sowie entsprechende Zugriffmethoden implementiert.

Bei Beziehungen bestimmt die „Kardinalität“ der Beziehung, wie die Abpictureung in eine Programmiersprache erfolgt. Es ist zwischen 1:1-, 1:N- und N:M-Beziehungen zu unterscheiden und ggf. noch darauf zu achten, ob die Beziehung optional ist, d.h. ob eine Seite der Beziehung existieren muss oder nicht.

Bei Beziehungen werden die Seiten der Beziehung mit Rollennamen belegt. Eine 1:1-Beziehung wird auf jeder Seite der Beziehung behandelt wie ein Attribut d.h. es wird ein „Verweisattribut“ auf die Nachbarklasse in der Programmiersprache erzeugt. Diese Beziehung hat zwei „zu 1“-Rollen.

Für eine 1:N-Beziehung wird in Blickrichtung „1“ wie bei einer der „zu 1“-Rollen der 1:1-Beziehung mit einem Verweisattribut gearbeitet. In Blickrichtung „N“ ist es erforderlich, mehrere Funktionen zu unterstützen – mindestens muss das Hinzufügen und Löschen von Elementen sowie die Abfrage der gesamten Menge der N „Nachbarn“ ermöglicht werden. Elegant ist z.B. für die N-Richtung ein „Collection“-Interface anzubieten.

Für eine N:M-Beziehung werden in beide Richtungen die oben genannten Funktionen für die „zu-N“-Richtung benötigt, auch hier ist die „Collection“-Unterstützung ein sinnvoller Weg.

Für nicht objektorientierte Sprachen wie C oder COBOL lassen sich für alle oben genannten Abpictureungen von UML-Elementen auf die Zielprogrammiersprache entsprechende Ersatzkonstrukte finden.

CORBA-IDL als plattformunabhängiges Typkonzept

Für die Modellierung der Typen wird empfohlen, ein plattformunabhängiges Typkonzept einzusetzen. Es bietet sich auch hier ein offener OMG [3] Standard an: CORBA-IDL. Es gibt quasi für jede Zielumgebung ein entsprechendes Mapping, was bei der Generierung entsprechend umgesetzt wird. Für Java und das Projektverwaltungsbeispiel ist folgendes Mapping relevant:

Beispiel für Typendeklaration und sprachspezifisches Mapping von CORBA-IDL zu Java:

Abb. 1: Das fachliche Modell (Platform Independant Model – PIM) für die Projektverwaltung

Nachdem die fachliche Seite des Systems modelliert wurde, folgt nun das Design, das die technischen Aspekte festlegt. In unserem Fall verwenden wir ein sehr einfaches Design, damit der Rahmen dieses Artikels nicht gesprengt wird. Dieses Design beruht maßgeblich auf dem oben beschriebenen Mapping von CORBA-Typen auf Java-Konstrukten. Die hier verwendete Generierungstechnologie verwendet dazu Vorlagen, so genannte .in-Dateien, die den Generator definieren.

Der Generierungsvorgang sieht wie in Abpictureung 2 zu sehen aus.

Abb. 2: .in-Dateien definieren den Generierungsvorgang

Während des Generierungsvorganges werden die im Modell hinterlegten CORBA-IDL Typen auf Java Typen gemapped und das ausgewählte Minimaldesign, mit der Trennung von Interface und Implementierung, Exception-Handling und einheitlichen Zugriffsmethoden für Attribute und Beziehungen, für Java generiert.
Nachdem der Generator auf Basis des PSM (Platform Specific Model) die fachlichen Anforderungen im PIM (Platform Independent Model) verarbeitet hat, kann die Arbeit des Entwicklers beginnen. Im Gegensatz zur Implementierung aller notwendigen Code-Elemente per Hand, kann sich der Entwickler auf die Implementierung der Fachlogik konzentrieren.

Das Ergebnis der Generierung

Es wurden Insgesamt ca. 2000 Zeilen Code in ca. 2 Sekunden erzeugt. Für jede UML-Klasse wurde eine Java-Interface-Datei und eine Klassen-Datei erzeugt. Dabei haben die Klassen-Dateien die Endung Impl, um anzuzeigen, dass diese Dateien für die Implementierung vorgesehen sind.

In jede generierte Datei wurden bereits die für ein Konfigurationsmanagement (hier RCS) erforderlichen Informationen generiert. In den so genannten User defined sections (durch eine führende „>>>“- und eine abschließende „
Das oben beschriebene Minimaldesign wird nun in Ausschnitten betrachtet.

Trennung von Interface und Implementierung

Gemäß der Vorgehensweise der MDA ist das Modell die zentrale Stelle, an der die Bestandteile des Softwaresystems festgelegt sind und ggf. geändert werden. Dabei stellt das Interface eine Art Vertrag zwischen dem Modellierer und dem Entwickler dar.
Mit dieser Trennung von objektorientiertem Design und objektorientierter Implementierung, wird eine systemweit einheitliche Entwicklung sichergestellt. Wenn die im Interface bereit gestellte Funktionalität nicht ausreichend ist, um ein Problem zu lösen, wird das Modell entsprechend verändert und erneut generiert. Nun kann der Entwickler auf die geänderte Funktionalität im Interface bei seiner Implementierung zugreifen.
Auch ein Wechsel aufgrund von geänderten technischen Rahmenbedingungen während des Projektes ist nun schneller möglich, weil nur die Implementierung geändert werden muss.

Die ganze Kreativität des Entwicklers kann sich auf die Lösung der fachlichen Probleme konzentrieren.

/**
*
*  @version $Id: Projekt.java,v 1.0 2002-04-30 08:18:48+02 osp Exp osp $
*  @author  $Author: osp $
*  
Template-Version: Id: interfacegen.in,v 1.21
* 2002/02/14 11:39:14 as Exp as

*/
public interface Projekt {
/**
* ID for GNU Revision Control System – will show in the class file and
* can be looked for
* using the ident command of RCS
*/
public final String RCSID=“$Id: Projekt.java,v 1.0 2002-04-30 08:18:48+02 osp Exp osp $“;

/**
*
* @version $Id: ProjektImpl.java,v 1.0 2002-04-30 08:18:47+02 osp Exp osp $
* @author $Author: osp $
*

Template-Version: Id: implgen.in,v 1.18 2002/01/09
* 10:35:10 as Exp as

*/
public class ProjektImpl implements Projekt {
/**
* ID for GNU Revision Control System – will show in the class file and
* can be looked for
* using the ident command of RCS
*/
private final static String RCSID=“$Id: ProjektImpl.java,v 1.0 2002-04-30 08:18:47+02 osp Exp osp $“;

Ein systemweit einheitliches Exception-Handling zu implementieren, ist aufwendig und für den Entwickler eine immer wiederkehrende Aufgabe. Die Implementierung dieser Funktionalität kann die Generierungstechnologie übernehmen. Nachdem einmal festgelegt wurde, wie das Exception-Handling im bei der Softwareentwicklung aussehen soll, kann es in eine Generierungsvorlage einfließen und steht damit allen Anwendungen zur Verfügung. Dadurch ist einerseits der Entwickler von dieser Aufgabe befreit und andererseits ein unternehmensweit einheitliches Exception-Handling sichergestellt.

Sollte sich an der Architektur des Exception-Handlings etwas ändern, braucht dies nur noch in der Generierungsvorlage geändert zu werden und steht damit allen Softwaresystemen zur Verfügung. Das Rad wird nur einmal erfunden und an zentraler Stelle weiterentwickelt. Außerdem kann es nicht mehr vergessen werden.

/**
*
* @throws throws Exception if gesamtBudgetErrechnen fails
* @return float as a result of gesamtBudgetErrechnen
*/
public float gesamtBudgetErrechnen() throws Exception {
float result=null0.0F;
String ExcHint="?";
try {
// >>>{Op }{gesamtBudgetErrechnen}{S3C9F26370263}
// no implementation yet !!!
// 

Im Projektveraltungsbeispiel werden bei der Generierung von 1:1-Beziehungen zwei verschiedene Arten von Zugriffsmethoden generiert. Diese sind sowohl CORBA-konform als auch JavaBeans-konform.
In einem tatsächlichen Entwicklungsprozess wird man sich auf eine Art einigen. Dieses Beispiel zeigt, wie einfach von einer Konvention auf eine andere gewechselt werden kann – einheitlich und im gesamten System. Von Hand wäre dies sehr aufwändig, zeitraubend und damit teuer.

/**
* add the given Aufwand to meineAufwaende
* @param pAufwand The Aufwand to be added
* @throws Exception if addTomeineAufwaende fails
* @see #removeFrommeineAufwaende
*/
public void addTomeineAufwaende(Aufwand pAufwand) throws Exception {
String ExcHint="?";
try {
lazyevalmeineAufwaende();
if (pAufwand==null)
throw new Exception("addTomeineAufwaende(Aufwand pAufwand) called with null parameter");
meineAufwaende.addElement(pAufwand);
pAufwand.meinProjekt((Projekt)this);      // basic link
}	catch (Throwable t) {
throw new ExceptionStack("addTomeineAufwaende(Aufwand pAufwand) failed: "+ExcHint,t,this);
}
};

/**
* remove the given Aufwand from meineAufwaende
* @param pAufwand The Aufwand to be removed
* @throws Exception if removeFrommeineAufwaende fails
*/
public void removeFrommeineAufwaende(Aufwand pAufwand) throws Exception {
if (pAufwand==null)
throw new Exception("removeFrommeineAufwaende(Aufwand pAufwand) called with null parameter");
pAufwand.meinProjekt(null);   // unlink
meineAufwaende.removeElement(pAufwand);
};

/**
* check whether meineAufwaende contains the given Aufwand
* @param pAufwand The Aufwand to check whether it is contained
* @return true if pAufwand is contained in Aufwand
* @throws Exception if meineAufwaendeContains fails
*/
public boolean meineAufwaendeContains(Aufwand pAufwand) throws Exception {
if (meineAufwaende==null)
return false;
else
return meineAufwaende.contains(pAufwand);
};

Die hier vorgestellte Generierungstechnologie zeigt einen durchgängigen Softwareentwicklungsprozess mit einer MDA-konformen Modellierung und Generierung. Durch die getrennte Modellierung von fachlichen und plattformspezifischen Komponenten und der Benutzung eines Generators wird es möglich, Softwaresysteme schneller als bisher zu entwickeln und die Systeme einfacher an geänderte Anforderungen anzupassen. Darüber hinaus wurde auch gezeigt, wie wiederkehrende Implementierungsschritte durch Generierungstechnologie übernommen werden können. Die hier gezeigten Schritte sind nicht die einzigen Einsatzmöglichkeiten. Zum Beispiel können so auch Persistenzlösungen und Synchronisationslösungen wiederverwendet werden.

Dadurch steigt die Qualität des Systems. Die eingesparte Zeit kann für die Implementierung der Fachlogik, das Design der graphischen Benutzerschnittstelle und das Testen des Systems verwendet werden. Denn der gezeigte Code ist zu hundert Prozent generiert. Dabei hat die eigentliche Fachlogik einen Anteil von ca. zwei Prozent.
Für den Entwickler gibt es also auch beim Einsatz von Generatoren noch genügend kreative Aufgaben, auf die er sich mehr als bisher konzentrieren kann.

Wolfgang Fahl ist Geschäftsführer, Markus Guske ist Senior Berater und Oliver Pospisil ist Berater bei der BITPlan GmbH.

  • Wütherich, Gerd: Der Klassen-Klon, Java Magazin 10.2001
  • Stube, Philip: MDA: Modelle für alle Plattformen, Java Magazin 5.2002
  • OMG und CORBA: http://www.corba.org/
Geschrieben von
Wolfgang Fahl, Markus Guske, Oliver Pospisil
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.