Microservices [1] dienen eigentlich „nur“ zur Modularisierung von Software. Für Modularisierung gibt es aber unzählige Ansätze: Klassen, Packages oder JARs dienen in der Java-Welt beispielsweise diesem Ziel. Nur so können auch große Projekte in kleine Einheiten aufgeteilt werden und bleiben dadurch erweiterbar und wartbar.

Aber Microservices sind anders: Die wesentliche Eigenschaft von Microservices ist das unabhängige Deployment. Ein Microservice kann in Produktion gebracht werden, ohne dass die anderen Microservices ebenfalls neu deployt werden müssen. Während also bei den klassischen Deployment Monolithen die Module wie Klassen, Packages oder JARs alle gleichzeitig in Produktion gebracht werden müssen, ist das bei Microservices nicht so. Übrigens können Deployment Monolithen durchaus intern sauber modularisiert sein – nur müssen eben alle Module gemeinsam in Produktion gebracht werden.

Konkret können Microservices beispielsweise als einzelne Docker Container umgesetzt sein. Mehrere solcher Container bilden dann zusammen eine Anwendung. Jeder einzelne Microservice kann eine REST-Schnittstelle haben oder für bestimmte URLs die Web-Oberfläche als HTML-Seiten erzeugen. Durch die Aufteilung in mehrere Deployment-Einheiten wie z.B. Docker Container ergeben sich neben dem unabhängigen Deployment noch verschiedene andere Konsequenzen: Jeder Microservice kann in einer anderen Programmiersprache auf einer anderen Plattform implementiert sein. In einem Docker Container kann schließlich fast jede Infrastruktur laufen. Ebenso kann jeder Microservice einzeln skaliert werden. Dazu ist nur ein Load Balancer und mehrere Instanzen des Docker Containers notwendig. Auch bezüglich der Robustheit ist ein Microservice eine Einheit: Wenn beispielsweise ein Microservice zu viel Speicher allokiert oder die CPU stark belastet, beeinflusst das nur diesen einen Microservice. Wenn die anderen Microservices sogar den Ausfall dieses Microservices tolerieren können, entsteht so ein sehr robustes System.

Es gibt auch ganz andere Infrastrukturen für Microservices. Beispielsweise erlaubt Amazon Lambda 5] das Deployment einzelner in Java, JavaScript oder Python geschriebener Funktionen. Diese werden auch automatisch in ein Monitoring integriert. Die Abrechnung erfolgt pro Aufruf, wobei die erste Million Aufrufe kostenlos ist.

Eine solche Technologie verringert den Aufwand für einen Microservice erheblich: Er muss nur mit einem Kommandozeilen-Werkzeug deployt werden, den Rest erledigt die Infrastruktur. Es ist also kein Aufbau eines Docker Containers oder ähnliches notwendig. Solche Technologien können die Nutzung von Microservices erheblich vereinfachen. Dennoch bieten sie eine gute Isolation und eine gute Skalierbarkeit einzelner Services. Microservices müssen also nicht unbedingt Docker nutzen – im Gegenteil, andere Ansätze wie Amazon Lambda können sogar noch einfacher und effizienter sein.

Microservice-Varianten

Am Ende entsteht aber durch Microservices ein verteiltes System und viel zusätzliche Komplexität. Es muss also gute Gründe geben, warum man Microservices nutzt.

Ein radikales Beispiel für Microservices ist Fred Georges Developer Anarchy [2]. Alle Mitglieder der Teams entwickeln – es gibt also auch keine Requirements Engineers oder Manager. Das Ziel der Entwickler sind die Geschäftsziele – also beispielsweise ein höherer Umsatz. Erfahrungen mit diesem Ansatz gibt es sowohl in modernen Umgebungen wie Internet-Anzeigen aber auch in klassischen Organisationen wie dem Online-Magazin des Guardian. Typische Microservices sind in diesem Szenario sehr klein – 10 oder 100 Zeilen Code sind durchaus im Bereich des Möglichen. Jeder der Microservices kann beispielsweise einen Teil eines Algorithmus für die Platzierung von Anzeigen implementieren. Die Microservices sind in verschiedenen Programmiersprachen geschrieben und es ist sogar denkbar, einen Microservices in einer anderen Programmiersprache neu zu schreiben. Die Entscheidung dazu treffen die Entwickler. Die Microservices kommunizieren asynchron – und über diesen Kanal können sie auch Log- und Monitoring-Daten bereitstellen.

Netflix steht für einen anderen Ansatz: Die Microservices implementieren REST-Schnittstellen, die von den Clients genutzt werden können. Ziel ist nicht nur eine unabhängige Entwicklung der Microservices, sondern auch eine Skalierung der Microservices. Für die Implementierung setzt Netflix primär auf Java. Die meisten Werkzeuge und Bibliotheken aus dem Netflix-Universum sind auf diese Programmiersprache abgestimmt. Diese Tools stehen als Open Source zur Verfügung. Andere Technologien sind zwar auch möglich – aber nur über einen Sidecar. Er ist in Java geschrieben und stellt die Funktionalitäten über eine REST-Schnittstelle zur Verfügung. Die Microservices sind wesentlich größer und implementieren komplexere Geschäftsfunktionalitäten.

Ein weiterer Ansatz sind Self-contained Systems (SCSs) [3]. Jedes SCS ist eine eigene Web-Anwendung. Sie können Daten replizieren oder auch Logik in einem anderen SCS aufrufen, aber der Schwerpunkt des Ansatzes liegt auf der Integration von Web-Inhalten. Im einfachsten Fall sind das Links auf einen anderes SCS, aber es können auch HTML-Schnipsel anderer SCSs integriert werden („Transklusion“). Ein SCS ist typischerweise in der Verantwortung eines Teams und kann intern in mehrere Microservices aufgeteilt sein. SCSs können auch außerhalb von Microservices ein guter Ansatz sein, um die Arbeit mehrerer Teams bei der Entwicklung von Portalen zu koordinieren. Da der Fokus auf einer Integration der Web-Frontends liegt, muss bei der Nutzung von SCS auch auf diesen Bereich ein Hauptaugenmerk liegen.

Tabelle 1: Microservices-Ansätze im Vergleich
Developer Anarchy Netflix Self–contained–System
Größe eines Microservice 10 - 100 Zeilen Von einem Team gut zu bewältigen 1 SCS = 1..n Microservices
Integration Asynchrone Kommunikation / Messaging REST Bevorzugt Web-Integration, ansonsten asynchrone Kommunikation
Programmiersprachen Beliebig Beliebig, aber Fokus auf Java Beliebig

Gründe für Microservices

Wie schon erwähnt, gibt es für Microservices viele unterschiedliche Gründe – die unabhängige Skalierung oder auch die Robustheit zählen dazu. Ein weiterer Grund ist sicher auch, dass gerade Java-Anwendungen dazu neigen, in schlicht zu großen Deployment-Einheiten ausgeliefert zu werden. Wenn die Startzeit einer Anwendung eine viertel oder halbe Stunde dauert, ist das sicher zu viel. Solche Anwendungen könnten zwar sauber strukturiert sein, aber sie sind oft unübersichtlich. Eine Aufteilung in Microservices erzwingt eine strikte Trennung. Bestandteile anderer Microservices können nur über die Schnittstelle genutzt werden und dazu ist entsprechender Code notwendig. Es schleichen sich also nicht einfach so Abhängigkeiten ein, sondern sie müssen explizit ermöglicht und eingeführt werden. Das gewährt auch langfristig eine gute Strukturierung der Software.

Wenn man sich jedoch die Nutzung von Microservices in der Praxis anschaut, so ist ein Grund besonders wichtig: Die größere technische Unabhängigkeit erlaubt auch ein unabhängiges Arbeiten der Teams. Wenn jedes Team an getrennten Microservices arbeitet, kann jedes Team einen eigenen Technologie-Stack nutzen, die Anwendungen unterschiedlich deployen und monitoren. Nur wenige Entscheidungen müssen übergreifend getroffen werden: Die Integration der Microservices muss einheitlich sein – sei es mit REST, Messaging, einer Web-Integration wie beim SCS-Ansatz oder mit Datenbank-Replikation.

Auch ohne Microservices ist schon eine recht unabhängige Arbeit möglich: Wenn jedes Team an eigenen fachlichen Anforderungen arbeitet und an einer eigenen fachlichen Komponente, ist die Entwicklung scheinbar unabhängig möglich. Eine solche fachliche Aufteilung mit einer klaren Zuordnung von Fachlichkeiten zu bestimmten Teams und einer Aufteilung der Architektur nach fachlichen Aspekten bekommt durch Microservices gerade mehr Aufmerksamkeit – aber eigentlich war es schon immer ein sinnvoller Ansatz für die Architektur.
Aber selbst wenn die Fachlichkeiten in jeweils einer Komponente angesiedelt sind und auch Änderungen nur eine solche Komponente betreffen: Es ist dennoch eine gemeinsame technische Koordination notwendig. Schließlich muss der gesamte Deployment Monolith eine einheitliche technische Basis haben. Im Falle von Java müssen die exakten Versionen aller Bibliotheken festgelegt werden. Ebenso müssen die Releases koordiniert werden: Der Stand aller Module muss zusammenpassen und jedes Modul muss eine Qualität haben, die für ein Release in Produktion ausreichend ist. Also können Fachlichkeiten unabhängig entwickelt werden, aber die Arbeit der Teams ist durch die enge technische Verzahnung in dem Deployment Monolithen immer noch nicht unabhängig. (siehe Abb. 1).

Abb. 1: Deployment Monolithen erzwingen technische Entscheidungen und Releases über das gesamte System
Abb. 1: Deployment Monolithen erzwingen technische Entscheidungen und Releases über das gesamte System

Microservices erlauben es, die technische Koordination auf ein Mindestmaß zu reduzieren. Dabei können Makro- und Mikro-Architektur unterschieden werden. Die Makro-Architektur umfasst alle Entscheidungen, die im Gesamtprojekt getroffen werden und dann für alle Microservices verpflichtend sind. Die Mikro-Architektur umfasst alle Entscheidungen, die jeder Microservice einzeln treffen kann. Nur wenige Entscheidungen gehören zwingend zur Makro-Architektur: So beispielsweise der Integrationsansatz. Die Programmiersprache, die Plattform und auch das Deployment und Monitoring können in jedem Microservice anders gelöst sein. Natürlich sind das Optionen: Es gibt wohl kaum einen Vorteil, wenn jeder Microservice andere Werkzeuge für Deployment und Monitoring nutzt, aber der Aufwand für den Betrieb der Gesamtplattform steigt. Ebenso kann die technische Basis beispielsweise auf Java eingeschränkt werden, aber jeder Microservice kann nun ohne größere Probleme ein Bug-Fix-Release einer Library in Produktion bringen, ohne dass dazu eine Koordination mit anderen Teams notwendig wäre. Ebenso kann beispielsweise das Testen der einzelnen Microservices unterschiedlich gelöst werden.

Abb. 2: Microservices isolieren technische Entscheidungen
Abb. 2: Microservices isolieren technische Entscheidungen

Durch das Festlegen der Makro-Architektur kann jedes Projekt entscheiden, wie viel Freiheiten die Teams auf technischer Ebene haben sollen. Microservices ermöglichen es erst, solche Freiheiten zu schaffen. Da Microservices in der Umsetzung aufwändiger als andere Architekturansätze sind, ist es sinnvoll, den Teams möglichst viele Freiheiten zu geben.

Und natürlich ist es ohne weiteres möglich, jeden Microservice einzeln in Produktion zu bringen. Das ergänzt die unabhängigen fachlichen Features sehr gut: Das Team kann sie nicht nur entwickeln, sondern auch in Produktion bringen. Also ist die Koordination bezüglich Releases wie auch bezüglich Technologien auf jeweils einen Microservice beschränkt (Abb. 2).

Beyond the Hype

Paradoxerweise ist der Erfolg von Microservices auch gleichzeitig ein Problem. Microservices werden sicher in Zukunft bei vielen Projekten genutzt werden, weil sie die „übliche“ Architektur sind. Das ist problematisch, weil Microservices wie alle anderen Architekturen ein Trade-Off sind. Neben den schon erwähnten Vorteilen gibt es auch Herausforderungen – wenn man nicht sogar das Wort „Probleme“ dafür nutzen will.

Schon die Unabhängigkeit der Teams ist eine Herausforderung: Microservices können nur die technischen Voraussetzungen für eine größere Unabhängig schaffen. Aber tatsächliche die Unabhängigkeit zu ermöglichen, erfordert entsprechende organisatorische Änderungen. Die Teams müssen die dafür notwendigen Kompetenzen bekommen – und das bedeutet einen Machtverlust von Managern oder teamübergreifenden Architekten. Das ist nicht immer einfach umsetzbar. Ebenso bedeutet es, dass die Teams nicht nur mehr Freiheiten bekommen, sondern auch mehr Verantwortung übernehmen müssen. Es ist eben nicht mehr damit getan, dass sich das Team an die Vorgaben einer zentralen Architektur und engen technischen Regeln halten, sondern sie müssen selber die Entscheidungen treffen und am Ende auch mit den Konsequenzen leben und für die Ergebnisse gerade stehen.

Auch auf technischer Ebene gibt es ebenfalls Herausforderungen: Viel mehr deploybare Einheiten müssen in Produktion gebracht und überwacht werden. Die Anzahl solcher Einheiten in einem Microservices-Projekt kann durchaus vergleichbar mit der Anzahl der Deployment Einheiten aus allen klassischen Projekten in einer Organisation sein. Das alleine kann für eine IT-Abteilung schon kaum zu lösen sein – hinzu kommt dann aber noch, dass die Services für die verschiedenen Test-Phasen deployt werden müssen. Das macht eine Automatisierung von Deployment und Test zwingend erforderlich – aber genau in diesen Bereichen haben viele Organisationen noch Nachholbedarf.

Es gibt auch einige nicht so offensichtliche Herausforderungen: So muss neben der Software selbst auch der Test passend modularisiert sein. Es ist nicht ausreichend, wenn Microservices einzeln in Produktion gebracht werden können, aber durch einen gemeinsamen Integrationstest de facto schon beim Test eine Koordination notwendig ist. Jede Änderung muss in einer Integration mit allen anderen Microservices getestet werden. Durch diese Phase kann jede Änderung nur alleine gehen, denn sonst ist bei einem Fehler nicht klar, welcher Microservice verantwortlich ist. Wenn beispielsweise ein solcher Test eine Stunde dauert, gibt es pro Tag nur acht Durchläufe durch die Tests – bei acht Teams also genau einen pro Team. Also kann ein Team genau einmal pro Tag einen Microservice ändern. Hier gilt es, Maßnahmen zu ergreifen, die den Integrationstest entlasten – beispielsweise Consumer-driven Contract Test.

Fundamentale Änderungen

In bestimmten Bereichen gehen Microservices ganz anders mit Architektur um als klassische Entwürfe. Beispielsweise ist ein Ziel von Microservices, dass jeder einzelne Service ersetz werden kann – beispielsweise durch eine Neuimplementierung. Normalerweise ist das kein Aspekt, der bei einer Architektur relevant ist. Im Fokus steht eher die Änderbarkeit der einzelnen Teile als ihre Ablösung. Warum sollte man auch bei dem Entwurf eines Microservice darauf achten, dass er am Ende durch etwas Anderes ersetzt wird? Es erscheint logischer, den Microservice so aufzubauen, dass er auch langfristig den Anforderungen genügt. Aber viele Projekte und Entwickler beschäftigen sich damit, alte System durch neue Implementierungen abzulösen. Daher ist die Idee, die Ablösung einer Software gleich schon beim Entwurf zu beachten, sicher ein wichtiger neuer Aspekt, den Microservices in den Mittelpunkt rücken.

Ebenso verstehen Microservices Organisation und Architektur als zwei Seiten einer Medaille. Das Gesetz von Conway [4] beschreibt genau diesen Umstand: Eine Organisation kann nur solche Architekturen hervorbringen, die ihren Kommunikationsbeziehungen entspricht. Dieses Gesetzt ist von 1968, hat aber erstaunlich wenig Konsequenzen auf die Architekturarbeit. Bei Microservices wird eine fachliche Aufteilung durch eine passende Aufteilung in die Teams und damit in der Organisation unterstützt. Es wird also nicht einfach hingenommen, dass die Organisation zu bestimmten Architekturen führt, sondern die gewünschte Architektur wird durch die Aufteilung in Teams weiter unterstützt. Auch das ist bei klassischen Architekturen als Ansatz kaum bekannt.

Schließlich unterstützen Microservices die unabhängige Arbeit von Teams. Letztendlich wird durch die Architektur sichergestellt, dass auch ein großes komplexes Gesamtsystem entwickelt werden kann. Dazu wird das große System in technisch unabhängige Systeme unterteilt. So wird letztendlich ein großes Projekt in viele kleine Projekte unterteilt. Dadurch können Probleme bezüglich der Skalierung der Organisation eines Projekts durch eine geschickte Architektur gelöst werden. Die Skalierung von Projekten wird üblicherweise auf der organisatorischen Ebene angegangen. Microservices lösen es durch technische Maßnahmen und die Architektur – auch das eine interessante Herangehensweise.
Also beschreiten Microservices bei der Ersetzbarkeit von Komponenten, der Äquivalenz von Organisation und Architektur sowie der Skalierung von Prozessen neue Wege. Auch wenn man nicht Microservices machen möchte, sollte man dennoch diese neuen Wege in der Software Architektur für eigene Projekte evaluieren und gegebenenfalls nutzen.

Fazit

Microservices bieten eine andere Art der Modularisierung und haben wie jeder Architektur-Ansatz Vor- und Nachteile. Der wichtigste Vorteil ist die unabhängige Arbeit der Teams. Allerdings ist gerade der Aufwand für den Betrieb, das Deployment und das Monitoring höher. Microservices können erhebliche Vereinfachungen und Vorteile zur Folge haben – und gerade bei großen Projekten sind sie oft eine sehr sinnvolle Alternative. Aber wenn die Herausforderungen nicht beachtet werden, wird die Umsetzung nicht zu den gewünschten Erfolgen führen. Und es muss auch eine für das Projekt geeignete Spielart der Microservices ausgewählt werden. Auf jeden Fall ist es nicht sinnvoll, Microservices einfach so zu nutzen, weil das eben der aktuell trendige Architektur-Ansatz ist.

Eine etwas weitergehende Einführung findet sich im kostenlosen Microservices Primer [5] oder auch in anderer Literatur [1].

Links & Literatur

  1. Eberhard Wolff, Microservices: Grundlagen flexibler Softwarearchitekturen. dpunkt, 2015, ISBN 978–3–8649–0313–7  ↩

  2. http://www.infoq.com/news/2012/02/programmer-anarchy/  ↩

  3. http://scs-architecture.org/  ↩

  4. http://www.melconway.com/Home/Committees_Paper.html/  ↩

  5. http://microservices-book.com/primer.html/  ↩