Lösungsansätze für die Entwicklung komplexer Webanwendungen: Domain-Driven Design
Die Zeiten, in denen auch professionell gestaltete Websites auf statischen HTML-Dateien basierten, sind lange vorbei, und auch die erste Euphorie, mit der Gästebuch- und Forum-Skripte eingesetzt wurden, ist längst verflogen. Stattdessen basieren moderne Internetauftritte auf Content Management Systemen, und die Grenze zwischen (Desktop-) Applikationen und traditionellen Websites verschwimmt zusehends.
Gut zu beobachten ist dieser Wandel auch bei den Erweiterungen, die für das Content Management System TYPO3 veröffentlicht wurden: Traf man anfangs vor allem auf Extensions, die neue Inhaltstypen und kleine Verbesserungen des Grundsystems beinhalteten, so findet sich heute eine Vielzahl von ausgewachsenen Applikationen. So zum Beispiel Online-Shops oder Buchungssysteme sowie Bibliotheken, die die Integration von Services wie Google, Amazon, SAP & Co. ermöglichen.
Kunden stellen heute höhere Anforderungen an Agenturen, die sich bisher mit klassischen Internetauftritten beschäftigt haben. Wer sich nicht der Herausforderung stellt, die Softwareentwicklung den neuen Bedingungen anzupassen und sich vermehrt auch für die Geschäftsprozesse des Kunden zu interessieren, bleibt mittelfristig auf der Strecke. Doch wie lässt sich bei ständigem Zeitdruck gute Qualität bei wachsender Komplexität gewährleisten?
Komplex, nicht kompliziert
Die folgende Situation hat so oder ähnlich sicherlich schon jeder Entwickler erlebt: Eine größere Website mit einigen raffinierten Funktionen, für die Erweiterungen entwickelt wurden, soll online gehen. In letzter Minute wird noch ein kleiner Fehler entdeckt. Kein Problem – Ihr Gefühl sagt Ihnen, dass Sie nur diese eine Kleinigkeit in diesem einen Skript ändern müssen und schon wird wieder alles funktionieren. Und in der Tat, der Fehler ist verschwunden. Zufrieden fahren Sie in Ihren wohlverdienten Urlaub: vier Wochen Chile – ohne Handy.
Leider hat Ihr vermeintlicher Bugfix eine andere Funktion der Website zerstört – Ihr Kunde entdeckt das Problem und wendet sich an einen Ihrer Kollegen. Dieser ist ratlos, denn obwohl er ebenfalls für dieses Projekt entwickelt hat, kennt er Ihren Code nicht. Wenn er bloß wüsste, was diese Funktion „valCustOrd()“ tut – validiert sie die Bestellung oder ermittelt sie den … Hmm, und wofür ist bloß das Feld „refspec“ aus der Tabelle „tx_customerorder“? Was genau passiert eigentlich, wenn ein Kunde eine Bestellung abschickt? Welches Kabel muss ich durchschneiden – das schwarze oder das rote?
Die Komplexität der Anwendung ist nicht mehr zu durchschauen. Im besten Fall findet sich noch eine Dokumentation, die etwas Licht ins Dunkel bringt, natürlich ist diese veraltet und unvollständig, denn zum Ende des Projekts war dafür einfache keine Zeit mehr.
Lesbarer Code ist ein Anfang
Es gibt viele Faktoren, die eine Anwendung unübersichtlich machen und naturgemäß konzentrieren sich Entwickler bei der Suche nach Übersichtlichkeit auf die technischen Möglichkeiten. Guten Entwicklern ist zum Beispiel bereits während des Projekts gut lesbarer Quellcode sehr wichtig – hier Zeit zu sparen, wäre am falschen Ende gespart. Die naheliegende Quick-and-Dirty-Lösung entpuppt sich nämlich häufig als in keinster Weise zeitsparend – der Ärger kommt spätestens bei größeren Änderungen.
Codelayout und automatisierte Tests sind nur zwei von zahlreichen technischen Aspekten, die helfen, die Qualität des wichtigsten Produkts Ihres Projekts – dem Quellcode – zu verbessern. Allerdings ist es nicht die Technik, die bestimmt, wie viel Komplexität Ihre Software verkraftet: Wenn die eigentliche Aufgabe, in der die Anwendung glänzen soll, eine triviale Größe übersteigt und die Anforderungen und Geschäftsprozesse Ihnen Kopfschmerzen bereiten, sind Sie auf das Herz Ihrer Anwendung gestoßen – die Domäne.
Domänen
Jede Software hat zum Ziel, Probleme innerhalb eines Fachgebiets – der Domäne – für ihren Anwender zu lösen. Alle weiteren Funktionen dieser Software sind lediglich Beiwerk und dienen dazu, dieses Ziel zu unterstützen.
Wenn die Domäne Ihrer Software das Buchen von Hotelzimmern ist, gehört das Reservieren und Stornieren von Zimmern zu ihren Hauptaufgaben. Die Darstellung von Buchungsformularen oder das Protokollieren von sicherheitsrelevanten Ereignissen gehört jedoch nicht zur Domäne „Hotelzimmerbuchung“ und dient vornehmlich dazu, die Durchführung der Hauptaufgabe zu unterstützen.
Sie können die Zugehörigkeit einer Funktion zu einer Domäne ganz einfach überprüfen: Stellen Sie sich vor, Sie wünschen ein Zimmer von einem Rezeptionisten. Diese Aufgabe ist ihm bestens vertraut und er wird Ihrem Wunsch gerne entsprechen. Stellen Sie sich nun vor, wie dieser Angestellte reagiert, wenn er für Sie ein Buchungsformular rendern oder überprüfen soll, ob Ihre Sitzung noch gültig ist. Die Aufgaben liegen nicht in seiner Domäne.
In den seltensten Fällen heißt die Domäne einer Anwendung „Software“. Die meisten Programme bieten vielmehr Lösungen für Prozesse aus dem wirklichen Leben. Um der Komplexität Ihrer Applikation Herr zu werden, ist es deshalb unverzichtbar, die Bereiche, die die Domäne betreffen, sauber von dem Code zu trennen, der nur der Infrastruktur dient. Sie benötigen hierfür eine Schichtenarchitektur – ein Ansatz, der sich seit Jahrzehnten bewährt hat.
Auch wenn Sie bisher keine bewusste Aufteilung in Schichten betrieben haben, sollte Ihnen das Mantra „Model-View-Controller“ locker von den Lippen gehen. Das Modell, das Teil dieses MVC-Musters ist, ist nämlich im besten Fall ein Modell eines Teils der Domäne. Es wird als „Domain Model“ von der restlichen Applikation getrennt und residiert in seiner eigenen Schicht, dem „Domain Layer“.
Destilliertes Wissen
Nun, da Sie wissen, dass Sie die Business-Logik von den übrigen Teilen der Anwendung trennen sollten, machen wir uns daran, ein Modell der Domäne zu erstellen. Projektleiter, Entwickler und Kunden erarbeiten zusammen ein Abbild der Realität, ein Modell, das das destillierte Wissen der Domäne enthält. Sie können hierfür UML-Diagramme anfertigen, auf einer Flipchart Skizzen anfertigen oder ein Brainstorming mit Metaplan-Wänden durchführen. Wichtig ist nur, dass sich alle Beteiligten auf ein Modell einigen können, das die Essenz des Fachgebiets enthält, Abläufe und Zusammenhänge verdeutlicht und Unwichtiges weglässt.
Betrachten wir noch einmal das Beispiel eines Buchungssystems. Während eines Meetings skizzieren Sie ein erstes Modell, das ein Hotelzimmer beschreiben könnte:
Es gibt eine Zimmernummer, die Anzahl an Betten … die wichtigsten Eigenschaften eines Hotelzimmers können gespeichert werden. Natürlich ist das Modell unvollständig, es wirft jedoch auch andere Probleme auf:
- Im Modell werden deutsche Bezeichner verwendet. Aus meiner Erfahrung sollten Sie, wenn es irgendwie geht, ausschließlich englische Bezeichner verwenden, auch wenn sämtliche Projektteilnehmer Deutsch sprechen. Die Mischung aus Ihrem deutschen und fremden englischen Quellcode würde sonst sehr unübersichtlich.
- Es handelt sich um ein Datenmodell und kein ausdrucksvolles Modell einer Domäne. Machen Sie sich frei von dem Drang, in Datenbanktabellen zu denken. Ein Domain-Model besteht nicht nur aus Eigenschaften, sondern verkörpert auch die Aktionen, die das entsprechende Objekt bietet.
Der zweite Versuch berücksichtigt diese beiden Punkte. Ihr Kunde, der Domänenexperte, ist jedoch noch nicht zufrieden: Die bloße Angabe der Bettenanzahl reicht nicht aus, denn schließlich müssen Sie zwischen Einzel- und Doppelbett, Queen-Size-, King-Size- und Kinderbett unterscheiden. Außerdem müssen die „Belegungsarten“ vermerkt werden: Das Zimmer ist vielleicht grundsätzlich ein Doppelzimmer, das aber auch als Einzelzimmer oder als Eltern-und-Kind-Zimmer angeboten wird. Sie einigen sich mit dem Hotelier darauf, eine Standard-, Minimal- und Maximalbelegung aufzunehmen und hierfür den englischen Begriff „occupancy“ zu verwenden.
Gemeinsames Vokabular
Die Bildung eines gemeinsamen Vokabulars ist immens wichtig für den Erfolg eines Projekts. Allzu häufig nehmen Entwickler nicht an der Auftragsklärung teil und Kundengespräche bleiben dem Projektleiter vorbehalten. Dieser übernimmt eine Übersetzerrolle ein und versucht zwischen dem Vokabular des Kunden und dem der Entwickler zu vermitteln. Mit viel Glück kennt sich der Projektleiter ausreichend in der Domäne der Softwareentwicklung aus, um sich verständlich mit seinen Entwicklern auszutauschen. Dass er sich jedoch ebenso gut in dem Aufgabenbereich des Kunden auskennt, ist ziemlich unwahrscheinlich.
Entwickeln Sie mit dem gesamten Team eine universelle Sprache, die in jedem Aspekt des Projekts verwendet wird: in den Modellen, im Quellcode, der Dokumentation und in Gesprächen und Korrespondenz. Das Vokabular verfeinern Sie fortwährend und bemühen sich, Unklarheiten möglichst früh zu beseitigen.
Nachdem Sie zusammen ein erstes Modell entwickelt und sich auf die grundlegenden Begriffe geeinigt haben, sollten Sie so früh wie möglich mit der Entwicklung eines Prototypen beginnen. Der traditionelle Weg, das Design einer Software im Voraus festzulegen und dieses dann zu implementieren, birgt Risiken und berücksichtigt nicht das wertvolle Wissen, das Sie sich erst während des Projektverlaufs aneignen. Es ist nämlich keineswegs so, dass der Kunde von Anfang an genau weiß, was er will und Sie genau wissen, wie Sie diese unbekannten Anforderungen lösen werden.
Nutzen Sie also die Chance, während der Entwicklung zusammen mit Ihrem Kunden zu lernen. Bleiben Sie flexibel und stellen Sie eine Verbindung zwischen Ihrem Modell und der Implementation her. Diese Verbindung ist sehr wichtig! Nach jedem Entwicklungsschritt sollten Sie die bei der Programmierung gewonnenen Kenntnisse wieder in das Modell einfließen lassen und mit den Domänenexperten besprechen. Nicht selten erlangt der Kunde bei dieser Gelegenheit selbst einen tieferen Einblick in sein eigenes Fachgebiet.
Der Kunde liest Quellcode
Bereitet Ihnen der Gedanke Unbehagen, mit Ihrem Kunden, der selbstverständlich von Programmierung keinen blassen Schimmer hat, Teile des Quellcodes zu besprechen? Warum sollten Sie überhaupt auf diese Idee kommen? Im Verlauf eines Projekts stoßen Sie unter Umständen auf Regeln und Geschäftsprozesse, die für Sie als Fachfremden schwer zu verstehen oder missverständlich sind. Solche Regeln werden üblicherweise in eigene Klassen und Module ausgelagert (das sollten sie zumindest). Wenn Sie Ihre Sache gut gemacht haben, sollte es auch für jemanden, der keine Programmiersprache spricht, möglich sein, die Logik der implementierten Regeln zu erkennen.
Vielleicht bietet ein Hotel seinen Gästen ab der vierten Nacht einen Preisnachlass an. Für normale Kunden beträgt dieser 20 Prozent, für Platinkunden sogar 30 Prozent. Schauen Sie sich einmal das folgende Listing an – auch ohne Kommentare wird deutlich, wie sich der Preis für ein Hotelzimmer errechnet.
class PriceCalculator { public function calculatePrice(Room $room, $numberOfNights, Customer $customer) { $basePrice = $room->getBasePrice(); if ($numberOfNights <= 3) { $totalPrice = $basePrice * $numberOfNights; } else { if ($customer->hasPlatinumStatus()) { $discountFactor = 0.30; } else { $discountFactor = 0.20; } $totalPrice = ($basePrice * 3) + (($numberOfNights – 3) * ($basePrice * $discountFactor)); } } return $totalPrice; }
Listing 1
Technische Voraussetzungen
Wie bereits angesprochen, geht es beim Domain-Driven Design vornehmlich nicht um die technische Umsetzung, sondern vielmehr um Ihre Denkweise und den Aufbau Ihrer Anwendung. Sobald Sie sich mit dem Thema intensiver beschäftigen, werden Sie jedoch feststellen, dass Ihnen bestimmte Techniken helfen, ein domänengerechtes Design sauberer umzusetzen.
Ein wichtiger Aspekt dabei ist die saubere Trennung zwischen der Geschäftslogik (die Sie in Domain Models verwandeln) und der Infrastruktur und den Services (die in anderen Schichten ihr Zuhause finden). In der Praxis lässt sich diese saubere Trennung jedoch nicht immer durchsetzen. Wenn Sie eine Reservierung stornieren, möchten Sie sicherlich weitere Überprüfungen und Aktionen durchführen, die nicht in der Domäne der Hotelreservierung liegen: Hat der aktuell angemeldete Benutzer ausreichende Rechte? Muss ein Administrator per E-Mail benachrichtigt werden?
Dieses Problem wird als „cross-cutting concern“ (oder, für Germanophile, „querschnittlicher Belang“) bezeichnet und es gibt unterschiedlich saubere Möglichkeiten, diesem zu begegnen. Die beste von allen ist die Anwendung eines Programmierparadigmas, dem „aspektorientierten Programmieren“ (AOP).
Aspektorientiertes Programmieren
AOP ermöglicht dem Entwickler, die verschiedenen Belange in jeweils eigenen Modulen zu entwickeln. Die verschiedenen Aspekte werden dann erst zur Laufzeit zur endgültigen Anwendung zusammengefügt und verhalten sich so, als wären sie als ein Teil entwickelt worden.
In der Praxis entwickeln Sie ein Domain Model, das sich um Sicherheitsaspekte, Protokollierung usw. nicht kümmert. Separat programmieren Sie Klassen, die nur auf Sicherheit und Logging spezialisiert sind. Schließlich definieren Sie, wie die Sicherheits- und Loggingaspekte zur Laufzeit in das Domain Model eingewoben werden sollen.
Die Möglichkeiten, die AOP bietet, sind sehr mächtig. So lassen sich eben durch zentral definierte Sicherheitsmechanismen auch Code-Teile schützen, die erst später, von vielleicht eher unerfahrenen Entwicklern beigesteuert werden. Auch lassen sich Transaktions- und Persistenzmanagement durch Aspekte für den Anwendungsentwickler transparent integrieren. So kann sich der Entwickler auf seine eigentliche Aufgabe, die Entwicklung einer Software im Sinne seines Auftraggebers, konzentrieren.
Das TYPO3-Framework
Bisher waren Techniken wie AOP anderen Programmiersprachen – vornehmlich Java – vorbehalten. Ein sehr populäres Projekt, das auch zur weiteren Verbreitung von AOP beigetragen hat, ist das Spring Framework [1].
Da das Entwicklerteam von TYPO3 5.0 Domain-Driven Design für eine sehr sinnvolle Herangehensweise hält, beschloss man im Zuge der Entwicklung von TYPO3 5.0, ein PHP-basiertes Application-Framework zu entwickeln, das die Anwendung von Domain-Driven Design und die Verwaltung von Inhalten besonders unterstützt. Dieses Framework ist selbstverständlich auch ohne das TYPO3-CMS verwendbar und eignet sich für sämtliche PHP-Applikationen, die einen Hello-World-Funktionsumfang überschreiten.
Obwohl es noch kein offizielles Release des Frameworks gibt, lohnt es sich bereits, einen Blick auf die Funktionen zu werfen und erste Experimente zu wagen. Den Link zu weiteren Informationen finden Sie am Ende des Artikels.
Fazit
Domain-Driven Design ist eine Philosophie, die es Software-Architekten, Entwicklern, Projektleitern und Anwendern erlaubt, aussagekräftige Modelle eines Fachgebiets zu entwickeln, die sich im gesamten Projekt widerspiegeln. Sie ist besonders dazu geeignet, der Komplexität eines Problems Herr zu werden und das Verständnis der Projektteilnehmer untereinander zu verbessern.
Auch wenn dieser neue Ansatz besonders in größeren Projekten glänzt, können selbst kleinste Projekte von dieser Denkweise profitieren. Voraussetzung ist jedoch ein iterativer Ansatz bei der Entwicklung, um auch von dem Wissen zu profitieren, das erst während der Implementation gewonnen wird.
Domain-Driven Design [2] ist ein weites Thema und der Begriff, der von Eric Evans geprägt wurde, ist noch recht frisch auf dem Markt, obwohl zwanzig Jahre Erfahrung nötig waren, zu diesen Einsichten zu kommen. Lesen Sie Erics Buch [3] und lassen Sie sich auch von diesem Virus anstecken – ich bin mir sicher, dass Sie sich später kaum noch vorstellen können, Software anders zu entwickeln. Wenn es Sie erwischt hat, lassen Sie es mich wissen: robert@typo3.org.