Read in English · Leer en español · Lire en français
Abschnitt 6.3 von 6.8 · Fortgeschritten · 26 Min. Lesezeit
Microservices: Prinzipien, Nutzen und Kosten
Videolektion
5 Min. · auf Englisch gesprochen, mit UntertitelnNur anhören (Audio)
Transkript lesen
Microservices sind viel diskutiert und leicht falsch umzusetzen. Sie erfahren, was sie wirklich sind, wie groß ein Service sein sollte und was sie kosten.
Stellen Sie sich statt eines großen Restaurants eine große Markthalle mit Essensständen vor. Jeder Stand hat sein eigenes kleines Team, seine eigene Küche und seine eigene Speisekarte und kann seine Rezepte ändern, ohne die anderen Stände zu fragen. Die Gäste bestellen an einem gemeinsamen Tresen, der jede Bestellung an den richtigen Stand weiterleitet. Die Halle kann wachsen, indem sie Stände hinzufügt, und die ausgebrannte Küche eines Standes legt die anderen nicht lahm. Aber irgendjemand muss den Tresen, die Lieferungen, die Reinigung und die Sicherheitskontrollen für jeden Stand betreiben, und ein Essen von drei Ständen erfordert sorgfältige Abstimmung. Microservices sind die Markthalle.
Microservices bauen eine Anwendung als Verbund kleiner Services. Jeder läuft in einem eigenen Prozess, ist um eine Geschäftsfähigkeit herum gebaut, lässt sich durch automatisierte Werkzeuge eigenständig deployen und spricht mit den anderen über leichtgewichtige Mechanismen wie HTTP-APIs oder Messaging. Die schwierigste Frage ist, wo die Grenzen zwischen den Services verlaufen.
Die beste Antwort ist, jeden Service an einem Bounded Context auszurichten: einem Teil des Geschäfts, in dem ein Modell und seine Sprache konsistent sind. Eine Bestellung ist in den Bestellungen ein Warenkorb, in der Disposition aber eine Abholung und eine Zustellung. Ein Kontext ist meist das, was ein Team verantworten kann. Die bestimmende Eigenschaft ist die unabhängige Deploybarkeit: Ein Team kann seinen Service ausliefern, ohne einen anderen auszuliefern. Das hängt von den Verträgen ab, nicht von den Containern: abwärtskompatible Änderungen und Contract Tests. Jeder Service besitzt seine Daten, und kein anderer Service liest oder schreibt sie direkt. Der Preis ist hoch. Joins werden zu Aufrufen zwischen Services, und aus einer Transaktion wird eine Saga, die nur eventual consistent ist. Und Geschäftslogik gehört in die Services. Das Netzwerk und der Message Broker transportieren nur Nachrichten: Smart Endpoints and Dumb Pipes.
Hier ist die Essenslieferplattform als Microservices. Oben stehen drei Clients: die Kunden-App, das Restaurant-Tablet und die Kurier-App. Anfragen laufen über ein API-Gateway, einen einzigen Einstiegspunkt, der Verschlüsselung, Authentifizierung, Rate Limits und Routing übernimmt. Halten Sie Geschäftslogik daraus fern, sonst bauen Sie den alten Service Bus nach. Als Nächstes kommt für jede Client-Art ein Backend for Frontend: ein schlankes Backend, das dem Team dieses Clients gehört, die Services aufruft und die Antworten formt. Dann die Services: Bestellungen, Speisekarte, Preisgestaltung, Disposition und Zahlungen. Jeder besitzt seine eigene Datenbank, und jeder kann die Art von Speicher wählen, die zu ihm passt. Am unteren Rand verläuft ein Message Broker, der Events wie „Bestellung aufgegeben“ transportiert. Jeder unterstützende Teil löst ein echtes Problem, ist aber eine weitere Sache, die betrieben werden muss.
Wie groß sollte ein Service also sein? Es gibt keine richtige Größe in Codezeilen oder Personen. Ein besseres Werkzeug ist, die Kräfte abzuwägen, die auf die Service-Granularität wirken. Sechs Kräfte drängen auf eine Aufteilung: der Umfang des Service, wie oft sich sein Code ändert, sehr unterschiedliche Skalierungsanforderungen, Fehlertoleranz, Sicherheit und Erweiterbarkeit. Vier drängen auf Zusammenhalt: Transaktionen, die als Ganzes gelingen oder scheitern müssen, Abläufe, die zu langen Aufrufketten würden, gemeinsamer Code und eng verbundene Daten. Granularität ist eine Abwägung, die im Einzelfall entschieden wird.
So sieht die Abwägung in der Praxis aus. Ein Preisgestaltungsservice berechnet Warenkorbsummen, einschließlich Liefergebühren und Aktionen. Aktionen ändern sich mehrmals pro Woche, und jedes Release testet die Gebührenberechnung erneut. Jemand schlägt eine Aufteilung vor. Die Code-Volatilität spricht stark dafür. Aktionen ändern sich wöchentlich, Gebührenregeln vierteljährlich. Die Fehlertoleranz spricht mäßig dafür. Eine fehlerhafte Aktionsregel hat die Preisgestaltung zweimal zum Absturz gebracht. Getrennt würde ein Ausfall einfach bedeuten, dass kein Rabatt angezeigt wird. Der Workflow spricht mäßig dagegen. Jede Warenkorbsumme braucht beide, daher fügt eine Aufteilung einen synchronen Hop und dessen Fehlermodi hinzu. Die Entscheidung: Aktionen herauslösen, mit Bedacht. Die Preisgestaltung ruft sie mit einem Timeout von hundertfünfzig Millisekunden auf und berechnet den Warenkorb bei einem Fehler ohne Rabatte und im Bezahlvorgang neu.
Der gravierendste Fehlschlag ist der verteilte Monolith: Services, die eine Datenbank teilen, einander in langen Ketten aufrufen oder gemeinsam ausgeliefert werden müssen. Alle Kosten der Verteilung, keine Unabhängigkeit. Entity-Services sind nach Substantiven wie Kunde oder Bestellung benannt und bieten kaum mehr als das Speichern und Abrufen von Datensätzen, sodass jeder Geschäftsprozess mehrere von ihnen aufrufen muss. Gesprächige Services machen aus einer Benutzeranfrage Dutzende Aufrufe, sodass die Latenz mit jedem Feature wächst. Der Stil braucht außerdem zuerst die Grundlagen: schnelles Provisioning, Monitoring jedes Service und automatisiertes Deployment mit einfachem Rollback. Und Teams müssen betreiben, was sie bauen, mit Rufbereitschaft. Microservices passen also zu vielen Teams mit gut verstandenen Domänen. Sie passen nicht zu einem kleinen Team oder zu einem neuen Produkt, dessen Domäne noch erkundet wird. Sie sind ein Werkzeug mit Kosten, kein Ziel.
Der vollständige Abschnitt enthält jede Granularitätskraft mit einem Beispiel und die vollständige Liste der Anti-Patterns. Als Nächstes betrachten wir in Abschnitt sechs Punkt vier die ereignisgesteuerte Architektur. Bis gleich.
Was Sie wissen müssen
- Microservices (Lewis und Fowler, 2014) bauen eine Anwendung aus kleinen Services, jeder in einem eigenen Prozess, um eine Geschäftsfähigkeit herum gebaut, unabhängig deploybar und über leichtgewichtige Mechanismen kommunizierend.
- Service-Grenzen funktionieren am besten, wenn sie an Bounded Contexts und an Teams ausgerichtet sind; Grenzen, die um technische Schichten oder Datenbanktabellen gezogen werden, ergeben gesprächige, eng gekoppelte Services.
- Unabhängige Deploybarkeit ist die bestimmende Eigenschaft; sie hängt von abwärtskompatiblen Verträgen, Contract Tests und Versionierung ab, nicht nur von getrennten Containern.
- Datenbank pro Service macht jeden Service zu seinem eigenen Quantum, ersetzt aber Joins und Transaktionen durch API-Aufrufe, Events, Sagas und Eventual Consistency.
- Zu den unterstützenden Mustern gehören API-Gateways, Backends for Frontends, Service Discovery und Service Meshes; jedes löst ein echtes Problem und bringt etwas mit, das betrieben werden muss.
- Über die Granularität entscheidet man, indem man Desintegratoren (Umfang, Volatilität, Skalierbarkeit, Fehlertoleranz, Sicherheit, Erweiterbarkeit) gegen Integratoren (Transaktionen, Workflow, gemeinsamer Code, Datenbeziehungen) abwägt.
- Typische Fehlschläge sind der verteilte Monolith, Entity-Services, gesprächige Services und gemeinsame Bibliotheken, die Deployments koppeln; der Stil braucht Automatisierung, Monitoring und Teams, die betreiben, was sie bauen.
Definition und Herkunft
Microservices sind ein Architekturstil, bei dem eine Anwendung als Verbund kleiner Services gebaut wird, von denen jeder in einem eigenen Prozess läuft, um eine Geschäftsfähigkeit herum gebaut ist und durch automatisierte Werkzeuge unabhängig deployt werden kann; sie kommunizieren über leichtgewichtige Mechanismen wie HTTP-APIs oder Messaging, mit einem Minimum an zentraler Steuerung. Diese Zusammenfassung folgt dem Artikel, der den Begriff für den größten Teil der Branche definiert hat: Microservices von James Lewis und Martin Fowler, im März 2014 auf martinfowler.com veröffentlicht. Der Artikel berichtet, dass der Name im Mai 2011 auf einem Workshop von Softwarearchitekten in der Nähe von Venedig diskutiert und von der Gruppe im Mai 2012 übernommen wurde; Unternehmen wie Amazon und Netflix arbeiteten bereits auf diese Weise.
Lewis und Fowler gaben keine formale Definition. Sie beschrieben neun gemeinsame Merkmale, die Fachleute bis heute als Checkliste nutzen: Komponentisierung über Services; Organisation um Geschäftsfähigkeiten; Produkte statt Projekte; Smart Endpoints and Dumb Pipes; dezentrale Governance; dezentrales Datenmanagement; Infrastrukturautomatisierung; Design for Failure; und evolutionäres Design. Die folgenden Abschnitte behandeln die architektonisch bedeutsamsten davon der Reihe nach.
An Bounded Contexts ausgerichtete Services
Die schwierigste Frage bei Microservices ist, wo die Grenzen verlaufen. Die verlässlichste Antwort kommt aus dem strategischen Domain-Driven Design (Kapitel 5): Ein Bounded Context ist ein Teil des Geschäfts, innerhalb dessen ein Modell und seine Sprache konsistent sind. Bei der Essenslieferung bedeutet „Bestellung“ in den Bestellungen einen Warenkorb, der bezahlt wird, im Küchenkontext des Restaurants ein Ticket, das gekocht werden muss, und in der Disposition eine Abholung und eine Zustellung. Jeder Kontext ist ein natürlicher Kandidat für einen Service oder für eine kleine Zahl von Services, die einem Team gehören.
Services an Bounded Contexts auszurichten, verleiht ihnen hohe Kohäsion (was sich gemeinsam ändert, liegt zusammen) und geringe Kopplung (Kontexte kommunizieren über wenige explizite Verträge, die an den Rändern übersetzt werden). Es richtet sie außerdem an Teams aus, weil ein Bounded Context meist die Größe hat, die ein Team verstehen und verantworten kann. Services, die um technische Schichten oder um Datenbanktabellen gezogen werden, fehlen beide Eigenschaften, und die Anti-Patterns weiter unten in diesem Abschnitt sind meist die Folge.
Unabhängige Deploybarkeit: die bestimmende Eigenschaft
Wenn eine Eigenschaft Microservices definiert, dann die unabhängige Deploybarkeit: Ein Team kann seinen Service ändern und in Produktion ausliefern, ohne gleichzeitig einen anderen Service zu ändern, zu testen oder auszuliefern. Sam Newman behandelt sie in Building Microservices (zweite Auflage, 2021) als die Eigenschaft, die vor allen anderen zu schützen ist, weil fast jeder andere Nutzen von ihr abhängt. Schnellere Auslieferung, kleinerer Blast Radius, gezielte Skalierung und Teamautonomie verschwinden, wenn Releases abgestimmt werden müssen.
Unabhängige Deploybarkeit ist eine Eigenschaft der Verträge, nicht der Container. Zwei Services, die getrennt deployt werden, aber immer gemeinsam ausgeliefert werden müssen, weil eine Änderung am einen den anderen bricht, sind nicht unabhängig deploybar. Um sie zu schützen, braucht es abwärtskompatible Schnittstellenänderungen (Felder hinzufügen, sie aber nie ohne Deprecation-Phase entfernen oder umbenennen), Consumer-Driven Contract Tests und versionierte APIs, die alle unter Kommunikation und APIs behandelt werden.
Datenbank pro Service sowie Smart Endpoints and Dumb Pipes
Das dezentrale Datenmanagement von Lewis und Fowler wird in der Praxis zur Regel Datenbank pro Service: Jeder Service besitzt seine Daten, und kein anderer Service liest oder schreibt sie direkt. Andere Services erhalten die Daten über die API des Eigentümers oder über Events, die der Eigentümer veröffentlicht. Diese Regel macht einen Service zu einem eigenen Quantum: Sein Schema kann sich ändern, und er kann skaliert, gesichert und wiederhergestellt werden, ohne jemanden zu fragen. Jeder Service kann außerdem den Speicher wählen, der zu ihm passt (eine relationale Datenbank für Bestellungen, einen Key-Value-Speicher für Sitzungen, eine Suchmaschine für Speisekarten); das nennt man polyglotte Persistenz.
Der Preis ist hoch und muss bewusst bezahlt werden. Abfragen, die früher ein SQL-Join über Tabellen waren, überqueren jetzt Service-Grenzen. Änderungen, die früher eine Transaktion waren, brauchen jetzt eine Saga und werden eventual consistent. Reporting braucht eine eigene Pipeline. Kapitel 7 behandelt die Datenhoheit ausführlich.
Das andere Merkmal mit den größten architektonischen Folgen ist Smart Endpoints and Dumb Pipes. Geschäftslogik gehört in die Services; das Netzwerk und der Message Broker transportieren nur Nachrichten. Das ist eine direkte Reaktion auf den Enterprise Service Bus der klassischen SOA, bei der sich Routing-Regeln, Transformationen und Prozesse in zentraler Middleware ansammelten.
Eine Microservice-Topologie für die Essenslieferung
Unterstützende Muster
Rund um den Stil ist eine Reihe unterstützender Muster entstanden. Jedes löst ein echtes Problem, und jedes fügt eine Komponente hinzu, die betrieben werden muss; führen Sie sie also ein, wenn das Problem auftritt, statt alle auf einmal.
Gängige unterstützende Muster
| Muster | Gelöstes Problem | Was er ist | Worauf zu achten ist |
|---|---|---|---|
| API-Gateway | Clients sollten weder die Adressen Dutzender Services kennen noch sich bei jedem einzeln authentifizieren müssen. | Ein einziger Einstiegspunkt, der TLS terminiert, authentifiziert, Rate Limits durchsetzt und Anfragen an Services weiterleitet; er kann mehrere Aufrufe zu einer Antwort zusammenfassen. | Geschäftslogik, die sich ins Gateway einschleicht und einen ESB nachbildet; ein Single Point of Failure, der hohe Verfügbarkeit braucht. |
| Backend for Frontend (BFF) | Eine mobile App, eine Web-App und eine Partner-API brauchen unterschiedlich geformte Daten; eine allgemeine API bedient keine davon gut. | Ein eigenes, schlankes Backend pro Client-Art, das dem Team dieses Clients gehört, Services aufruft und Antworten formt. Der Name stammt von Phil Calçado bei SoundCloud; Sam Newman machte das Muster 2015 bekannt. | Duplizierte Logik in mehreren BFFs; ein BFF, das zu einem Mini-Monolithen wird. |
| Service Discovery | Service-Instanzen starten, stoppen und wandern ständig; Adressen lassen sich nicht von Hand konfigurieren. | Ein Verzeichnis der aktuell gesunden Instanzen, das von Clients oder einem Load Balancer abgefragt wird. Kubernetes stellt es über DNS-Namen für Services bereit. | Veraltete Einträge im Verzeichnis; Clients, die Adressen zu lange cachen. |
| Service Mesh | Jeder Service braucht Mutual TLS, Wiederholungen, Timeouts, Traffic Shifting und Telemetrie; all das in jeder Sprache zu implementieren, ist Verschwendung. | Eine Infrastrukturschicht aus Proxys (traditionell ein Sidecar neben jeder Service-Instanz), die zentral gesteuert wird, etwa Istio oder Linkerd. | Beträchtliche betriebliche Komplexität; Wiederholungen, die sowohl im Mesh als auch im Code konfiguriert sind, vervielfachen die Last. |
Fowlers Voraussetzungen und die organisatorischen Kosten
Im August 2014 nannte Martin Fowler drei Dinge, die eine Organisation haben sollte, bevor sie überhaupt Microservices baut. Schnelles Provisioning: die Fähigkeit, einen neuen Server oder eine neue Umgebung in Stunden bereitzustellen, was Automatisierung bedeutet. Grundlegendes Monitoring: sowohl technische Kennzahlen (Fehler, Latenz, Verfügbarkeit) als auch geschäftliche Kennzahlen (Bestellungen pro Minute) für jeden Service, damit Probleme schnell auffallen. Schnelles Deployment von Anwendungen: eine automatisierte Pipeline, die einen Service schnell in Test und Produktion bringt, mit einfachem Rollback. Ohne diese Voraussetzungen vervielfachen viele kleine Services die manuelle Arbeit. Seine begleitende Idee, die Microservice Premium (2015), besagt, dass der Stil feste Kosten an Komplexität und Produktivität mit sich bringt, die sich nur für Systeme auszahlen, die komplex genug sind, um ihn zu brauchen.
Die organisatorischen Kosten sind ebenso real wie die technischen. Der Stil setzt langlebige Teams voraus, die ihre Services in Produktion verantworten, nach dem Prinzip, das Amazons CTO Werner Vogels 2006 als You build it, you run it
beschrieb. Das bedeutet, dass jedes Team betriebliche Fähigkeiten und einen Rufbereitschaftsplan braucht. Er braucht eine Plattform: Deployment-Pipelines, Observability, Secrets-Management und Service-Vorlagen, meist von einem Plattformteam bereitgestellt (Kapitel 10). Er braucht Einigkeit über übergreifende Standards (Logging-Format, Korrelations-IDs, API-Konventionen), ohne dass jede Änderung zentral genehmigt wird. Und er verlangt, das Gesetz von Conway bewusst zu nutzen: Team- und Service-Grenzen sollten übereinstimmen, sonst müssen sich bei jeder Änderung mehrere Teams abstimmen.
Granularität: Wie groß sollte ein Service sein?
Es gibt keine richtige Größe in Codezeilen oder in Personen. Lewis und Fowler fanden Teams von etwa einem Dutzend Personen, die einen Service verantworteten, und Teams von etwa sechs Personen, die sechs Services verantworteten, und bedauerten, dass das Wort „Micro“ die Aufmerksamkeit auf die Größe lenkt. Ford, Richards, Sadalage und Dehghani bieten in Software Architecture: The Hard Parts (2021) ein besseres Werkzeug: Wägen Sie die Kräfte, die auf eine Aufteilung eines Service drängen und die sie Granularitäts-Desintegratoren nennen, gegen diejenigen ab, die darauf drängen, ihn zusammenzuhalten, die Integratoren.
Kräfte, die auf die Service-Granularität wirken
Granularitäts-Desintegratoren und -Integratoren mit Beispielen aus der Essenslieferung
| Kraft | Richtung | Frage | Beispiel |
|---|---|---|---|
| Umfang und Funktion | Teilen | Erledigt der Service eine zusammenhängende Aufgabe oder mehrere, die nichts miteinander zu tun haben? | Ein Benachrichtigungsservice, der auch Treuepunkte berechnet, erledigt zwei Aufgaben. |
| Code-Volatilität | Teilen | Ändert sich ein Teil viel häufiger als der Rest? | Aktionsregeln ändern sich wöchentlich, der Rest der Preisgestaltung vierteljährlich. |
| Skalierbarkeit und Durchsatz | Teilen | Brauchen Teile sehr unterschiedliche Kapazität? | Standortmeldungen der Kuriere kommen tausendfach pro Sekunde an, Kurier-Onboardings ein paar Mal am Tag. |
| Fehlertoleranz | Teilen | Würde ein Ausfall in einem Teil unnötigerweise einen anderen lahmlegen? | Ein Absturz im PDF-Quittungsgenerator sollte das Aufgeben von Bestellungen nicht stoppen. |
| Sicherheit | Teilen | Brauchen Teile unterschiedliche Zugriffskontrollen oder Audits? | Gespeicherte Kartentokens gegenüber öffentlichen Speisekartendaten. |
| Erweiterbarkeit | Teilen | Werden häufig neue Varianten hinzukommen? | Alle paar Monate neue Zahlungsmethoden, jede eine eigene deploybare Einheit. |
| Datenbanktransaktionen | Zusammenhalten | Müssen diese Änderungen atomar gelingen oder scheitern? | Eine Bestellung anlegen und ihr Lieferzeitfenster reservieren. |
| Workflow und Choreografie | Zusammenhalten | Würde eine Aufteilung für gewöhnliche Anfragen lange Aufrufketten erzeugen? | Warenkorb, Checkout und Bestellstatus rufen sich bei jeder Anfrage gegenseitig auf. |
| Gemeins. Code | Zusammenhalten | Teilen die Teile wesentliche Geschäftslogik, die sich gemeinsam ändert? | Speisekarte und Preisgestaltung teilen komplexe Regeln für Gerichtsoptionen. |
| Datenbeziehungen | Zusammenhalten | Sind die Daten eng verbunden, mit Joins und Fremdschlüsseln? | Bestellungen und Bestellpositionen; sie zu trennen bringt nichts. |
Ausgearbeitetes Beispiel
Ausgearbeitetes Beispiel: Sollte die Preisgestaltung aufgeteilt werden?
Die Essenslieferplattform hat einen Preisgestaltungsservice, der Warenkorbsummen berechnet: Gerichtspreise, Aufpreise für Optionen, Liefergebühren (die von Entfernung und Nachfrage abhängen) und Aktionen (Gutscheine, Rabatte auf die erste Bestellung, Restaurantangebote). Das Aktionsteam liefert mehrmals pro Woche Änderungen aus, und jedes Release der Preisgestaltung erfordert einen vollständigen Regressionslauf der Gebührenberechnung. Jemand schlägt vor, die Preisgestaltung in die Services Gebühren und Aktionen aufzuteilen. Die Architektin wägt die Kräfte ab.
| Code-Volatilität (teilen) | Stark: Aktionen ändern sich wöchentlich, Gebührenregeln vierteljährlich. Eine Trennung ließe Aktionen ausliefern, ohne die Gebühren erneut zu testen. |
|---|---|
| Skalierbarkeit (teilen) | Schwach: Beide werden einmal pro Warenkorbansicht aufgerufen, mit derselben Rate. |
| Fehlertoleranz (teilen) | Mittel: Eine fehlerhafte Aktionsregel hat die Preisgestaltung zweimal zum Absturz gebracht. Wären die Aktionen getrennt, ließe sich ein Ausfall auf „kein Rabatt angezeigt“ herabstufen, während die Gebühren weiter funktionieren. |
| Sicherheit (teilen) | Schwach: gleiche Datensensibilität. |
| Datenbanktransaktionen (zusammenhalten) | Schwach: Kein Schreibvorgang muss beide umfassen; ein Gutschein wird beim Aufgeben der Bestellung eingelöst, im Bestellservice. |
| Workflow (zusammenhalten) | Mittel: Jede Warenkorbsumme braucht beide. Nach einer Aufteilung muss die Preisgestaltung (oder ein BFF) die Aktionen bei jeder Warenkorbansicht synchron aufrufen, was einen Hop und dessen Fehlermodi hinzufügt. |
| Gemeinsamer Code (zusammenhalten) | Mittel: Beide brauchen das Warenkorbmodell und die Rundungsregeln für Währungen, die sich selten ändern. |
| Datenbeziehungen (zusammenhalten) | Schwach: Aktionen verweisen auf Restaurants und Gerichte nur über Kennungen. |
| Entscheidung | Aufteilen, mit Bedacht: Aktionen herauslösen; die Preisgestaltung ruft sie mit einem Timeout von 150 ms auf und berechnet den Warenkorb bei einem Fehler ohne Rabatte und markiert ihn zur Neuberechnung im Bezahlvorgang. Nur eine kleine, versionierte Bibliothek für das Warenkorbmodell teilen. |
Die Entscheidung beruht darauf, dass zwei starke Desintegratoren (Volatilität und Fehlerisolierung) mittlere Integratoren überwiegen. Hätten Aktionen dieselben Zeilen wie die Gebührenberechnung in einer Transaktion aktualisieren müssen, wäre die Antwort wahrscheinlich anders ausgefallen.
Wer die Kräfte aufschreibt, macht aus einer Geschmacksfrage ein Argument, das andere prüfen können, und die Aufzeichnung gehört in einen Architecture Decision Record.
Anti-Patterns
Die meisten gescheiterten Microservice-Programme scheitern auf erkennbare Weise. Am gravierendsten ist der verteilte Monolith: ein System mit allen Kosten der Verteilung und keiner der Unabhängigkeit, weil seine Services eine Datenbank teilen, einander in langen synchronen Ketten aufrufen oder von denselben deploybaren Bibliotheken abhängen.
Microservice-Anti-Patterns
| Anti-Pattern | Symptom | Ursache und Abhilfe |
|---|---|---|
| Verteilter Monolith | Services müssen gemeinsam in einer festen Reihenfolge ausgeliefert werden; der Ausfall eines Service stoppt alle. | Gemeinsame Datenbank, synchrone Ketten und gemeinsame deploybare Bibliotheken. Abhilfe: Grenzen um Bounded Contexts neu ziehen, jedem Service seine Daten geben, Aufrufe auf Events umstellen oder alles wieder zu einem modularen Monolithen zusammenführen. |
| Entity-Services | Services, die nach Substantiven benannt sind (Customer, Order, Restaurant), mit Operationen zum Anlegen, Lesen, Aktualisieren und Löschen; jeder Geschäftsprozess ruft mehrere von ihnen auf. | Grenzen um Daten statt um Verhalten gezogen. Michael Nygard nennt das das Entity-Service-Anti-Pattern. Abhilfe: nach Geschäftsfähigkeiten und Prozessen organisieren. |
| Gesprächige Services | Eine Benutzeranfrage löst Dutzende Aufrufe zwischen Services aus; Latenz und Fehlerraten steigen mit jedem Feature. | Grenzen schneiden durch einen zusammenhängenden Ablauf, oder die Schnittstellen sind zu feingranular. Abhilfe: gröbere APIs, lokale Kopien von Referenzdaten, Zusammenlegen von Services, die sich immer gemeinsam ändern. |
| Gemeinsame Bibliotheken, die Deployments koppeln | Eine Änderung an einer gemeinsamen Bibliothek zwingt jeden Service zu neuem Build und Deployment; die Versionen driften auseinander. | Geschäftslogik oder Domänenmodelle in gemeinsamem Code. Abhilfe: nur stabile, technische Bibliotheken teilen; kleine Mengen Geschäftscode duplizieren; alles Geteilte versionieren und mehrere Versionen parallel zulassen. |
| Microservice-Neid | Ein kleines Team mit einem neuen Produkt startet mit dreißig Services und verbringt die meiste Zeit mit Infrastruktur. | Den Stil aus Mode- oder Lebenslaufgründen statt wegen der Treiber wählen. Abhilfe: mit einem modularen Monolithen beginnen und Services herauslösen, wenn ein Treiber auftaucht. |
| Nanoservices | Services, die so klein sind, dass der Großteil ihres Codes Infrastruktur-Klempnerei ist; ein Feature erfordert Änderungen in fünf von ihnen. | „Klein“ überstrapaziert. Abhilfe: die Granularitäts-Integratoren nutzen; Services nach Kohäsion bemessen, nicht nach Codezeilen. |
Merkmale von Microservices
| Merkmal | Bewertung | Grund |
|---|---|---|
| Deploybarkeit | Hoch | Jeder Service wird eigenständig ausgeliefert, viele Male am Tag, mit kleinem Blast Radius. |
| Skalierbarkeit und Elastizität | Hoch | Jeder Service skaliert unabhängig; kleine Services starten schnell und skalieren in feinen Schritten. |
| Fehlertoleranz | Hoch (wenn darauf ausgelegt) | Ausfälle lassen sich pro Service isolieren, aber nur mit Timeouts, Bulkheads, Fallbacks und asynchroner Kommunikation; sonst pflanzen sich Fehler kaskadenartig fort. |
| Evolvierbarkeit | Hoch | Kleine, gut abgegrenzte Services lassen sich einzeln neu schreiben oder ersetzen. |
| Testbarkeit eines Service | Hoch | Jeder Service ist klein; Contract Tests prüfen seine Interaktionen. |
| Test des Gesamtsystems | Niedrig | Das End-to-End-Verhalten entsteht aus vielen Services und ist in einer Testumgebung schwer nachzustellen. |
| Performance | Niedrig bis mittel | Netzwerk-Hops, Serialisierung und Sicherheitsprüfungen fügen jeder Interaktion Latenz hinzu. |
| Datenkonsistenz | Niedrig | Keine gemeinsamen Transaktionen; Geschäftsinvarianten über Services hinweg beruhen auf Eventual Consistency und Sagas. |
| Einfachheit | Niedrig | Viele bewegliche Teile, viele Fehlermodi, viel Infrastruktur. |
| Gesamtkosten | Niedrig (teuer) | Plattform, Werkzeuge, Observability und Personal, um Dutzende Services zu betreiben. |
Wann Microservices passen und wann nicht
Microservices passen zu Organisationen mit vielen Teams, die an einem Produkt arbeiten und unabhängig ausliefern müssen, zu Systemen, deren Teile wirklich unterschiedliche Anforderungen an Skalierbarkeit, Verfügbarkeit oder Sicherheit haben, und zu Domänen, die gut genug verstanden sind, um stabile Grenzen zu ziehen. Sie erfordern Investitionen in eine Plattform und in betriebliche Fähigkeiten, bevor der erste Service abgespalten wird.
Sie passen nicht zu einem kleinen Team, zu einem neuen Produkt, dessen Domäne noch erkundet wird (zu früh gezogene Grenzen sind teuer über das Netzwerk hinweg zu verschieben), zu einem System, dessen Kernabläufe über den Großteil seiner Daten starke transaktionale Konsistenz brauchen, oder zu einer Organisation ohne automatisiertes Deployment und Monitoring. Fowlers Rat MonolithFirst (Kapitel 5) und die Kehrtwenden, die manche Unternehmen dokumentiert haben, etwa die Zusammenführung von mehr als 140 Destination-Services zurück in einen einzigen bei Segment im Jahr 2018, zeigen, dass der Stil ein Werkzeug mit Kosten ist, kein Ziel.
Prüfen Sie Ihr Verständnis
Zwei Services werden in getrennten Containern deployt, müssen aber immer gemeinsam ausgeliefert werden. Sind sie unabhängig deploybar?
Nein. Bei unabhängiger Deploybarkeit geht es darum, einen Service ohne den anderen ändern und ausliefern zu können; ein gekoppelter Vertrag oder eine gemeinsame Datenbank macht sie für Releases zu einer Einheit.
Was ist der Unterschied zwischen einem API-Gateway und einem Backend for Frontend?
Ein Gateway ist ein allgemeiner Einstiegspunkt, der übergreifende Anliegen wie TLS, Authentifizierung, Rate Limiting und Routing übernimmt; ein BFF ist ein clientspezifisches Backend, das Daten für eine Art von Frontend aufbereitet und dem Team dieses Frontends gehört.
Welcher Granularitäts-Integrator spricht am stärksten dagegen, zwei Teile aufzuteilen, die Daten atomar aktualisieren müssen?
Datenbanktransaktionen: Eine Aufteilung würde eine ACID-Transaktion durch einen verteilten Ablauf wie eine Saga ersetzen.
Warum sind Services namens Customer, Order und Restaurant, die nur Operationen zum Anlegen, Lesen, Aktualisieren und Löschen bieten, ein Warnsignal?
Es sind Entity-Services: Grenzen um Daten statt um Verhalten, sodass jeder Geschäftsprozess mehrere Services aufrufen muss, was gesprächige, eng gekoppelte Interaktionen erzeugt.
Quellen und weiterführende Literatur
Haben Sie eine Frage zu diesem Abschnitt? ArchitectBot, der KI-Lernassistent des Kurses, antwortet zuerst aus dem Kurs.
Fragen: „Wir haben 25 Microservices, und für jedes Release müssen sich vier Teams abstimmen. Wie erkenne ich, ob wir einen verteilten Monolithen haben, und was sollten wir zuerst beheben?“