Microservice-Architekturen: Einführung in die Software-Architektur
Schon lange teilen Internet-Firmen große Systeme in Microservices auf: Unabhängige Prozesse, die einzeln entwickelt und in Produktion gebracht werden. Dieser Ansatz vereinfacht Continuous Delivery, unterstützt Agilität auf Architektur-Ebene, verspricht eine nachhaltige Entwicklung komplexer Software-Systeme und bietet neue Ansätze für den Umgang mit Legacy-Anwendungen.
Probleme mit agilen Projekten
Agile Prozesse sind mittlerweile über 15 Jahre alt. Von Anfang an stand im Mittelpunkt ein kleines Team, das Software in enger Abstimmung mit dem Kunden entwickelt. Mittlerweile sind die Scrum-Rollen sogar zu Berufsbezeichnungen geworden. Es gibt also ein weitgehend einheitliches Verständnis für die Organisation von agilen Teams. Die Skalierung zu größeren Teams ist auch möglich, findet aber meistens vor allem auf der Prozessebene statt. Ein Ansatz sind Scrum of Scrums zur Koordination der einzelnen Scrum-Teams.
Aber ist es überhaupt ausreichend, Agilität auf der Ebene der Organisation zu skalieren? Gibt es vielleicht andere Ansätze?
Oft gibt es für ein großes System schon verschiedene fachliche Anforderungsströme. Ein Team im Rahmen eines E-Commerce-Shop soll mehr Registrierungen erreichen. Ein anderes Projekt soll die Produktsuche verbessern. Und dann gibt es noch verschiedene Fachbereiche, die ebenfalls ständig Änderungen an dem E-Commerce-Shop wollen. Mindestens Suche und Registrierung haben eigentlich nicht viel miteinander zu tun. Wieso braucht man dennoch eine übergreifende Organisation? Kann man die Architektur nicht so wählen, dass weniger
Koordination notwendig ist, um so die verschiedenen fachlichen Anforderungen unabhängig zu implementieren?
Kommunikation zu begrenzen ist ein wesentlicher Ansatz, um große Teams effektiv arbeiten zu lassen. Wenn jeder mit jedem redet, sind das bei 4 Personen im Team 6 Kommunikationsbeziehungen, bei 10 schon 45 und bei 60 schließlich 1770. Also muss es effektive Mechanismen geben, um die Kommunikation zu begrenzen, weil sonst zu viel Zeit auf Kommunikation und zu wenig Zeit auf die tatsächliche Entwicklung verbraucht wird.
Der US-amerikanische Informatiker Melvin Edward Conway formulierte 1968 unter dem Begriff "Das Gesetz von Conway" seine Beobachtung, dass Strukturen von Systemen vorbestimmt sind durch die Kommunikationsstrukturen der sie umsetzenden Organisationen. (Quelle: Wikipedia)
Das Gesetz von Conway
Einen ersten Hinweis auf eine Lösung gibt das Gesetz von Conway [1]. Es besagt, dass eine Organisation nur solche Architekturen hervorbringen kann, die den Kommunikationsstrukturen der Organisation entsprechen. Beim E-Commerce-Shop könnten die Frontend-Entwickler ein Team bilden, die Middletier-Entwickler und die Datenbank-Experten jeweils ein weiteres Team. Das erscheint sinnvoll – gleiche Skills in einem Team fördern den fachlichen Austausch. Ein Kollege kann die Aufgaben eines anderen Kollegen relativ einfach übernehmen, weil alle ähnliche Fähigkeiten haben.
Für die Architektur bedeutet das entsprechend dem Gsetz von Conway, dass es drei Schichten gibt: Das Frontend, den Middletier und die Datenbank. Das führt zu einigen Herausforderungen:
- Fachliche Änderungen müssen mit allen Teams koordiniert werden, wenn sie alle Schichten betreffen – was meistens der Fall ist.
- Die fachlichen Änderungen müssen gegenüber den anderen fachlichen Anforderungen in jedem Team priorisiert werden. Das erhöht den Koordinationsaufwand. Jedes Team muss die fachliche Änderung verstehen und auch priorisieren können.
- Das Middletier-Team kann erst an den Änderungen arbeiten, wenn das Datenbank-Team die notwendigen Änderungen in der Datenbank vorgenommen hat. Ohne die Datenbank-Änderungen kann das Middletier neue Datenstrukturen nicht ablegen. Erst nach der Lieferung des Middletier-Teams kann das Frontend-Team mit der Arbeit beginnen. Das bedeutet, dass eine fachliche Änderung erst nach drei Sprints tatsächlich fertig implementiert ist, weil sie drei Teams durchlaufen muss, die jeweils auf die Änderungen der anderen Teams angewiesen sind.
Ursache dieser Probleme ist die Aufteilung der Teams nach technischen Schichten. Organisieren wir die Teams also um: Jedes Team hat nun Frontend-Experten, Middletier-Experten und Datenbank-Spezialisten. Die Teams werden nach Fachlichkeiten organisiert. Es gibt also nun zum Beispiel ein Team für die Registrierung und eines für die Suche. Die neuen Anforderungen können diese Teams nun selbstständig abarbeiten. Da sie Experten für sämtliche Schichten haben, können sie Koordinierung über Teams hinweg vermeiden. Jedes Team kann die notwendigen Änderungen alleine durchführen. Der Kommunikationsaufwand sinkt: Jedes Team muss nur noch "seine" Funktionalität verstehen und sich mit den anderen Teams nur koordinieren, wenn die fachlichen Anforderungen verschiedene Funktionalitäten überdecken.
Bei der Aufteilung der Teams nach Schichten sind die Auswirkungen des Gesetzes von Conway einfach hingenommen worden: Es sind drei Schichten in der Architektur entstanden. Die Aufteilung der Teams nach Fachlichkeiten sollte dazu führen, dass auch die Architektur sich nach Fachlichkeiten orientiert. Schließlich besagt das Gesetz von Conway, dass sich die Team-Struktur auch in der Architektur wiederfindet. Das Team für die Registrierung sollte
also an einer Komponente in der Architektur arbeiten, die für diesen Geschäftsprozess zuständig ist – und zwar für alle Schichten: Frontend, Middletier und Datenbank. Dasselbe gilt für die Registrierung.
Diese Komponente würde alle Schichten umfassen und sich an fachlichen Aufgaben orientieren. Eine neue Anforderung an die Registrierung und die Suche kann ein Team dann ganz selbstständig umsetzen – mit minimaler Kommunikation mit anderen Teams. Eine gewisse Kommunikation ist immer noch notwendig, weil die Fachlichkeiten Schnittstellen haben. Eine Suche soll beispielsweise einen Bestellprozess auslösen können. Also muss die Suche auch eine Schnittstelle zu dem Bestellprozess haben. Wenn eine Änderung notwendig ist, mit der der Übergang von der Suche zum Bestellprozess optimiert werden soll, sind dazu Änderungen an beiden Komponenten notwendig.
Microservices
Nun arbeiten mehrere Teams an dem E-Commerce-Shop. Er ist ein Deployment-Monolith – das bedeutet, dass der gesamte E-Commerce-Shop als eine Einheit deployed wird. Aus diesem Grund ist immer noch eine Koordination notwendig – nicht mehr wegen der Fachlichkeiten, sondern wegen der notwendigen technischen Koordination.
Zunächst muss das Deployment koordiniert werden: Alle Teams müssen einen Software-Stand liefern, der ausgeliefert werden kann. Dazu muss die Software getestet werden. Selbst wenn nur eine Komponente geändert wird, müssen umfangreiche Tests durchgeführt werden. Die dazu notwendigen Umgebungen bereitzustellen ist eine Herausforderung, weil sie der Produktionsumgebung weitgehend entsprechen muss. Ein weiteres Problem ist die Komplexität des Deployments: Einen E-Commerce-Shop in Produktion zu bringen ist schwierig. Es müssen Integrationen mit vielen anderen Komponenten und anderen Systemen getestet werden. Das Risiko eines Deployments ist auch sehr hoch: Alle Features werden neu deployed, so dass bei einem Fehler potentiell jeder Teil der Anwendung beeinträchtigt sein kann.
Ansätze wie Continuous Delivery [2] setzen darauf, Software automatisiert zu deployen. Das erhöht die Reproduzierbarkeit und damit die Sicherheit des Deployments. Aber die Automatisierung wird durch die Komplexität eines großen Systems wie einem E-Commerce-Shop so kompliziert, dass sie oft praktisch unmöglich sind. Aber ein automatisertes reproduzierbares Deployment ist eigentlich die Voraussetzung, um Software wirklich sicher in Produktion zu bringen. Manuelle Prozesse sind zu fehleranfällig und auch zu aufwändig.
Es liegt auf der Hand, die Software also in kleinere Deployment-Einheiten aufzuteilen: Jedes Team sollte seine Fachlichkeit wie Registrierung oder Suche in einer oder mehreren Deployment-Einheiten ausliefern können.
Diese Deployment-Einheiten sind Microservices [4]: Sie können einzeln in Produktion gebracht werden. Sie können über REST-Schnittstellen miteinander kommunizieren. Und sie können einen Teil der Web-Oberfläche erzeugen, also alle Web-Seiten, die für die Registrierung oder die Suche notwendig sind. Natürlich zählt auch die Logik und die Datenbanken für diese Funktionalitäten zu einem Microservice.
Dank der Aufteilung in Microservices können die Teams nun ihre neuen Funtionalitäten unabhängig voneinander in Produktion bringen. Außerdem ist es einfacher, Continuous-Delivery-Pipelines aufzubauen. Die Pipelines sind für kleinere Einheiten zuständig, die einfacher deployed werden können. Tests und die notwendige Infrastruktur sind ebenfalls einfacher. Dadurch wird der Aufbau einer Continuous-Delivery-Pipeline und einer Deployment-Automatisierung wesentlich einfacher.
Mikro- und Makro-Architektur
Die Aufteilung in Microservices hat noch weitere Vorteile. Entscheidungen, die alle Microservices betreffen, können auf ein Minimum reduziert werden. Solche Entscheidungen nennt man auch die Makro-Architektur, weil sie das System als ganzes betreffen – also im Beispiel den vollständigen E-Commerce-Shop. Viele Entscheidungen können in die Teams deligiert werden, die sie dann selbst für ihre Microservices treffen und umsetzten können. Diese Entscheidungen sind die Mikro-Architektur. Eine möglichst kleine Makro-Architektur reduziert technische Koordination – ergänzend zu der Beschränkung der Kommunikation über Fachlichkeiten wegen der Aufteilung der Teams nach Fachlichkeiten.
Im Extremfall können praktisch alle Entscheidungen durch die Teams getroffen werden. Ein praktikabler Kompromiss könnte sein:
- Die Programmiersprache und Plattform entscheiden die Teams. Ein Deployment-Monolith zwingt die Teams, sich auf eine gemeinsame Plattform zu einigen. Bei Sprachen wie Java müssen dazu die genauen Versionen aller Bibliotheken festgelegt werden, da von jeder Bibliothek nur jeweils eine Version im Speicher gehalten werden kann. Wenn also ein Team eine andere Version benötigt, müssen alle anderen Teams mitziehen und die Version muss kompatibel mit allen anderen Bibliotheken sein. Microservices erlauben technisch eine freie Wahl der Plattform. Die kann natürlich eingeschränkt werden – jeden Microservice in einer anderen Sprache zu programmieren ist einfach nicht immer notwendig. Aber auf jeden Fall ist es nicht mehr notwendig, jede Version jeder Bibliothek genau festzuschreiben.
- Die Makro-Architektur gibt eine einheitliche Schnittstelle zum Betrieb vor. Jeder Microservice bietet eine Schnittstelle für das Monitoring und Logging, so dass der Betrieb nur diese Schnittstellen nutzen muss, um jeden Microservice zu überwachen. Ebenso sollte das Deployment vereinheitlicht sein. Auch ein einheitliches Starten und Stoppen kann Teil der Makro-Architektur sein. Diese Elemente über alle Microservices einheitlich zu handhaben, verringert den Aufwand und die Komplexität des Betriebs. Wenn fünfzig oder hundert Microservices jeweils eigene Lösungen für Betrieb und Monitoring haben, wäre das Gesamtsystem viel zu schwer zu betreiben.
- Damit alle Microservices miteinander kommunizieren können, muss die Makro-Architektur einheitliche Kommunikationsprotokolle wie REST vorgeben. Das betrifft auch die Sicherheit. Für das Gesamtsystem aller Microservices sollte nur ein Login notwendig sein. Aber jeder Microservice kann einem Nutzer den Zugriff auf bestimmte Funktionalitäten erlauben oder verwehren. Dazu muss ein einheitlicher Mechanismus definiert sein, der den Microservices die notwendigen Informationen über den Benutzer bei jedem Aufruf mitteilt.
- Die fachliche Architektur innerhalb jedes Microservices ist ein Teil der Mikro-Architektur. So kann jeder Microservice die Funktionalitäten unterschiedlich strukturieren. Genau diese unabhängige Entwicklung ist ein wesentlicher Grund, warum Microservices eine parallele Entwicklung mehrerer Teams erlauben.
- Die Aufteilung der Fachlichkeiten auf Microservices muss allerdings auf der Ebene der Makro-Architektur festgelegt werden, um so den Teams ihre jeweiligen fachlichen Domänen zuzuweisen.
Durch einen geschickten fachlichen Schnitt erlauben Microservices, dass Features unabhängig voneinander in verschiedenen Teams implementiert werden können. Außerdem erlauben Microservices einen weitgehenden Verzicht auf technische Koordination, indem die Teams ihre eigene Mikro-Architektur definieren können und die übergreifende Makro-Architektur auf ein Minimum reduziert wird.
Weitere Vorteile
Starke Modularisierung: In einem Deployment-Monolithen ist schnell irgendwo eine Abhängigkeit eingebaut, indem ein Entwickler in einer Klasse eine andere Klasse nutzt. Dann ist die Architektur missachtet und eine ungewünschte Abhängigkeit eingebaut. Nach kurzer Zeit kommen dann weitere Abhängigkeiten dazu und irgendwann ist das System kaum noch zu verstehen oder weiterzuentwickeln. In einem Microservices-System ist die Nutzung der Schnittstelle eines anderen Microservices aufwändig und wird wohl kaum unbeabsichtigt eingebaut werden. Daher schleichen sich Abhängigkeiten weniger leicht ein.
Ersetzbarkeit: Einzelne Microservices können im Vergleich zu einem Deployment-Monolithen viel einfacher ersetzt werden. Es muss lediglich ein neuer Microservice mit identischer Schnittstelle und Funktionalität umgesetzt werden. Wenn also ein Microservice nicht
mehr wartbar ist, steht einer Ablösung durch eine Neuimplementierung viel weniger im Weg als bei einem Deployment-Monolithen.
Nachhaltige Entwicklung: Wegen der starken Modularisierung wird die Architektur auf Ebene des Gesamtsystems mit der Zeit kaum schlechter. Auf Ebene der einzelnen Microservices kann bei schlechter Qualität im Extremfall der Microservice einfach ersetzt werden. Also kann auf Ebene des Gesamtsystems und der einzelnen Microservices eine hohe Architektur-Qualität langfristig gewährleistet werden. So wird eine nachhaltige Entwicklung möglich: Die Qualität des Systems ist auch nach längerer Zeit noch so gut, dass eine produktive Entwicklung möglich ist.
Ergänzung von Legacy-Systemen: Microservices können dazu genutzt werden, neue Funktionalitäten für Legacy-Systeme zu implementieren. Neue Features werden in Microservices umgesetzt, wähhrend die alten Funktionalitäten im Legacy-System unverändert bleiben. Dieser Umgang mit Legacy-Systemen hat den Vorteil, dass die alte Code-Basis nicht angepasst werden muss und auch völlig neue Technologien genutzt werden können.
Technologie-Freiheit: Wie schon erwähnt, kann jedes Team einen anderen Technologie-Stack nutzen. Das macht Experimente mit neuen Technolgien wesentlich einfacher. Wenn eine Technologie nicht geeignet ist, kann im Extremfall der Microservice einfach neu geschrieben werden. Und natürlich können spezielle Lösungen beispielsweise für die Produktsuche recht einfach in das System integriert werden.
Robustheit: Wenn ein Microservice in einem eigenen Prozess läuft, kann bei einem Fehler auch nur dieser Microservices mit seiner Funktionalität ausfallen – selbst wenn der gesamte Prozess abstürzt. Bei einem Deployment-Monolithen kann ein Fehler den gesamten Monolithen mit allen Funktionalitäten zum Absturz bringen. Das ist die Basis für ein sehr robustes System.
Fazit
Microservices ermöglichen die Skalierung agiler Prozesse mit Hilfe der Software-Architektur. Letztendlich wird aus einem großen Projekt eine Vielzahl kleiner Projekte, die unabhängig voneinander von eigenen fachlichen Teams implementiert werden. Die Teams müssen nur wenig kommunizieren, so dass diese Skalierungshemmnis entfällt. Zentral ist, dass die
Microservices sich unabhängig deployen lassen. So gewinnen die Teams auch viele technische Freiheiten. Aber Microservices sind nicht auf die Unterstützung von agilen Prozessen begrenzt: Es gibt noch viele weitere technische Vorteile. Und neu ist die Idee auch nicht: Amazon sprach
schon 2006 davon, kleine Teams Services umsetzen zu lassen, für die sie dann auch voll verantwortlich sind – eine zentrale Idee der Microservices [3].
- Wikipedia: Gesetz von Conway
- Eberhard Wolff (2014): Continuous Delivery: Der pragmatische Einstieg, dpunkt-verlag (s.u.)
- Blogeintrag von Eberhard Wolff auf "J and I and Me": JAOO 2006: Werner Vogels - CTO Amazon
- Eberhard Wolff: Microservices - Grundlagen flexibler Software-Architekturen, dpunkt, ISBN 978-3864903137, erscheint 2015