Mikroservices in C#: Architektur und Praxis auf fortgeschrittenem Niveau
Der Übergang vom Monolithen zur Mikroservice-Architektur auf der .NET-Plattform ist nicht nur eine technische Entscheidung, sondern ein strategischer Schritt hin zu Skalierbarkeit und Ausfallsicherheit. Auf fortgeschrittenem Niveau stehen Entwickler jedoch vor nicht trivialen Herausforderungen: verteilte Transaktionen, Datenkonsistenz und komplexe Orchestrierung. In diesem Artikel analysieren wir die Architekturmuster, die helfen, ein zuverlässiges Mikroservice-Ökosystem in C# aufzubauen, und betrachten praktische Codebeispiele.
1. Saga-Pattern: Verwaltung verteilter Transaktionen
In Mikroservices sind herkömmliche ACID-Transaktionen nicht möglich. Das Saga-Pattern löst das Problem der Datenkonsistenz, indem es eine globale Operation in eine Abfolge lokaler Transaktionen mit kompensierenden Aktionen aufteilt.
Choreografie vs. Orchestrierung:
- Choreografie – jeder Service veröffentlicht Ereignisse und reagiert darauf. Geeignet für einfache Szenarien.
- Orchestrierung – ein zentraler Koordinator (Orchestrator) verwaltet die Schritte. Empfohlen für komplexe Geschäftsprozesse.
Beispiel einer Orchestrator-Implementierung in C# mit MassTransit:
public class OrderSaga : MassTransitStateMachine<OrderSagaState>{ public State Submitted { get; set; } public State PaymentPending { get; set; } public State Completed { get; set; }
public Event<OrderSubmitted> OrderSubmitted { get; set; } public Event<PaymentProcessed> PaymentProcessed { get; set; } public Event<PaymentFailed> PaymentFailed { get; set; }
public OrderSaga() { InstanceState(x => x.CurrentState);
Initially( When(OrderSubmitted) .Then(context => context.Saga.OrderId = context.Message.OrderId) .TransitionTo(Submitted) .Publish(context => new ProcessPayment { OrderId = context.Saga.OrderId, Amount = context.Message.Amount }) );
During(Submitted, When(PaymentProcessed) .TransitionTo(Completed) .Finalize(), When(PaymentFailed) .Then(context => Console.WriteLine("Kompensation: Stornierung der Bestellung")) .TransitionTo(Final) ); }}Dieser Code zeigt, wie der Orchestrator den Lebenszyklus einer Bestellung verwaltet: Bei erfolgreicher Zahlung wird die Saga abgeschlossen, bei einem Fehler wird die Kompensation gestartet.
2. CQRS und Event Sourcing: Trennung der Verantwortlichkeiten
Das CQRS-Pattern (Command Query Responsibility Segregation) trennt Befehle (Schreiben) und Abfragen (Lesen) in verschiedene Modelle. In Kombination mit Event Sourcing (ES) bietet es vollständige Auditierbarkeit und Flexibilität.
CQRS+ES-Architektur:
- Befehle – ändern den Zustand durch Ereignisse.
- Ereignisse – werden im Event Store gespeichert (z. B. EventStoreDB).
- Projektionen – erstellen Read-Modelle für schnelle Abfragen.
Beispiel eines Befehls und Handlers:
public class CreateOrderCommand : ICommand{ public Guid OrderId { get; set; } public decimal Amount { get; set; }}
public class CreateOrderHandler : ICommandHandler<CreateOrderCommand>{ private readonly IEventStore _eventStore;
public async Task Handle(CreateOrderCommand command) { var orderCreated = new OrderCreatedEvent { OrderId = command.OrderId, Amount = command.Amount, Timestamp = DateTime.UtcNow };
await _eventStore.AppendEventAsync( $"order-{command.OrderId}", orderCreated ); }