5 Punkte von GN⁺ 2023-10-31 | 1 Kommentare | Auf WhatsApp teilen
  • Backends starten meist als Monolith, aber wenn mehrere Feature-Teams an derselben Codebasis arbeiten, kann die Kopplung steigen und die Änderungsgeschwindigkeit sinken
  • Microservices teilen Services nach Geschäftsfunktionsbereichen auf, um unabhängige Deployments und die Autonomie kleiner Teams zu erreichen
  • Werden Services zu klein, steigt die operative Last stärker als der Nutzen; der Name „micro“ kann die Beurteilung sogar eher trüben
  • Eine vollständige Einführung bringt weitreichende Kosten mit sich: Standardisierung, Resource Provisioning, Absicherung von Remote Calls, CI/CD, Betrieb und eventuelle Konsistenz
  • Meist ist es sicherer, erst im Monolithen genügend Erfahrung mit Grenzen zu sammeln und dann bei wachsenden Schmerzen schrittweise auszulagern; ein Microservices-first-Ansatz eignet sich, wenn Erfahrung und Plattformvorbereitung vorhanden sind

Grenzen, die sichtbar werden, wenn der Monolith wächst

  • Das Backend moderner Single-Page-JavaScript-Anwendungen beginnt meist als ein einzelner zustandsloser Webservice, der eine RESTful-HTTP-API bereitstellt und eine relationale Datenbank nutzt
  • Dieser Service besteht aus Komponenten oder Bibliotheken, die unterschiedliche Geschäftsfunktionen implementieren
  • Je mehr Feature-Teams an derselben Codebasis mitarbeiten, desto stärker wird die Kopplung zwischen Komponenten, und die Arbeitsbereiche der Teams überschneiden sich leichter
    • Die Produktivität sinkt, und es wird schwieriger, den Einflussbereich von Änderungen vorherzusagen
  • Wird die Codebasis komplex genug, versteht niemand mehr das Gesamtsystem vollständig, sodass neue Features und Bugfixes mehr Zeit brauchen
  • Selbst wenn das Backend in teambezogene Bibliotheken aufgeteilt ist, führen Änderungen an Bibliotheken letztlich dennoch zu einem Redeployment des Service
    • Tritt ein Bug wie ein Memory Leak auf, kann der gesamte Service betroffen sein
    • Wird ein fehlerhafter Build zurückgerollt, beeinflusst das nicht nur das Team, das den Bug verursacht hat, sondern das Tempo aller Teams

Autonomie durch Service-Grenzen

  • Eine Möglichkeit, die Wachstumsschmerzen eines monolithischen Backends zu verringern, besteht darin, das Backend in eine Sammlung unabhängig deploybarer Services aufzuteilen
  • Die Services kommunizieren über APIs, und APIs schaffen Grenzen, die schwerer zu durchbrechen sind als Komponentengrenzen innerhalb desselben Prozesses
  • Diese Architektur wird als Microservices-Architektur bezeichnet, aber der Ausdruck micro kann irreführend sein
    • Services müssen nicht unbedingt klein sein
    • Services mit zu wenig Funktionalität verursachen unter Umständen mehr operative Last als Nutzen
  • Ein passenderer Name wäre vielleicht service-oriented architecture, aber auch dieser Begriff trägt Altlasten mit sich

Geschwindigkeit durch kleine Teams und unabhängige Services

  • Wenn das Backend in Services entlang von Geschäftsfunktionen aufgeteilt wird, kann jeder Service von einem kleinen einzelnen Team entwickelt und betrieben werden
  • Kleine Teams sind effektiver, weil die Kommunikationskosten mit der Teamgröße quadratisch steigen
  • Wenn jedes Team seinen eigenen Release-Zeitplan festlegt und die Kontrolle über seine Codebasis hat, sinkt der Abstimmungsaufwand zwischen Teams und Entscheidungen werden schneller getroffen
  • Ist die Codebasis eines Service klein, können Entwickler sie leichter verstehen, und auch die Einarbeitungszeit neuer Mitarbeiter sinkt
    • Kleine Codebasen verringern Verlangsamungen in der IDE und helfen damit ebenfalls der Produktivität
  • Dank starker Service-Grenzen müssen Entwickler beim Ändern eines Teils des Backends nicht das Ganze verstehen, sondern nur einen kleinen Ausschnitt
  • Jeder Service kann unabhängig skaliert werden und einen zu seinen Anforderungen passenden Tech-Stack wählen
    • API-Konsumenten müssen sich nicht darum kümmern, wie eine Funktion intern implementiert ist
    • Neue Technologien lassen sich leichter ausprobieren und evaluieren, ohne andere Systembereiche zu beeinflussen
  • Jeder Microservice kann ein eigenes Datenmodell und einen eigenen Datenspeicher haben
    • Entwickler können das Schema ändern, ohne andere Services zu beeinträchtigen

Die tatsächlichen Zusatzkosten von Microservices

  • Microservices fügen dem Gesamtsystem mehr bewegliche Teile hinzu, und diese Kosten sind nicht kostenlos
  • Die Kosten einer vollständigen Einführung lohnen sich nur, wenn sie über Dutzende Entwicklungsteams hinweg verteilt abgeschrieben werden können
  • Entwicklererfahrung

    • Es ist zwar möglich, pro Service unterschiedliche Sprachen, Bibliotheken und Datenspeicher zu verwenden, aber das kann die Wartung der Anwendung erschweren
    • Sind die Software-Stacks vollständig unterschiedlich, fällt es Entwicklern schwerer, von einem Team in ein anderes zu wechseln
    • Um gemeinsame Funktionen zu liefern, die jeder Service braucht, etwa Logging, müssen Bibliotheken für jede verwendete Sprache unterstützt werden
    • Ein gewisses Maß an Standardisierung ist notwendig
    • Teams, die dem empfohlenen Portfolio an Sprachen und Technologien folgen, kann man eine gute Developer Experience bieten und so den Einsatz bestimmter Technologien locker fördern
  • Resource Provisioning

    • Um viele unabhängige Services zu unterstützen, müssen Server, Datenspeicher und andere allgemeine Ressourcen leicht erstellt werden können
    • Man sollte nicht zulassen, dass jedes Team seine eigene Art der Ressourcenerstellung entwickelt
    • Bereitgestellte Ressourcen müssen anschließend erneut konfiguriert werden; um das zu bewältigen, ist erhebliche Automatisierung nötig
  • Kommunikation

    • Remote Calls sind teuer und führen neue Arten ein, auf die ein System scheitern kann
    • Um Ausfälle abzufangen, braucht es Schutzmechanismen wie Timeouts, Retries und Circuit Breaker
    • Um Performance-Verluste durch Netzwerkkommunikation zu reduzieren, sollte man auch asynchrone Verarbeitung und Batching einsetzen
    • Diese Faktoren erhöhen die Systemkomplexität
    • Auch Monolithen können wegen Remote-Client-Zugriffen und der Nutzung von Third-Party-APIs ähnliche Probleme haben, allerdings in kleinerem Ausmaß
  • Kontinuierliche Integration, Delivery und Deployment

    • Kontinuierliche Integration sorgt dafür, dass Codeänderungen erst nach automatischen Builds und Tests in den Main-Branch gemergt werden
    • Sobald Codeänderungen gemergt sind, sollten sie automatisch in eine produktionsähnliche Umgebung veröffentlicht und deployt werden
    • In dieser Umgebung sollten Integrations- und End-to-End-Tests laufen, um sicherzustellen, dass ein Microservice keine anderen Services beschädigt, von denen er abhängt
    • Das Testen einzelner Microservices ist nicht schwieriger als das Testen eines Monolithen, aber Integrationstests für alle Microservices zusammen sind deutlich schwieriger
    • Wenn einzelne Services miteinander interagieren, können sehr subtile und unerwartete Verhaltensweisen auftreten
  • Betrieb

    • Anders als beim Monolithen ist es deutlich teurer, für jedes zuständige Service-Team ein separates Ops-Team bereitzustellen
    • Das Team, das einen Service entwickelt, übernimmt in der Regel auch dessen On-Call-Bereitschaft
    • In jedem Sprint muss das Team entscheiden, ob Entwicklungsarbeit oder operative Last Vorrang hat, und daraus entsteht Reibung
    • Auch das Debugging von Systemausfällen wird schwieriger
    • Man kann nicht die gesamte Anwendung auf einer lokalen Maschine starten und sie im Debugger Schritt für Schritt durchlaufen
    • Mehr bewegliche Teile bedeuten auch mehr mögliche Fehlerarten
    • Gutes Logging und Monitoring werden auf allen Ebenen wichtiger
  • Eventuelle Konsistenz

    • Wenn eine Anwendung in mehrere Services aufgeteilt wird, existiert das Datenmodell nicht mehr nur in einem einzigen Datenspeicher
    • Datensätze in unterschiedlichen Datenspeichern atomar zu aktualisieren und starke Konsistenz zu garantieren, ist langsam, teuer und schwer umzusetzen
    • Eine solche Architektur muss in der Regel eventuelle Konsistenz akzeptieren

Wann sollte man aufteilen?

  • Wenn eine Anwendung in Services aufgeteilt wird, steigt die Komplexität des Gesamtsystems erheblich
  • Im Allgemeinen ist es besser, mit einem Monolithen zu beginnen und erst dann aufzuteilen, wenn es dafür ausreichend gute Gründe gibt
  • Die richtigen Grenzen zwischen Services zu finden, ist schwierig; daher ist es oft einfacher, innerhalb des Monolithen Grenzen zu verschieben und so den passenden Zuschnitt zu finden
  • Ist der Monolith reif genug und werden die Wachstumsschmerzen größer, kann man nach und nach einzelne Microservices herauslösen
  • Ein Microservices-first-Ansatz ist nur dann sinnvoll, wenn bereits Erfahrung vorhanden ist und entweder schon eine passende Plattform aufgebaut wurde oder die dafür nötige Aufbauzeit eingeplant ist

1 Kommentare

 
GN⁺ 2023-10-31
Hacker-News-Kommentare
  • Ich stimme dem Artikel im Großen und Ganzen zu, aber andersherum betrachtet gibt es auch Fälle, in denen die Kosten tragbar sind: Man kann alte Kompatibilitätsprobleme mit Third-Party-Abhängigkeiten in einen separaten Service auslagern und so schmerzhafte Refactorings oder eine eigene Neuimplementierung vermeiden; man kann verhindern, dass Deployment-Beschränkungen eines hochverfügbaren Kernsystems die Deployments anderer Systeme blockieren; und man kann Design-Zwänge isolieren, die große Kunden nicht ändern können
    Das heißt nicht, dass es kostenlos ist, sondern dass es Kosten sind, die es wert sein können, um zu vermeiden, dass ein einfacher Monolith zu einem Müllhaufen wird, durch riskante Deployments ins Wanken gerät oder an einen Geschäftspartner mit einem noch schlechteren Tech-Stack gekettet bleibt

    • Klingt für mich eher so, als würde man einfach ein oder zwei Services haben. Ich würde nicht sagen, dass das gleichbedeutend mit „Microservices“ ist
      Sind Microservices nicht eher ein Ansatz, bei dem interne Komponenten grundsätzlich als Services statt als Bibliotheken oder Klassen gebaut werden?
    • Noch ein Punkt: Ein Vorteil ist, dass sich unterschiedliche Programmiersprachen leicht einsetzen lassen. Nicht jede Sprache passt gut zu jedem Problem
      Wenn man etwa einen Machine-Learning-Inferenzservice in Python, ein serverseitig gerendertes UI in Rails und einen Service mit viel I/O und Nebenläufigkeit in Go schreibt, kann der zusätzliche Aufwand, diese drei als getrennte Services aufzuteilen, gerechtfertigt sein
    • Es gibt viele Wege, Dinge gut zu entwerfen, und das heißt nicht, dass man Microservices vorschnell einführen sollte
      Ich mag Microservices, aber frühe Optimierung und frühe Skalierung können genauso schlimm sein wie technische Schulden, die entstehen, wenn man später in eine völlig andere Richtung optimieren muss
    • Bei Punkt 1 verstehe ich nicht, warum es besser sein soll, das als separaten Service herauszulösen, statt es hinter einem Interface mit klarer API zu kapseln
    • Ich vermute, solche Fälle betreffen weniger als 10 % der gesamten Codebasis
  • Der richtige Zeitpunkt, etwas in einen separaten Service auszulagern, ist dann erreicht, wenn ein Problem auftritt, das sich anders nicht mehr auf beherrschbare Weise lösen lässt
    Architekturkomplexität zu erhöhen, um Grenzen zu erzwingen, kann keine Lösung für mangelnde organisatorische Disziplin sein, aber mittelgroße Tech-Unternehmen behandeln es ständig so. Wenn schwache Domänengrenzen Probleme verursachen, ist es gut möglich, dass das Herauslösen eines Services sie auch nicht wirklich löst

    • The last responsible moment ist eine Strategie, bei der man eine Entscheidung so lange aufschiebt, bis die Kosten weiteren Aufschubs höher sind als die Kosten, sie jetzt zu treffen“
      Zitat: https://www.oreilly.com/library/view/software-architects-han...
    • Man sagt zwar, dass mehr Architekturkomplexität zur Durchsetzung von Grenzen keine Lösung für fehlende organisatorische Disziplin ist, aber in der Praxis macht man genau das ständig
      Auch CI/CD, das PRs blockiert, bis Tests grün sind, ist eine teure technische Lösung für ein Problem organisatorischer Disziplin
    • Klingt nach SISP, einer Lösung auf der Suche nach einem Problem. Oder danach, einem Problem eine Lösung überzustülpen, ohne die eigentliche Ursache zu verstehen
    • Auf HN scheint sich das Meme verbreitet zu haben, dass Microservices niemals gerechtfertigt sein können, was seltsam ist, weil die Branche insgesamt das nicht so sieht
      Alle erkennen an, dass es sinnvoll ist, die Auflösung von Domainnamen durch einen externen Service erledigen zu lassen, und kaum jemand integriert einen rekursiven DNS-Resolver samt Cache in einen Monolithen. Trotzdem wird diese alte Verantwortungstrennung nicht gut als Beispiel anerkannt
    • Ich habe das immer für das beste Beispiel von deferred execution gehalten. Es ist erstaunlich, wie oft Unternehmen dabei falsch liegen
      Das Problem scheint zu sein, dass gerade die „guten“ Mitarbeiter oft so motiviert sind, Wert zu zeigen, dass sie ins Overengineering abgleiten, und am Ende wird alles zur Hölle
  • Microservices
    grug fragt sich, warum Großhirne sich das schwierigste Problem vornehmen, nämlich ein System korrekt zu zerlegen, und dann auch noch Netzwerkaufrufe hineinpacken
    Für grug wirkt das sehr verwirrend
    https://grugbrain.dev/#grug-on-microservices

    • grug scheint nie erlebt zu haben, wie große Teams sich gegenseitig in ihre Domänen und Datenmodelle hineinpfuschen, auf bestimmte Implementierungen festgenagelt werden und riesige organisatorische Anstrengungen nötig sind, damit ein Team überhaupt Features ausliefern kann. Microservices und die Kontrolle über den jeweils eigenen Datenspeicher erhöhen die Teamgeschwindigkeit
      „Wir wollen wegen SCALE_REASONS den Zugriff auf FOO_TABLE modernisieren und dafür von MySQL auf DynamoDB wechseln, aber 32 von 59 Teams greifen direkt auf FOO_TABLE zu oder rufen direkt private Methoden unserer Klasse auf. Diese Teams haben andere Prioritäten und können weder auf FOO_SERVICE umsteigen noch ihre Queries so anpassen, dass sie Sharding-Tabellen verwenden. Jetzt wird die Skalierung von FOO_TABLE zu einem mehrquartaligen Projekt, und wir müssen den Teams ermöglichen, sich langsam zu aktualisieren. In 1–2 Jahren können wir den alten Ansatz, der gerade brennt, vielleicht abschalten. Viel Spaß bis dahin mit On-Call“
      Mit Microservices merkt das Team zwar, dass die Tabelle nicht skaliert, aber die Daten werden über eine API bereitgestellt. Im nächsten Sprint plant und führt es die Migration durch, und die API-Nutzer stellen nur fest, dass alles viel schneller geworden ist
    • Für Autovervollständigung sind statische Typen nicht zwingend nötig. JetBrains hat IDEs mit Autovervollständigung auch für Sprachen wie Ruby oder Python
      Wenn man ein REPL in aktuellem Ruby öffnet, bekommt man in Bezug auf Autovervollständigung und Syntaxprüfung schon einen großen Teil dessen, was man von einer IDE für statisch typisierte Sprachen erwartet
    • Netzwerkaufrufe einzuführen ist mächtig. Man schafft damit tatsächlich eine physisch erzwungene harte Grenze, und die beiden Services müssen einander als isoliert behandeln
      Isolation sollte man nicht leichtfertig abtun; sie ist eine der mächtigsten Eigenschaften, die man in Software einbauen kann. Sie kann die Performance erhöhen, Fehlergrenzen schaffen und Sicherheitsgrenzen bereitstellen
      Das ist dasselbe Grundkonzept wie beim Actor-Modell. Statt dass zwei Komponenten denselben Speicher gemeinsam nutzen und verändern, kommunizieren zwei isolierte Systeme wie Actoren oder Microservices nur über ein definiertes Protokoll miteinander
  • Ich denke, viele Menschen verstehen Modularität falsch. Modularität ist wichtig, aber ich bin zu der Überzeugung gelangt, dass es noch ein weiteres wichtiges Architekturprinzip gibt: das Prinzip des einzelnen Wirbels
    In Softwaresystemen ist ein Wirbel eine Schleife im Datenfluss. Wenn man Daten irgendwohin sendet und das verarbeitete Ergebnis zurückbekommt oder auf irgendeine Weise von den Auswirkungen dieser Daten betroffen ist, gibt es einen Wirbel. Veränderliche Variablen sind ein Beispiel für einen sehr kleinen Wirbel
    Das Prinzip des einzelnen Wirbels bedeutet, dass es in einem Softwaresystem idealerweise nur einen Wirbel geben sollte, oder anders gesagt, dass alle Komponenten wissen sollten, in welche Richtung sich ihr Wirbel dreht
    Wenn man Module zusammenführt, die zwei Wirbel bilden, ist die Komposition einfach, wenn die Wirbelrichtung übereinstimmt; bei entgegengesetzter Richtung wird es knifflig, weil man entscheiden muss, wie die Richtung des neuen Wirbels aussehen soll. Deshalb ist es am besten, wenn alle Module dieselbe Wirbelrichtung haben und einen einzigen Wirbel bilden
    Dieses Prinzip ist eine Verallgemeinerung von Ideen wie dem Flux-Muster, CQRS, Event Sourcing und Unveränderlichkeit

    • Ein sehr guter Punkt, dazu ließen sich allein mehrere Artikel schreiben. Service A kann B aufrufen, B kann C aufrufen, und C kann dann wieder A aufrufen; dann gibt es Probleme
      Oder in A passiert etwas Unerwartetes und dadurch kann C blockiert werden. Im Idealfall sollte der Parent nur seine Children aufrufen und nicht von seinem Parent abhängen; wenn das scheitert, ist auch die Architektur gescheitert
    • Mir gefällt dieses Konzept
      Wenn man sich ein Architekturdiagramm ansieht, besteht die halbe Arbeit darin zu erkennen, was asynchron oder synchron ist, was Push oder Pull ist und in welche Richtung und Reihenfolge der Fluss verläuft
      Enterprise Service Bus und Message Queues wirken wie eine Lösung dafür, einen Wirbel dort zu erzeugen, wo die Richtungen auf beiden Seiten nicht zusammenpassen
    • Dieses Wirbel-Konzept ist interessant. Ich frage mich, ob es dazu Bücher oder Online-Material gibt, die man sich anschauen sollte
    • Den vorletzten Absatz, besonders die Begründung, habe ich nicht ganz verstanden. Ich würde gern wissen, ob du das ausführlicher erklären kannst
  • Es gibt die Stelle, dass die „Anzahl der Libraries, die man unterstützen muss, um gemeinsame Funktionen wie Logging bereitzustellen, die alle Services brauchen, jeweils eine pro eingesetzter Sprache“ ein Problem sei
    Das ist der größte Grund, warum wir Microservices aufgegeben haben. Mit den Tools von 2023, besonders in rein Cloud-nativen oder endlos skalierenden FaaS-Umgebungen, ist es völlige Verschwendung, dafür mentale Bandbreite aufzubringen. Das gilt umso mehr bei Kunden wie Banken und Finanzinstituten, die jede einzelne Third-Party-Abhängigkeit gnadenlos prüfen
    Wir betreiben das Ganze jetzt als monolithisches .NET-Binary-Deployment, mit gzip etwa 250 MB groß, und es gibt keinerlei Anzeichen von Rissen. Wenn du bei SaaS-Deployments von 10–100 MB pedantische Sorgen hast wie „meine exe passt nicht mehr in L2“, kannst du beruhigt sein. Die Reise zu monolithischer Software hat noch nicht einmal richtig begonnen
    Wenn man irgendwann in die Lage kommt, einen dieser Müllhaufen neu schreiben zu müssen, wäre es dann nicht viel einfacher, wenn alle Commits an einer Stelle liegen und global konsistent sind?

    • Ich halte das für eine falsche Dichotomie. Die meisten Microservice-Organisationen, die ich erlebt habe, haben aus genau solchen Gründen nur 2–3 freigegebene Sprachen zugelassen, und wenn ein Team zeigen konnte, dass es keine andere brauchbare Option gab, konnte die Führung eine Ausnahme erlauben
      Microservices bedeuten nicht automatisch Anarchie und dass alle Teams handeln dürfen, ohne die Gesamtorganisation zu berücksichtigen. Es kann und sollte Regeln geben, um ein gewisses Maß an Konsistenz zwischen Teams zu wahren
    • Tatsächlich ist das bei Microservices viel einfacher. Man hat klare Schnittstellen, die man unterstützen muss, und der Code ist nicht wie ein französischer Zopf mit dem Rest des Monolithen verflochten
      Der beste Code ist Code, den man leicht wegwerfen und neu schreiben kann. Realistisch gesehen geht alter Code durch mehr Hände, wird dadurch schlechter und vor allem will ihn irgendwann niemand mehr warten
    • Entscheidend ist, wie viele Entwickler an diesem Monolithen arbeiten. Die Größe des Binarys ist normalerweise nicht der Grund, warum Teams anfangen, einen Monolithen aufzubrechen
    • Der Satz „Wäre es nicht viel einfacher, wenn alles an einem Ort wäre und jeder Commit global konsistent?“ wirkt auf mich immer etwas komisch. Beide Lager sagen das oft, aber sie denken selten aus der Perspektive der anderen Seite
      Befürworter von Microservices sagen, Änderungen oder Neuschreibungen seien einfacher, weil es klare Verträge dafür gibt, wie ein Service funktionieren soll, und weil die Codebasis dieses Services viel kleiner ist
      Die Monolith-Seite sagt, Änderungen oder Neuschreibungen seien einfacher, weil alles ein großes Garnknäuel sei und man zum Ändern eines Fadens nur wissen müsse, an welchen Stellen dieser Faden mit anderen verflochten ist
      Ich weiß nicht, wer recht hat. Für langjährige Mitarbeiter, die sich die Codebasis über Jahre hinweg unter dem Mikroskop angeschaut haben, ist ein Monolith wahrscheinlich besser; für alle anderen eher Microservices
    • Niemand sagt, dass alle Microservices dieselbe Sprache verwenden müssen
  • Aus Sicht des Release-Managements wird der Schritt vom Monolithen zu Microservices oft aus den falschen Gründen gemacht
    Der einzig wirklich legitime Grund für eine Änderung scheint ein Skalierbarkeitsproblem zu sein, etwa ein Performance-Engpass. Alles andere verlagert nur die Komplexität der Softwareentwicklung in die Systemwartung
    Natürlich werden Entwickler es mögen, wenn die große Last abnimmt, sich mit anderen Teams abstimmen zu müssen. Aber klarzumachen, wann und wie eine Funktion implementiert, über Microservices hinweg korrekt getestet, in Produktion aktiviert und zentral gesteuert wird, wird viel schwieriger, wenn die Kommunikation zwischen den Entwicklungsteams nicht reif genug ist. Und genau diese Unreife ist oft der eigentliche Grund, warum man den Monolithen überhaupt aufspaltet

    • Es gibt viele legitime Gründe und viele falsche Gründe. Wenn es mehrere Stakeholder gibt, sich Business-Anforderungen ständig ändern und man mehr als zehn Entwickler hat, kann es gute Gründe geben, Einheiten zu haben, die unabhängig deployt, getestet und released werden können
      Umgekehrt ist es schmerzhaft, mit mehreren Microservices umzugehen, wenn es nur wenige Entwickler gibt und der Kontext klar abgegrenzt ist
      Die Aussage „Alles andere verlagert nur die Komplexität der Softwareentwicklung in die Systemwartung“ klingt plausibel, solange die Software aktiv weiterentwickelt wird. Entwicklung ist teuer. Es kann sein, dass die Kosten für den Betrieb verteilter Systeme bei einem großen Team niedriger sind als die Kosten für die Entwicklung eines riesigen Monolithen; am Ende hängt es von der konkreten Situation ab
    • Das ist auch der einzige Grund, warum wir diesen Teil unserer App in eine separate Komponente auslagern wollen. Er ist so groß geworden, dass er sich nicht mehr effektiv warten lässt, und der Rest der App soll unverändert bleiben
  • Wo ist denn der Artikel über die „Kosten des Monolithen“? Hier sieht man ihn nicht. Das liegt nur daran, dass alle auf dumme Weise Microservices umsetzen und deshalb nur dieses Problem sichtbar ist
    Wenn alle auf dumme Weise Monolithen umgesetzt hätten, gäbe es massenhaft Artikel darüber, dass „Monolithen schlecht sind“
    Die Leute verstehen nicht, dass beide Systeme am Ende dieselbe Menge an Problemen erzeugen. Es ist der Unterschied zwischen einem Elefanten, der die Arbeit erledigt, und 1000 Mäusen, die sie erledigen. Beide haben Probleme, nur unterschiedliche
    Wenn man sich für eines von beiden entscheidet, verschwinden die Probleme nicht. In beiden Fällen entstehen Probleme, und man muss für die gewählte Lösung Antworten liefern. Wenn in einem Unternehmen die größte Hürde die Frage „Welche Architektur?“ ist, würde ich dort gerne arbeiten

    • Dass wir gerade so übermäßig in Microservices versunken sind, liegt daran, dass es früher gefühlt 8 Billionen Texte à la Monolithen sind schlecht gab. Jetzt ist es eine Gegenreaktion darauf, dass man gemerkt hat, dass Kosten und Nutzen für einen selbst nicht zusammenpassen
  • Wenn ein Monolith gut zerlegt ist, worin unterscheidet er sich dann von Microservices, die am selben Ort deployed sind? Vermutlich nur in der Schnittstelle. Ein Funktionsaufruf wird zu RPC, und man akzeptiert etwas Overhead für Vorteile wie die getrennte Behandlung einzelner Komponenten, etwa für Patches
    Worin unterscheiden sich verteilte Microservices von Microservices am selben Ort? Das Deployment ist komplexer, aber man kann Prozesse intelligenter auf besser geeigneter Hardware platzieren. Es gibt mehr Fehlermodi, dafür erhält man höhere Fehlertoleranz
    Darauf gibt es keine Universallösung. Wenn man die Vorteile braucht, zahlt man die Kosten. Ich glaube, diese Debatte Microservices gegen Monolithen entsteht daraus, dass man ein Muster falsch angewandt hat, dass Werkzeuge fehlen, die das Leben leichter machen würden, oder meistens daraus, dass man 10 Jahre alte Software einfach weiter betreibt, ohne sie fortlaufend zu refaktorisieren und neu zu entwerfen, sodass sie unabhängig von der ursprünglichen Architektur unerquicklich zu bearbeiten geworden ist

    • Vom Monolithen zu Microservices zu wechseln, ist selbst dann keine kleine Änderung, wenn alles anfangs noch so gut zerlegt ist. Natürlich wird der Wechsel noch schwieriger, wenn das Design schlecht ist
      „Ein Funktionsaufruf wird zu RPC“ klingt simpel, aber in dem Moment muss die Client-App plötzlich Ausführungspfade wie keinen Zugriff auf den DNS-Server, Fehler bei der Hostnamenauflösung, Host nicht erreichbar, Verbindung abgelehnt, Authentifizierung abgelehnt, nicht vertrauenswürdiges TLS-Zertifikat, Rate Limit überschritten, 301 Permanent Redirect, verzögerte Verfügbarkeit des Ergebnisses und Timeout behandeln
      Selbst bei einer gut zerlegten App ist oft ein Redesign nötig, damit sie das robust abfangen kann: mit asynchronen Queues, Request-Rate-Limits, Ergebnis-Caches, Retry mit logarithmischem Backoff, konfigurierbaren Client-Credentials, Trust Stores und Ressourcen-URLs sowie Logging von Fehlern
      Bei lokalen Funktionsaufrufen braucht man zusätzlich RPC-Funktionen und Parameter, um Daten bereitzustellen, die vorher einfach im Kontext vorhanden waren. Außerdem muss eine Monolith-UI dem Benutzer womöglich Netzwerklatenz und Ausfälle vermitteln, die es vorher gar nicht gab
      Selbst der bestgemachte Monolith erfordert erheblichen Aufwand, um in Microservices refaktoriert zu werden
    • Hinter dem Satz „Ein Funktionsaufruf wird zu RPC“ verbergen sich neue Fehlermodi und deutlich mehr Plumbing
      Kann dieser RPC-Aufruf sicher wiederholt werden? Mit exponentiellem Backoff? Funktioniert der API-Load-Balancer korrekt, dann braucht man dafür jetzt auch Monitoring. Wie macht man Tracing zwischen Microservices, jetzt braucht man plötzlich so etwas wie OpenTelemetry
      Wie viel schwieriger ist Debugging mit Breakpoints in einer Microservices-Architektur? Wie rollt man eine Datenbanktransaktion zwischen zwei Microservices zurück? Ein einfacher Funktionsaufruf wird auf einmal viel komplexer
    • Wenn über Microservices diskutiert wird, vergessen Befürworter fast immer den Micro-Teil, der den Kern des Konzepts ausmacht. Services an sich kennen ohnehin alle, und wenn man sie braucht, baut man sie eben; das ist kein Konzept, von dem man jemanden erst überzeugen müsste
      Immer wenn dieses Konzept verteidigt wird, lässt man die Fragmentierung außen vor, die durch die Forderung entsteht, dass Services sehr klein sein müssen. Konkreter gesagt: Wenn man einen Monolithen und einen Microservice hat, dann sind das noch keine Microservices. Der Begriff impliziert, dass alles in kleine Services zerlegt ist und es keinen Monolithen mehr gibt
      Die meisten Argumente für Microservices brechen in sich zusammen, sobald man erkennt, dass es zwingend um „Micro“ gehen muss. Und wenn man diese Anforderung aufgibt, bleibt daran nichts Neues oder Tiefgehendes mehr übrig
    • Aus Entwicklersicht liegt der Unterschied in der Schnittstelle. Es gibt alle möglichen Werkzeuge, um einen Monolithen synchronisiert und korrekt zu halten, aber Werkzeuge, die Interprozesskommunikation erleichtern sollen, können keine statische Auswertung leisten und sind überhaupt nicht interaktiv
      Aus Betriebssicht ist „Das Deployment ist komplexer“ eine massive Untertreibung. Jedes Paket ist ein eigenständig zu verwaltendes Objekt mit eigenen Eigenschaften und Problemen
    • Man verliert atomare Transaktionen. Dadurch wird jede fachliche Operation um mehrere Größenordnungen komplexer
  • Ähnlich wie Frontend-Entwickler glauben, man könne ohne Frontend-Frameworks oder -Bibliotheken wie React/Vue/Svelte keine „moderne Web-App“ bauen, scheint heute auch die Vorstellung umzugehen, ein „Monolith“ sei ein furchteinflößender riesiger Teerklumpen, der als einzelne Instanz läuft und deshalb „nicht skaliert“ — das ist Unsinn
    Ein weiterer Punkt ist, dass die gesamte Codebasis viel größer wird und die meisten Services aus 20 % Business-/Domänencode und 80 % Verarbeitung von Netzwerk-Nachrichtenversand und -empfang bestehen. Man kann das noch so gut verstecken, am Ende ist es da, und man muss das Netzwerk auf die eine oder andere Weise behandeln
    Wie beim Frontend-Wahnsinn wird auch dieser Microservices-Kult erst enden, wenn die Wirtschaft zusammenbricht und kein Geld mehr da ist, um diese Türme von Babel des Untergangs zu finanzieren
    Ergänzend: Es gibt durchaus Einsatzorte für Microservices. Innerhalb mancher Unternehmen ist das sinnvoll, weil der erzielte Wert die Kosten übersteigt

    • Was Leute bei Microservices übersehen, ist, dass das Ziel meistens nicht darin besteht, ein technisches Problem zu lösen, sondern Organisations- und Personalprobleme
      Es gibt zwar technische Vorteile wie unabhängige Skalierung pro Service, aber der größte Vorteil sind klare Ownership-Grenzen und die Vermeidung unnötig enger Kopplung
      Wenn ein kleines Team ein einzelnes Produkt baut, gibt es die organisatorischen Probleme, die Microservices lösen, womöglich noch gar nicht, also braucht man sie vielleicht auch nicht. In diesem Fall sind Microservices vorzeitige Optimierung, und man zahlt Kosten, um Probleme zu lösen, die noch gar nicht existieren
    • React/Vue/Svelte sind keine Frameworks, sondern View-Bibliotheken, und ich werde bei dieser Position bis zum Ende bleiben
      Darüber hinaus ist Frontend-Komplexität komplett im Kopf: https://bower.sh/front-end-complexity
    • Ich stimme zu, dass viele Websites JavaScript und Frameworks übermäßig einsetzen. Aber wenn man Web-Apps auf Desktop-Niveau bauen will, womit soll man das dann tun, ohne dass es im völligen Chaos endet oder man am Ende Konzepte neu erfindet, die in solchen Frameworks ohnehin schon vorhanden sind?
  • Der Text scheint davon auszugehen, dass die Entwicklungsorganisation völlig kaputt und außer Kontrolle ist. Man kann nicht festlegen, was während eines Sprints getan werden soll, schmuggelt heimlich Third-Party-Bibliotheken und unbekannte Sprachen ein und spielt stillschweigend inkompatible Änderungen in die Produktion aus
    Wenn die Entwickler keine Idioten sind, scheint auch Microservices einfacher zu sein

    • Leider ist es ziemlich häufig, dass es im Team Idioten gibt oder sogar das ganze Team, das am selben Projekt arbeitet, aus Idioten besteht
      Wenn man in einem Team aus klugen und fähigen Entwicklern arbeitet, bin ich sicher, dass Microservices einfacher sind. Wobei dann natürlich alles einfacher wäre
    • Jeder Entwickler ist in den ersten Monaten in einem neuen Job zwangsläufig ein Idiot. Es braucht Zeit, all die Informationen aufzunehmen, die nötig sind, um zu verstehen, wie das bestehende System funktioniert