José Díaz
Fundador de JoeDayz y JefToken | Arquitecto Senior
Sagas, CQRS, la plataforma e-commerce multi-tenant y cómo un checkout viaja desde el tenant_id hasta Kafka.
Cómo se mantiene la consistencia cuando el pedido, el pago y el stock viven en servicios distintos, y cómo ese diseño se ejecuta: multi-tenant, virtual threads y el flujo de checkout.
Escribir el pedido en la base y publicar el evento en Kafka por separado es una escritura dual. Si el pedido queda grabado y el evento no sale, el inventario se desincroniza.
En una sola transacción:
Un poller o Debezium (CDC) publica después a Kafka. La garantía es at-least-once, así que los consumidores tienen que ser idempotentes.
OrderSaga coordina el camino feliz: pago y después stock.
Si Stock responde StockInsufficient, no hay rollback distribuido. Hay compensación: reembolso del pago y el pedido pasa a CANCELLED.
La consistencia es eventual, mediante un undo semántico. La orquestación centralizada sirve para flujos complejos.
Las operaciones se separan en dos modelos:
Cada canal tiene su backend:
Eso reemplaza a la God API.
Clientes mobile y web entran con JWT por el API Gateway y Keycloak. Detrás están los BFF y los servicios:
| Servicio | Persistencia |
|---|---|
| Order | PostgreSQL |
| Catalog | PostgreSQL + Elasticsearch |
| Inventory | PostgreSQL |
| Payment | — |
| Notification | MongoDB |
Todos publican al bus de eventos (Kafka). La observabilidad es OpenTelemetry y la malla de servicios es Istio.
Tres formas de aislar, de más cara a más escalable:
| Estrategia | Qué implica |
|---|---|
| DB por tenant | Aislamiento máximo, costo alto |
| Schema por tenant | Aislamiento medio |
Schema compartido + tenant_id | Escala SaaS |
Toda query, todo evento y toda caché llevan el tenant_id.
ThreadLocal es frágil en un pool de hilos: es mutable y el contexto de un tenant se puede filtrar al request siguiente.
ScopedValue (Java 25+) es inmutable y queda encapsulado en un structured scope. Encaja con virtual threads. TenantContext inyecta ese token desde el gateway hasta JPA.
ScopedValue.where(TENANT_ID, "empresa-123").run(() -> {
procesar();
});
Un platform thread bloqueado en I/O limita la escala y sube la latencia. Un virtual thread es liviano: el código sigue siendo bloqueante, pero el flujo no ocupa un hilo del sistema.
Spring MVC o Quarkus REST sobre virtual threads mantienen el código imperativo y el modelo de dominio, sin la complejidad de WebFlux.
ScopedValue captura el tenant_id y un virtual thread toma el I/O.Order, el aggregate root, valida las reglas.PLACED y el evento.OrderSaga orquesta pago, stock y envío. CQRS actualiza las vistas en background.ScopedValue y concurrencia estructurada en Java 21+.Completa el curso y obtén tu certificado
11 actividades restantes