Capstone: diseña de extremo a extremo un sistema de checkout de e-commerce escalable: inventario, carrito, pago, confirmación de pedido
Lo más difícil del checkout no es cobrar la tarjeta, es garantizar que dos compradores no puedan llevarse la última unidad, y revertir todo con limpieza cuando un paso falla.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
Capstone: diseña de extremo a extremo un sistema de checkout de e-commerce escalable: inventario, carrito, pago, confirmación de pedido
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Qué significa realmente "de extremo a extremo" aquí
Para cuando un comprador hace clic en "Realizar pedido", al menos cuatro servicios necesitan ponerse de acuerdo, en el orden correcto, sin que ninguno le mienta a los demás: Carrito (qué hay en el pedido y a qué precio), Inventario (¿hay stock realmente, y se puede reservar para este pedido específico?), Pago (¿se puede efectivamente cobrar el dinero? — esto es toda la lección anterior, reutilizada aquí como subsistema), y Pedido (el registro durable que ata a los otros tres una vez que todo tuvo éxito). Acertar con cualquiera de estos por separado es fácil; la dificultad de diseño de sistemas está enteramente en qué pasa cuando el paso 3 falla después de que los pasos 1 y 2 ya tuvieron éxito, y en qué pasa cuando dos peticiones golpean el paso 2 para el mismo artículo en el mismo instante.
#Límites de servicio y requisitos de consistencia
No todas las partes de este sistema necesitan la misma garantía de consistencia, y suponer lo contrario es el error de sobre-ingeniería más común al diseñar un checkout:
| Aspecto | Consistencia necesaria | Por qué |
|---|---|---|
| Catálogo de productos / páginas de exploración | Eventualmente consistente (caché, índice de búsqueda) | Lectura intensiva; un precio/descripción con segundos de desfase es inofensivo |
| Contenido del carrito | Fuertemente consistente, por usuario | Solo el dueño lo lee/escribe; no hay contención entre usuarios |
| Decremento de inventario en el checkout | Fuertemente consistente, atómico | Este es el límite del oversell, el único lugar donde la corrección no puede ceder |
| Autorización de pago | Fuertemente consistente, idempotente (ver la lección anterior) | Es dinero |
| Confirmación de pedido, envío, recomendaciones | Eventualmente consistente, orientada a eventos | Ocurre después de un pedido ya confirmado; unos segundos de retraso están bien |
Esta separación es una forma de CQRS a nivel de sistema: la experiencia de navegación lee de almacenes rápidos, desnormalizados y eventualmente consistentes, mientras que el camino angosto del checkout — carrito, inventario, pago, pedido — lee y escribe sobre la fuente de verdad fuertemente consistente. Intentar que la página de catálogo sea fuertemente consistente (para que el stock mostrado sea siempre exacto) no aporta nada y cuesta la capa de caché que hace rápido al catálogo; intentar que el decremento de inventario sea eventualmente consistente es precisamente cómo se vende de más.
#El problema del oversell, resuelto en la capa de base de datos
La carrera clásica: dos compradores ven "1 en stock" y ambos hacen clic en comprar con milisegundos de diferencia. Cualquier servicio que ingenuamente haga leer cantidad, comprobar mayor que cero, escribir cantidad menos uno como tres pasos separados dejará pasar a ambos, porque las dos lecturas ocurren antes de cualquiera de las dos escrituras. La solución es convertir el "comprobar y decrementar" en una única operación atómica que la base de datos garantiza, no algo que coordina la aplicación:
UPDATE inventorySET quantity = quantity - 1WHERE sku = 'X' AND quantity >= 1;
Si esta sentencia afecta cero filas, el artículo ya estaba agotado y esta petición pierde la carrera — de forma limpia, sin mantener ningún lock durante una ida y vuelta de red y sin necesidad de coordinación distribuida. Esto es una actualización condicional (optimista); la alternativa es un SELECT ... FOR UPDATE pesimista que mantiene un lock de fila durante toda la transacción, más simple de razonar pero que serializa cada intento de checkout de un SKU popular detrás de un único lock — aceptable a volumen moderado, un cuello de botella a volumen de flash sale. Con contención muy alta sobre un solo SKU caliente, algunos sistemas fragmentan el propio contador de stock (por ejemplo, 10 contadores de 100 unidades en vez de 1 contador de 1000) para repartir la contención del lock, reconciliando a un único número periódicamente.
#La saga de checkout
Una única transacción ACID no puede abarcar Carrito, Inventario, Pago y Pedido cuando Pago significa llamar a una pasarela externa por red — no se puede revertir un cobro de tarjeta con un ROLLBACK de base de datos. La respuesta estándar es una saga: una secuencia de transacciones locales, cada una con una acción compensatoria definida si un paso posterior falla.
- Reservar inventario (la actualización condicional atómica de arriba, o una reserva blanda con TTL si se quiere permitir una ventana de checkout antes del decremento final).
- Cálculo de precio e impuestos — aplicar promociones, calcular el monto final a cobrar.
- Cobrar el pago — llamada síncrona al Servicio de Pagos de la lección anterior, usando el id del pedido como clave de idempotencia para que un reintento del cliente (doble clic en "Realizar pedido") nunca pueda crear un segundo cobro ni un segundo pedido.
- Confirmar el pedido — solo después de que la autorización de pago tenga éxito; escribir el registro durable de Pedido.
- Compensar ante fallo — si el pago es rechazado o hace timeout de forma irrecuperable, la acción compensatoria libera la reserva de inventario (cantidad + 1) y marca el pedido como CANCELLED. Si la propia reserva de inventario falla (paso 1), la saga nunca avanza hasta el pago — falla rápido, antes de tocar el dinero.
#El flujo completo: camino síncrono, fan-out asíncrono, y la carrera
#Idempotencia a nivel de checkout
Todo lo que la lección anterior dijo sobre claves de idempotencia aplica de nuevo aquí, un nivel más arriba: la propia petición de checkout necesita una clave de idempotencia (típicamente derivada del id del carrito más un token generado por el cliente), de modo que un doble clic en "Realizar pedido", o un reintento del cliente tras una respuesta lenta, no puedan crear dos pedidos ni cobrar la tarjeta dos veces — el primer paso del orquestador, antes de tocar siquiera el inventario, es comprobar si este checkout ya se inició y, de ser así, devolver el resultado de la saga en vuelo o ya completada en vez de iniciar una segunda.
#TTLs de reserva y carritos abandonados
Un decremento duro al "agregar al carrito" dejaría que los compradores acaparen inventario solo con navegar. Un decremento duro únicamente al "pago confirmado" reabre la ventana de oversell durante la propia llamada de pago. El término medio práctico es una reserva blanda con TTL: el decremento condicional ocurre cuando empieza el checkout (no al agregar al carrito), y si la saga no llega a un estado terminal (CONFIRMED o CANCELLED) dentro de una ventana acotada — unos minutos —, un barrendero en segundo plano libera la reserva a la fuerza y devuelve la unidad al pool, el mismo patrón de sesión abandonada de la lección de subidas por partes anterior en este módulo, aplicado a inventario en vez de a partes de archivo.
#Modos de fallo y sus compensaciones
| Punto de fallo | Acción compensatoria |
|---|---|
| Falla la reserva de inventario (agotado) | Fallar el checkout de inmediato; nunca llamar a Pagos |
| Pago rechazado | Liberar la reserva de inventario; marcar el pedido como CANCELLED |
| Timeout de la pasarela de pago (ambiguo) | Resolver con una consulta de estado (lección anterior) antes de compensar o confirmar |
| El orquestador se cae a mitad de la saga | El estado de la saga se persiste por paso; un proceso de recuperación reanuda o compensa desde el último checkpoint durable |
| El pedido se confirma pero falla la publicación del evento | Patrón outbox — el evento es durable en la misma transacción que la escritura del pedido; un relay reintenta la publicación de forma independiente |
| El comprador abandona el checkout | Un barrendero basado en TTL libera la reserva |
🧠 Check your understanding
0/3 · 0/3 answered1. ¿Por qué el UPDATE condicional (WHERE quantity >= 1) resuelve la carrera de oversell sin necesidad de un lock distribuido?
2. En la saga de checkout, ¿qué pasa si el pago es rechazado después de que ya se reservó el inventario?
3. ¿Por qué la propia petición de checkout necesita su propia clave de idempotencia, separada de la del pago?