zurück zum Artikel

Modellbasierte Anwendungsentwicklung mit .NET

Erwin Tratar

Im .NET-Umfeld gibt es bis heute keine Frameworks, die durchgängig modellbasierte Techniken einsetzen. Doch ermöglichen gerade diese, speziell bei betrieblichen Anwendungen, einen hohen Produktivitäts- und Qualitätsgewinn in der Entwicklung. Deshalb wurden wesentliche Erfahrungen aus der Java- in die .NET-Welt überführt.

Im .NET-Umfeld gibt es bis heute keine Frameworks, die durchgängig modellbasierte Techniken einsetzen. Doch ermöglichen gerade diese, speziell bei betrieblichen Anwendungen, einen hohen Produktivitäts- und Qualitätsgewinn in der Entwicklung. Aus dem Grund hat der Autor wesentliche Erfahrungen aus der Java- in die .NET-Welt überführt.

Unter modellbasierter Anwendungsentwicklung versteht man ein Vorgehen, das wesentliche Teile der Anwendung in einem Modell festlegt. Das Modell kann eine beliebige Darstellung haben (etwa Text), in der Regel werden jedoch Diagramme auf Basis der UML (Unified Modeling Language) oder von Ähnlichem verwendet.

Ziel ist es, durch die Ableitung aus Modellen den Kodieraufwand zu reduzieren und die Qualität zu erhöhen. Als angenehmen Nebeneffekt lassen sich die Diagramme wunderbar für die Dokumentation und als Einführung für neue Entwickler einsetzen, da sie das System aus der Vogelperspektive beschreiben.

Der größtmögliche Gewinn durch eine Modellierung ergibt sich deshalb, wenn sich immer wiederkehrende Aspekte möglichst einfach darstellen lassen. Fatal ist jedoch der Versuch, jedes noch so kleine Detail modellieren zu wollen, denn dann ist das Modell keine Abstraktion mehr und wird sogar unübersichtlicher als Programmcode. Das gilt insbesondere für grafische Modelle.

Auf die Frage nach "modellbasierter Entwicklung" bekommt man mit ziemlicher Sicherheit eine Antwort, die das Wort "Codegenerierung" enthält. Das ist zugleich ein oft vorgebrachter Kritikpunkt an modellbasierter Entwicklung: Übermäßiger Einsatz von Codegenerierung führt schnell in eine "Einbahnstraße", denn wenn generierter Code doch einmal manuell zu ändern ist, gibt es keinen Weg zurück. Das Modell (Diagramm) und die Anwendung laufen auseinander – das Modell wird wertlos.

Normalerweise wird dem begegnet, indem man die Codegenerierung so konfiguriert, dass eine manuelle Anpassung des generierten Codes nicht notwendig ist. Abgesehen von der enormen Komplexität, die in solchen "Templates" stecken kann und die nur wenige im Team beherrschen, lassen sich damit trotzdem niemals alle Fälle abdecken. Gleichzeitig wird der Entwickler von der Vielzahl der Modellierungsmöglichkeiten erschlagen.

Auch die ebenfalls gerne verwendete Variante, "geschützte" Bereiche im generierten Code einzuführen, die bei einer Neugenerierung erhalten bleiben, hat ihre Tücken. Es geht darum, spezielle Kommentare zu nutzen, um solche Bereiche zu markieren. Löscht oder ändert der Entwickler derartige Kommentare aus Versehen, werden die Anpassungen beim nächsten Generatorlauf fatalerweise entfernt – ohne dass er das überhaupt bemerkt.

Einzig das "Generation Gap"-Entwurfsmuster [1] scheint eine sinnvolle Alternative zu sein. Dabei generiert der Entwickler typischerweise abstrakte statt konkreter Klassen. Anpassungen lassen sich dann in Subklassen vornehmen. Das ist ein Pluspunkt für die Kombination aus modellbasierter Entwicklung und .NET, denn mit partiellen Klassen, wie sie C# im Gegensatz zu Java kennt, kann man die Spezialisierung noch wesentlich eleganter lösen. Man teilt eine konkrete Klasse in zwei Teile (Dateien) auf: Eine wird ausschließlich generiert, die andere ausschließlich manuell bearbeitet. Das entspricht der Arbeitsweise diverser Editoren in Visual Studio, zum Beispiel dem für WPF-Formulare (Windows Presentation Foundation).

Eine modellbasierte Entwicklung muss jedoch nicht zwangsläufig zu ausuferndem generierten Code führen: In der obigen Definition ist von "Generierung" nirgends die Rede. Ganz im Gegenteil kommt der Entwickler fast ohne generierten Code aus, wenn er die Herangehensweise nur umkehrt.

Nimmt man den oft überstrapazierten Vergleich mit einem Bauplan für ein Haus, würde doch niemand auf die Idee kommen, daraus für jeden einzelnen Bauarbeiter einen exakten Handlungsplan für jeden Tag und jeden Arbeitsschritt abzuleiten, auszudrucken und an die Handwerker zu verteilen. Die einzelnen Gewerke und der Bauarbeiter können stattdessen die Regeln selbst und anhand des Bauplans die meisten Schritte für sich ableiten. Es genügt vollkommen, den Bauplan zu verteilen und in dem einen oder anderen Fall noch eine Ergänzung oder Konkretisierung beizufügen.

Dieses Vorgehen entspricht einem "generischen Ansatz", bei dem man Komponenten wie einem Eingabefeld durch eine Erweiterung beibringt, selbst in das Modell zu schauen und sich entsprechend zu verhalten. Den Ansatz verfolgt zum Beispiel das TREND-Framework der Gebit Solutions, für die der Autor arbeitet: Statt aus den Modellen zur Entwicklungszeit Code zu generieren und das Modell dann zu "vergessen", stellt es die semantische Information des Modells zur Laufzeit zur Verfügung. Es verwirft lediglich die Information, wo auf dem Bildschirm welches Element grafisch angeordnet ist, und wandelt den Rest in eine kompakte sowie maschinell gut verarbeitbare Darstellung um. Die Anwendung lässt sich nun generisch anhand des Modells konfigurieren, indem das Framework etwa eine Menüleiste nach den im Modell definierten Anwendungsfällen aufbaut.

Nun sei die Frage gestellt, was genau man überhaupt sinnvoll in Geschäftsanwendungen modellieren kann. Die Erfahrung zeigt, dass Modellierung vor allem in drei Bereichen wesentliche Vorteile bietet:

Geschäftsobjekte sind der Kern jeder Anwendung. Mit ihnen arbeitet die Anwendung, und in diesen Objekten lassen sich die Daten erfassen, typischerweise in einer relationalen Datenbank ablegen und wieder daraus laden.

Schreibt der Entwickler ein Geschäftsobjekt als .NET-Klasse auf, hat er bereits einige Merkmale festgelegt: Namen (der Klassennamen), Felder (die Properties) und deren Datentypen. Darüber hinaus sind weitere Eigenschaften von Interesse, zum Beispiel ob ein Feld ein Pflichtfeld ist, wie viele Zeichen eine bestimmte Zeichenfolge aufnehmen kann, wie ein Datumswert zu formatieren ist und wer überhaupt auf das Feld zugreifen darf. Um das festzulegen, eignen sich Attributierungen, mit denen sich das Geschäftsobjekt und seine Felder auszeichnen lässt. Die Klasse mit allen ihren Attributen wird demnach zum Modell. Alle wesentlichen Merkmale eines Geschäftsobjekts werden somit festgehalten.

Die Merkmale können sich nun an unterschiedlichen Stellen auswirken. Als Beispiel sei eine Property "Name" vom Typ String herangezogen, für die mit zusätzlichen Attributen festgehalten ist, dass die maximale Textlänge 60 Zeichen beträgt und das Feld ein Pflichtfeld ist. Daraus lässt sich ableiten:

Konkret gibt es wesentlich mehr Attribute, die ein Entwickler für eine Property festlegen kann, zum Beispiel auch die Zugriffskontrolle. Abhängig von solchen Attributen und vom aktuellen Anwender werden UI-Steuerelemente unsichtbar gemacht oder in einen Read-only-Modus versetzt. Entscheidend sind zwei Punkte: Die Eigenschaften sind zentral definiert und sie werden automatisch angewendet, beispielsweise wenn anhand eines Geschäftsobjekts eine Eingabemaske darzustellen ist.

Änderungen geschehen damit automatisch an allen Stellen. Im oben genannten Beispiel würde sich eine Änderung der Textlänge auf 80 Zeichen sofort auf die generierte Datenbank und auf alle Eingabemasken auswirken. Selbst zur Laufzeit lassen sich so Eigenschaften ändern, zum Beispiel abhängig vom angemeldeten Benutzer oder von den aktivierten Modulen.

Das Modell für Geschäftsobjekte ist also ihr Quellcode in Verbindung mit besonderen Attributierungen. Als grafische Modellierung lassen sich damit direkt die in Visual Studio ohnehin enthaltenen Klassendiagramme verwenden.

Es gibt viele Persistier-Techniken im .NET-Bereich, etwa Linq-to-SQL, Entity Framework, ADO.NET direkt und NHibernate. Will man möglichst unabhängig von einer konkreten Persistenztechnik sein, sollte sie transparent sein. Das bedeutet, dass die Geschäftsobjekte nicht speziell anzupassen oder gar aus einer weiteren Modellierung zu generieren sind. Stattdessen lassen sich POCOs (Plain old CLR Objects) direkt persistieren, gegebenenfalls mit einer für die Persistenztechnik typischen Konfiguration (Mapping). Derzeit ist das beispielsweise mit NHibernate möglich, aber ab .NET 4.0 auch mit dem Entity Framework.

Um Redundanzen und damit Fehler zu vermeiden, ist es sinnig, Eigenschaften wie die Feldlänge nur an einer Stelle vorzugeben. Benötigt werden sie jedoch sowohl für das Modell als auch für das Datenbank-Mapping. Daher macht es Sinn, beide Herangehensweisen zu unterstützen: Anhand der Informationen aus dem Modell lassen sich die benötigten Mappings für die Datenbank direkt generieren. Sollte ein Mapping nicht dem Standardfall entsprechen, kann man auch umgekehrt das Mapping definieren und daraus die Modelleigenschaften ableiten. In beiden Fällen ist die Information nicht redundant nur an einer einzigen Stelle hinterlegt.

Der nächste große Block für den Einsatz modellbasierter Entwicklung ist die Modellierung von Anwendungsabläufen. Typischerweise handelt es sich dabei eher um Screenflows als um richtige Workflows. Der Unterschied ist, dass Letztere persistent sind und gegebenenfalls mehrere Sessions und/oder Anwender involvieren. Im Weiteren sei nur der gemeinsame Begriff "Workflow" verwendet.

Worflows lassen sich hervorragend grafisch modellieren, zum Beispiel mit Zustands- oder Aktivitätsdiagrammen. Jeder Zustand entspricht einer Eingabemaske, Übergänge wiederum ausgelösten Aktionen auf einer Maske. Solche Diagramme vermitteln eine gute Abstraktion. Man versteht schnell, was passieren soll. Die Abbildung 1 bildet einen solchen Workflow ab. Offensichtlich handelt es sich um einen zum Verwalten von Kunden. Im ersten Schritt kann man alle Kunden anzeigen, suchen und filtern. Außerdem lassen sich neue Kunden anlegen oder existierende Kunden bearbeiten, wodurch man sich weiter im Diagramm verzweigt.

Modellierung eines Beispiel-Workflows zum Verwalten von Kunden (Abb. 1)

Müsste der Entwickler nun jeden Schritt einzeln implementieren, wäre nicht viel gewonnen. Allerdings ist es so, dass er für typische Geschäftsanwendungen bereits mit einigen wenigen Standardkomponenten weit kommt. Allein mit zwei Standardkomponenten kann er die meisten Anforderungen umsetzen:

Der Clou ist nun, dass man die Standardkomponenten einmal mit dem Entwurfsmuster "Modell View Controller" (MVC) implementiert und dann an allen Stellen nur noch konfigurieren muss. Vereinfacht gesagt ist dabei das Geschäftsobjekt das Modell, der Controller die Standardimplementierung und die View die Eingabemaske, die der Anwender zum Beispiel mit dem WPF Designer in Visual Studio erstellt. Im einfachsten Fall muss man nicht einmal eine Eingabemaske gestalten, sondern sie lässt sich aus dem Modell (generisch) ableiten, etwa anhand der verfügbaren Felder in einem Geschäftsobjekt.

Damit kann der Entwickler in kurzer Zeit lauffähige Entwürfe erstellen, über die er mit dem Auftraggeber konkret diskutieren kann. Erst später erfolgt die Verfeinerung, indem er die Masken gestaltet und vielleicht eine angepasste Implementierung für den Controller verwendet. Ähnlich wie bei geänderten Attributen für Geschäftsobjekte wäre eine solche Änderung sofort an allen Stellen aktiv – ein unschätzbarer Vorteil.

Der dritte und häufig vernachlässigte Block modellbasierter Entwicklung ist die Konfiguration des Anwendungsrahmens. In diesem ist zusammengefasst, wie der Benutzer einzelne Aktionen (Workflows) starten kann und wie er sie steuert. Dazu gehören das Hauptfenster inklusive Menüs und/oder Symbolleisten und die Fenstersteuerung. Während damit früher Begriffe wie MDI (Multiple Document Interface) und SDI (Single Document Interface) verknüpft waren, handelt es sich mittlerweile meist um eine Form von Tabs, mit denen sich zwischen den aktiven Workflows umschalten lässt.

Ein solcher Anwendungsrahmen ist nur einmalig zu implementieren beziehungsweise an Kundenwünsche anzupassen. Er konfiguriert sich dann anhand des Modells.

Wie man einen Anwendungsrahmen konfiguriert, ist ein exzellentes Beispiel für die Kombination von modellbasierter Entwicklung und den ausgefeilten Datenbindungsmöglichkeiten in WPF und Silverlight. Abbildung 2 stellt einige Anwendungsfälle für ein Beispielszenario dar. Es gibt dort offensichtlich drei Bereiche, in denen sie gruppiert sind. Hinter jedem Anwendungsfall verbirgt sich ein Workflow, der beschreibt was passiert, wenn dieser gestartet wird.

Modellierung der Anwendungsfälle mit einem Use-Case-Diagramm. Die Use Cases sind per Hyperlink mit dem entsprechenden Workflow-Diagramm verbunden (Abb. 2).

Aus diesem Modell wird ein hierarchisches Objektgeflecht in der folgenden Art angelegt:

Tracking
CurrentMonth
CurrentYear
Management
Reports
CheckTimesheets
Billing
Administration
MySettings
MaintainDevelopers
MaintainProjects
MaintainClients
MaintainAccountants
MaintainBranches

Anwendungsfälle als Menüleiste (Abb. 3)

Das Geflecht lässt sich nun mit Datenbindung unterschiedlich darstellen. Dafür wird das Objektgeflecht in XAML mit einem Template in die gewünschte Form gebracht. Neben der Darstellung als Menüleiste (vgl. Abbildung 3) sind auch andere Darstellungen (vgl. Abbildung 4) denkbar.

Anwendungsfälle als Explorerleiste (Abb. 4)

Das Besondere hierbei ist, dass das allein der Gestalter der Anwendungsoberfläche festlegen kann, ohne das irgendetwas an der Anwendung oder dem Modell zu ändern wäre. Er kann dazu beispielsweise Microsofts Werkzeug Expression Blend einsetzen, das speziell für Gestalter ausgelegt ist.

Bei der Betrachtung modellbasierter Entwicklung sind die Vorteile insbesondere in drei Bereichen zu sehen. Zunächst wäre da der Aspekt der Qualität der erstellten Software. Zum einen wird durch Modelle, die per Definition eine abstrakte Repräsentation der Anwendung darstellen, eine hervorragende Basis für die Dokumentation der Anwendung gelegt. Wenn beispielsweise alle Workflows durch ein Diagramm beschrieben sind, kann man sich schnell einen Überblick über die Software verschaffen, selbst ohne den Code jemals gesehen und verstanden zu haben. Das hilft nicht nur dem Austausch von Entwicklern, sondern auch dem Dialog mit Nichttechnikern und Kunden.

Vorteile modellbasierter Entwicklung (Abb. 5)

Für eine verbesserte Qualität spricht auch, dass man durch die Modellierung eben jene Teile der Softwareentwicklung automatisiert beziehungsweise durch das Framework ersetzt, die normalerweise besonders repetitiv und somit fehleranfällig sind. Es ist davon auszugehen, dass allein dadurch, dass der Framework-Code bereits in vielen Projekten eingesetzt wurde, er eine höhere Qualität hat als neu geschriebener. Letztlich gilt: Je weniger Code der Entwickler schreiben muss, desto weniger Fehler kann er machen. Auch die Softwarewartung und die Weiterentwicklung sind dank stets aktueller Modelle und geringerer Code-Menge mit weniger Aufwand durchzuführen.

Der zweite Aspekt ist der offensichtlichste: Modellbasierte Entwicklung ist schneller und führt schneller zu Ergebnissen. Das wirkt sich positiv auf die Kosten aus. Die Erfahrung zeigt, dass man leicht von 25 Prozent Einsparungen ausgehen kann, in bestimmten Fällen sogar weit darüber. Grundsätzlich besteht das meiste Einsparpotenzial, wenn viele (technisch) ähnliche Eingabemasken zu implementieren sind und keine permanent wechselnden Ansprüche an das User Interface bestehen; das einmal definierte Standardverhalten und Aussehen genügt.

Schließlich gibt es noch den Aspekt des Projektrisikos, hier im Sinne von Fehlschätzungen. Durch die Verwendung von Komponenten in den Workflows erleichtert sich die Abschätzung enorm. Für die einzelnen Komponenten (zum Beispiel Eingabemaske ohne Besonderheiten, mit mittlerer oder hoher Komplexität) findet man aus vorangegangenen Projekten schnell gute und solide Schätzwerte. Summiert man sie auf, erhält man eine belastbare Schätzung zumindest für den Teil der Anwendung, der modelliert ist.

Auch die frühe Verfügbarkeit lauffähiger Prototypen reduziert Projektrisiken, hier im Sinne, eine Anwendung architekturell "zum Laufen" zu bekommen oder die Anforderungen falsch umgesetzt zu haben. Anhand eines solchen Prototypen lässt sich in Rücksprache mit dem Auftraggeber schnell feststellen, ob die Software den realen Anforderungen entspricht (die bekanntlich gerne einmal von den spezifizierten Anforderungen abweichen).

Im letzten Jahr hat Microsoft seine Pläne und eine erste Beta-Version von Visual Studio LightSwitch vorgestellt. Im Gegensatz zur Modellierungssprache M – die inzwischen offenbar keine Rolle mehr spielt – kann man davon ausgehen, dass die Technik in naher Zukunft verfügbar sein wird. In LightSwitch finden sich ähnliche Ideen wieder, allerdings mit einem ganz anderen Fokus. Die Zielgruppe sind "Anwendungsentwickler", die bisher Excel oder Access verwendet haben, um kleine Anwendungen wie eine Reisekostenabrechnung umzusetzen. Dabei soll keiner oder nur wenig eigener Code notwendig sein, der Rest wird einfach nur zusammengeklickt.

Nach der Definition des Datenmodells wird auch das User Interface teilweise automatisch abgeleitet. Dabei werden sowohl (für den Anwender unsichtbar) Codegenerierung als auch generische Komponenten verwendet. Während man Validationsregeln und berechnete Eigenschaften noch relativ einfach direkt als Code (C# oder VB.NET) integrieren kann, ist weitere Anpassbarkeit nicht mehr ohne Weiteres möglich. Hier sind eher Drittanbieter angesprochen, entsprechende Plug-ins anzubieten.

Völlig fehlt bei LightSwitch eine Möglichkeit, Abläufe abzubilden. Es lassen sich lediglich unterschiedliche Masken für die einzelnen Datenobjekte definieren (Forms over Data). Während also durchaus Merkmale für modellbasierte Anwendungsentwicklung vorhanden sind, ist dieser Ansatz jedoch zumindest aktuell stark eingeschränkt.

Mit modellbasierter Anwendungsentwicklung kann man einheitlichere Software bauen. Das betrifft sowohl den Entwicklungsprozess als auch ein einheitliches Look & Feel für die Anwender. Das heißt selbstverständlich nicht, dass jede damit erstellte Software gleich aussehen muss. Die Anpassung an das "Corporate Design" oder an die GUI-Standards eines Unternehmens sind jedoch nur einmalig zu leisten und stehen dann immer wieder direkt zur Verfügung.

Bezogen auf modellbasierte Entwicklung bei .NET bleibt festzustellen, dass modellbasierte Konzepte oder gar Werkzeuge längst nicht auf dem Niveau sind wie die in der Java-Welt. Abgesehen von einigen wenigen großen Anwendungen in Deutschland kann eine Ursache darin liegen, dass viele .NET-Anwendungen zunächst nur als kleinere, abteilungsbezogene Projekte gestartet wurden. Sie haben sich über die Jahre – teils ungeplant – zu einer Enterprise-Anwendung entwickelt, mit allen offensichtlichen Problemen in der Wartung und Weiterentwicklung. Modellbasierte Ansätze bieten hier große Verbesserungspotenziale und Chancen, die nicht nur IT-Leitung, sondern auch den Finanzvorstand freuen werden.

Erwin Tratar
ist bei GEBIT Solutions GmbH als Entwicklungsleiter für die TREND-Produktfamilie tätig, die durchgängig modellbasierte Techniken vom Requirements Engineering bis zur Entwicklung und dem Test umsetzt. Seine Schwerpunkte sind MDD, Eclipse-Techniken und Requirements-Engineering.

(ane [4])


URL dieses Artikels:
https://www.heise.de/-1251467

Links in diesem Artikel:
[1] http://www.research.ibm.com/designpatterns/pubs/gg.html
[2] https://www.heise.de/hintergrund/Visual-Studio-LightSwitch-unter-der-Lupe-1066115.html
[3] https://www.heise.de/hintergrund/Werkzeuge-fuer-domaenenspezifische-Sprachen-227190.html
[4] mailto:ane@heise.de