Was sind Microservices?
Microservices [1],[2] sind kleine, unabhängig deploybare Software-Module. Große Systeme in kleine Module aufzuteilen ist nichts Neues. Die unabhängige Deploybarkeit ist der entscheidende Unterschied: Jeder Microservice kann unabhängig von anderen Microservices in Produktion gebracht werden. Andere Ansätzen für die Modularisierung reichen nicht so weit: die Entwicklung ist zwar in einzelne Module aufgeteilt, aber für den Betrieb ist die Anwendung ein Monolith. Da solche Anwendungen nur als Ganzes in Produktion genommen werden können, spricht man auch von einem Deployment-Monolithen. Microservices hingegen sind auch für den Betrieb klar als Module erkennbar.
Es besteht keine Einigkeit, ob Microservices auch eine UI enthalten sollen. In einem engen Zusammenhang damit steht der Schnitt der Microservices. Ein Microservice sollte eine fachliche Einheit sein, so dass eine fachliche Anforderung am besten nur Änderungen an einem Microservice bedingt. Da fachliche Anforderungen oft eine Änderung an der UI benötigen, kann es sinnvoll sein, auch die UI als Teil desselben Microservice zu implementieren.
Ein Beispiel kann ein E-Commerce-Shop sein: Die Produktsuche, die Anmeldung und Verwaltung von Benutzern und der Bestellprozess können jeweils eigene Microservices sein - oder auch mehrere Microservices. Jeder der Microservices wird von einem eigenen Team entwickelt und betrieben.
Um Microservices unabhängig voneinander in Produktion bringen zu können, muss jeder Microservice ein eigener Prozess sein - oder sogar eine eigene virtuelle Maschine mit einer vollständigen Software-Installation einschließlich Betriebssystem. Seit einiger Zeit wird dafür Docker [3] verstärkt eingesetzt: Docker bietet viele Vorteile von virtuellen Maschinen, nutzt aber Linux-Container und ein spezielles Dateisystem. Dadurch verursachen Docker-Container im Vergleich zu einfachen Prozessen praktisch keine zusätzliche Last.
Wegen dieses Vorgehen können Microservices in verschiedenen Technologien implementiert sein: Die Programmiersprache und -plattform kann frei gewählt werden. Die Microservices müssen aber miteinander kommunizieren. Dazu muss eine gemeinsame Kommunikationstechnologie wie REST oder eine Messaging-Plattform festgelegt werden. Ebenso sollte ein gemeinsames Monitoring, eine einheitliche Überwachung von Log-Dateien und Standards für das Deployment definiert werden. Nur so ist der Aufwand für den Betrieb klein genug, um 50 oder 100 Microservices in Produktion zu bringen. Ein so hoher Grad an Automatisierung können im Moment nur wenige Organisationen vorweisen. Die Implementierung solcher Automatisierungen ist eine wichtige Voraussetzung für die Nutzung von Microservices.
Microservices müssen einen getrennten Datenhaushalt haben: Die Nutzung einer gemeinsamen Datenbasis würde die Microservices zu eng aneinander binden. Eine Änderung der Daten-Schemata wäre nur noch durch die Koordination der beteiligten Services möglich - und das kann sehr aufwendig sein. Eine so starke Abhängigkeit widerspricht auch der weitgehenden Unabhängigkeit beim Deployment, das auch eine unabhängige Entwicklung ermöglicht.
Letztendlich erzeugen Microservices nämlich eine große Unabhängigkeit zwischen den einzelnen Modulen: Technologie-Entscheidungen können in jedem Microservice einzeln getroffen werden und ebenso können die Microservices getrennt voneinander in Produktion gebracht werden. Ebenso kann der interne Aufbau und die Architektur der Microservices unabhängig voneinander entschieden und definiert werden.
Die Größe eines Microservice
Der Name „Microservice“ verrät schon, dass die Größe eines Microservices wichtig ist. Es gibt jedoch keine feste Größe für einen Microservices, sondern nur bestimmte Grenzen:
Ein Microservice sollte auf jeden Fall so groß sein, dass ein Team ihn alleine weiterentwickeln kann. Die ideale Größe eines Scrum-Teams sind drei-neun Personen. Ein solches Team ist in diesem Zusammenhang gemeint. Wenn der Microservice größer ist, müssen Änderungen an einem Microservice zwischen mehreren Teams koordiniert werden. Sind die Microservices so klein, dass ein Team sie alleine entwickeln kann, kann das Teams die technischen und architekturellen Entscheidungen für den Microservice alleine treffen. Das ermöglicht eine parallele Entwicklung in den Teams ohne großen Koordinationsaufwand.
Microservices sind Module. Module sollten so klein sein, dass ein Entwickler sie verstehen kann. Sonst ist die Modularisierung nicht sinnvoll. Schließlich ist das Ziel der modularen Software-Entwicklung, dass einzelne Module unabhängig verstanden und geändert werden können. Microservices sind dabei keine Ausnahme. Was ein Entwickler verstehen kann, ist weniger, als was ein Team weiterentwickeln kann. Daher schränkt diese Grenze die Größe weiter ein.
Schließlich sollen Microservices ersetzbar sein. Das ist eine sehr wichtige Eigenschaft: Viele Software-Systeme sollen alte Systeme ablösen. Solche Projekte sind oft in einer schwierigen Lage: Das alte System vollständig zu ersetzen, ist ein hohes Risiko, weil dann alle Funktionalitäten neu implementiert werden müssen. Dabei schleichen sich leicht Fehler ein. Das Risiko der Produktivstellung ist hoch, weil sich dann das gesamte System auf einmal ändert. Eine schrittweise Migration ist schwierig, weil dazu Änderungen am Code notwendig sind. Die Code-Strukturen von alten System sind aber oft nur schwer änderbar. Microservices gehen mit der Situation anders um: Ein einzelner Microservice sollte so groß sein, dass er noch ersetzt werden kann. Dadurch ist es möglich, einen Microservice neu zu schreiben. Wenn er nicht mehr wartbar ist oder aus anderen Gründen nicht mehr weiterentwickelt werden soll, kann er einfach komplett neu implementiert werden.
Aber Microservices können nicht beliebig klein werden - es gibt auch untere Grenzen für die Größe eines Microservice:
Transaktionen können nur einen Microservice umfassen, da die Kommunikationsprotokolle keine Transaktionen über Microservices hinweg erlauben. Außerdem koppeln Transaktionen die Microservices sehr eng aneinander. Transaktionen bedingen, dass Daten gesperrt werden. Das erzeugt hinter den Kulissen eine starke Bindung der Services aneinander.
Ebenso kann die Konsistenz der Daten nur in einem Microservice gewährleistet werden. Konsistenz kann zum Beispiel bedeuten, dass die Summe des Umsatz mit der Summe aus den Bestellungen übereinstimmt. So etwas kann in einem verteilten System nur sichergestellt werden, wenn alle Daten auf einem Knoten gespeichert sind - und damit in einem Microservice.
Für Microservices muss eine automatisierte Infrastruktur bereit stehen. Dennoch kann die Infrastruktur die Anzahl der Microservices begrenzen - denn auch automatisiertes Deployment und Monitoring bedeuten Aufwand. Und schließlich bringt jeder Prozess zusätzlichen Aufwand mit sich. So wird wohl kaum jemand für 10 Zeilen Code eine Java Virtual Machine starten, die ohne weiteres Hunderte von Megabytes RAM verbrauchen können. Also ergibt auch die Infrastruktur eine untere Schranke für die Größe eines Microservice.
Schließlich sind Microservices im Netzwerk verteilt. Daher kommunizieren die Microservices über das Netzwerk. Im Vergleich zu einem lokalen Aufruf dauert ein Aufruf über das Netzwerk viel länger. Also sollten die Microservices nicht zu klein sein, weil viel Kommunikation über das Netzwerk geht und sich die Performance verschlechtert. Allerdings ist die verteilte Kommunikation häufig in der Praxis kein Problem [4]. Wenn die UI im Microservice implementiert ist, hat das den Vorteil, dass Kommunikation zwischen UI und Logik nicht über das Netzwerk geht - was das Problem weiter reduziert.
Letztendlich hängt also die minimale Größe eines Microservice entscheidend von der Infrastruktur ab. Amazon Lambda [5] ermöglicht beispielsweise sehr einfach das Deployment einer einzelnen JavaScript-Funktion oder Java-Klasse. Dabei wird jeder Aufruf des Codes einzeln abgerechnet. Mit einer solchen Infrastruktur sind viel kleinere Microservice-Größen möglich als mit Docker-Containern und Application Servern, weil der Aufwand für das Deployment viel geringer ist.
Warum Microservices?
Offensichtlich haben Microservices einige Herausforderungen - verteilte Systeme zu entwickeln und zu betreiben ist nicht einfach. Ebenso ist das Management der Architektur über die Microservices hinweg komplex.
Gründe für Microservices gibt es viele:
- Microservices ermöglichen, dass technische Entscheidung unabhängig pro Microservice getroffen werden. Wenn die fachliche Aufteilung der Microservices gut ist, können Features in genau einem Microservice implementiert werden. Wenn man das mit unabhängigen Anforderungsströmen aus verschiedenen Fachbereichen kombiniert, ergeben sich so unabhängige agile Teams. Das erlaubt es, auch größere Projekte mit agilen Teams durchzuführen: Letztendlich wird das Projekt in viele kleine Projekte aufgeteilt.
Der Umgang mit Legacy-Systemen ist recht einfach. Microservices erlauben es, die Legacy-Systeme um Funktionalitäten zu erweitern, ohne dabei den Code anzupassen. Ein Aufruf, der eigentlich für ein Legacy-System bestimmt ist, kann von einem Microservice abgefangen und verarbeitet werden. Der Microservice kann die Anfrage bearbeiten oder an das Legacy-System weitergeben. So können Funktionalitäten des Legacy-Systems ersetzt oder ergänzt werden, ohne dabei den Code des Legacy-Systems anzupassen. Solche Anpassungen sind bei Legacy-Systemen oft schwierig. Daher sind Microservices für die Weiterentwicklung von Legacy-Systemen oft ein nützliches Werkzeug.
Continuous Delivery [6] strebt an, dass Änderungen an der Software weitgehend automatisiert durch eine Continuous-Delivery-Pipeline in Produktion gehen. Eine solche Pipeline enthält neben der automatisierten Installation der Software vor allem verschiedene Test-Phasen. Microservices erleichtern den Aufbau einer Continuous-Delivery-Pipeline, da jeder Microservice unabhängig in Produktion gebracht werden kann. Es kann für jeden Microservice eine Continuous-Delivery-Pipeline aufgebaut werden. Die ist weniger komplex und daher einfacher umzusetzen als die Pipeline für einen Monolithen. Außerdem ist das Risiko für die Produktivstellung eines Microservice gering: Wenn es zu einem Fehler kommt, fallen nur einige Bestandteile des Systems aus und nicht das gesamte System, wie dies bei einem Deployment-Monolithen der Fall wäre. Microservices profitieren auch von Continuous Delivery. Sie benötigen eine Continuous-Delivery-Pipeline, weil sonst der Aufwand für die Produktivstellung viel zu groß wäre.
Viele Unternehmen migrieren von einem Deployment-Monolithen zu einer Microservices-Architektur, weil sie damit eine größere Unabhängigkeit der Teams erreichen und Microservices mit Continuous Delivery schneller in Produktion bringen können. Dabei kommt ihnen zu Gute, dass Legacy-Systeme sehr einfach mit Microservices ergänzt werden können.
Neben diesen drei Vorteilen gibt es weitere Gründe für Microservices:
Microservices können die langfristige Wartbarkeit des Systems positiv beeinflussen. Ein einzelner Microservice ist recht klein, so dass Änderungen einfach sind. Wenn das nicht der Fall ist, kann der Microservice neu geschrieben werden, da Microservices ersetzbar sind. Auch die Architektur bleibt bei Microservices lange Zeit erhalten: In Deployment-Monolithen entstehen oft aus Versehen Abhängigkeiten zwischen Modulen, die eigentlich nicht gewünscht sind. Die vielen Abhängigkeiten führen dazu, dass Änderungen Auswirkungen auf viele Teile des Systems haben. Dann ist das System kaum noch änderbar, weil die Auswirkungen einer Änderung kaum abschätzbar sind. Microservices haben explizite Schnittstellen, die nur über das Netzwerk angesprochen werden können. Abhängigkeiten können sich so kaum unabsichtlich einschleichen, weil der Entwickler die Schnittstelle nutzen muss. Das ist nur mit einem entsprechenden Aufwand möglich und passiert nicht aus Versehen. Weil einzelne Microservices einfach änderbar sind und die Architektur des Gesamtsystem auch langfristig erhalten bleibt, sollten Microservices langfristig wartbar bleiben.
Microservice-Systeme sind robust. Das ist zunächst überraschend - schließlich hat ein Microservice-System viel mehr Server, die ausfallen können. Also ist der Ausfall eines einzelnen Microservice recht wahrscheinlich. Aber Microservices sollten gegen den Ausfall anderer Microservices abgesichert sein. Dann ist das System ingesamt robuster: Zwar fällt vielleicht mal eine Server und ein Microservice aus. Die Auswirkungen sind aber begrenzt. Bei einem Deployment-Monolithen kann hingegen ein Flüchtigkeitsfehler in der Speicherverwaltung den gesamten Prozess und damit das gesamte System zum Absturz bringen. Das ist durch die Aufteilung des Systems in Microservices nicht mehr so einfach möglich.
Microservices können einzeln skaliert werden, statt das gesamte System zu skalieren. Das ist einfacher umzusetzen und benötigt auch weniger Hardware als die Skalierung eines Deployment-Monolithen. Da die Anfragen meistens nicht gleichmäßig über die Module verteilt sind, kann so die Skalierung wesentlich vereinfacht werden.
Schließlich können Microservices unterschiedliche Technologien nutzen. So kann beispielsweise die Produktsuche eine eigene Suchmaschine nutzen, ohne dass andere Microservices beeinflusst werden. Aber es muss nicht unbedingt um solche fundamentale technischen Entscheidungen gehen: In einem Java-System müssen die Versionen aller Bibliotheken festgeschrieben werden. Wenn ein Modul eine neue Version benötigt, muss das bei einem Deployment-Monolithen zwischen allen Modulen abgestimmt werden. Bei Microservices ist das nicht notwendig.
Diese Vorteile von Microservice können alleine schon Grund genüg sein, um Microservices einzuführen. So gibt es beispielsweise Projekte, in denen das System durch Microservices robuster gemacht werden soll. Das hat natürlich Auswirkungen darauf, wie Microservices eingeführt werden. In diesem Fall würde zunächst das System mit den entsprechenden Technologien versorgt werden, um bei Ausfällen von Teilen die Verfügbarkeit der anderen Teilen des Systems zu garantieren. Danach können diese Teile in einzelne Microservices überführt werden. Dieser Ansatz ist ganz anders also die Ergänzung eines Monolithen mit dem Ziel von Continuous Delivery und abhängiger Entwicklung.
Es gibt also nicht nur einen Grund für die Einführung von Microservices, sondern eine Vielzahl. Die Gründe lösen konkrete Problemen, denen immer mehr Unternehmen gegenüber stehen. Und die Idee ist auch nicht besonders neu: Die Idee, dass Teams jeweils einen Service entwickeln und betreiben, hat Amazon schon 2006 umgesetzt [7]. Microservices sind also deutlich mehr als ein neuer Hype.
Einsteigen oder weiter wie bisher?
Wie bei jeder Architektur-Entscheidung sollte man auch bei Microservices überlegen, ob sich der Einstieg lohnt. Microservices habe viele Vorteile, so dass mehr als ein Faktor betrachtet werden muss. Außerdem ist der Einstieg einfach: Einen Microservice zu schreiben ist nicht besonders aufwendig. Und ebenso ist es nicht besonders schwierig, ein Legacy-System mit Microservices zu ergänzen.
Bei dem Einstieg in Microservice ist die Migration weg von einem Monolithen gang und gäbe. Interessanterweise gibt es bei Microservices-Architektur auf der grünen Wiese keine Einigkeit, ob es besser ist mit einem Monolithen zu starten [8] oder nicht [9]. Die Struktur eines Monolithen ist leichter änderbar, was gerade am Anfang eines Projekts ein Vorteil sein kann. Auf der anderen Seite muss der Monolith später in Microservices aufgebrochen werden.
Eine wesentliche Herausforderung ist der Betrieb. Um mit einer großen Anzahl von Microservices zurecht zu kommen, ist ein hohes Maß an Automatisierung notwendig. Das stellt viele Betriebseinheiten vor unlösbare Herausforderungen. Technisch sind solche Herausforderungen lösbar - aber oft steht die Barriere zwischen Entwicklung und Betrieb einem reibungslosen Projekt im Weg.
Fazit
Microservices sind ein weiterer Ansatz für die Modularisierung von Systemen. Microservices können unabhängig voneinander in Produktion gebracht werden. Das hat viele Vorteile - vor allem im Bereich von Änderbarkeit und Continuous Delivery. Damit passen Microservices gut zu Herausforderungen, denen immer mehr Unternehmen gegenüber stehen - nämlich schneller mehr Änderungen in Produktion zu bringen. Aber darüber hinaus gibt es noch viele weitere Gründe für Microservices. Daher sind Microservices mehr als ein Hype, sondern ein Architektur-Trend, der eine Folge aktueller Anforderungen ist. Firmen wie Netflix oder Amazon zeigen, wie Microservices erfolgreich umgesetzt werden können.
Der Einsteig in Microservices ist ohne größere Schwierigkeiten möglich, denn ein kleinen Service zu entwickeln und in Produktion zu bringen ist nicht allzu komplex. Später ergeben sich Herausforderung bei Deployment und Betrieb. Sie hängen damit zusammen, dass die Anzahl Microservices irgendwann sehr groß sein wird.
Meta-Fazit
Auf einer anderen Ebene sind Microservices ebenfalls interessant: Schon 1967 hat Mel Conway das nach ihm benannte Gesetz aufgestellt [10]. Es besagt, dass eine Organisation nur Architekturen hervorbringen kann, die ihren Kommunikationsbeziehungen entspricht. Das ist logisch, denn eine Schnittstelle in der Architektur bedingt, dass Teams sie abstimmen müssen. Microservices nutzen das aus. Die Architektur wird dadurch unterstützt, dass die Aufteilung in fachliche Microservices in der Organisation durch fachliche Teams gespiegelt wird. Jeder Microservices wird durch ein Team umgesetzt. Auch außerhalb von Microservices wäre eine solche Integration von Organisation und Architektur sicher hilfreich.
Außerdem berücksichtige Microservices, dass Software irgendwann abgelöst wird. Da einzelne Microservices ersetzt werden können, ist eine Migration einzelner Teile des System einfacher. Sicher wäre es hilfreich, wenn die Entsorgung von Software bei Architekturen generell stärker betrachtet wird. Schließlich gibt es sehr viele Projekte, die alte System ablösen sollen - und diese Projekte sind oft sehr aufwändig und komplex. Wenn die Architektur ähnlich wie Microservices eine Ablösung vereinfacht, würden solche Projekt weniger risikoreich sein.
Referenzen
-
Sam Newman: Building Microservices,O'Reilly, 2015,ISBN 978–1–4919–5035–7 ↩
-
Eberhard Wolff: Microservices: Grundlagen flexibler Softwarearchitekturen, dpunkt Verlag, 2015, ISBN 978–3–8649–0313–7 ↩
-
http://martinfowler.com/articles/distributed-objects-microservices.html ↩
-
Eberhard Wolff: Continuous Delivery: Der pragmatische Einstieg, dpunkt Verlag, 2014, ISBN 978–3–8649–0208–6 ↩
-
http://jandiandme.blogspot.com/2006/10/jaoo-2006-werner-vogels-cto-amazon.html ↩