Satélite Intermedio–Avanzado Contratos de API

Consumer-Driven Contract Testing: cuándo vale la pena y cuándo alcanza un OpenAPI gobernado

Guía de decisión sobre CDCT con Pact: qué verifica realmente, sus costos de gobernanza y cuándo un OpenAPI versionado bien gobernado es suficiente.

Subtítulo: Una decisión de arquitectura de calidad, no una moda de herramienta.

Nexo Finanzas es ficticio. Los ejemplos son ilustrativos; verificá la sintaxis contra la versión vigente de cada herramienta.

El problema

El equipo de plataforma de Nexo propone “hacer contract testing con Pact en todos los servicios este trimestre”. Suena responsable. Pero tres semanas después, dos equipos discuten quién es dueño de un ProviderState, un pacto roto bloquea un release por un cambio aditivo, y nadie sabe si un consumidor del data-lake —que también lee la Transfer API— está cubierto. El problema no era Pact: era adoptarlo sin decidir cuándo aporta más de lo que cuesta.

Este artículo es una guía de decisión. Asume que ya distinguís contrato, especificación e integración (ver el pilar: Contratos de API en una plataforma de transferencias).

Qué verifica —y qué no— un contrato consumer-driven

En consumer-driven contract testing (CDCT), el consumidor declara las interacciones que necesita; esa expectativa se publica como pacto; el proveedor la verifica contra su implementación real usando estados controlados (Pact Docs).

  • Verifica: que el proveedor responde con la forma y semántica que ese consumidor espera, para las interacciones que el consumidor declaró.
  • No verifica: reglas de negocio no expresadas en el pacto, concurrencia real, integridad contable, autorización de todos los roles, UX, ni la existencia de consumidores que no publicaron pacto (por definición, los desconocidos quedan fuera).

Flujo (mismo que el diagrama de verificación del pilar): consumer → registro/broker → provider verification → estado de compatibilidad. La pieza que hace CDCT gobernable es un broker que responde “¿puedo desplegar esta versión sin romper a un consumidor?” — en Pact, can-i-deploy (Pact Docs).

Ejemplo — verificación del proveedor (ilustrativo, desarmado)

@ProviderState("source account has sufficient synthetic balance")
void sourceAccountReady() {
  testData.reset();
  testData.createAccount("acct-source", "USD", "100.00");
  testData.createAccount("acct-target", "USD", "0.00");
}

@ContractVerification
void createTransfer(ProviderClient client) {
  Response response = client.post(
      "/v1/transfers",
      headers("Idempotency-Key", "test-key-001"),
      body("{...synthetic payload...}"));

  assertThat(response.status()).isEqualTo(201);
  assertThat(response.json("$.status")).isEqualTo("PENDING");
}

Bloque por bloque:

  • @ProviderState(...): define un estado controlado. El proveedor real debe poder ponerse en ese estado con datos sintéticos — este es el corazón (y el costo) de CDCT.
  • testData.reset(): aísla la verificación; sin reset, un pacto contamina a otro.
  • createTransfer: ejercita la implementación real del proveedor, no un mock.
  • Los assertThat: verifican una expectativa contractual bajo un estado controlado.

Qué NO valida este ejemplo (obligatorio decirlo): concurrencia real, integridad contable, autorización de todos los roles, ni la UX. Qué deben conservar los artefactos: versión del contrato, commit, imagen/entorno, logs sanitizados y resultado.

Cuándo CDCT vale la pena

CDCT rinde cuando se cumplen varias de estas condiciones (decisión, no regla universal):

  • Muchos consumidores conocidos e internos, con equipos que pueden mantener sus pactos.
  • Despliegues independientes frecuentes donde querés un gate “¿rompo a alguien?” antes de integrar.
  • Ownership claro de estados del proveedor y de los datos de prueba.
  • El costo de una falla de integración es alto (aquí: dinero) y el ciclo de e2e es lento.

Cuándo NO conviene (y qué hacer en su lugar)

  • API pública o con consumidores desconocidos. CDCT sólo cubre a quien publica pacto. Aquí un OpenAPI versionado y gobernado + validación de compatibilidad de esquema (linter en el PR) suele ser más honesto: no promete cubrir a quien no conocés.
  • Un solo consumidor y proveedor en el mismo equipo/repo. El overhead de broker y estados supera el beneficio; un test de integración directo alcanza.
  • Contrato inestable / producto en exploración temprana. Vas a pelear con pactos que cambian cada día.
  • Sin capacidad de poner al proveedor en estados controlados. Sin ProviderState reproducible, CDCT degenera en respuestas hardcodeadas — el anti-patrón que sigue.

Decisión práctica para Nexo: empezar con OpenAPI gobernado para POST /v1/transfers (contrato de proveedor versionado, linter de compatibilidad en CI). Introducir Pact sólo cuando haya ≥2 consumidores internos que desplieguen por separado y puedan mantener sus pactos. Es una decisión de diseño, no una verdad universal.

Comparación (con trade-offs, sin declarar “el mejor”)

CriterioOpenAPI gobernado (provider-driven)CDCT (Pact)
Cubre consumidores desconocidosParcial (spec pública)No (sólo quien publica pacto)
Detecta “rompo a un consumidor concreto”No directamente (can-i-deploy)
Costo de gobernanzaBajo–medioMedio–alto (broker, estados, ownership)
Requiere estados del proveedorNo
Mejor cuando…1 owner, API abierta, arranqueMuchos consumers internos, deploys independientes

Ninguna es “la mejor”: dependen del contexto (cantidad y tipo de consumidores, madurez del contrato, costo de falla).

Anti-patrones

Anti-patrónSíntomaRaízImpactoAlternativa
Proveedor “pasa” con respuestas hardcodeadasVerificación verde sin ejecutar lógica realNo poder crear ProviderState reproducibleContrato que miente; falsa confianzaEstados con datos sintéticos sobre la implementación real
Pact en todo, sin decidir dónde aportaOverhead, pactos frágiles, releases bloqueados por cambios aditivosAdopción por modaFricción y desconfianza en la suiteDecidir por frontera; OpenAPI gobernado donde alcanza
Ignorar consumidores desconocidos”Tenemos 100% de pactos”Confundir conocidos con todosRompés a quien no medíasDeclarar el límite; sumar monitoreo posdespliegue

Conexión con Nexo Finanzas

  1. nexo-transfer-api: OpenAPI versionado + linter de compatibilidad en el PR como primer gate (sin broker).
  2. nexo-web-banking-e2e / nexo-wallet-mobile: si adoptan Pact, consumen sólo la API documentada y declaran interacciones de journeys de alto riesgo.
  3. nexo-quality-platform: si hay broker, ejecutar can-i-deploy antes de integrar; guardar reportes sanitizados.
  4. ADR sugerido: ADR-000X: contract testing provider-driven ahora, CDCT cuando ≥2 consumers internos.

Qué aprendimos / próximos pasos

  • CDCT es una decisión de costo/beneficio, no un default.
  • El broker y los estados del proveedor son el core del valor —y del costo—.
  • Declará siempre el límite: los consumidores desconocidos no están cubiertos.

Enlaces internos: el marco general está en el pilar; la semántica que tus pactos deben expresar (idempotencia, dinero, estados) está en Idempotencia, dinero y eventos; cómo evolucionar sin romper pactos, en Evolución de contratos y cambios rompientes.

Checklist final

  • Conté cuántos consumidores conocidos/desconocidos tiene la frontera.
  • Verifiqué que puedo poner al proveedor en estados controlados y reproducibles.
  • Decidí, por frontera, entre OpenAPI gobernado y CDCT — con un ADR.
  • Ningún proveedor “pasa” con respuestas hardcodeadas.
  • Los artefactos guardan versión, commit, entorno, logs sanitizados y resultado.
  • Documenté cuándo evitar CDCT.

Relacionados en el blog

esc
↑↓ navegar abrir