Microservices mit .NET 9: Architekturmuster für Enterprise-Systeme
Bewährte Microservices-Muster in .NET 9 — Service-Grenzen, API-Gateways, asynchrones Messaging und Resilienz für Enterprise-Teams.
Microservices sind nach wie vor das dominierende Architekturthema, sobald Enterprise-Teams über eine einzelne Deployment-Einheit hinauswachsen. Mit .NET 9 hat Microsoft substanzielle Verbesserungen bei Hosting, Native AOT und OpenTelemetry-Unterstützung ausgeliefert, die die Plattform für grosse verteilte Systeme qualifizieren. Doch das Framework allein löst die schwierigen Probleme nicht — falsch gezogene Service-Grenzen bleiben die häufigste Ursache für gescheiterte Microservices-Projekte.
Dieser Beitrag behandelt sechs Architekturmuster, die wir in .NET-9-Projekten mit über 20 Services in Produktion durchgehend einsetzen.
1. Service-Grenzen richtig ziehen
Der teuerste Fehler: zu früh oder entlang der falschen Naht schneiden. Unsere Faustregel lautet: Wenn zwei Services nicht unabhängig voneinander deployt werden können, ohne ein Release zu koordinieren, sind sie ein Service.
Bewährte Heuristiken:
- An Geschäftsfähigkeiten ausrichten, nicht an Datenentitäten. Ein „OrderService", der auch das Lager verwaltet, ist ein verkleideter Monolith.
- Mit einem modularen Monolithen starten. Die verbesserten Minimal APIs und Keyed DI in .NET 9 machen es einfach, interne Modulgrenzen zu definieren, die später extrahiert werden können.
- Den Team-Ownership-Test anwenden. Wenn ein einziges Team beide Seiten einer Grenze besitzt, braucht diese Grenze wahrscheinlich keinen Netzwerk-Call.
Tipp: Wir skizzieren Service-Grenzen am Whiteboard mit Event-Storming-Workshops, bevor eine einzige Zeile Code geschrieben wird. Zwei Stunden Workshop sparen Monate an Refactoring.
2. API-Gateway: Routing, keine Logik
Das API-Gateway sollte eine schlanke Routing-Schicht sein — niemals Geschäftslogik hierhin verlagern. Wir setzen typischerweise YARP (Yet Another Reverse Proxy) auf .NET 9 ein, wegen der tiefen Plattformintegration.
Zentrale Konfigurationsentscheidungen:
- Route-Aggregation für Mobile-/BFF-Szenarien: auf einfaches Fan-out beschränken, keine Transformation.
- Authentifizierung auslagern: JWT-Token am Gateway validieren, Claims per Header an nachgelagerte Services weiterreichen.
- Rate Limiting: die integrierte
RateLimiter-Middleware von .NET 9 am Gateway einsetzen. Policies pro Client-Stufe definieren (Free, Standard, Premium). - Health-Check-Routing: ungesunde Downstream-Services automatisch aus der Route-Tabelle entfernen.
// YARP-Routen-Konfiguration mit Rate Limiting (vereinfacht)
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("standard", opt =>
{
opt.Window = TimeSpan.FromMinutes(1);
opt.PermitLimit = 100;
});
});Vermeiden Sie die Falle, ein „intelligentes Gateway" zu bauen, das Aufrufe über Services hinweg orchestriert — dieser Weg führt geradewegs in den verteilten Monolithen.
3. Asynchrones Messaging: Das Rückgrat der Entkopplung
Synchrone HTTP-Aufrufe zwischen Services erzeugen enge Kopplung und Kaskadenfehler. Für alles, was keine sofortige Antwort erfordert, gilt: standardmässig asynchron kommunizieren.
Unser Standardstack auf Azure:
- Azure Service Bus für Command-artige Nachrichten (ein Consumer) und Pub/Sub-Topics (mehrere Consumer).
- MassTransit als .NET-Abstraktionsschicht — Retries, Dead-Letter-Routing und Saga-State-Machines sind direkt integriert.
Muster, die wir konsequent einsetzen:
- Outbox-Pattern: Nachricht und Datenbank-Schreibvorgang niemals ohne transaktionale Outbox kombinieren. EF Core 9 + MassTransit-Outbox-Support machen das handhabbar.
- Idempotente Consumer: Jeder Handler muss doppelte Zustellung tolerieren. Deduplizierung über eine
MessageId-Tabelle implementieren. - Nachrichten-Versionierung: von Tag eins eine
SchemaVersion-Eigenschaft mitführen. Das kostet jetzt nichts und erspart schmerzhafte Migrationen später.
Achtung: Verwenden Sie asynchrones Messaging nicht für Abfragen. Wenn Service A Daten von Service B braucht, um auf eine Benutzeranfrage zu antworten, nutzen Sie einen synchronen Aufruf (mit Resilienz) oder pflegen Sie ein lokales Read Model.
4. Distributed Tracing mit OpenTelemetry
.NET 9 bietet erstklassige OpenTelemetry-Unterstützung über System.Diagnostics.Activity. Für Microservices in Produktion ist das nicht verhandelbar.
Unser Standard-Setup:
- Instrumentierung:
OpenTelemetry.Extensions.Hostingin jedem Service. HTTP-Clients, EF Core, MassTransit und benutzerdefinierte Spans instrumentieren. - Exporter: Traces an Azure Monitor (Application Insights) oder Jaeger senden, je nach Kundenumgebung.
- Korrelation:
traceparent-Header über sämtliche HTTP- und nachrichtenbasierte Kommunikation propagieren. MassTransit erledigt das automatisch. - Business-Spans: Jede signifikante Geschäftsoperation in einer benannten
Activitykapseln, damit Traces eine fachliche Geschichte erzählen — nicht nur eine Infrastruktur-Geschichte.
Praxis-Checkliste für jeden Service:
- HTTP-Server-Instrumentierung aktiviert
- HTTP-Client-Instrumentierung aktiviert
- Datenbank-Instrumentierung aktiviert
- Message-Broker-Instrumentierung aktiviert
- Benutzerdefinierte Business-Spans angelegt
- Trace-Sampling konfiguriert (niemals 100 % in Produktion)
5. Health Checks: Mehr als eine Liveness-Probe
Das Health-Check-Framework von .NET 9 (Microsoft.Extensions.Diagnostics.HealthChecks) unterstützt drei Probe-Typen, die direkt auf Kubernetes abbilden:
- Liveness (
/healthz): Lebt der Prozess? Minimal halten — keine Datenbank-Aufrufe. - Readiness (
/ready): Kann der Service Traffic verarbeiten? Datenbank-Konnektivität, Message-Broker-Verbindung und Downstream-Abhängigkeiten prüfen. - Startup (
/startup): Hat der Service seine Initialisierung abgeschlossen? Kritisch bei Services mit Warm-up-Caches oder Migrationsphasen.
Gestaltungsregeln:
- Readiness-Checks brauchen Timeouts. Ein Health Check, der bei einer langsamen Datenbank hängt, verursacht kaskadierende Pod-Neustarts.
- Graceful Degradation. Wenn eine unkritische Abhängigkeit ausfällt,
Degradedmelden — nichtUnhealthy. Die Entscheidung liegt beim Orchestrator. - Health-Check-UI bereitstellen für Betriebsteams —
AspNetCore.HealthChecks.UIbietet ein Dashboard, das alle Services aggregiert.
6. Resilienz-Muster mit Polly v8 und .NET 9
.NET 9 integriert Microsoft.Extensions.Resilience (basierend auf Polly v8) direkt in die HTTP-Client-Factory. Das ist ein erheblicher Fortschritt gegenüber dem manuellen Verdrahten von Polly-Policies.
Die vier Muster, die jeder Inter-Service-Call braucht:
- Retry mit Jitter: Exponentielles Backoff plus zufälliger Jitter, um Thundering Herds zu vermeiden. Drei Versuche sind ein sinnvoller Standard.
- Circuit Breaker: Nach 5 aufeinanderfolgenden Fehlern auslösen, nach 30 Sekunden Half-Open. Verhindert, dass ein fehlerhafter Downstream alle Threads belegt.
- Timeout: Aggressive Timeouts setzen (2-5 Sekunden für Inter-Service-Calls). Eine langsame Antwort ist schlimmer als ein schneller Fehler.
- Bulkhead Isolation: Gleichzeitige Aufrufe an einen einzelnen Downstream begrenzen. Verhindert, dass eine langsame Abhängigkeit den Connection Pool erschöpft.
// .NET 9 Resilience Pipeline (vereinfacht)
builder.Services.AddHttpClient("OrderService")
.AddStandardResilienceHandler(options =>
{
options.Retry.MaxRetryAttempts = 3;
options.CircuitBreaker.FailureRatio = 0.5;
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(3);
});Reihenfolge der Muster: Bulkhead -> Timeout -> Retry -> Circuit Breaker (von aussen nach innen).
Architektur-Entscheidungs-Checkliste
Bevor Sie sich auf eine Microservices-Architektur mit .NET 9 festlegen, beantworten Sie diese Fragen ehrlich:
- Haben Sie unabhängige Teams, die unabhängig deployen müssen?
- Ist Ihre Domäne gut genug verstanden, um stabile Grenzen zu ziehen?
- Verfügen Sie über die operative Reife für Distributed Tracing, zentralisiertes Logging und Container-Orchestrierung?
- Können Sie die Latenz von Netzwerk-Aufrufen zwischen Services verkraften?
Wenn Sie eine dieser Fragen mit „Nein" beantworten, könnte ein modularer Monolith auf .NET 9 die bessere Wahl sein — der Migrationspfad zu Microservices bleibt dabei offen.
Bei CC Conceptualise unterstützen wir Enterprise-Teams dabei, diese Architekturentscheidung fundiert zu treffen und produktionsreife Muster umzusetzen. Wenn Sie Microservices für Ihre .NET-Landschaft evaluieren, sprechen Sie uns an.