Zum Hauptinhalt springen
Alle Beiträge
Software Engineering5 Min. Lesezeit

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.
Code
// 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.Hosting in 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 Activity kapseln, 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, Degraded melden — nicht Unhealthy. Die Entscheidung liegt beim Orchestrator.
  • Health-Check-UI bereitstellen für Betriebsteams — AspNetCore.HealthChecks.UI bietet 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.
Code
// .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:

  1. Haben Sie unabhängige Teams, die unabhängig deployen müssen?
  2. Ist Ihre Domäne gut genug verstanden, um stabile Grenzen zu ziehen?
  3. Verfügen Sie über die operative Reife für Distributed Tracing, zentralisiertes Logging und Container-Orchestrierung?
  4. 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.

Microservices .NET 9Enterprise-Microservices-ArchitekturAPI-Gateway-MusterDistributed Tracing .NETResilienz-Muster

Brauchen Sie Expertenberatung?

Unser Team ist spezialisiert auf Cloud-Architektur, Security, KI-Plattformen und DevSecOps. Lassen Sie uns besprechen, wie wir Ihrem Unternehmen helfen können.

Verwandte Artikel