Microservices en C# : architecture et pratique de niveau avancé
Passer du monolithe à une architecture microservices sur la plateforme .NET n'est pas seulement une décision technique, mais une étape stratégique vers l'évolutivité et la tolérance aux pannes. Cependant, au niveau avancé, les développeurs sont confrontés à des problèmes non triviaux : transactions distribuées, cohérence des données et orchestration complexe. Dans cet article, nous analyserons les patterns architecturaux qui aident à construire un écosystème de microservices fiable en C#, et nous examinerons des exemples de code pratiques.
1. Pattern Saga : gestion des transactions distribuées
Dans les microservices, les transactions ACID traditionnelles sont impossibles. Le pattern Saga résout le problème de cohérence des données en décomposant une opération globale en une séquence de transactions locales avec des actions compensatoires.
Chorégraphie vs. Orchestration :
- Chorégraphie — chaque service publie des événements et y réagit. Convient aux scénarios simples.
- Orchestration — un coordinateur central (Orchestrator) gère les étapes. Recommandée pour les processus métier complexes.
Exemple d'implémentation d'un orchestrateur en C# avec 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("Compensation : annulation de la commande")) .TransitionTo(Final) ); }}Ce code montre comment l'orchestrateur gère le cycle de vie d'une commande : en cas de paiement réussi, la saga se termine ; en cas d'erreur, une compensation est déclenchée.
2. CQRS et Event Sourcing : séparation des responsabilités
Le pattern CQRS (Command Query Responsibility Segregation) sépare les commandes (écriture) et les requêtes (lecture) en différents modèles. Combiné à l'Event Sourcing (ES), il assure une traçabilité complète et une flexibilité.
Architecture CQRS+ES :
- Commandes — modifient l'état via des événements.
- Événements — stockés dans un Event Store (par exemple, EventStoreDB).
- Projections — construisent des modèles de lecture pour des requêtes rapides.
Exemple de commande et de gestionnaire :
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 ); }