Contract First o Morir en el Intento: La Única Forma Sensata de Diseñar APIs
Necesito desahogarme. Algo que ha estado pudriéndose en mi alma desde la primera vez que me uní a un proyecto a mitad de desarrollo y hice la pregunta fatídica: “¿Dónde está la documentación de la API?”
La respuesta, invariablemente, era una de las siguientes:
- “Revisa la colección de Postman.” (Traducción: un cementerio de 200 peticiones, la mitad desactualizadas, con nombres como
GET users FINAL v2 (copia)) - “Simplemente mira el código.” (Traducción: haz ingeniería inversa de nuestro espagueti y buena suerte)
- “La documentaremos más tarde.” (Traducción: nunca la documentaremos)

Esto, amigos míos, es la consecuencia del diseño de API Code First. Y estoy aquí para decirles que es una enfermedad. Una enfermedad contagiosa, que mata proyectos y se propaga por los equipos como un virus en una oficina mal ventilada.
La Escena del Crimen: Code First
Permítanme pintarles un cuadro. Eres un desarrollador backend. Recibes un ticket: “Crear endpoint para obtener transacciones de usuario”. Abres tu IDE, creas un Controller, conectas un Service, devuelves algún DTO que inventaste en el momento, y haces push a develop. Hecho. A producción.
Mientras tanto, el desarrollador frontend está esperando. Te pregunta: “¿Cómo es la respuesta?” Dices: “Solo llámalo y mira”. Lo llaman. Ven un campo llamado txn_dt. Preguntan: “¿Es un timestamp? ¿Una cadena? ¿Qué zona horaria?” Te encoges de hombros. “Es lo que sea que LocalDateTime de Java serialice por defecto”.
Felicitaciones. Acabas de crear un desastre de comunicación envuelto en un sándwich de deuda técnica.
El desarrollador móvil, que se unió a la llamada cinco minutos tarde, ahora tiene que implementar el mismo endpoint. Mira la “documentación” (¿recuerdas la colección de Postman?) y encuentra una petición de hace tres sprints que devuelve una estructura completamente diferente porque alguien refactorizó el DTO y se olvidó de actualizar cualquier cosa.
Esto es Code First. Escribes el código, y el contrato es lo que sea que el código produzca. La especificación de la API es una ocurrencia tardía, un efecto secundario, un accidente.
La Salvación: Contract First
Ahora imagina un universo paralelo donde prevalece la cordura.
Antes de que nadie escriba una sola línea de Java, Go o cualquier veneno que prefieras, el equipo se sienta y define el contrato. Este contrato es un archivo .yaml o .json escrito en OpenAPI (antes Swagger). Especifica:
- Cada endpoint.
- Cada método HTTP.
- Cada parámetro de petición y su tipo.
- Cada cuerpo de respuesta y su estructura.
- Cada posible código de error y lo que significa.
Este archivo se convierte en la única fuente de verdad. El backend genera stubs de servidor a partir de él. El frontend genera SDKs de cliente a partir de él. El equipo móvil genera sus modelos a partir de él. Todos trabajan contra la misma especificación, sincronizados como una orquesta bien dirigida.
openapi: 3.0.3
info:
title: Transaction API
version: 1.0.0
paths:
/users/{userId}/transactions:
get:
summary: Fetch user transactions
parameters:
- name: userId
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: A list of transactions
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Transaction'
'404':
description: User not found
components:
schemas:
Transaction:
type: object
properties:
id:
type: string
format: uuid
amount:
type: integer
description: Amount in cents
currency:
type: string
example: "EUR"
timestamp:
type: string
format: date-time
required:
- id
- amount
- currency
- timestamp
Mira eso. Hermoso. Explícito. Sin ambigüedad. El timestamp es un date-time. El amount está en centavos (sin pesadillas de punto flotante). El userId es un UUID, no “cualquier cadena que se te ocurra enviar”.
Si el backend se desvía de este contrato, el código generado no compilará. Si el frontend espera un campo diferente, el cliente generado les gritará. El contrato impone disciplina.
“Pero Escribir YAML es Aburrido”
Sí. Lo es. ¿Sabes qué más es aburrido? Pasar cuatro horas en una videollamada depurando por qué la aplicación Android se cierra porque alguien en el backend decidió renombrar user_id a userId sin decirle a nadie.
¿Sabes qué es aburrido? Reescribir pruebas de integración porque la estructura de la respuesta cambió y nadie actualizó los mocks.
¿Sabes qué es aburrido hasta aplastar el alma? Ser el pobre diablo que se une a un proyecto de dos años y tiene que entender 150 endpoints leyendo archivos Controller esparcidos en 47 paquetes sin documentación alguna.
Escribir el contrato de antemano es una inversión. Es como cepillarse los dientes: tedioso en el momento, pero previene un tratamiento de conducto después.
Las Herramientas: Edición Java
Ya que prometí ejemplos de código, déjame mostrarte cómo funciona esto en el ecosistema Spring. Usamos un plugin de Maven llamado openapi-generator-maven-plugin. Lo apuntas a tu contrato y genera las interfaces por ti.
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>7.2.0</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/api/openapi.yaml</inputSpec>
<generatorName>spring</generatorName>
<apiPackage>me.doismiu.api.generated</apiPackage>
<modelPackage>me.doismiu.api.generated.model</modelPackage>
<configOptions>
<interfaceOnly>true</interfaceOnly>
<useSpringBoot3>true</useSpringBoot3>
<useTags>true</useTags>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
Ejecuta mvn compile, y de repente tienes una interfaz Java que se ve así:
@Generated(value = "org.openapitools.codegen.languages.SpringCodegen")
@Tag(name = "Transactions")
public interface TransactionsApi {
@Operation(summary = "Fetch user transactions")
@GetMapping(value = "/users/{userId}/transactions", produces = "application/json")
ResponseEntity<List<Transaction>> getUserTransactions(
@PathVariable("userId") UUID userId
);
}
Tu Controller simplemente implementa esta interfaz:
@RestController
public class TransactionsController implements TransactionsApi {
private final TransactionService service;
public TransactionsController(TransactionService service) {
this.service = service;
}
@Override
public ResponseEntity<List<Transaction>> getUserTransactions(UUID userId) {
return ResponseEntity.ok(service.findByUser(userId));
}
}
Si intentas cambiar el tipo de retorno a algo que no coincida con el contrato, el compilador te dará una bofetada. Si intentas agregar un parámetro que no está en la especificación, el compilador te dará una bofetada más fuerte. El contrato es la ley, y el código debe obedecer.
El Problema Cultural
Aquí está la verdad incómoda: Contract First no es un desafío técnico. Es uno cultural.
Los desarrolladores se resisten porque se siente como “trabajo extra”. Los gerentes de producto se resisten porque “ralentiza la entrega”. Todos quieren moverse rápido y romper cosas, excepto que las cosas que rompen son la cordura de sus colegas y la estabilidad del sistema.
Contract First te obliga a pensar antes de codificar. Te obliga a tener conversaciones sobre tipos de datos, casos límite y manejo de errores antes de que se conviertan en incidentes de producción. Impone disciplina en una industria que adora “moverse rápido”.
Y esa es exactamente la razón por la que la mayoría de los equipos se niegan a adoptarlo. La disciplina es difícil. El caos es fácil. Hasta que no lo es.
Cuándo NO Usar Contract First
Seré justo. Hay escenarios donde Code First tiene sentido:
- Prototipado: Estás explorando, aún no sabes cómo se verá la API y necesitas iterar rápido. Bien. Pero en el momento en que va a producción, escribes ese contrato.
- Microservicios internos con gRPC: Si ya estás usando Protobuf (como me quejé anteriormente), el archivo
.protoes tu contrato. Ya estás haciendo Contract First sin llamarlo así. - Proyectos en solitario: Si eres el único desarrollador, frontend y backend, puedes mantener el contrato en tu cabeza. Pero en el momento en que alguien más se una, escríbelo.
Para todo lo demás, especialmente equipos de más de una persona, especialmente proyectos que se espera que vivan más que un hackathon, Contract First no es negociable.
Conclusión
He visto proyectos morir porque nadie sabía qué se suponía que debía hacer la API. He visto desarrolladores renunciar porque pasaban más tiempo haciendo ingeniería inversa de endpoints no documentados que construyendo características. He visto caídas de producción causadas por “cambios menores” que rompieron todos los clientes porque no había un contrato contra el cual validar.
Contract First no es una metodología. Es una estrategia de supervivencia. Es la diferencia entre construir sobre terreno sólido y construir sobre arenas movedizas con los ojos vendados.
Escribe el contrato. Genera el código. Duerme por la noche.
O no. Sigue viviendo en el caos. Sigue depurando colecciones de Postman a las 2 AM. Sigue explicándole al equipo móvil por qué el campo ahora se llama transactionDate en lugar de txn_dt por tercera vez este mes.
Tú decides.
