Diseño Contract-First

Standards Jan 9, 2026 YAML
contract-first api-design design-methodology openapi specification

Definición

El diseño contract-first es un enfoque de desarrollo de APIs donde comienzas definiendo el contrato completo de la API - endpoints, esquemas de petición/respuesta, autenticación, manejo de errores - en una especificación formal (usualmente OpenAPI) antes de escribir cualquier código de implementación. Esto invierte el enfoque tradicional code-first donde construyes la API primero y la documentas después.

La filosofía detrás de contract-first es que el diseño de API es un problema de comunicación, no un problema de codificación. Al crear el contrato primero, fuerzas discusiones iniciales sobre nombres, modelos de datos, manejo de errores y casos límite. Las partes interesadas (desarrolladores frontend, equipos móviles, partners) revisan el contrato y dan feedback antes de que comience la implementación. Esto detecta problemas de diseño temprano cuando son baratos de arreglar.

Contract-first habilita verdadero desarrollo paralelo. Los ingenieros de backend implementan el contrato mientras los ingenieros de frontend construyen contra servidores mock generados desde el mismo contrato. La integración ocurre más rápido porque ambos lados están codificando contra una especificación acordada. Las herramientas validan que las implementaciones coincidan con el contrato, previniendo divergencia entre documentación y realidad.

Ejemplo

Equipo de Microservicios: Un equipo construyendo un nuevo microservicio de pagos comienza con una especificación OpenAPI definiendo todos los endpoints, esquemas y códigos de error. Gerentes de producto, ingenieros frontend e ingenieros backend revisan colaborativamente la especificación en sesiones de diseño. Identifican problemas como paginación faltante en endpoints de listado y respuestas de error poco claras. Después de aprobación, backend implementa la especificación mientras frontend genera tipos TypeScript desde ella y construye la UI contra servidores mock.

Lanzamiento de API Pública: Stripe diseña nuevas características de API como especificaciones OpenAPI primero. Comparten especificaciones con socios de diseño (clientes selectos) que dan feedback sobre usabilidad, completitud y casos límite antes de que Stripe escriba código de implementación. Este feedback temprano previene cambios costosos después del lanzamiento.

Aplicación Móvil + Backend: Una startup construyendo aplicaciones iOS, Android y web comienza cada sprint definiendo cambios de API como especificaciones OpenAPI. Los tres equipos cliente inmediatamente generan SDKs desde la especificación y comienzan a construir características usando servidores mock. Backend implementa la especificación en paralelo. Cuando backend termina, los clientes intercambian servidores mock por endpoints reales con cero cambios de código.

Integración Empresarial: Una gran empresa exponiendo APIs internas a partners comienza con contract-first. Equipos legales, de seguridad e ingeniería revisan la especificación OpenAPI juntos, asegurando cumplimiento, manejo apropiado de errores y seguridad antes de la implementación. La especificación se convierte en el acuerdo formal con partners.

Prevención de Cambios Incompatibles: Un equipo quiere agregar un campo requerido a un endpoint existente. Durante la revisión de la especificación (antes de codificar), las herramientas de diff de contratos marcan esto como un cambio incompatible. El equipo pivotea a hacerlo opcional con un valor por defecto, preservando compatibilidad hacia atrás.

Analogía

Planos Arquitectónicos: No comienzas a construir una casa vertiendo concreto aleatoriamente, luego dibujando planos después para documentar lo que construiste. Los arquitectos crean planos detallados primero, obtienen aprobación de todas las partes interesadas (cliente, inspectores de la ciudad, contratistas), luego comienza la construcción. Contract-first es lo mismo - diseña el plano (especificación de API), obtén aprobación, luego implementa.

Guión de Película: Los cineastas escriben el guión antes de filmar. Actores, directores, productores lo revisan, sugieren cambios, estiman presupuesto. Solo después de que el guión es aprobado comienza la filmación. Code-first sería como improvisar toda la película durante la filmación, luego escribir un guión para documentar lo que pasó.

Contratos Legales: Cuando dos empresas forman una asociación, los abogados redactan un contrato primero, ambos lados negocian términos, y solo después de firmar comienzan a trabajar juntos. Nadie construye productos primero y luego escribe un contrato describiendo lo que construyeron. El contract-first de APIs sigue el mismo principio - acuerda términos antes de invertir en implementación.

Ejemplo de Código

Paso 1: Definir Contrato (Especificación OpenAPI)


# contract/api-spec.yaml - Creado ANTES de la implementación
openapi: 3.1.0
info:
  title: Task Management API
  version: 1.0.0
  description: API para gestionar tareas en un proyecto

paths:
  /tasks:
    get:
      summary: Listar todas las tareas
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [open, in_progress, completed]
        - name: assignee
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Lista de tareas
          content:
            application/json:
              schema:
                type: object
                properties:
                  tasks:
                    type: array
                    items:
                      $ref: '#/components/schemas/Task'
                  totalCount:
                    type: integer

    post:
      summary: Crear una nueva tarea
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTaskRequest'
      responses:
        '201':
          description: Tarea creada
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Task'
        '400':
          description: Petición inválida
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

components:
  schemas:
    Task:
      type: object
      required: [id, title, status, createdAt]
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
          maxLength: 200
        description:
          type: string
        status:
          type: string
          enum: [open, in_progress, completed]
        assignee:
          type: string
          format: email
        createdAt:
          type: string
          format: date-time

    CreateTaskRequest:
      type: object
      required: [title]
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
        description:
          type: string
        assignee:
          type: string
          format: email

    Error:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
        message:
          type: string

Paso 2: Generar Servidor Mock (se ejecuta inmediatamente, antes de que exista el backend)


# Usando Prism para crear servidor mock desde el contrato
npx @stoplight/prism-cli mock contract/api-spec.yaml

# El servidor mock se ejecuta en http://localhost:4010
# Frontend puede desarrollar contra él inmediatamente

Paso 3: Desarrollo Frontend (en paralelo con backend)


// Generar tipos TypeScript desde la especificación OpenAPI
// Usando openapi-typescript
import type { paths } from './generated/api-types';

type TaskListResponse = paths['/tasks']['get']['responses']['200']['content']['application/json'];
type CreateTaskRequest = paths['/tasks']['post']['requestBody']['content']['application/json'];

// Usar tipos generados para llamadas a API type-safe
async function fetchTasks(status?: string): Promise<TaskListResponse> {
  const response = await fetch(`http://localhost:4010/tasks?status=${status || ''}`);
  return response.json(); // TypeScript conoce la forma exacta
}

async function createTask(data: CreateTaskRequest) {
  const response = await fetch('http://localhost:4010/tasks', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  });
  return response.json();
}

// Usando las funciones tipadas
const tasks = await fetchTasks('open'); // TypeScript conoce la estructura de tasks
const newTask = await createTask({
  title: "Implementar backend",
  assignee: "[email protected]"
}); // TypeScript valida que este objeto coincida con el esquema

Paso 4: Implementación Backend (valida contra el contrato)


// Backend implementa el contrato
const express = require('express');
const { OpenApiValidator } = require('express-openapi-validator');

const app = express();

// Validar automáticamente peticiones/respuestas contra el contrato
app.use(OpenApiValidator.middleware({
  apiSpec: './contract/api-spec.yaml',
  validateRequests: true,
  validateResponses: true
}));

app.get('/tasks', (req, res) => {
  const { status, assignee } = req.query;
  // Implementación...

  const tasks = [
    { id: '123', title: 'Tarea 1', status: 'open', createdAt: '2024-01-01T10:00:00Z' }
  ];

  // El validador asegura que la respuesta coincida con el esquema del contrato
  res.json({ tasks, totalCount: tasks.length });
});

app.post('/tasks', (req, res) => {
  // El validador ya verificó que req.body coincida con el esquema CreateTaskRequest
  const newTask = {
    id: generateUUID(),
    ...req.body,
    status: 'open',
    createdAt: new Date().toISOString()
  };

  // El validador asegura que la respuesta coincida con el esquema Task
  res.status(201).json(newTask);
});

// Si la respuesta no coincide con el esquema, el validador devuelve 500 y registra error
app.listen(3000);

Diagrama

graph TB
    subgraph "Flujo Contract-First"
        START[Definir Contrato
Especificación OpenAPI] START --> REVIEW[Revisión de Interesados
Frontend, Backend, Producto] REVIEW --> ITERATE{¿Cambios
Necesarios?} ITERATE -->|Sí| START ITERATE -->|No| APPROVE[Contrato Aprobado] APPROVE --> PARALLEL[Desarrollo Paralelo] subgraph "Pista Frontend" PARALLEL --> GEN_SDK[Generar SDK
Tipos TypeScript] GEN_SDK --> MOCK[Servidor Mock
Desde Contrato] MOCK --> FE_DEV[Desarrollo Frontend] end subgraph "Pista Backend" PARALLEL --> BE_DEV[Implementación Backend] BE_DEV --> CONTRACT_TEST[Pruebas de Contrato
Validar Coincidencia] end FE_DEV --> INTEGRATION CONTRACT_TEST --> INTEGRATION[Integración
Intercambiar Mock por API Real] end style START fill:#90EE90 style APPROVE fill:#FFD700 style INTEGRATION fill:#87CEEB

Buenas Prácticas

  1. Involucra a todas las partes interesadas temprano - Frontend, backend, producto, seguridad revisan la especificación juntos
  2. Usa herramientas de linting de especificaciones - Ejecuta Spectral o similar para hacer cumplir estándares de diseño en especificaciones
  3. Genera todo desde el contrato - SDKs, servidores mock, pruebas, documentación, todo viene de la especificación
  4. Versiona el contrato - Usa versionado semántico, rastrea cambios incompatibles vs. no incompatibles
  5. Automatiza pruebas de contrato - CI/CD valida que la implementación coincida con el contrato en cada commit
  6. Diseña para evolución - Usa patrones extensibles (additionalProperties, campos opcionales) para crecimiento futuro
  7. Revisa antes de implementación - Detecta fallas de diseño en revisión de especificación, no durante codificación
  8. Servidor mock en desarrollo - Frontend usa mocks basados en contrato hasta que backend esté listo

Errores Comunes

Especificación como idea tardía: Crear una especificación “contract-first” después de que la implementación está hecha. Esto frustra el propósito - pierdes los beneficios de diseño y desarrollo paralelo.

Saltar revisión de interesados: Backend escribe la especificación solo, no involucra a frontend o producto. Descubren problemas de usabilidad solo después de la implementación.

No hacer cumplir el contrato: Crear una especificación pero no validar la implementación contra ella. Contrato y código divergen con el tiempo.

Parálisis de especificación perfecta: Pasar semanas perfeccionando la especificación antes de permitir implementación. Itera rápidamente, llega a código funcional más rápido.

Ignorar SDKs generados: Escribir la especificación pero codificar clientes a mano de todos modos. Esto desperdicia el beneficio principal de contract-first.

Sin pruebas de contrato: Confiar que la implementación coincida con la especificación sin validación automatizada. Las pruebas manuales pierden violaciones sutiles de esquema.

Contrato de una sola vez: Crear la especificación para v1, luego abandonarla para cambios futuros. El contrato debe evolucionar con la API.

Standards & RFCs

Standards & RFCs
1)- [OpenAPI 3](https://reference.apios.info/es/terms/openapi-3/).1.0 Specification
2)- JSON [Schema](https://reference.apios.info/es/terms/schema/) Draft 2020-12
3)- Spectral (OpenAPI linting)
4)- Dredd (contract testing)

Términos Relacionados