9 Design und Entwicklung von Microservices 2

9.1 REST APIs und Event-driven Design

Die Art und Weise, wie Microservices miteinander kommunizieren, ist entscheidend für die Gesamtqualität der Architektur. In modernen Microservices-Landschaften haben sich zwei Kommunikationsparadigmen als besonders wertvoll erwiesen: REST APIs für synchrone Interaktionen und ereignisgesteuerte Architekturen für asynchrone Kommunikation. Diese beiden Ansätze ergänzen sich und bilden gemeinsam das Fundament für flexible, skalierbare und robuste Microservices-Systeme. In diesem Abschnitt untersuchen wir beide Paradigmen, ihre Prinzipien, Anwendungsfälle und wie sie zusammenwirken können.

9.1.1 REST APIs: Grundlagen und Prinzipien

Representational State Transfer (REST) ist ein architektonischer Stil für verteilte Hypermedia-Systeme, der von Roy Fielding in seiner Dissertation im Jahr 2000 formalisiert wurde. REST hat sich als dominanter Ansatz für die Gestaltung von APIs in Microservices-Architekturen etabliert, was auf seine Einfachheit, Skalierbarkeit und die breite Unterstützung durch Frameworks und Tools zurückzuführen ist.

9.1.1.1 Die sechs REST-Prinzipien

Ein vollständig RESTful API folgt sechs grundlegenden Prinzipien:

1. Client-Server-Architektur

Die Trennung von Client und Server ist fundamental für REST und Microservices gleichermaßen:

Diese Trennung ist besonders relevant für Microservices, wo jeder Service idealerweise unabhängig entwickelt, bereitgestellt und skaliert werden kann.

2. Zustandslosigkeit

Jede Anfrage an einen Server muss alle notwendigen Informationen enthalten, um verstanden zu werden:

Dieses Prinzip stimmt mit dem “Stateless Processes”-Konzept der 12-Factor-App-Methodik überein und ist für die horizontale Skalierbarkeit von Microservices entscheidend.

3. Cacheability

Antworten müssen explizit als cacheable oder nicht-cacheable gekennzeichnet sein:

Effektives Caching verbessert die Performance und Skalierbarkeit des Gesamtsystems, indem es unnötige Service-zu-Service-Kommunikation reduziert.

4. Einheitliche Schnittstelle

REST betont eine standardisierte Schnittstelle zwischen Komponenten:

Die einheitliche Schnittstelle vereinfacht die Interaktion zwischen Services und verbessert die Entdeckbarkeit und Selbstdokumentation der API.

5. Schichtenarchitektur

REST erlaubt eine hierarchische Systemorganisation mit mehreren Schichten:

Dieses Konzept passt hervorragend zur Schichtenorganisation in Microservices-Architekturen, insbesondere im Zusammenhang mit API-Gateways und Service-Proxies.

6. Code on Demand (optional)

Clients können ihre Funktionalität durch herunterladbaren Code erweitern:

Dieses optionale Prinzip findet in Microservices-APIs seltenere Anwendung, ist aber für bestimmte Szenarien wie dynamisch generierte UI-Komponenten relevant.

9.1.1.2 RESTful Ressourcenmodellierung

Eine der Herausforderungen bei der Gestaltung von REST APIs liegt in der effektiven Modellierung von Domänenkonzepten als Ressourcen. Hier einige bewährte Praktiken:

Ressourcenidentifikation:

Ressourcenhierarchien:

Ressourcenoperationen:

Abfrageoperationen:

Eine konsequente und intuitive Ressourcenmodellierung macht APIs einfacher zu verstehen, zu nutzen und zu erweitern.

9.1.1.3 REST-Reifegradmodell nach Richardson

Das Richardson Maturity Model definiert vier Reifegrade für REST APIs:

Level 0 (Sumpf des POX): Verwendung von HTTP als reiner Transportmechanismus für Remote Procedure Calls, oft mit einer einzelnen URL und POST-Methode.

Level 1 (Ressourcen): Einführung von Ressourcen mit individuellen URLs, aber noch immer beschränkte Nutzung von HTTP-Methoden.

Level 2 (HTTP-Verben): Korrekte Verwendung von HTTP-Methoden und Statuscodes. Die meisten “RESTful” APIs in Microservices-Architekturen erreichen dieses Level.

Level 3 (Hypermedia Controls): Implementierung von HATEOAS, wobei Antworten Links enthalten, die den Client zu weiteren möglichen Aktionen führen. Dies ist der vollständigste RESTful-Ansatz, wird aber in der Praxis seltener vollständig implementiert.

Für Microservices ist typischerweise Level 2 ein angemessenes Ziel, das die meisten Vorteile von REST bietet, ohne den Entwicklungsaufwand und die Komplexität von Level 3.

9.1.1.4 API-Design-First-Ansatz

Ein bewährter Ansatz für REST-APIs in Microservices ist der “API-Design-First”-Ansatz:

  1. API-Spezifikation: Definieren der API mit OpenAPI (Swagger) oder ähnlichen Standards vor der Implementierung
  2. Review: Überprüfung und Validierung des Designs durch Stakeholder und andere Teams
  3. Mock-Implementierung: Generierung von Mock-Servern für frühe Integration und Tests
  4. Implementierung: Entwicklung der tatsächlichen API basierend auf der abgestimmten Spezifikation
  5. Dokumentation: Automatische Generierung einer konsistenten Dokumentation aus der Spezifikation

Dieser Ansatz fördert wohlüberlegte Schnittstellen, verbessert die Zusammenarbeit zwischen Teams und erleichtert die Integration.

9.1.1.5 Herausforderungen und Grenzen von REST

Trotz seiner Popularität hat REST auch Einschränkungen, besonders in komplexen Microservices-Szenarien:

Request-Response-Kopplung: REST ist inhärent synchron, was in verteilten Systemen zu Latenzproblemen und Kaskadierungsfehlern führen kann.

Ineffiziente Datenabrufe: Clients müssen oft mehrere Anfragen stellen, um zusammenhängende Daten zu erhalten (N+1-Problem).

Mangelnde Semantik: HTTP-Methoden bieten nur grundlegende Operationen, komplexere Geschäftsoperationen müssen darauf abgebildet werden.

Protokolloverhead: HTTP und textbasierte Formate wie JSON können in hochperformanten Systemen zu signifikantem Overhead führen.

Diese Einschränkungen haben zur Entwicklung komplementärer Ansätze wie GraphQL (für effizientere Datenabrufe) und ereignisgesteuerten Architekturen geführt.

9.1.2 Event-driven Design: Grundlagen und Prinzipien

Ereignisgesteuerte Architekturen (Event-driven Architecture, EDA) repräsentieren einen Paradigmenwechsel weg von direkten Anfrage-Antwort-Interaktionen hin zu einem Modell, bei dem Services durch den Austausch von Ereignissen kommunizieren. Dieser Ansatz ist besonders wertvoll für Microservices, da er lose Kopplung, Skalierbarkeit und Resilienz fördert.

9.1.2.1 Kernkonzepte des ereignisgesteuerten Designs

Ereignisse (Events): Ein Ereignis ist eine Aufzeichnung einer Tatsache, die zu einem bestimmten Zeitpunkt eingetreten ist. Beispiele sind “Bestellung aufgegeben”, “Zahlung eingegangen” oder “Benutzer registriert”. Wichtige Eigenschaften von Ereignissen sind:

Event Producers: Komponenten, die Ereignisse generieren und veröffentlichen. In einer Microservices-Architektur ist jeder Service potenziell ein Event Producer für Ereignisse, die seine Domäne betreffen.

Event Consumers: Komponenten, die auf Ereignisse reagieren. Ein Service kann Ereignisse von vielen anderen Services abonnieren und entsprechend darauf reagieren.

Event Broker/Bus: Eine Infrastrukturkomponente, die die Übermittlung von Ereignissen zwischen Producern und Consumern vermittelt. Beispiele sind Apache Kafka, RabbitMQ oder Amazon SNS/SQS.

Event Channels: Logische Kanäle (Topics, Queues), über die Ereignisse übertragen werden. Diese können nach Domänen, Funktionen oder anderen Kriterien organisiert sein.

9.1.2.2 Zentrale Muster des ereignisgesteuerten Designs

Publish-Subscribe (Pub/Sub): Das grundlegendste Muster, bei dem Producer Ereignisse veröffentlichen, ohne die Empfänger zu kennen, und Consumer diese abonnieren, ohne die Sender zu kennen:

Event Sourcing: Ein Muster, bei dem der Zustand einer Anwendung als Sequenz von Ereignissen gespeichert wird:

Command Query Responsibility Segregation (CQRS): Ein Muster, das Operationen zum Lesen (Queries) von Daten von Operationen zum Schreiben (Commands) trennt:

Saga Pattern: Ein Muster zur Koordination verteilter Transaktionen über mehrere Services hinweg:

9.1.2.3 Vorteile des ereignisgesteuerten Designs in Microservices

Ereignisgesteuerte Architekturen bieten mehrere entscheidende Vorteile für Microservices-Landschaften:

1. Lose Kopplung: Services interagieren indirekt über Ereignisse, was die Abhängigkeiten minimiert und die unabhängige Entwicklung und Evolution fördert.

2. Verbesserte Resilienz: Temporäre Ausfälle von Services beeinträchtigen nicht unmittelbar andere Services, da Ereignisse gepuffert werden können.

3. Natürliche Asynchronität: Ereignisgesteuerte Interaktionen sind inhärent asynchron, was zu verbesserter Skalierbarkeit und Responsiveness führt.

4. Emergente Funktionalität: Neue Funktionen können durch das Abonnieren existierender Ereignisströme implementiert werden, ohne bestehende Services zu ändern.

5. Zeitliche Entkopplung: Producer und Consumer müssen nicht gleichzeitig verfügbar sein, Interaktionen können über Zeit hinweg stattfinden.

Diese Eigenschaften machen ereignisgesteuerte Designs besonders wertvoll für komplexe, sich entwickelnde Microservices-Ökosysteme.

9.1.2.4 Ereignismodellierung und -design

Die effektive Modellierung von Ereignissen ist entscheidend für den Erfolg ereignisgesteuerter Architekturen:

Ereignisidentifikation:

Ereignisschema-Design:

Ereignis-Hierarchien und -Taxonomien:

Ereignisfluss-Modellierung:

Tools wie Event Storming (eine kollaborative Modellierungstechnik) können Teams dabei helfen, ereignisgesteuerte Systeme effektiv zu gestalten.

9.1.2.5 Herausforderungen und Lösungsstrategien

Ereignisgesteuerte Architekturen bringen spezifische Herausforderungen mit sich:

Eventual Consistency: Durch die asynchrone Natur können temporäre Inkonsistenzen entstehen - Lösungsstrategie: Explizites Modellieren von Konsistenzanforderungen, Optimistic Concurrency Control, Versöhnungsmechanismen

Komplexes Debugging und Fehlersuche - Lösungsstrategie: Verteiltes Tracing, Korrelations-IDs, Event-Logging und -Replay

Genau-einmal-Verarbeitung - Lösungsstrategie: Idempotente Consumer, Ereignis-Deduplizierung, transaktionale Outbox-Muster

Ereignisschema-Evolution - Lösungsstrategie: Versionierte Schemas, Kompatibilitätstests, Schema-Registries

Monitoring und Observability - Lösungsstrategie: Spezialisierte Überwachungstools, Event-Flow-Visualisierung, Consumer Lag Monitoring

Die Bewältigung dieser Herausforderungen erfordert sowohl technische Lösungen als auch organisatorische Praktiken wie DevOps-Kultur und enge Zusammenarbeit zwischen Teams.

9.1.3 Integration von REST und Event-driven Design

In realen Microservices-Architekturen werden REST APIs und ereignisgesteuerte Ansätze oft komplementär eingesetzt, wobei jeder Ansatz für spezifische Interaktionsmuster genutzt wird.

9.1.3.1 Komplementäre Anwendungsfälle

REST für - Direkte Benutzerinteraktionen, die sofortige Antworten erfordern - CRUD-Operationen auf Ressourcen - Abfragen mit strengen Konsistenzanforderungen - Service-zu-Service-Kommunikation mit Anfrage-Antwort-Charakter - Öffentliche APIs für externe Konsumenten

Ereignisgesteuert für - Benachrichtigungen über Systemänderungen - Langläufige Prozesse und Workflows - Szenarien mit Eventual Consistency - Datensynchronisation zwischen Services - Datenreplikation für Read-Modelle - Systemweite Beobachtbarkeit und Audit-Trails

9.1.3.2 Hybride Muster

Mehrere Muster kombinieren die Stärken beider Ansätze:

API-Komposition mit Event-Sourcing - REST APIs für Client-Interaktion - Interne Ereignisströme für Zustandsverwaltung und Service-Kommunikation

Event-Notification mit Request-Reply - Ereignisse zur Benachrichtigung über Änderungen - REST-Aufrufe zur Abfrage detaillierter Informationen bei Bedarf

CQRS mit RESTful Queries - Ereignisgetriebene Command-Seite - RESTful APIs für optimierte Abfragen

API Gateway mit Event Backplane - REST-basiertes API-Gateway für externe Clients - Interne ereignisgesteuerte Kommunikation zwischen Services

9.1.3.3 Implementierungsstrategien für hybride Ansätze

Die effektive Integration beider Paradigmen erfordert durchdachtes Design:

Konsistente Ressourcen- und Ereignismodellierung - Abstimmung zwischen Ressourcenrepräsentationen und Ereignisstrukturen - Gemeinsames Domänenmodell als Grundlage

Transaktionale Outbox - Speicherung von Ereignissen in derselben Transaktion wie REST-API-Änderungen - Asynchrone Veröffentlichung der Ereignisse durch einen separaten Prozess

Korrelation zwischen REST und Ereignissen - Korrelations-IDs verbinden REST-Anfragen mit resultierenden Ereignissen - End-to-End-Tracing über beide Kommunikationsparadigmen hinweg

Konsistente Fehlerbehandlung - Einheitliche Strategie für Fehler in beiden Paradigmen - Kompensationsmechanismen für fehlgeschlagene Operationen

9.1.4 Technologische Umsetzung

Die Implementierung von REST APIs und ereignisgesteuerten Designs wird durch ein reiches Ökosystem an Technologien und Frameworks unterstützt:

9.1.4.1 REST-Technologien

Frameworks und Bibliotheken - Spring Boot/Spring WebFlux (Java) - ASP.NET Core (C#) - Express.js/NestJS (Node.js) - Django REST/FastAPI (Python)

API-Spezifikation und -Dokumentation - OpenAPI (Swagger) - RAML - API Blueprint

API-Gateways und -Management - Kong - Apigee - AWS API Gateway - Azure API Management

9.1.4.2 Ereignisgesteuerte Technologien

Message Broker - Apache Kafka - RabbitMQ - NATS - Amazon SNS/SQS - Azure Event Grid/Service Bus

Event-Processing-Frameworks - Spring Cloud Stream - Apache Camel - MassTransit (.NET) - Temporal.io

Event Store Databases - EventStoreDB - Apache Kafka with infinite retention - Axon Server

9.1.4.3 Integrationstools

Service Mesh - Istio - Linkerd - Consul Connect

API-Client-Generierung - OpenAPI Generator - NSwag - gRPC

Event Schema Registries - Confluent Schema Registry - Apicurio Registry - AWS Glue Schema Registry

Die Auswahl der richtigen Technologien sollte auf spezifischen Anforderungen, Team-Expertise und dem bestehenden Technologie-Stack basieren.

9.1.5 Best Practices für die Microservices-Kommunikation

Unabhängig vom gewählten Kommunikationsparadigma gibt es übergreifende Best Practices:

1. Klar definierte Service-Grenzen - Services sollten entlang von Bounded Contexts definiert werden - APIs und Ereignisse sollten diese Grenzen respektieren

2. Vertragsorientierte Entwicklung - Explizite Definitionen von API-Contracts und Ereignisschemas - Automatisierte Tests für Vertragskonformität

3. Versionierungsstrategie - Klare Richtlinien für API- und Schemaevolution - Unterstützung für parallele Versionen während Übergangsperioden

4. Fehlertolerante Kommunikation - Resilienz-Patterns wie Circuit Breaker, Retry und Timeout - Idempotente Operations für sichere Wiederholungen

5. Umfassende Observability - Verteiltes Tracing über Service-Grenzen hinweg - Einheitliches Logging- und Metriken-Format

6. Sicherheitskonzepte - Konsistente Authentifizierungs- und Autorisierungsmechanismen - Sichere Kommunikationskanäle (TLS, Verschlüsselung)

7. Performance-Optimierung - Angemessene Nutzung von Caching - Effiziente Serialisierungsformate - Batch-Verarbeitung für Hochdurchsatzszenarien

9.1.6 Der richtige Mix für erfolgreiche Microservices

Die Wahl zwischen REST APIs und ereignisgesteuertem Design ist keine Entweder-oder-Entscheidung. Erfolgreiche Microservices-Architekturen kombinieren beide Ansätze strategisch, um ihre jeweiligen Stärken zu nutzen und Schwächen zu kompensieren.

REST APIs bieten eine intuitive, standardisierte und entwicklerfreundliche Schnittstelle für synchrone Interaktionen, während ereignisgesteuertes Design eine lose gekoppelte, skalierbare und resiliente Basis für asynchrone Kommunikation bereitstellt. Die Kunst des Microservices-Designs liegt darin, für jeden Interaktionstyp den passenden Ansatz zu wählen und beide Paradigmen in eine kohärente Gesamtarchitektur zu integrieren.

Letztendlich sollte die Wahl des Kommunikationsparadigmas von den geschäftlichen Anforderungen, dem Datenmodell, den Konsistenzanforderungen und den operativen Überlegungen geleitet sein – nicht von technischen Modetrends oder dogmatischen Positionen. Eine pragmatische, anforderungsgeleitete Kombination beider Ansätze führt zu den erfolgreichsten Microservices-Implementierungen.