Monolith vs. Microservices Part 8: Monolith first, really?
BettercallPaul
BettercallPaul

// //
Lesedauer: 7 Minuten

Monolith vs. Microservices Part 8

Monolith first, really?


Bevor man überlegt, ob man lieber mit einem Monolith oder einer Microservice-Architektur startet, sollte man gut darüber nachdenken, ob sich Microservices für den Anwendungsfall lohnen. Wenn man zu dem Schluss kommt, dass Microservices für den Anwendungsfall geeignet sind und der Nutzen den zusätzlichen Aufwand zum Meistern der Herausforderungen von verteilten Systemen übersteigt, dann sollte man auch von Anfang an auf Microservices setzen. Sollte aber ein Monolith für den Anwendungsfall ausreichen, so ist ein Monolith eine gute Wahl. Ein Monolith ist sowohl für die Entwicklung als auch für den Betrieb das Einfachste.

Für die Entscheidungsfindung müssen aber drei wesentliche Kriterien beantwortet werden.

Zusammenfassung

Wenn man tatsächlich zu dem Schluss kommt, dass Microservices für den Anwendungsfall geeignet sind und der Nutzen den zusätzlichen Aufwand zum Meistern der Herausforderungen von verteilten Systemen übersteigt, dann sollte man auch von Anfang an auf Microservices setzen. Die Artikel von Fowler und Tilkov stammen aus dem Jahr 2015 – mittlerweile haben wir über sechs weitere Jahre Erfahrungen rund um Microservices gesammelt und kennen mehr Herausforderungen und wie man diese angehen kann. Wichtig ist aber trotzdem der richtige Schnitt: Dafür benötigt es Domänenexperten. Aber auch diese können mit dem ersten Entwurf etwas daneben liegen – deshalb ist es ratsam, mit etwas größeren Microservices zu starten und fachliches Monitoring einzuführen, um herauszufinden, wie die Anwendung benutzt wird (was wird wann aufgerufen) und wie sich einzelne Teile der Anwendung verhalten (Antwortzeiten). Durch dieses Monitoring kann man im Live-Betrieb feststellen, ob es zum Beispiel Bottlenecks gibt, die durch Herauslösen weiterer Teile der Anwendung in eigene Microservices gelöst werden können.

Sollte ein Monolith für den Anwendungsfall ausreichen, so sollte auch ein Monolith gebaut werden. Ein Monolith ist sowohl für die Entwicklung als auch für den Betrieb das Einfachste. Dennoch sollten Monolithen – genau wie Microservices – sauber in Module strukturiert werden, damit sie auf Dauer wartbar und erweiterbar bleiben.

Microservices machen Sinn, wenn mindestens eines der folgenden Kriterien erfüllt ist:

  • Hohe Skalierbarkeit wird benötigt. Dies trifft für viele Anwendungen nicht zu – Details können im Artikel zu Betreibbarkeit nachgelesen werden.
  • Unterschiedliche nicht-funktionale Anforderungen für verschiedene Teile der Anwendung.
  • Unabhängige Entwicklung oder Deployments für einzelne Teile der Anwendung.

Auch Moni und Michi überlegen, wie sie denn am besten mit ihrer Restaurant-App starten sollen.

Monolith first

Bereits in der Einführung dieser Blogserie wurde der Diskurs von Martin Fowler und Stefan Tilkov („Monolith First“ vs „Don’t start with a Monolith“) angerissen.

Moni: „Lass uns doch mit dem modularen Monolithen beginnen. Wir bauen Sollbruchstellen ein und zerlegen den Monolithen später bei Bedarf in Microservices.“

Michi: „Du hast ja selbst immer wieder darauf hingewiesen, dass Microservices einen Overhead mitbringen. Aber wenn wir die Voraussetzungen für eine Microservice-Architektur von Anfang an schaffen, dann können wir später einfach bestehende Services zerlegen. Wenn wir dies am Anfang versäumen, ist der Aufwand später wesentlich höher.“

Moni nimmt die Position von Martin Fowler ein und möchte gerne mit einem Monolith beginnen (Monolith First). Michi entgegnet, dass der Aufwand, Microservices später einzuführen, deutlich höher ist, als von Anfang an auf Microservices zu setzen (Don’t start with a Monolith).

Martin Fowler führt in seinem Blogartikel „Monolith First“ an, dass er basierend auf den Erfahrungen von ihm und seinen Kolleg:innen mit einem Monolith starten würde.

Begründet wird diese Aussage einerseits durch „YAGNI“ (You aren’t gonna need it): In der Anfangsphase eines Softwareprodukts ist unklar, wie die Anwendung genutzt wird und wie viele Benutzer die Anwendung einmal haben wird. Daher ist Geschwindigkeit (schnelle Markteinführung) und zügiges Nutzerfeedback am Anfang sehr wichtig. Martin Fowler ist – genau wie wir in einem früheren Artikel – der Ansicht, dass ein MVP, also eine möglichst simple, leichtgewichtige Version der Anwendung, schneller und leichter mit einem Monolith umsetzbar ist. Je nach Feedback und Anzahl der Nutzer:innen sollte dann weiterentwickelt werden – entweder als Monolith, oder wenn der Aufwand gerechtfertigt ist, als Microservices.

Andererseits ist aus der Sicht von Fowler der richtige Schnitt das A & O für erfolgreiche Software – insbesondere für Microservices. Warum der richtige Schnitt entscheidend ist und welche Auswirkungen ein falscher Schnitt nach sich zieht, kann auch in anderen Teilen unserer Artikelserie nachvollzogen werden. Für den richtigen Schnitt müssen die richtigen Kontextgrenzen gefunden und die geeigneten Bounded Contexts geschaffen werden. Dies stellt eine sehr große Herausforderung dar – vor allem zu Beginn eines Projekts. Selbst erfahrene Domänenexperten würden nicht immer sofort den richtigen Schnitt wählen. Das Problem bei Microservices ist, dass serviceübergreifendes Refactoring, z. B. das Umziehen von Logik, Schnittstellen oder Daten sehr viel aufwändiger ist als in einem Monolith. Fowler empfiehlt deshalb erst einen Monolith zu bauen und die Anwendung bei Bedarf in Microservices zu zerlegen, wenn die Bounded Contexts stabil sind.

Zur Umsetzung von Monolith First gibt Fowler vier Strategien vor:

  1. Einen modularen Monolith bauen und sowohl Schnittstellen als auch die Daten-Persistenz schon auf einen Split vorbereiten, sodass einzelne Module möglichst leicht heraustrennbar sind. Das heißt: Es gibt eine Deployment-Einheit mit unterschiedlichen Modulen, die über definierte Schnittstellen miteinander kommunizieren. Aber jedes Modul hat beispielsweise sein eigenes Schema in der Datenbank, von dem die anderen Module nichts wissen:
  1. Mit einem Monolith starten und einfache, lose gekoppelte Teile der Anwendung heraustrennen in eigene Microservices. Der Kern der Anwendung bleibt erst mal ein Monolith und wird nach und nach zerlegt.
  2. Anwendung als einen Monolith bauen und irgendwann als Microservices neu bauen. Als Beispiel für diese Vorgehensweise wird Ebay genannt.
  3. Mit größeren, gröberen Services starten. Man kann dadurch Erfahrungen mit verteilten Systemen sammeln und reduziert zeitgleich die Gefahr von größeren serviceübergreifenden Refactorings. Sobald die Schnittstellen und Bounded Contexts stabil sind, können die größeren Services nach Bedarf weiter zerlegt werden.

Monis gewünschter Ansatz entspricht also der ersten Strategie, die von Fowler für „Monolith First“ angeführt wird.

Don’t start with a Monolith

Stefan Tilkov stimmt in seinem Artikel „Don’t start with a Monolith“ mit der Meinung von Fowler überein, dass man die Domäne sehr gut kennen muss, bevor man anfängt, sie in Bounded Contexts zu zerlegen. Allerdings führt Tilkov an, dass es aus seiner Sicht schwer ist, existierende Monolithen nachträglich in Microservices zu zerlegen. Nach seinen Erfahrungen gibt es in Monolithen meist eine hohe Kopplung zwischen Modulen, gemeinsam genutzte Abstraktionsschichten und Domain-Objekten sowie ein gemeinsames Datenmodell. Tilkov beschreibt also, dass es kaum Monolithen gibt, die so funktionieren wie in der ersten Monolith First-Strategie von Fowler beschrieben.

Tilkov empfiehlt stattdessen, erst mal abzuwägen, ob Microservices für den gegebenen Anwendungsfall tatsächlich notwendig sind und Nutzen stiften. Wann Microservices Sinn machen und der Nutzen größer ist als die Herausforderungen, haben wir bereits in früheren Artikeln beschrieben. Sollte man zu dem Entschluss kommen, dass Microservices für den Anwendungsfall genutzt werden sollen, dann sollte man auch mit unabhängigen Microservices starten. Voraussetzung dafür ist aber, dass man die Domäne sehr gut kennt, damit sinnvolle Bounded Contexts entstehen. Auch Tilkov betont, dass Refactoring im Kleinen innerhalb einer Microservice-Architektur leichter ist, Refactoring im Großen (serviceübergreifende Änderungen) dafür deutlich aufwändiger als im Monolithen.

Auf die Argumentation von Tilkov setzt auch Michi: Der Aufwand, später Microservices einzuführen, ist deutlich höher, als von Anfang an auf Microservices zu setzen.

Und wer hat nun Recht?

Auf diese Frage kann leider keine allgemeingültige Antwort gegeben werden, denn es kommt – wie fast immer – auf den Anwendungsfall an. Für die Restaurant-App macht es zum Beispiel durchaus Sinn, von Anfang an auf Microservices zu setzen. Vielleicht sind die Services am Anfang etwas größer und nicht so feingranular, wie man sich das für Microservices vorstellt. Aber es gibt in der Restaurant-App klare Kontext-Grenzen und unterschiedliche nicht-funktionale Anforderungen für die unterschiedlichen Teile der Anwendung. So könnte unsere Restaurant-App mit zwei Microservices starten: ein Microservice für den Bestellprozess mit Kundeninteraktion sowie zur Pflege der Produktdaten der Restaurants und ein Microservice rund um das Thema Lieferung (Routenoptimierung, Lieferantensuche). Ein Vorteil bei diesem Schnitt ist auch, dass nicht viele Daten zwischen den beiden Services ausgetauscht werden müssen. Zum Thema Datenhaltung innerhalb eines verteilten Systems wird es noch einen Artikel geben.