Sie sind in aller Munde: Microservices. Jedoch handelt es sich bei ihnen, wie bei jeder anderen Architektur auch, um ein Trade-off. Diese Tatsache führt umgehend zu der Frage, in welchen Bereichen Microservices überhaupt sinnvoll sind, wie es mit ihnen weitergeht und was nach ihnen kommt.
Um die Vorteile und Herausforderungen von Microservices [1] zu verstehen, sollte zunächst der Begriff definiert werden. Dabei helfen die ISA-Prinzipien (Independent Systems Architecture) [2]:
Da es sich bei Microservices nur um eine andere Art von Modulen handelt, die als Docker-Container oder beispielsweise als Serverless-Funktionen umgesetzt werden, stehen sie in Konkurrenz zu anderen Modularisierungsvarianten wie Java Packages oder JARs. Bei der Implementierung von Microservices gibt es jedoch viel mehr Freiheiten; jeder Microservice kann in einer anderen Programmiersprache geschrieben sein. Es gibt zwei Ebenen von Architektur: Die Makroarchitektur umfasst Entscheidungen, die alle Microservices beeinflussen. Die Mikroarchitektur betrifft Entscheidungen, die nur einen Microservice beeinflussen. Diese Unterscheidung ist wichtig, weil erst durch Microservices so viele Freiheiten entstehen, dass eine Mikroarchitektur sinnvoll ist. Darüber hinaus verfügt jeder Microservice über eine eigene Continuous Delivery Pipeline. Ansonsten ginge ein wesentlicher Vorteil von Microservices verloren: das unabhängige Deployment.
Durch Microservices ergeben sich Vorteile im Bereich der Organisation. Das erscheint zunächst merkwürdig, schließlich sind Microservices „nur“ eine Architektur. Jedoch hat Architektur eben auch Auswirkungen auf die Organisation: Die Alternative zu Microservices wäre ein Deployment-Monolith, in dem alle Module zusammen deployt werden, was jedoch einige Einschränkungen bedeutet. Es ist beispielsweise nicht möglich, ein Feature in einem Modul zu implementieren und dann nur dieses Modul zu deployen. Deshalb erfordert das Deployment eine Koordinierung: Alle Module müssen in einer Version vorliegen, die tatsächlich sinnvoll deployt werden kann. Außerdem ist es notwendig, dass der Deployment-Monolith eine gemeinsame technische Basis hat – zum Beispiel die JVM oder eine bestimmte Programmiersprache. Im Fall von Java müssen alle Bibliotheken in nur einer festgelegten Version vorhanden sein. Deshalb ist es notwendig, dass alle Module mit diesen Bibliotheksversionen umgesetzt sind und diese technische Basis auch mit allen Teams koordiniert wird (Abb. 1). Im Gegensatz dazu können Microservices einzeln deployt werden und jeweils andere technologische Entscheidungen umsetzen (Abb. 2). Diese technologischen Entscheidungen müssen nicht unbedingt dazu führen, dass jeder Microservice in einer anderen Programmiersprache implementiert ist; aber ein Team kann beispielsweise eine neue Version einer Library mit einem Bugfix ausliefern, ohne dass die anderen Microservices beeinflusst werden. Microservices machen die Teams unabhängiger; insbesondere entfällt die Koordinierung bei den Deployments. Das macht beispielsweise den Release Train aus dem SAFe Framework [3] zur Skalierung von agilen Prozessen überflüssig, da Releases und Deployments nicht mehr koordiniert werden müssen. So vereinfacht sich das agile Vorgehen auch in großen Projekten.
Microservices sollten idealerweise jeweils einen Bounded Context implementieren. Dabei handelt es sich um einen Teil der Fachlichkeit, der über ein eigenes Domänenmodell verfügt. Beispielhaft lässt sich das anhand des Lieferprozesses in einem E-Commerce-System illustrieren: Das Domänenmodell für den Bounded Context Lieferprozess kennt Lieferadressen oder die Größen und Gewichte der Waren. Für die Rechnungslegung muss das Domänenmodell hingegen die Preise, Steuern und Rechnungsadressen kennen. Das bedeutet, dass die Domänenmodelle jeweils einige Use Cases vollständig abbilden. Eine Fachlichkeit lässt sich oft durch Änderung von nur einem Bounded Context umsetzen. Wenn der Bounded Context in einem Microservice implementiert ist, kann er unabhängig von den anderen Bounded Contexts und Microservices deployt werden. So entsteht eine große fachliche Unabhängigkeit: Ein Feature verursacht Änderungen an einem Bounded Context, der in einem Microservice implementiert ist, sodass die Änderungen mit nur einem Deployment in Produktion gebracht werden können.
Durch Microservices entsteht ein hohes Maß an Unabhängigkeit, durch Bounded Contexts zusätzlich ein hohes Maß an fachlicher Unabhängigkeit, sodass die Teams sich selbst organisieren können. Letztendlich erlaubt die Microservice-Architektur, ein großes Projekt in mehrere kleine, unabhängige Projekte aufzuteilen. Das reduziert beispielsweise den Kommunikationsaufwand und die Risiken.
Auch auf technischer Ebene bieten Microservices zahlreiche Vorteile: Module sollten entkoppelt sein. Bei klassischen Modulen bedeutet Entkopplung, dass eine Änderung an einem Modul die anderen Module nicht beeinflusst. Die einzelnen Module können unabhängig voneinander weiterentwickelt werden. Microservices bieten Entkopplung auch auf anderen Ebenen: Technische Entscheidungen und Deployment kamen schon zur Sprache, doch auch die Skalierbarkeit kann pro einzelnem Microservice erfolgen. Fällt ein Microservice aus, laufen die anderen weiter, sodass auch dieser Ausfall isoliert ist. Außerdem ist es möglich, Microservices beispielsweise durch Firewalls...