Frontend-Entwickler leiden häufig unter langen Round-Trip-Zeiten beim Bauen und Deployen einer Java-Web-Anwendung. Selbst bei kleinen Layout-Änderungen muss der vollständige Bau-Prozess durchlaufen werden. Mock-Objekte können hier viel Arbeitszeit sparen.
Wer ist von dem Problem überhaupt betroffen? Die Entwickler von Play-, Vaadin– oder Grails-Projekten kennen langsame Build-Prozesse kaum. Auch diejenigen Entwickler, deren Anwendungen in reinen Web-Containern wie Tomcat laufen, profitieren in der Regel von ihrer schlanken Infrastruktur.
Sobald aber „schwerere“ Technologien wie O/R-Mapping, EJBs oder Messaging dazukommen, benötigt das Bauen und vor allem das Deployen einer Anwendung spürbar Zeit. Vor allem der Einsatz von Anwendungsservern bremst die Entwicklung aus. Zum einen funktioniert das Hot-Code-Replacement von Code-Änderungen im laufenden Betrieb meistens schlecht, zum anderen schlagen Änderungen an Ressourcen wie JSF- oder Wicket-Html-Dateien nicht sofort durch. Jedenfalls, solange man nicht Produkte wie JRebel einsetzt.
Was bremst die Frontend-Entwicklung aus?
In einer typischen schichtenbasierten Architektur nutzt das Frontend Dienste, die das Backend bereitstellt. Das Backend ist dafür verantwortlich, die Anwendungsdaten zu bearbeiten, zu speichern und wieder bereitzustellen. Das Frontend nutzt diese Dienste, um die Webseiten zu rendern und auf die Nutzereingaben zu reagieren. In einer solchen Architektur kann das Frontend nicht ohne das Backend laufen.
Häufig lässt sich das Frontend nur gemeinsam mit dem Backend bauen. Ein Frontend-Entwickler muss also nach jeder Änderung erst einmal das ganze Projekt bauen und anschließend neu im Anwendungsserver deployen, um die Auswirkungen der Änderungen zu sehen.
Wie lässt sich die Frontend-Entwicklung beschleunigen?
Eine Lösung sind Mock-Objekte. Mocks sind weit verbreitet zum automatisierten (Unit-)Testen von Sourcecode, vor allem im Backend. Während ein Test eine bestimmte Funktionalität prüft, werden andere Funktionalitäten abgekoppelt, um die Komplexität der Tests zu reduzieren.
Mock-Objekte sind aber auch sehr hilfreich, um Frontend-Code vom Backend zu entkoppeln.
Die Voraussetzung dazu ist, dass jeder vom Backend bereitgestellte Dienst durch ein Mock-Objekt ersetzt wird. Dazu muss zunächst jeder Dienst durch eine Schnittstelle definiert werden und zu jeder Schnittstelle muss neben der „richtigen“ Implementierung eine Mock-Variante erstellt werden. Während die richtigen Implementierungen die volle Systemfunktionalität garantieren, unterstützen die Mock-Varianten nur die für das Frontend wichtigsten Funktionen.
Diese Mock-Objekte gehören dem Frontend und dürfen von den Frontend-Entwicklern für ihre Bedürfnisse angepasst werden. Idealerweise erstellt man eine Klasse mit einer main-Methode, die einen eingebetteten Web-Container startet. Innerhalb dieses Containers läuft dann die Web-Anwendung und greift nur auf die Mock-Objekte zu. Die Web-Schicht der Anwendung lässt sich somit einfach und schnell aus der verwendeten IDE heraus starten. Der zweite Teil des Artikels liefert dazu ein detailliertes Beispiel.
Das klingt erst einmal nach viel Arbeit!
Ist es erfahrungsgemäß auch: Jede Service-Methode im Backend braucht eine analoge Implementierung als Mock-Objekt. Die Erfahrung zeigt aber, dass ein pragmatischer 80/20-Ansatz viel Arbeitszeit spart.
Viele Funktionen, die im richtigen Betrieb wichtig sind, können stark vereinfacht oder weggelassen werden. Wenn beispielsweise ein Suchdialog viele verschiedene Such-Parameter zur Verfügung stellt, reicht es für die Web-Entwicklung meistens aus, im Mock-Objekt nur einige wenige Parameter zu berücksichtigen. Daten-Validierungen können vereinfacht werden, und anstatt Objekte persistent zu speichern, werden sie einfach im Arbeitsspeicher gehalten.
Große Vorteile bringen Mock-Objekte bei Anwendungsfällen, die sich mit dem richtigen Backend nur schwer testen lassen. Die Web-Entwickler selbst können in den Mocks beliebige Datensätze vorbereiten, die die Bedienoberfläche in vordefinierte Zustände bringt. Durch Mocks lassen sich auch aufwändige Geschäftsprozesse abkürzen, indem dort vorbereitete Ergebnisse für bestimmte Interaktionen hinterlegt werden.
Mock-Objekte durch richtige Java-Klassen auszuprogrammieren, funktionieren besser als Mock-Frameworks wie Mockito oder Easymock einzusetzen. Solche Mock-Frameworks sind sehr hilfreich, wenn man einzelne Unit-Tests getrennt voneinander entwickelt. Bei dem hier vorgestellten Ansatz hingegen erstellt man Mock-Objekte, die sich auch gegenseitig aufrufen dürfen, um komplexe Logik möglichst einfach nachzubilden.
Ein nicht zu unterschätzendes Risiko des Ansatzes ist, dass die fertige Web-Anwendung natürlich mit den richtigen Backend-Diensten laufen muss. Wenn das Frontend nur auf Basis der Mock-Objekte programmiert wird, kann es nach einem vollständigen Deployment leicht zu Überraschungen kommen. Weiterhin kann es leicht passieren, dass sich die Semantik der Backend-Dienste verändert, diese Änderungen aber nicht in den Mock-Objekten nachgezogen werden.
Wenn solche Mock-Objekte jedoch einmal existieren, dann lassen sie sich meistens auch sinnvoll für Unit-Tests des Web-Frontends nutzen. Im Fall von Wicket hat beispielsweise eine Kombination mit dem Wicket Tester gut funktioniert.
Meine Projekterfahrung hat gezeigt, dass die Entwicklung von Mock-Objekten am besten funktioniert, wenn sie parallel zur Entwicklung der richtigen Dienste stattfindet. Mock-Objekte führen initial zu einem spürbaren Mehraufwand in der Entwicklung. Mein grober Erfahrungswert ist, dass der Aufwand für die Mock-Objekte bei rund einem Fünftel des Aufwands für die Backend-Services liegt, wobei dieser Aufwand zum Teil von den Frontendentwicklern getragen wird. Mittel- bis langfristig aber sparen Mock-Objekte viel Arbeitszeit, vor allem, wenn viele kleine Änderungswünsche an der Bedienoberfläche umgesetzt werden müssen.
Nach der Theorie in diesem Artikel zeigt Teil 2 ein Beispiel für den Einsatz von Mock-Objekten im Zusammenspiel mit Apache Wicket.