16.06.2005, 09:15 Uhr

Contract First Design für jedermann

Die klare Definiotion der Schnittstellen ist die Grundlage bei der komponentenorientierten ­Programmierung.
Die Geschichte der Softwareentwicklung ist auch eine Geschichte der Arbeit mit Bauteilen. Die Grundbauteile aller Programme sind Befehle. Sie werden «zusammengesteckt» und ergeben lauffähige Software. Da das schnell unübersichtlich wird, sind schon früh Bauteile auf einer höheren Ebene eingeführt worden: Unterprogramme, heute Methoden genannt. Wir «stecken sie zusammen», ohne uns dabei über ihre Struktur Gedanken zu machen. Die nächste Ebene darüber bilden Typen, darüber liegen Assemblies, darüber Komponenten usw. Das «Software Universum» (1) hat viele Ebenen und die Elemente jeder Ebene können als «Bauteile» verstanden werden (2). Die Sprache Modula erhob das Bauteile- bzw. Modulkonzept sogar zu ihrem zentralen Konzept. Inzwischen ist die Softwareentwicklung aber sogar bei ganzen Lösungen, d.h. verteilt laufender Software, als Bauteilen angekommen. Schnittstellen zusammengesteckt Um nun Bauteile «zusammenstecken» zu können, sind Schnittstellen nötig. Stecker und Buchse sind eine treffende Analogie in der physischen Welt. Methoden definieren ihre Schnittstellen in Form einer Signatur. Typen durch die veröffentlichten Methoden und Felder. Und die Schnittstelle einer Assembly sind ihre öffentlichen Typen. IntelliSense in Visual Studio .NET macht es Ihnen dann leicht, «Bauteile» «ineinander zu stecken»: Für eine Objektvariable bekommen Sie auf Wunsch eine Liste der Methoden, ihren Signaturen, und können in diese Buchsen die passenden Stecker in Form aktueller Parameter stecken. Das funktioniert ganz wunderbar, solange das Bauteil, das Sie verwenden wollen, bereits existiert. Dann kennen Sie nämlich seine Schnittstelle und können Ihren Code darauf abstimmen. Der Umgang mit Bibliotheken wie der .NET Framework Base Class Library oder einem 3rd Party Datagrid ist also kein Problem. Was aber, wenn Sie ein Bauteil nutzen müssen, das noch nicht existiert? DieserFall tritt leicht in Ihren Projekten ein, wenn die selbst aus mehreren Bauteilen/Komponenten bestehen, z.B. eines für die Geschäftslogik und eines für den Datenbankzugriff. Zwei übliche Lösungen überspielen dieses Prob-lem gewöhnlich: Entweder ordnen Sie die Bauteile in einem Abhängigkeitsgraphen an und implementieren zuerst die unabhängigen und dann die abhängigen. Oder Sie definieren Schnittstellen ad hoc durch beliebige Eingriffe in die Implementation von Bauteilen zu jeder Zeit. Oder allgemeiner ausgedrückt: Dieses grundsätzliche Probleme wird meist dadurch überdeckt, dass sie sowohl Konsument wie Produzent Ihrer Bauteile und Schnittstellen sind. Lassen Sie sich dadurch aber nicht in Sicherheit wiegen! Beide skizzierten Wege sind auf Dauer kontraproduktiv: Im ersten Fall kann ihre Entwicklung nur sequenziell voranschreiten, da Bauteile nur nacheinander entwickelt werden können, wenn sie von einander abhängen. Im zweiten Fall stellt sich sehr schnell enge Kopplung zwischen Bauteilen ein, da Wissen über die Implementationsinterna der Bauteile überall verfügbar ist - und daher auch genutzt wird. Wenn Sie die Vorteile der Softwareentwicklung mit Bauteilen auf immer höherer Abstraktionsebene einstreichen wollen, müssen Sie anders vorgehen. Entkopplung von Bauteilen Produktivitätszuwächse durch Parallelisierung der Entwicklungsarbeit können Sie nur erwarten, wenn Sie die Implementierung an einem Bauteil möglichst unabhängig von der an anderen Bauteilen gestalten. Dasselbe gilt für bessere Wartbarkeit und hö-here Flexibilität durch Entkopplung von Bauteilen. Die Industrialisierung anderer Branchen hat vorgemacht wie das geht. Dort sind die Schnittstellen unabhängig von den Implementationen, d.h. den konkreten Produkten definiert. (3) listet z.B. einige DIN-Normen für Stecker, die genau beschreiben, welche Eigenschaften sie haben müssen. Die Hersteller von Steckern und Buchsen werden damit unabhängig voneinander. Jeder kann sein Produkt so herstellen, wie er will - und weiss doch, dass es am Ende zu jedem Produkt jedes Herstellers der anderen Seite passen wird, solange beide die DIN-Norm einhalten. Standards für Schnittstellen sind also die Lösung für eine Entkopplung der Herstellung von Bauteilen. Die Softwarebranche ist jedoch in dieser Hinsicht noch unterentwickelt. Es gibt zwar Standards wie SMTP (4), die eine Schnittstelle für einen bestimmten Zweck definieren. Aber die Zahl solcher Standards ist im Vergleich zur Zahl und Verschiedenheit der Bauteile und ihrer Zwecke verschwindend gering (und muss es bleiben). Dennoch weisen sie den Weg, denn der Erfolg des Internets in den letzten 10 Jahren ist zu einem Gutteil auf die Existenz einer kritischen Masse an Schnittstellenstandards zurückzuführen. Hersteller von Kommunikationsbauteilen für Clients und Server konnten dadurch maximal unabhängig voneinander arbeiten und eine Vielzahlvon kooperierenden und konkurrierenden Lösungen anbieten. Standardisierung Wie lässt sich nun aber das, was die Basis für den Erfolg in anderen Industrien und des Internets ist, auf Ihre Projekte anwenden? Die Lösung liegt in der Abkopplung der Schnittstellenbeschreibung von der Imp-lementation. Dass der Standard für runde Stecker durch eine DIN-Norm beschrieben wird, d.h. von einer herstellerunabhängigen Institution, ist weniger wichtig, als dass die Beschreibung des Steckers überhaupt getrennt vom konkreten Stecker existiert. Bei physischen Bauteilen mag uns das ganz natürlich erscheinen: die Beschreibung der Schnittstelle liegt auf Papier vor, der Stecker implementiert sie. Bei Software ist die Praxis jedoch anders: Die Beschreibung einer Schnittstelle mag zwar auf Papier existieren, ist jedoch bei der Programmierung wenig relevant. Viel wichtiger ist die Implementation z.B. in Form der Signatur einer Methode in einer Assembly. Denn ein Aufrufer der Methode möchte nicht in einer Dokumentation nachlesen, welche Parameter er zu übergeben hat und dann darauf vertrauen, dass zur Laufzeit kein Fehler auftritt. Er möchte vielmehr zur Übersetzungszeit schon die Gewissheit haben, ob er den «Stecker» korrekt in die «Buchse» gesteckt hat. Dazu muss die Schnittstelle aber als Softwareartefakt vorliegen, an das er seinen Code zur Entwicklungszeit binden kann, und nicht als Papierdokumentation. Contract First Um gleichzeitig eine Trennung von Schnittstellendefiniton und Implementation sowie Gewissheit zur Übersetzungszeit zu bekommen, muss die bisherige Implementationspraxis verändert werden. Die Lösung lautet Contract First (CF) Design (5). Entstanden ist dieser Begriff in der aktuellen Diskussion um Service Oriented Architectures (SOA) als Folge der Soap-Web-Services-Bewegung. Hier trat das Problem der Schnittstellen von Bauteilen schon früh ins Bewusstsein, da Konsument und Produzent von Bauteilen (Services) eben nicht mehr zusammenfallen. Amazon als Beispiel eines Web-Service-Anbieters entwickelt sein Bauteil völlig unabhängig von Ihnen, der Sie es in Ihrem Projekt benutzen wollen. Damit Sie als Konsument eines solchen Servicebauteils sich nun aber nicht ständig um Veränderungen in der Implementation des Service sorgen müssen und umgekehrt Ama-zon als Serviceanbieter keine Rücksicht auf die vielen unterschiedlichen plattformbedingten Bedürfnisse der Nutzer seines Servicebauteils nehmen muss, liegt die Beschreibung der Schnittstelle (Contract) des Amazon-Bauteils getrennt vom Bauteil als WSDL/XML-Schema-Beschreibung vor. Und gemäss der Philosophie des CF wurde diese Schnittstelle definiert, bevor der Service implementiert wurde. Sie ist räumlich und zeitlich getrennt vom Bauteil. Und wie bei der DIN-Norm entsteht daraus Unabhän-gigkeit für Implementation und Nutzung. Auch wenn CF vor allem in der SOA-Diskussion auftaucht, ist CF doch kein Konzept, das auf SOA beschränkt ist. Im Gegenteil! CF ist ein allgemeingültiges Entwurfsprinzip für alle Arten von Bauteilen, wie die industrielle Fertigung in anderen Branchen zeigt. Es ist daher sogar eher unverständlich, warum CF nicht zur Praxis in jedem Softwareprojekt gehört. Denn allemal seit Modula oder gar schon seit C mit seinen Header-Dateien existieren die Mittel zur Beschreibung von Schnitt-stellen unabhängig von der Implementation. Die Interface Description Language (IDL) der OMG (6) hat schliesslich sogardie Schnittstellendefinition von der konkreten Programmiersprache getrennt und zu -einem Standard erhoben. Contract First in .NET Projekten CF sollte die Grundlage für die komponentenorientierte Entwicklung in all Ihren Projekten werden. Die Vorteile sind vielfältig, der Aufwand vergleichsweise klein. Gefordert ist allerdings etwas Disziplin und Wille zum Umdenken. Die Definition von Schnittstellen «im Vorbeigehen» ist dann passé; das führt anfänglich zu Friktionen in der Entwicklungsarbeit - wird aber schnell durch grössere Unabhängigkeit der Beteiligten aufgewogen. Gehen Sie wie folgt vor: Teilen Sie beim Entwurf Ihre Lösung in klar voneinander getrennte Bauteile (Komponenten) auf. Jede Komponente wird später durch eine oder mehrere zusammengehörige Assemblies implementiert. Definieren Sie die Schnittstellen der Komponenten dann bevor Sie mit ihrer Implementation beginnen. Bestimmen Sie dazu alle Typen der Komponente, die später für andere Komponenten sichtbar sein sollen. Definieren Sie für jeden dieser Typen ein Interface. Die Schnittstelle einer Komponente besteht ausschliesslich aus Interfaces, da nur sie eine vollständige Unabhängigkeit der späteren Implementation ermöglichen. (Schon eine abstrakte Klasse reduziert diese Unabhängigkeit und reicht ein Implementationsdetail nach aussen.) Fassen Sie die Interfaces in eigenen Schnittstellenassemblies zusammen. Sie sind die Contracts Ihrer Komponenten. Nachdem Sie die Schnittstellen definiert haben, können Sie parallel und unabhängig mit der Implementation aller (!) Bauteile beginnen; Typen in Komponenten implementieren ihre Interfaces, andere Komponenten nutzen diese Interfaces. Beachten Sie dabei jedoch, dass eine Komponente nicht mehr eine andere direkt referenziert! Stattdessen referenzieren Bauteile ausschliesslich die Contract-Assemblies anderer Bauteile. Nur so ist eine maximale Entkopplung der Implementationen gewährleistet. Daraus ergibt sich kurzfristig allerdings ein Problem der Instanzierung von Typen anderer Komponenten. Ohne Referenz auf die Komponentenassembly und nur mit Kenntnis des Interface eines zu instanzierenden Typs können Sie nicht mehr einfach mit new ein Objekt erzeugen. Abhilfe bringen hier jedoch so genannte IoC (Inversion of Control) oder Mircrokernel Frameworks wie Spring.NET (7). Mit ihnen können Sie zu einem gegebenen Contract-Interface zur Laufzeit den passenden Typen einer anderen Komponente instanzieren. Eine ausführlichere Darstellung bietet Ihnen (8). CF auf der Basis von Contract-Assemblies bietet Ihnen also eine solide Entkopplung von Bauteilen auf industriellem Niveau ohne Verlust an Gewissheit zur Entwicklungszeit, ob Sie eine Schnittstelle korrekt benutzen. Download Ressourcen [1] Ralf Westphal, Software Cells #5: A journey through the software universe from single statement to Software Societies, http://weblogs.asp.net/ralfw/archive/2005/05/05/405728.aspx[2] Ralf Westphal, Software Cells #9: Applications as Holons - Becoming a little more systematic, http://weblogs.asp.net/ralfw/archive/2005/05/27/409299.aspx[3] DIN-Stecker, http://lexikon.izynews.de/de/lexw.aspx?doc=DIN-Stecker[4] RFC 821 - Simple Mail Transfer Protocol, http://www.ietf.org/rfc/rfc0821.txt[[5] Aaron Skonnard, Contract-First Service Development, http://msdn.microsoft.com/msdnmag/issues/05/05/ServiceStation/default.aspx[[6] OMG, OMG IDL: Details, http://www.omg.org/gettingstarted/omg_idl.htm[7] Spring.NET, www.springframework.net[8] Ralf Westphal, Contract First Design und Microkernel-Frameworks, dotnetpro 6/05, http://www.dotnetpro.de/articles/onlinearticle1703.aspxRalf Westphal


Das könnte Sie auch interessieren