Kleinere Teams können bekanntermaßen sehr effizient und effektiv agil arbeiten. Bei großen Teams ist das oft schon nicht mehr so einfach – aber größere Projekte haben auch einen viel größeren Kommunikationsbedarfs und eine komplexere Organisation. Das scheint unvermeidlich – schließlich muss mit der Größe des Projekts auch die Organisation wachsen und damit steigt auch der Kommunikationsbedarf.
Conways Gesetz
Aber es gibt Möglichkeiten, mit diesen Herausforderungen anders umzugehen. Basis ist das Gesetz von Conway [1] von 1968. Es stellt einen interessanten Zusammenhang zwischen der Organisation und der Software Architektur her: Es besagt, dass Organisationen, die Systeme modellieren, auf solche Modelle festgelegt sind, die der Kommunikationsstruktur dieser Organisationen entsprechen. Software besteht letztendlich aus Modellen und daher gilt das Gesetzt gerade auch für die Software-Entwicklung. Es konnte in verschiedenen Kontexten auch schon empirisch nachgewiesen werden.
Ein Beispiel für das Gesetz von Conway: Nehmen wir an, dass ein Software-Projekt in ein Team für das GUI-Design, ein Team für die Geschäftslogik und ein Team für die Datenbank aufgeteilt wird (Abb. 1). Conways Gesetz besagt nun, dass dieses System der Organisationsform dieser drei Team entsprechen wird – also wird es eine GUI-Komponente, eine Komponente für die Geschäftslogik und eine Komponente für die Datenbank geben. Natürlich kann es in diesen Komponenten noch weitere, feinere Aufteilungen geben. Aber eine Aufteilung ohne diese drei fundamentalen Komponenten ist nicht möglich.
In letzter Zeit rückt ein anderes Verständnis von Conways Gesetz in den Mittelpunkt: Die Organisation wird so gewählt, dass sie der Software-Architektur entspricht. Die Organisation des Projekts ist also ein Ergebnis der Software-Architektur. So wird die Umsetzung der Architektur erleichtert. Statt also Conways Gesetz als eine Begrenzung zu begreifen, wird nutzt dieser Ansatz es konstruktiv zur Umsetzung der Architektur. Letztendlich ergibt sich ein Dualismus: Die Architektur und die Team-Struktur sind nur zwei Seiten derselben Medaille. Beides beeinflusst sich so stark, dass es eigentlich untrennbar ist.
Das legt einen Verdacht nahe: Wenn die Organisation und Kommunikation großer Projekte so komplex ist – kann die Software Architektur dieses Problem gegebenenfalls lösen?
Typisch ist eine Aufteilung von Organisation und Architektur in technische Schichten wie oben beschrieben – also GUI, Geschäftslogik und Datenbank. Damit geht aber ein Nachteil einher: ein typisches fachliches Features erfordert Änderungen in allen drei Schichten und damit auch Kommunikation zwischen den drei Teams. Schließlich wird jedes Feature an der Oberfläche sichtbar sein, Logik enthalten und auch Daten speichern. Dazu müssen nicht nur in der Software Schnittstellen geschaffen und erweitert werden – vor allem müssen die Teams sich koordinieren und abstimmen, um das Feature in den drei Schichten zu implementieren. Auch die zeitliche Koordination der Teams ist wichtig, damit die Änderungen gemeinsam getestet und ausgerollt werden können.
Offensichtlich ist diese Struktur für die schnelle Entwicklung der Software keine gute Wahl. Besser wäre eine Aufteilung nach fachlichen Belangen – idealerweise so, das jedes Teams unabhängig von den anderen Teams Features implementieren kann. In einem ECommerce-Team könnte es beispielsweise Teams für die Bestellung, die Suche und die Registrierung geben (Abb. 2). Wenn nun jedes Team einen Product Owner hat, der fachliche Features definiert, können die Teams unabhängig voneinander neue Features implementieren und Produkte selbstständig weiterentwickeln.
Abhängigkeit durch Architektur
Ganz so einfach ist aber nicht. Die Komponenten haben natürlich immer noch Schnittstellen. So sollte eine Suche idealerweise in einer Bestellung münden – und dann müssen die Daten entsprechend von der Such-Komponente zur Bestell-Komponente weitergeben werden. Um diese Übergabe umzusetzen, müssen die Teams sich wieder koordinieren und kommunizieren. Also steigen in diesen Szenarien die Anforderungen an die funktionale Architektur: Wenn zwei Komponenten eng gekoppelt sind und von zwei unterschiedlichen Teams weiterentwickelt werden, sind Absprachen über die Schnittstellen notwendig und die Geschwindigkeit der Entwicklung leidet. Also müssen die Teams möglichst an unabhängigen Komponenten arbeiten, in denen sie ihre Funktionalitäten implementieren können. Wenn das in einem bestimmten Fall nicht möglich ist, müssen die Teams sich wieder abstimmen – oder die Änderung in der fremden Komponenten selbst vornehmen. Beides führt zu einer Verlangsamung der Entwicklung.
Neben der funktionalen Unabhängigkeit spielt auch die Technik eine Rolle. Typischerweise müssen in einem System technische Absprachen getroffen werden. Schließlich müssen Basistechnologien ausgewählt werden. Eine einheitliche technische Basis vereinfacht die Implementierung und auch den Betrieb. Oft ist es auch nur möglich, eine bestimmte Technologie in einer bestimmten Version zu nutzen. In Java-Systemen können beispielsweise verschiedene Versionen von Bibliotheken nicht so ohne weiteres gleichzeitig genutzt werden. Also muss ein bestimmter Stack definiert und durchgesetzt werden. Wenn nun aber in dem Beispiel für die Suche eine spezielle Such-Technologie eingesetzt werden soll, so werden die notwendigen Abstimmungen die Einführung dieser Technologie erheblich verzögern. Ebenso ist es praktisch unmöglich, für die Suche einen komplett anderen Technologie-Stack mit einer anderen Programmiersprache und anderen Bibliotheken zu nutzen, um beispielsweise die notwendige Performance zu erreichen.
Und natürlich müssen bei einem komplexen System die unterschiedlichen Änderungen koordiniert werden, bevor sie in Produktion ausgerollt werden können. Dazu zählt das Testen der Systeme im Rahmen einer Continuous Delivery Pipeline [2]. Aufgrund der Größe des Systems wird der Durchlauf sehr lange dauern. Die Risiken eines Deployments können so hoch sein, dass die Organisation versucht, nur mit einigen wenigen Releases pro Jahr auszukommen.
Technische Abhängigkeiten minimieren
Es gibt Möglichkeiten, die technischen Abhängigkeiten weitgehend zu beseitigen und so den technischen Koordinationsaufwand zu minimieren. Dazu muss die Architektur der Komponenten so gewählt werden, dass die Teams möglichst viel technische Freiheit haben und sich wenig koordinieren müssen. Damit werden der Entscheidungsspielraum und die Produktivität der Teams optimiert – und dem ordnet der Architekturansatz die Einheitlichkeit bei der Technologie und die damit verbundenen Vorteile unter. Der Technologie-Stack muss also sehr flexibel sein. Beispielsweise könnte jedes Team eine virtuelle Maschine liefern, auf der ihre Komponente läuft – oder Software-Pakete für eine automatisierte Installation beispielsweise mit einem Linux Package Manager. Dadurch können die Teams praktisch beliebige Technologien nutzen. Außerdem können die Komponenten nun unabhängig voneinander in Produktion gebracht werden – wenn die Architektur zusätzlich Vorkehrungen trifft, so dass anderen Komponenten mit neuen Version einer Komponente umgehen können. Beispielsweise müssen die Schnittstellen abwärtskompatibel bleiben. Wenn die Komponenten klein gewählt werden, sinkt auch die Komplexität der Continuous Delivery Pipelines und die Risiken bei einem Ausrollen in Produktion. So wird die Installation neuer Features in Produktion weiter vereinfacht.
Dieser Ansatz wird unter dem Begriff Microservices im Moment diskutiert – wobei es noch keine ganz einheitliche Definition gibt. Wesentliche Aspekt sind das unabhängige Deployment der Microservices und die damit einhergehende Möglichkeit, Microservices getrennt voneinander zu entwickeln. So grenzen sich Microservices von Deployment-Monolithen ab, die nur als ganzes in Produktion gebracht werden können. Das unabhängige Deployment ist ein sehr wichtiger Vorteil –sogar noch wichtiger als die technische Flexibilität. Die Teams müssen sich nun noch nicht einmal mehr abstimmen, um eine neue Version der Software in Produktion zu bringen. So können sie tatsächlich völlig unabhängig voneinander arbeiten und so viel mehr Features umsetzten. Das unterstützt natürlich ideal ein agiles Entwicklungsmodell, bei dem es mit dem schnelle Umsetzten von Features nicht getan ist: Die Features sollen dann anschließend auch schnell in Produktion gebracht werden, um so Feedback zu erhalten.
Dabei muss ein Team nicht unbedingt an nur einem Microservice arbeiten. In der Praxis unterteilen Teams große Komponenten wie das Abbilden des Bestellprozesses in mehrere kleine Microservice. Diese Services können dann alle von einem Team entwickelt werden, das für die Fachlichkeit dieser Services zuständig ist. Insgesamt können Systeme durchaus in der Größenordnung von hunderten Microservices haben. Letztendlich wird so aus dem großen Projekt zur Entwicklung des ECommerce-Systeme eine Menge kleiner Projekte, die jeweils einen Teil des Systems umsetzten. Natürlich ist eine Kommunikation zwischen den Microservices möglich – schließlich müssen sie zusammen beispielsweise das ECommerce-System abbilden. Die Kommunikation kann über REST oder Messaging erfolgen. Dann ruft die Logik eines Microservice die Logik eines anderen Microservice auf. Oder ein Microservice kann auf seiner HTML-Oberfläche ein Link anzeigen, der auf einen anderen Microservice verweist. Und schließlich könne die Microservices Daten replizieren – wie dies für Data Warehouses schon lange gang und gäbe ist (Abb 3). Das bietet sich natürlich vor allem an, wenn die Daten in eine andere Repräsentation überführt werden müssen – wie dies beispielsweise notwendig ist, wenn die Daten analysiert werden sollen.
Nachhaltige Entwicklung
Microservices haben noch weitere Vorteile. Letztendlich sind Microservices aber nur eine Modularisierungstechnik. Ein wichtiger Unterschied zu anderen Modularisierungsansätzen: Die Kommunikation zwischen Microservices herzustellen, ist gar nicht so einfach. Dazu müssen REST-Aufrufe oder Messaging genutzt werden. Das geht nur, wenn entsprechende Schnittstellen eingeführt und genutzt werden. Ebenso ist es schwierig, Funktionalitäten von einem Microservice in einen anderen zu verschieben. Solche Abhängigkeiten einzuführen oder Code zu verschieben, ist in einem Deployment-Monolithen mit einer modernen IDE sehr schnell erledigt – auch wenn diese Änderungen die Architektur torpedieren. Eine wesentliche Aufgabe von Architektur ist, die Abhängigkeiten zwischen Modulen so zu managen, dass ein Abhängigkeitschaos vermieden wird und die Software so leicht verstehbar und änderbar bleibt. Microservices erzwingen durch technische Barrieren, dass die Architektur auch langfristig durchgehalten wird. Deployment-Monolithen sind viel eher der Gefahr ausgesetzt, dass die einmal gewählte Architektur irgendwann durchbrochen wird. Die Folge ist dann oft ein unwartbares System.
Ein weiteres Problem mit Deployment-Monolithen: Wenn der Monolith nicht mehr erweitert werden kann, weil die Architektur oder die Codequalität nicht mehr stimmt, müsste er eigentlich durch eine Neuimplementierung abgelöst werden. Aber das Ablösen einer großen, komplexen Anwendung ist sehr schwierig. Viele große Projektfehlschläge sind darauf zurückzuführen, dass ein Deployment-Monolith abgelöst werden sollte und am Ende der Aufwand für die Ablösung viel zu groß war. Microservices helfen hier auf zwei Weisen: Sie können einen Deployment-Monolithen ergänzen. So ist es möglich, zunächst einige Funktionalitäten durch Microservices abzulösen. So können bestimmte Anfragen beispielsweise schon von den neuen Services bearbeitet werden und die anderen werden immer noch in dem Monolith bearbeitet. Die Microservices können dabei einen neuen modernen Technologie-Stack nutzen und sind auch sonst nicht mit den Problemen des Monolithen belastet. Ein solcher Ansatz wird von sehr vielen Projekten genutzt, um schrittweise den Monolithen durch Microservices abzulösen. Dadurch können die Vorteile moderner Technologien sofort genutzt werden und die Microservices können beispielsweise auch einfacher in einer Continuous-Delivery-Pipeline ausgeliefert werdenn.
Außerdem vermeiden Microservices das Problem, dass Software irgendwann weder weiterentwickelt noch abgelöst werden kann: Wegen der Größe können Microservice sehr leicht durch neue Implementierungen ersetzt werden, die natürlich auch andere Technologien nutzen können. So fällt das Ablösen einer Microservice-Architektur viel leichter. Genau genommen wird noch nicht einmal die Architektur abgelöst, sondern nur einzelne Teile ausgetauscht. Eigentlich ist es ein Fehler, dass Software-Architekturen die Ablösung eines Systems bisher nicht wirklich in Betracht gezogen haben – denn in der Praxis tauchen gerade bei der Ablösung immer wieder massive Probleme auf. Durch die Durchsetzung der Architektur und die Ersetzbarkeit von einzelnen Microservices versprechen Microservices eine nachhaltige Entwicklung von Software. Auch langfristig sollte es also möglich sein, Features mit einer hohen Geschwindigkeit umzusetzen. Damit wird also ein weiteres klassisches Problem aus der Software-Entwicklung mit Hilfe dieser Software-Architektur gelöst.
Wer tut es schon?
Microservices sind aus der Welt der Internet-Unternehmen nicht mehr wegzudenken. Amazon beispielsweise hat schon 2006 davon gesprochen, dass Teams jeweils für einen Service zuständig sind und diese Services unabhängig voneinander weiterentwickeln können. Viele Internet-Unternehmen nutzen einen ähnlichen Ansatz, denn gerade in diesem Bereich ist schnelles Time-to-Market für den Erfolg wesentlich. Ein weiteres Beispiel ist Netflix. Dieser Video-Streaming-Service baut auf der Amazon Cloud auf und ist für bis zu 30% des Internet Traffics in den USA verantwortlich. Für Netflix ist die Microservice-Architektur so wichtig, dass die Firma eine umfangreiche technologische Infrastruktur für die Implementierung von Microservices erstellt hat. Sie besteht aus verschiedenen Bibliotheken und steht mittlerweile als Open Source zur Verfügung. Auch sonst ist die Netflix-Architektur eines der wesentlichen Beispiele, wie ein System aus Microservices aufgebaut werden kann. Bei den Internet-basierten Unternehmen sind noch Twitter oder in Deutschland Soundcloud und Otto zu nennen, die solche Ansätze ebenfalls umsetzten. Ein eher klassisches Unternehmen ist der Finanzdienstleister Hypoport, der auch schon im Java Magazin über seine Erfahrungen berichtet hat. Ebenso finden sich auf den Konferenzen Beispiele von der Deutschen Post (EPost), die ebenfalls einen solchen Ansatz verfolgt. Aktuell ist das Thema mit einem gewissen Hype versehen – vermutlich, weil immer mehr Unternehmen vor den Problemen stehen, die Microservices lösen. Schließlich ist die schnelle Implementierung von Features und die nachhaltige Implementierung von Projekten eine zentrale Herausforderung in der Software-Entwicklung.
Microservices – ein Allheilmittel?
Allerdings stellen Microservices Teams auch vor neue Herausforderungen:
- Die Vielzahl an Services benötigt eine Infrastruktur-Automatisierung. Manuelles Aufbauen von Umgebungen oder manuelles Ausrollen in Produktion sind bei 100 oder mehr Microservices einfach nicht mehr möglich. Daher müssen Microservices mit Continuous Delivery einher gehen. Continuous Delivery ist aber sowieso sehr nützlich, wenn das Time-to-Market optimiert werden soll. Außerdem wird der Aufbau einer Continuous-Delivery-Umgebung mit Microservices einfacher, da die Dienste eben kleiner sind und das Risiko bei einem Deployment in Produktion ebenfalls sinkt. Oft ist Continuous Delivery sogar die Motivation, um die Architektur auf Microservices umzustellen.
- Der Betrieb und das Monitoring der Services müssen außerdem gewährleistet sein. Wesentlich ist auch in diesem Bereich die Automatisierung und die Einheitlichkeit. Ohne solche Vorkehrungen ist der Betrieb einer Microservices-Architektur kaum zu stemmen. Die betrieblichen Aspekte wie auch die Continuous Delivery Pipeline müssen zentral vorgegeben werden und von allen Komponenten eingehalten werden, um eine Entwicklung des Systems zu gewährleisten.
- Idealerweise sollten Microservices auch mit DevOps kombiniert werden. So können die Teams noch unabhängiger werden, weil dann auch die betrieblichen Aspekte von den Teams übernommen werden und so unabhängig voneinander bearbeitet werden können.
- Microservices sind verteilte Systeme. Das macht die Implementierung der Systeme komplexer, da Kommunikation über das Netzwerk länger dauert und unzuverlässiger ist. Wenn die Architektur aber gut umgesetzt ist, bietet es auch noch weitere Vorteile. So können Microservice-Architekturen den Ausfall eines Service kompensieren – während das bei einem Monolithen durchaus zum Ausfall des Gesamtsystems führen kann.
Aktuell entstehen viele Technologien wie beispielsweise der schon erwähnte Netflix-Stack, Spring Boot und Spring Cloud für Java oder auch Docker als leichtgewichtige Ablaufumgebung. Diese Technologien vereinfachen die Umsetzung von Microservices, so dass neue Projekte viel weniger in Technologien investieren müssen und ein geringeres Risiko haben, als dies bei den Pionieren der Fall war. Auch das trägt sicher dazu bei, dass Microservices gerade zunehmend populär werden.
Fazit
Architekturen können ihren Teil dazu beitragen, agile Software-Entwicklung zu skalieren. Grundsätzlich wird mit Ansätzen wie Microservices die Software soweit entkoppelt, dass die Kommunikation und Abstimmung zwischen den Teams erheblich vereinfacht wird und schließlich so die Prozesse viel einfache abgestimmt werden können. Diese Interaktion und mögliche Synergie zwischen Prozessen, Software-Architektur, Team-Organisation und Kommunikation wird viel zu selten betrachtet.
Die Idee für Microservices ist auch sinnvoll, weil die meisten Systeme eher zu große Deployment-Einheiten haben. Einige Technologien drängen Teams schon fast in die Richtung. So hat Java EE umfangreiche Möglichkeiten, auch große Deployment zu ermöglichen.
So können Microservices fundamentale Probleme lösen – nicht nur agile Software-Entwicklung im größeren Kontext, sondern auch den Umgang mit Legacy-Anwendungen und die nachhaltige Weiterentwicklung von Systemen. Dafür sind die technischen Hürden akzeptabel – wenn auch sicher in der Praxis nicht einfach zu lösen.
Referenzen
-
Eberhard Wolff: Continuous Delivery: Der pragmatische Einstieg, dpunkt.verlag, 2010, ISBN 3864902088 ↩