Disene y desarrolle una arquitectura de microservicios con .NET 8 aplicando Event Sourcing para reconstruir el estado de cada wallet desde su historial de eventos, CQRS para separar comandos de consultas, Outbox Pattern para garantizar la entrega de eventos entre servicios via RabbitMQ, y DDD con Aggregates y Domain Events. La comunicacion en tiempo real se maneja con SignalR para notificar balances actualizados. YARP como API Gateway unifica los endpoints, y toda la infraestructura se levanta con Docker Compose.
Arquitectura de 3 microservicios independientes: Users.API (gestion de usuarios), Wallets.API (billeteras y balances), Transactions.API (depositos, retiros, transferencias) — cada uno con su propia base de datos PostgreSQL
Event Sourcing: el estado de cada wallet se reconstruye desde un stream de eventos (WalletCreated, MoneyDeposited, MoneyWithdrawn, TransferSent, TransferReceived) — no hay UPDATE, solo INSERT de eventos
CQRS (Command Query Responsibility Segregation): los comandos mutan el estado via Domain Events, las queries leen desde proyecciones materializadas optimizadas para lectura
Outbox Pattern: los eventos se persisten en una tabla outbox dentro de la misma transaccion de base de datos, y un background worker los publica a RabbitMQ — garantiza at-least-once delivery sin two-phase commit
DDD con Aggregates: Wallet es el Aggregate Root que encapsula las reglas de negocio (saldo suficiente, wallet activa, montos positivos) y emite Domain Events
YARP (Yet Another Reverse Proxy) como API Gateway: rutea requests a los microservicios correspondientes, centraliza la entrada y permite agregar middleware transversal
Configuracion declarativa de rutas en YARP: cada prefijo (/api/users, /api/wallets, /api/transactions) se mapea a su microservicio destino
RabbitMQ como message broker para comunicacion asincrona entre microservicios — los eventos de dominio viajan como mensajes en colas dedicadas por tipo de evento
Consumers idempotentes: cada microservicio procesa eventos con deduplicacion basada en EventId para evitar efectos duplicados ante reintentos
SignalR Hub para notificaciones en tiempo real: cuando una transaccion se completa, el cliente recibe el balance actualizado via WebSocket sin polling
PostgreSQL como base de datos para cada microservicio — esquemas separados para aislamiento de datos (database-per-service pattern)
Event Store: tabla de eventos append-only con AggregateId, EventType, Data (JSON), Timestamp y Version para control de concurrencia optimista
Proyecciones materializadas: vistas read-only que se actualizan reactivamente cuando se procesan nuevos eventos — optimizadas para queries de balance y historial
Redis como cache distribuido para balances frecuentemente consultados — reduce la carga de lectura sobre PostgreSQL y acelera las queries del dashboard
Cache invalidation basada en eventos: cuando un evento de wallet se procesa, la entrada correspondiente en Redis se invalida automaticamente
Deposito: comando que valida monto positivo, emite MoneyDeposited, actualiza proyeccion y publica evento para notificacion en tiempo real
Retiro: valida saldo suficiente en el Aggregate antes de emitir MoneyWithdrawn — si el saldo es insuficiente, lanza InsufficientFundsException
Transferencia entre usuarios: operacion distribuida que emite TransferSent en wallet origen y TransferReceived en wallet destino, coordinada via eventos en RabbitMQ
Docker Compose orquesta toda la infraestructura: 3 APIs .NET, PostgreSQL, RabbitMQ, Redis y YARP Gateway — un comando para levantar el sistema completo
Health checks configurados en cada microservicio para monitoreo de disponibilidad y dependencias (DB, RabbitMQ, Redis)
JWT Authentication compartido entre microservicios — el token se valida en el Gateway y se propaga a los servicios internos
Validacion de ownership: un usuario solo puede operar sobre su propia wallet, verificado en cada comando
FLUJO — Deposito: [Cliente] POST /api/wallets/{id}/deposit {amount} → [YARP Gateway] rutea a Wallets.API → [DepositHandler] carga Wallet aggregate desde Event Store → Wallet.Deposit(amount) valida monto > 0 y wallet activa → emite MoneyDeposited event → persiste evento en Event Store + fila en Outbox (misma transaccion) → response 200 con nuevo balance
FLUJO — Outbox Worker: [Background Worker] cada 1s lee filas pendientes de la tabla Outbox → publica cada evento a RabbitMQ exchange → marca fila como Published → si falla, reintenta en el proximo ciclo (at-least-once)
FLUJO — Transferencia completa: [Cliente] POST /api/transactions/transfer {fromWalletId, toWalletId, amount} → [Transactions.API] valida datos y crea TransferInitiated event → publica a RabbitMQ → [Wallets.API consumer] recibe evento → carga wallet origen → Wallet.Withdraw(amount) → emite TransferSent → persiste + outbox → [segundo consumer] carga wallet destino → Wallet.Receive(amount) → emite TransferReceived → ambas proyecciones actualizadas → SignalR notifica a ambos usuarios
FLUJO — Reconstruccion de estado (Event Sourcing): [Query GetBalance] → carga todos los eventos del AggregateId desde Event Store ordenados por Version → aplica cada evento secuencialmente: WalletCreated(balance=0) → MoneyDeposited(+500) → MoneyWithdrawn(-100) → TransferSent(-50) → balance final = 350 → cachea resultado en Redis
FLUJO — Concurrencia optimista: [Comando A] lee Wallet con Version=5 → [Comando B] lee Wallet con Version=5 → [A] persiste evento con ExpectedVersion=5 → exito, Version=6 → [B] intenta persistir con ExpectedVersion=5 → ConcurrencyException → B reintenta: recarga aggregate (ahora Version=6) → reaplica logica → persiste con ExpectedVersion=6 → exito
FLUJO — Consistencia eventual: [Wallets.API] persiste MoneyDeposited en Event Store (fuente de verdad) → Outbox Worker publica a RabbitMQ (delay ~1s) → [Transactions.API consumer] recibe y actualiza su vista local de transacciones → [Projections Worker] actualiza proyeccion materializada de balance → [Redis] cache invalidado y actualizado → [SignalR] push al cliente → el sistema converge al estado correcto en ~2-3 segundos
FLUJO — Arquitectura completa: [Cliente HTTP/WS] → [YARP Gateway :5000] → {/api/users → Users.API :5001, /api/wallets → Wallets.API :5002, /api/transactions → Transactions.API :5003} → cada API: [Controller] → [MediatR Handler] → [Aggregate] → [Event Store PostgreSQL] + [Outbox] → [Worker] → [RabbitMQ] → [Consumer en otro servicio] → [Proyeccion] + [Redis cache] + [SignalR push]