Diseña la arquitectura de backend para el flujo de un pedido de e-commerce entre microservicios — recorre los servicios y cómo se comunican
Un solo pedido toca cinco servicios y dos estilos de comunicación — recorre el flujo y las sagas que lo mantienen correcto sin una transacción distribuida.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
Diseña la arquitectura de backend para el flujo de un pedido de e-commerce entre microservicios — recorre los servicios y cómo se comunican
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.#Planteando el problema
"Diseña el backend para colocar un pedido a través de microservicios" es uno de los prompts de diseño de sistemas más comunes en entrevistas senior de backend precisamente porque una respuesta correcta requiere casi todo lo demás de este módulo a la vez: límites de servicio trazados alrededor de capacidades de negocio, una mezcla de comunicación síncrona y asíncrona elegida deliberadamente y no por costumbre, sagas y acciones compensatorias en vez de una transacción distribuida, idempotencia en todos los lugares donde puedan ocurrir reintentos, y observabilidad capaz de reconstruir un pedido lógico único a través de cinco servicios independientes. No existe una única arquitectura correcta, pero sí existe un conjunto correcto de preocupaciones que una buena respuesta debe abordar, y esta lección recorre un diseño coherente que las cubre todas.
#Los servicios y qué posee cada uno
- API Gateway / Backend-for-Frontend — el único punto de entrada para los clientes: autenticación, rate limiting, enrutamiento de peticiones y, a menudo, agregación de varias llamadas downstream en una sola forma de respuesta para el frontend. No posee ningún dato de negocio.
- Order Service (Servicio de Pedidos) — posee el ciclo de vida del pedido como una máquina de estados explícita (CREATED → PENDING_PAYMENT → PAID → FULFILLING → SHIPPED → DELIVERED, con CANCELLED alcanzable desde casi todos esos estados) y su propia base de datos. Es la fuente de verdad de "en qué estado está este pedido", aunque él mismo no realiza el pago ni el envío.
- Inventory Service (Servicio de Inventario) — posee los niveles de stock y las reservas en su propia base de datos. Es el único servicio autorizado para incrementar o decrementar un conteo de stock.
- Payment Service (Servicio de Pagos) — posee los registros de pago y la interacción idempotente con un procesador de pagos externo (Stripe, Adyen, una red de tarjetas, lo que sea). Nunca deja que el Order Service ni nadie más toque directamente un número de tarjeta.
- Shipping / Fulfillment Service (Servicio de Envíos) — posee los registros de envío y la integración con APIs de transportistas, y sigue un envío a través de su propia máquina de estados (CREATED → DISPATCHED → IN_TRANSIT → DELIVERED).
- Notification Service (Servicio de Notificaciones) — no posee ningún estado de negocio del que dependa nadie más; existe puramente para traducir eventos de dominio en comunicación hacia el cliente (email, SMS, push), y su propia caída nunca debería poder bloquear un pedido.
- Bus de eventos — Kafka, RabbitMQ o similar, la columna vertebral que permite a Order, Inventory, Payment y Shipping propagar cambios de estado a quien necesite reaccionar, sin saber quiénes son esos consumidores.
#Recorriendo el flujo
- El cliente coloca el pedido. El cliente llama al API Gateway, que llama al Order Service de forma síncrona (REST o gRPC). Order Service valida la petición, persiste un pedido nuevo en estado CREATED / PENDING_PAYMENT, y devuelve el id del pedido de inmediato: no bloquea al cliente esperando inventario, pago o envío, todo lo cual ocurre después. Esta es la decisión de diseño más importante de todo el flujo: la petición orientada al usuario solo tiene que esperar la parte que determina qué mostrarle a continuación, no todo el pipeline de cumplimiento.
- Order Service publica OrderCreated, usando un outbox transaccional: la fila del evento se escribe en la misma transacción de base de datos que el propio pedido, y un proceso relay lo publica al broker, garantizando que el evento salga si y solo si el pedido realmente se persistió.
- Inventory Service consume OrderCreated e intenta reservar stock para cada línea del pedido. Publica InventoryReserved si tiene éxito, o InventoryReservationFailed si falla (por ejemplo, sin stock).
- Order Service consume el resultado de inventario. Si falla, transiciona el pedido a CANCELLED y publica OrderCancelled: nunca se intentó el pago. Si tiene éxito, llama al Payment Service, y aquí el diseño vuelve deliberadamente a lo síncrono: muchos equipos cobran la tarjeta mediante una llamada directa de petición/respuesta (con una idempotency key derivada del id del pedido) porque la experiencia de checkout comúnmente todavía quiere saber, dentro de la misma interacción, si el cobro tuvo éxito, fue rechazado, o necesita una acción secundaria como 3-D Secure. (Una alternativa completamente asíncrona —devolver "procesando" de inmediato y notificar al cliente después vía webhook o polling— también es válida y común con proveedores cuyos flujos de pago son inherentemente asíncronos; menciónalo explícitamente si te lo preguntan, ya que ambas son defendibles.)
- Payment Service cobra al cliente a través del procesador externo, usando la idempotency key para que un reintento de red desde Order Service no pueda cobrar dos veces el mismo pedido, registra la transacción, y publica PaymentSucceeded o PaymentFailed.
- Order Service reacciona al resultado del pago. Si tiene éxito, pasa a PAID y publica OrderPaid. Si falla, pasa a CANCELLED y —de forma crítica— publica un evento que le indica a Inventory Service que libere la reserva anterior. Esta liberación es una acción compensatoria: no existe una transacción distribuida que abarque las bases de datos de Order e Inventory, así que la corrección viene de deshacer explícitamente el paso anterior, no de un commit atómico entre servicios.
- Shipping Service consume OrderPaid, crea un registro de envío, y se integra con una API de transportista, publicando ShipmentDispatched y más tarde ShipmentDelivered a medida que avanza el proceso físico. Order Service se suscribe a estos eventos únicamente para mantener actualizada su propia máquina de estados (SHIPPED, DELIVERED); no habla directamente con el transportista.
- Notification Service se suscribe ampliamente —OrderCreated, PaymentFailed, OrderCancelled, OrderPaid, ShipmentDispatched— y convierte cada uno en el mensaje adecuado para el cliente, completamente desacoplado del flujo central, así que un proveedor de email lento o caído nunca puede detener un pedido.
#Coreografía vs. orquestación
El flujo anterior es coreografía: cada servicio reacciona a eventos publicados por otros, sin coordinador central. Escala bien organizacionalmente (cada equipo posee las reacciones de su servicio) y no agrega un componente nuevo que operar, pero puede volverse difícil de rastrear —"qué pasa realmente cuando se coloca un pedido" queda disperso entre los manejadores de eventos de cinco bases de código— y la ramificación condicional compleja (cumplimiento parcial, backorders, envíos divididos entre almacenes) se vuelve más difícil de razonar cuantos más pasos agregas. La alternativa es la orquestación: un Order Saga Orchestrator explícito emite cada paso como un comando (ReserveInventory, ChargePayment, CreateShipment) y emite explícitamente el comando compensatorio si un paso falla, lo que cambia una nueva dependencia central por mucha mejor visibilidad, pruebas más fáciles de todo el flujo, y un lugar natural para agregar lógica de ramificación compleja más adelante. Los entrevistadores generalmente aceptan cualquiera de las dos, siempre que puedas explicar por qué la elegiste y qué sacrificaste.
#Sagas y compensaciones, de forma explícita
| Paso hacia adelante | Acción compensatoria si un paso posterior falla |
|---|---|
| Reservar inventario | Liberar la reserva |
| Cobrar el pago | Reembolsar la transacción |
| Crear y despachar el envío | Cancelar el envío, o iniciar una devolución si ya salió del almacén |
#Preocupaciones transversales que una buena respuesta menciona explícitamente
- Idempotencia en todas partes. Los brokers entregan al menos una vez, no exactamente una vez, y los reintentos de red duplican llamadas; cada consumidor debe deduplicar por un id de evento o de pedido, y cada llamada externa con efectos secundarios (el cobro de pago sobre todo) necesita una idempotency key.
- Propiedad de los datos. Cada servicio posee su base de datos en exclusiva; ningún servicio entra a las tablas de otro. Las lecturas entre servicios ocurren mediante APIs o proyecciones cacheadas localmente y construidas a partir de eventos, nunca mediante un esquema compartido.
- ¿Por qué no una transacción distribuida (2PC)? Un commit de dos fases a través de cuatro bases de datos poseídas de forma independiente (y un procesador de pagos externo que jamás participará en tu coordinador 2PC) es tanto técnicamente impráctico como un riesgo de disponibilidad: bloquea a todos los participantes durante la duración del commit y convierte la caída de cualquier participante en la caída de todos. Las sagas cambian la atomicidad estricta por disponibilidad y aceptan una breve ventana de estado intermedio visible, compensado explícitamente si algo falla después.
- Observabilidad. Una única operación lógica de "colocar este pedido" ahora abarca cinco servicios y múltiples saltos asíncronos; un correlation id (o un id de traza distribuida completo) generado en el API Gateway debe propagarse por cada header de llamada síncrona y por los metadatos de cada evento, para que todo el recorrido pueda reconstruirse a partir de logs y trazas después de los hechos.
🧠 Check your understanding
0/2 · 0/2 answered1. En el flujo del pedido, ¿por qué Order Service típicamente llama a Payment Service de forma síncrona mientras llama a Shipping y Notification de forma asíncrona vía eventos?
2. Si Payment Service reporta un cobro fallido después de que Inventory ya reservó stock para el pedido, ¿qué debería ocurrir?