Componentware/Wie lassen sich Softwarebausteine besser wiederverwenden?

Mit UML trotz Defiziten zur Spezifikation

08.06.2001
Mitte der 90-er Jahre wurde die komponentenbasierende Softwareentwicklung vornehmlich unter dem Aspekt der Wiederverwendung proklamiert. Sie gelangte - wie die Wiederverwendung selbst - kaum in die Unternehmenspraxis. Mit Internet und E-Business hat sich die Situation grundlegend geändert. Von Matthias Ehlert*

Geschwindigkeit und Flexibilität sind bei komponentenbasierender Entwicklung keine Hexerei, sondern das Ergebnis eines definierten Vorgehens: Voraussetzung ist zunächst, dass ein Geschäftsprozess als eine Menge von Teilprozessen und elementaren Services verstanden wird. Die Leistung eines Teilprozesses oder ein Service wird jeweils von einer Komponente erbracht. Die Komponenten sind dabei lose gekoppelt und kommunizieren lediglich über möglichst schmale Schnittstellen. Schlüssel zum Erfolg der komponentengestützten Entwicklung sind die folgenden drei iterativen Schritte: die Identifizierung der Komponenten, ihre Spezifikation durch die Beschreibung ihrer Schnittstellen und der Entwurf einer Architektur, die eine optimale Integration von Komponenten ermöglicht.

Die eigentliche Bereitstellung der Komponenten kann auf verschiedene Weise erfolgen. Alternativen sind hier die Eigen- oder Auftragsentwicklung, der Kauf fertiger Komponenten, das Wrapping von Altsystemen und der Anschluss an Anwendungen oder an virtuelle Services im Internet. In jedem Fall bilden Komponentenspezifikation und -architektur die Grundlagen für die Bereitstellung. Komponenten bauen, bauen lassen oder evaluieren - jede dieser Alternativen wird einfacher, wenn Schnittstellen und Architektur mit Hilfe einheitlicher Sprachmittel spezifiziert sind.

Mit der Unified Modeling Language (UML) steht dafür inzwischen ein akzeptierter Standard zur Verfügung. Dieser bietet derzeit allerdings noch keine eigenen Ausdrucksmittel für eine Komponentenspezifikation. Die von der UML vorgegebenen Komponentendiagramme (Component Diagrams) decken nur den Implementierungs- und Verpackungsaspekt von Komponenten ab, die Einsatzdiagramme (Deployment Diagrams) ihre Verteilung. Erfreulicherweise bietet die UML mit Stereotypen und Tagged Values definierte Erweiterungsmöglichkeiten. Von ihnen wird in der nachfolgend beschriebenen Vorgehensweise intensiv Gebrauch gemacht.

Ziel der komponentenbasierten Entwicklung ist es, Geschäftsprozesse möglichst optimal und flexibel mit Softwarekomponenten zu unterstützen. Die angestrebte leichte Anpassbarkeit einer IT-Lösung kann nur im Wechselspiel zwischen Geschäftsprozess-Modellierung und komponentengestützter Entwicklung erreicht werden: zum einen durch eine Flexibilisierung der Geschäftsprozesse selbst, zum anderen durch eine bessere Abbildung der neu strukturierten Prozesse auf eine IT-Lösung.

Serviceorientierte ProzesseDie komponentenbasierte Entwicklung sollte also mit einer Restrukturierung der Geschäftsprozesse Hand in Hand gehen - weg von funktional gegliederten, eng gekoppelten Aufgaben mit komplexen Schnittstellen, hin zu serviceorientierten, lose gekoppelten Prozessen. Dabei ist eine reibungslose Kommunikation zwischen "Prozessmodellierern" und Komponentenentwicklern notwendig. Die UML bietet hier mit ihren Aktivitätsdiagrammen und Anwendungsfällen (Use Cases) geeignete Kommunikationshilfen an.

Wie findet man nun zu einem Geschäftsprozess die Komponenten einer IT-Lösung? Da Komponenten über ihre Schnittstellen spezifiziert werden, gestaltet sich also die Suche nach den Schnittstellen potenzieller Komponenten wie folgt:

Sobald ein Geschäftsprozess in Form von Aktivitätsdiagrammen beschrieben ist, lassen sich diejenigen Aktivitäten identifizieren, die es maschinell zu unterstützen gilt. Sie werden auf Anwendungsfälle abgebildet. Mit den Anwendungsfällen, ihrer Darstellung in Anwendungsfall-Diagrammen und gegebenenfalls einem Oberflächen-Prototyping kann die Interaktion eines Anwenders beziehungsweise Akteurs mit dem System konkretisiert werden. Jeder aus einem Geschäftsprozess abgeleitete Anwendungsfall entspricht einem Service für einen Akteur - und damit einer System-Schnittstelle. Semantisch eng zusammenhängende Services lassen sich gegebenenfalls zu einer Schnittstelle zusammenfassen.

Anwendungsfälle werden in der Projektpraxis meist textuell konkretisiert. Muster, die eine Beschreibung nach dem Schema "Akteur tut - System tut - Akteur tut - System tut ..." fördern, sind hier hilfreich. Denn jeder Schritt eines Anwendungsfalls vom Typ "System tut" entspricht der Operation einer System-Schnittstelle. Die so gefundenen Schnittstellen werden als Klassen modelliert. Es bietet sich an, sie mit dem Stereotyp zu versehen, den die UML mitbringt.

Die Operationen einer System-Schnittstelle arbeiten in der Regel mit geschäftstypischen Objekten. Das bedeutet: Aus den Anwendungsfällen und oft auch aus den zuvor modellierten Aktivitäten lassen sich Geschäftsklassen des Anwendungsbereichs ableiten. Es empfiehlt sich, sie als Klassen vom Stereotyp zu modellieren, um den Spezifikationsaspekt zu betonen. Geschäftsklassen werden über ihre Attribute spezifiziert, das heißt über die Informationen, die sie bereitstellen. Sie werden außerdem im Kontext von Klassendiagrammen abgebildet, um ihre Beziehungen zueinander zu erkennen.

Geschäftsklassen mit ihrer Geschäftslogik werden durch die Definition von Schnittstellen verborgen. Im Gegensatz zu den oben genannten System-Schnittstellen handelt es sich hier also um Business-Interfaces. Ein oder mehrere Business-Interfaces werden einer Komponente zugeordnet. Wichtig dabei: Die Behandlung einer Geschäftsklasse sollte überschneidungsfrei immer in der Verantwortung genau einer Komponente liegen.

Komponenten mit Hilfe von Packages gliedernMit der Definition von System-Schnittstellen und Business-Interfaces lassen sich also erste Komponenten identifizieren. Im nächsten Schritt geht es darum, dieses Ergebnis zu stabilisieren und die Komponenten für den Bereitstellungsprozess detailliert zu spezifizieren.

Es spricht nichts dagegen, Aktivitätsdiagramme, Anwendungsfälle und Geschäftsklassen zunächst ohne weitere Gliederung anzulegen. Sobald es jedoch um die Spezifikation von Komponenten geht, sollten die Arbeitsergebnisse klar strukturiert werden. Ziel dabei ist, Komponenten als in sich abgeschlossene Einheiten abzubilden, die nur wohldefinierte Artefakte veröffentlichen und klar festgelegte Abhängigkeiten nach außen besitzen. Mit dieser Strukturierung wird die Grundlage für Konfigurations-Management und Datenaustausch in verteilten Projekten geschaffen.

Die UML sieht Packages als wesentliches Strukturierungsmittel vor. Somit wird man für jede Komponente ein eigenes Package anlegen. Eine wesentliche Eigenschaft komponentenbasierter Entwicklung ist die Trennung von Spezifikation und Implementierung einer Komponente. Um diesen Aspekt sichtbar zu machen, sollte man jedes Package, das eine Komponente repräsentiert, in zwei Subpackages unterteilen: je eines für die Spezifikations- beziehungsweise Implementierungs-Artefakte.

Welches Package welchem Zweck dient, lässt sich mit Hilfe von Stereotypen beantworten: Der Container für die Gesamtkomponente ist ein Package vom Stereotyp "Component". Das Subpackage für die Spezifikation kann man beispielsweise mit einem Stereotyp "Comp Spec" versehen, die Implementierung mit "Implementation" kennzeichnen. Die gefundenen System-Schnittstellen und Business-Interfaces gehören nach dieser Gliederung also in "Comp Spec"-Subpackages.

Die Lösung zusammenstellenÜber die Schnittstellen der Komponenten beginnt man, den Lösungsbereich zu spezifizieren. Dies lässt sich vollkommen unabhängig von den möglichen Zielsprachen durchführen, was sich besonders dann empfiehlt, wenn die Entscheidung für eine Komponentenplattform (Corba, DCOM, J2EE) noch nicht gefallen ist. Kennt man aber die Komponentenplattform schon, wird das Ziel schneller erreicht, wenn die Ausdrucksmittel dieser Umgebung verwendet werden. Corba und COM bieten Programmiersprachen-unabhängige Interface-Beschreibungssprachen, bei J2EE bleibt man von Anfang an bei Java.

Die detaillierte Spezifikation der Schnittstellen umfasst die Beschreibung der Daten, auf denen die Schnittstellen arbeiten, sowie die Definition der Operationen, die sie anbieten. Zur Beschreibung dieser beiden Aspekte dienen die Attribute und Methoden der Schnittstellen-Klassen. Das wichtigste Hilfsmittel in diesem Schritt sind die Interaktionsdiagramme der UML. Da beide Arten von Interaktionsdiagrammen ihren Zweck erfüllen, muss sich ein Projektteam darauf einigen, ob es Kollaborations- oder Sequenzdiagramme verwenden will.

Die wichtigsten Aspekte, auf die man bei der Modellierung der Interaktion achten sollte, lauten:

- Optimierung der Operationsaufrufe in Hinsicht auf die Performance,

- Definition sinnvoller Datentypen (Einzelabfrage von Properties ist bei verteilten Anwendungen nicht sinnvoll),

- klare Verantwortlichkeiten,

- weit gehende Entkopplung der Einzelkomponenten (etwa Definition von unabhängigen Kernkomponenten oder Einhaltung des Layer-Pattern),

- Komponenten sollen möglichst in verschiedenen Kontexten eingesetzt werden können, das heißt unabhängig von der Art des Clients. Damit sind entsprechende Benachrichtigungsmuster vorzusehen.

Beschreibung komplettierenWenn die Daten, auf denen eine Schnittstelle arbeitet, und ihre Operationen spezifiziert sind, sollte die Beschreibung durch Pre- und Post-Conditions beziehungsweise Invarianten komplettiert werden. Pre- und Post-Conditions definieren Kontrakte auf Operationsebene, Invarianten beschreiben Constraints auf Typniveau. Die UML bietet mit der Object Constraint Language (OCL) eine dedizierte Beschreibungssprache für diesen Zweck an. Da die Services, welche die Operationen bereitstellen, in der Regel auf persistenten Daten arbeiten, gehört zur Spezifikation einer Operation schließlich auch der Transaktionsmodus.

Wenn die Schnittstellen der Komponenten spezifiziert sind, kann die Systemarchitektur der IT-Lösung beschrieben werden, das heißt die hierarchische Struktur und die Abhängigkeiten der Komponenten auf Systemebene. Dazu bieten sich die Package-Diagramme der UML an. Die Schnittstellen einer Komponente werden mit Hilfe der Lollipop-Notation sichtbar gemacht. Die Abhängigkeiten werden mit Hilfe stereotypisierter Dependency-Beziehungen abgebildet. Dabei unterscheidet man zwei Arten von Abhängigkeiten: Die "use"-Beziehung spezifiziert, welche Schnittstellen eines Servers ein Client verwendet. Die "implement"-Beziehung macht kenntlich, dass ein Package die Implementierung der Schnittstelle eines Servers enthält.

Abhängigkeiten prüfenAuf Systemebene werden die Diagramme angeordnet, welche die Systemarchitektur beschreiben. Im Komponenten-Package werden Package-Diagramme angelegt, die alle Komponenten zeigen, die von der betroffenen Komponente verwendet werden.

Natürlich gehören zur Komponentenspezifikation auch die zunächst "lose", also ohne Bezug zur Package-Struktur abgelegten Aktivitätsdiagramme, Anwendungsfälle und Geschäftsklassen. Diese Artefakte sollten nachträglich in die "Comp Spec"-Packages aufgenommen werden, deren Services sie beschreiben. Damit sind alle Artefakte, die eine Komponente spezifizieren, in einem Komponenten-Package zusammengefasst. Dieses bildet eine in sich geschlossene Einheit mit nur wenigen definierten Abhängigkeiten - gut zu versionieren und zu verteilen. Auf Systemebene bleiben nur Übersichtsdiagramme.

Die dargestellte Form einer Komponentenspezifikation enthält alle Informationen, die zur Implementierung der beschriebenen Schnittstellen notwendig sind.

Komponenten besitzen die Eigenschaft, ortstransparent zu sein. Standardisierte Middleware wie DCOM, Corba und J2EE stellt die notwendigen Kommunikationsdienste zur Verfügung. Jede Komponente benötigt damit eine "Integrationsschicht". Diese führt notwendige Parameterkonvertierungen durch und realisiert Fehlerbehandlungen nach den Middleware-Konventionen. Die Integrationsschicht ist objektorientiert. Wenn die Schnittstellen in einer Interface-Beschreibungssprache spezifiziert wurden, kann man über Compiler ein Gerüst für die Integrationsschicht generieren. Die fachliche Implementierung sollte von der Integrationsschicht getrennt in separaten Klassen erfolgen.

Welche Schnittstellen eine Klasse implementiert, wird über die Implement-Beziehungen der UML beschrieben. Alle Dateien, die zur Komponente als Verpackungseinheit gehören, werden in Komponentendiagrammen dargestellt. Die physikalische Verteilung der Komponenten kann nach Bedarf in Einsatzdiagrammen beschrieben werden.

Der geschilderte Prozess greift Konzepte von John Cheesman und John Daniels (UML Components, Addison-Wesley, 2001) auf und verarbeitet langjährige Anwendungserfahrungen mit komponentenbasierender Entwicklung. Der Prozess unterscheidet sich von den gängigen Vorgehensweisen der Softwareentwicklung: Er ist nicht entwicklungszentriert, sondern stellt die Komponentenspezifikation als Voraussetzung für die anschließende Bereitstellung von Komponenten in den Vordergrund. Gleichzeitig sieht er eine klare Trennung zwischen Spezifikation und Implementierung vor und definiert den Rahmen für eine wohldefinierte, gekapselte Funktionalität. Wichtig dabei ist, dass für alle Phasen der komponentengestützten Entwicklung durchgehend einheitliche UML-Standards verwendet werden. Die Artefakte werden damit zwischen den einzelnen Phasen austauschbar. Die erläuterte Form der Komponentenspezifikation eröffnet jeder IT-Organisation den Spielraum, zwischen den vielfältigen Möglichkeiten der Beschaffung von Komponenten zu wählen, Komponenten zu ergänzen oder auszutauschen.

*Matthias Ehlert ist Entwicklungsleiter bei der Microtool GmbH in Berlin (Matthias.Ehlert@microtool.de).

Abb.1: Ein Prozess zur komponentenbasierten Entwicklung

Aus Geschäftsprozessen lassen sich Anwendungsfälle ableiten, die einem Service für den Akteur und damit einer System-Schnittstelle entsprechen. Quelle: Microtool

Abb.2: Die Beziehungen

Die Client-Komponente "sieht" nur die spezifizierte Schnittstelle einer Komponente. Die "implement"-Beziehung stellt einen Kontrakt zur Realisierung einer Komponentenspezifikation dar. Quelle: Microtool