Para que múltiples servicios cooperen en una arquitectura composable, es indispensable definir contratos claros tanto para APIs REST como para los eventos entre servicios.
Un contrato especifica las reglas de interacción: endpoints, métodos, formato de datos, esquemas, campos requeridos, semántica de cada operación, y versiones soportadas.
🎁¿Te parece que AWS es complicado?
A mí también me lo parecía al inicio.
Con un plan de estudio claro y práctica constante, ahora diseño infraestructuras, automatizo despliegues y optimizo costos sin sudar.
Por eso lancé eScalando AWS: videos cortos y directos al grano, explicados en lenguaje sencillo para que domines AWS paso a paso. Además, de paso entreno mis habilidades de comunicación.
👉 Visita “eScalando AWS” en YouTube
👍 Suscríbete y activa la campanita para no perderte nada.
Contratos de APIs
Cada servicio Composable debe ofrecer APIs bien diseñadas, siguiendo principios RESTful en la medida de lo posible.
Por ejemplo, el servicio de Órdenes podría exponer POST /orders
(crear una orden), GET /orders/{id}
(consultar estado de la orden), etc.; el servicio de Pagos tal vez POST /payments
(procesar un pago) y así sucesivamente.
La especificación de estos endpoints debe incluir:
Esquemas de requests y responses
Utilizar formatos JSON (u otro estándar) con un esquema definido. Es recomendable proveer documentación en OpenAPI/Swagger que detalle cada campo. Por ejemplo, el POST /orders
podría esperar un JSON con el ID del carrito, dirección de envío, método de pago, etc., y responder con JSON de confirmación (orderId, estado, monto).
Campos clave y significados
Identificadores únicos como orderId, paymentId, userId deben estar claramente definidos para poder correlacionar las operaciones.
Estos IDs suelen propagarse a través de los servicios (por ejemplo, el orderId estará presente en eventos de pago o envío para saber a qué orden se refieren).
Otro campo importante en peticiones es un posible Idempotency-Key
para evitar duplicados en operaciones de creación.
Código de estado y errores
Definir qué códigos HTTP devuelve cada operación (201 Created, 400 Bad Request, 409 Conflict, etc.) y en qué casos, así como el formato de los cuerpos de error (por ejemplo, un JSON con código de error interno y mensaje). Esto forma parte del contrato para que el cliente (otro servicio o la frontend) pueda reaccionar apropiadamente.
Versionado de la API
Incluir la versión en la ruta (p.ej. /api/v1/checkout
) o en un header custom (Accept: application/vnd.myapi.v1+json
) desde el diseño inicial. De este modo, evoluciones futuras podrán introducirse en nuevas versiones sin romper clientes existentes.
Seguridad y autenticación
Aunque no es el foco de este análisis, parte del contrato es cómo se autorizan las llamadas (ej. mediante tokens JWT en header Authorization
). Para integraciones internas en microservicios puede usarse autenticación de servicio a servicio o confiar en un gateway.
Trazabilidad
Se recomienda que los contratos indiquen la utilización de Correlation IDs o identificadores de correlación en headers para el tracing distribuido.
Por ejemplo, cada petición entrante al sistema de checkout puede llevar un header X-Correlation-ID: <uuid>
generado en el frontend o API Gateway; todos los microservicios deben propagar este ID en sus llamadas subsecuentes y eventos, y usarlo en logs.
Esto permite reconstruir la traza completa de una orden en herramientas de logging/monitoring, crucial para depurar y monitorear (ej: saber qué ocurrió con la orden #1234
a lo largo de todos los servicios).
Contratos de eventos de dominio
Además de APIs síncronas, en composable commerce los servicios se integran fuertemente por eventos asíncronos. Cada tipo de evento publicado debe estar igualmente bien definido mediante un esquema. Esto incluye eventos de dominio como OrderPlaced, OrderConfirmed, PaymentProcessed, StockReserved, PaymentFailed
, etc.
Las consideraciones para diseñar buenos contratos de eventos son:
Esquema del evento
Definir una estructura de mensaje clara, normalmente con una envoltura (metadata) y un payload (datos de negocio). Por ejemplo, un evento OrderPlaced
podría serializarse en JSON así:
Aquí eventType
identifica el tipo de evento, version
es la versión del esquema de este evento (permite que productores y consumidores gestionen cambios ), timestamp
marca el momento de generación, correlationId
permite vincular este evento con la transacción o request original (podría ser el mismo ID de correlación que viene desde la UI), y data
contiene los campos de negocio relevantes (detalles de la orden en este caso). Incluir un campo de versión en cada mensaje es una buena práctica para permitir compatibilidad hacia atrás y adelante en la evolución de eventos .
Campos mínimos vs completos
Decidir cuánta información incluir en el evento. Un dilema común es entre publicar solo un identificador (ej. orderId) obligando al consumidor a llamar de vuelta al servicio origen para obtener detalles, o publicar todos los datos necesarios (embebiendo por ejemplo todo el resumen de la orden).
Incluir datos completos (como en el ejemplo arriba) puede reducir el acoplamiento temporal y la necesidad de más llamadas , al permitir que el consumidor haga su trabajo sin consultas adicionales.
De hecho, es usual que eventos como OrderCreated
contengan la orden completa, con sus ítems, pago, envío, etc., para que otros servicios (inventario, facturación) no tengan que hacer fetch al servicio de órdenes .
Sin embargo, esto aumenta el tamaño del mensaje y duplica información en múltiples lugares (consumidores almacenarán parte de esos datos).
La decisión debe equilibrar performance y decoupling.
En sistemas de ejemplo, a menudo OrderPlaced
incluye detalles del pedido (items, montos) y, por su parte, PaymentProcessed
podría incluir resultado del pago, ID de transacción, etc., suficiente para que Order Service actualice estado sin llamar al servicio de pago.
Esquema y validación
Así como con APIs, se deben definir esquemas formales para los eventos. Se pueden usar JSON Schema, AVRO u otros, idealmente registrados en un Catálogo de Esquemas central.
Por ejemplo, mantener un repositorio (o un schema registry si se usa Kafka) donde cada tipo de evento tiene sus definiciones versionadas.
Esto impone gobernanza: los productores no publican campos fuera del esquema, y los consumidores asumen solo lo definido.
Integrar la validación de esquema en CI/CD (tests que validan que los eventos emitidos por un servicio cumplen su contrato) ayuda a evitar incompatibilidades en producción.
Metadata de trazabilidad
Además del mencionado correlationId, es útil incluir un eventId único para cada evento (UUID), e incluso un campo causationId que indique el ID de evento que originó éste (en saga coreografiada, se puede encadenar la causalidad de eventos).
Esto permite rastrear en un sistema de log cada cadena de eventos y reconstruir el grafo de qué eventos llevaron a cuáles, mejorando la observabilidad .
Los eventos también pueden tener un campo de origen (qué servicio lo emitió) y quizá una partición o clave para ruteo en el broker (por ejemplo orderId como key para que todos eventos de una orden vayan al mismo partition en Kafka, garantizando orden).
Contratos orientados a dominio
Los eventos deben nombrarse y definirse desde la perspectiva de qué sucedió en el negocio, no detalles técnicos. Ejemplo: OrderPlaced
(orden realizada) es preferible a OrderService.OrderCreated
; PaymentProcessed
en vez de PaymentService.Response
.
Esto favorece que incluso si la implementación detrás cambia (otro servicio maneja pagos, etc.), el evento sigue representando la misma intención de negocio.
Cada evento de dominio debe tener una especificación clara de cuándo se emite y qué significa (ej. OrderConfirmed
significa que todos los pasos de checkout se completaron con éxito y la orden está lista para cumplir).
Conclusión
En definitiva, los contratos de APIs y eventos son los puntos de integración en una arquitectura composable.
Una práctica recomendable es tratarlos casi como “interfaces públicas” versionadas: documentados, con control de versiones y con compatibilidad hacia atrás en mente (nunca remover o cambiar significado de un campo existente sin subir versión, etc.).
La trazabilidad y observabilidad deben tejerse en estos contratos – por ejemplo, incluyendo los IDs de correlación en todos los mensajes, campos de auditoría (timestamps, actores) – de forma que el flujo distribuido no se vuelva una caja negra.
Un buen contrato, junto con pruebas de contrato (siguiente sección), asegura que equipos distintos puedan evolucionar sus servicios sin romper la delicada coreografía/orquestación que compone un sistema moderno.