Microservicios en C#: arquitectura y práctica de nivel avanzado
La transición del monolito a la arquitectura de microservicios en la plataforma .NET no es solo una decisión técnica, sino un paso estratégico hacia la escalabilidad y la tolerancia a fallos. Sin embargo, a nivel avanzado, los desarrolladores se enfrentan a problemas complejos: transacciones distribuidas, consistencia de datos y orquestación compleja. En este artículo, analizaremos los patrones arquitectónicos que ayudarán a construir un ecosistema de microservicios confiable en C# y revisaremos ejemplos prácticos de código.
1. Patrón Saga: gestión de transacciones distribuidas
En los microservicios, las transacciones ACID tradicionales no son posibles. El patrón Saga resuelve el problema de la consistencia de datos dividiendo la operación global en una secuencia de transacciones locales con acciones compensatorias.
Coreografía vs. Orquestación:
- Coreografía — cada servicio publica eventos y reacciona a ellos. Adecuado para escenarios simples.
- Orquestación — un coordinador central (Orchestrator) gestiona los pasos. Recomendado para procesos de negocio complejos.
Ejemplo de implementación de un orquestador en C# usando 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("Compensación: cancelación del pedido")) .TransitionTo(Final) ); }}Este código demuestra cómo el orquestador gestiona el ciclo de vida del pedido: cuando el pago es exitoso, la saga finaliza; cuando hay un error, se inicia la compensación.
2. CQRS y Event Sourcing: separación de responsabilidades
El patrón CQRS (Command Query Responsibility Segregation) separa los comandos (escritura) y las consultas (lectura) en modelos diferentes. Combinado con Event Sourcing (ES), proporciona auditoría completa y flexibilidad.
Arquitectura CQRS+ES:
- Comandos — modifican el estado a través de eventos.
- Eventos — se almacenan en un Event Store (por ejemplo, EventStoreDB).
- Proyecciones — construyen modelos de lectura para consultas rápidas.
Ejemplo de comando y manejador:
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 ); }