Buenas prácticas básicas para crear APIs en Java (sin convertirte en arquitecto senior)
Introducción
Crear una API que “responde” es fácil. Crear una API que no te explote dentro de seis meses es otra historia. La mayoría de APIs se rompen por lo mismo: contrato confuso, errores inconsistentes, seguridad improvisada, validación débil y cero observabilidad.
Esta guía está escrita para equipos reales: decisiones pequeñas, aplicables y sostenibles. Sin fanatismo REST, pero con semántica. Sin humo “arquitecto senior”, pero con criterio.
Mini guía en 10 minutos: el 80/20 que salva APIs
- Contrato primero: define recursos, respuestas y errores antes de implementar.
- Errores consistentes: estructura única + códigos HTTP correctos + un errorCode.
- Seguridad mínima seria: auth por tokens, mínimo privilegio, CORS restringido, rate limiting.
- Idempotencia en operaciones de riesgo (pagos, altas críticas, integraciones).
- Observabilidad: correlation id, logs estructurados, métricas de latencia y 4xx/5xx.
0) La regla base: tu API es un contrato
Una API no es “tu backend”. Es un contrato que otros consumirán (front, integraciones, partners, futuros equipos). Si el contrato es ambiguo, pagarás deuda técnica multiplicada por cada consumidor.
- Define recursos (sustantivos) y evita endpoints tipo RPC disfrazado.
- Decide versionado, paginación, filtros, errores, idempotencia y límites.
- Trata el contrato como artefacto: se versiona, se revisa en PR y se valida en CI.
OpenAPI como contrato vivo
OpenAPI no es “para que salga un Swagger bonito”. Es el contrato consumible por clientes, base de tests de contrato y base para generar SDKs o mocks en desarrollo.
1) Diseñar endpoints con semántica (REST con criterio)
No hace falta ser purista. Pero sí coherente. Una API coherente reduce bugs, fricción y cambios rompedores.
| Objetivo | Evítalo | Mejor | Por qué |
|---|---|---|---|
| Crear recurso | POST /crearUsuario |
POST /usuarios |
Los recursos son sustantivos |
| Actualizar parcial | POST /usuarios/123/update |
PATCH /usuarios/123 |
Semántica HTTP = menos inventos |
| Acción (evento) | POST /usuarios/123/activar |
POST /usuarios/123/activaciones |
Modela acciones como recursos/eventos |
| Listado | GET /usuarios/todos |
GET /usuarios?page=0&size=20 |
Paginación controlada |
Mini caso de diseño: API de pedidos
Ejemplo de tabla de endpoints típica (e-commerce / ERP ligero). No es “la única forma”, pero es coherente:
| Operación | Método | Endpoint | Notas |
|---|---|---|---|
| Crear pedido | POST | /api/v1/pedidos |
Devuelve 201 + Location |
| Listar pedidos | GET | /api/v1/pedidos?page&size&sort |
Límite size + whitelist sort |
| Ver un pedido | GET | /api/v1/pedidos/{id} |
404 si no existe |
| Actualizar estado (evento) | POST | /api/v1/pedidos/{id}/transiciones |
Evita “PATCH estado” si hay reglas complejas |
| Cancelar pedido | POST | /api/v1/pedidos/{id}/cancelaciones |
Acción = recurso/evento |
| Devolver pedido | POST | /api/v1/pedidos/{id}/devoluciones |
Reglas de negocio claras |
2) Controladores finos, casos de uso claros
Una API mantenible separa tres cosas:
- HTTP: parseo, validación de forma, status codes, headers.
- Casos de uso: decisiones de negocio, transacciones, idempotencia.
- Infra: persistencia, colas, proveedores externos.
Si mezclas negocio con HTTP, terminas con controladores “script”. Si mezclas negocio con repositorios, terminas con dominio secuestrado por la base de datos.
3) Validación: forma en el borde, reglas en el core
La validación funciona por capas:
- Forma (request): tipos, obligatorios, formato, rangos.
- Negocio (core): unicidad, transiciones válidas, límites por usuario/tenant, invariantes.
Señal de API frágil: “validación de negocio” distribuida en controladores o UI. En el mundo real, el backend es la fuente de verdad.
4) Errores consistentes (si falla mal, tu API está rota)
El manejo de errores es parte del contrato. Si cambias estructura de error por endpoint, obligas al cliente a programar “adivinación”.
Formato recomendado de error (estable y parsable)
Usa una estructura uniforme con campos estables. Por ejemplo:
{
"errorCode": "VALIDATION_ERROR",
"message": "La petición contiene campos inválidos",
"details": [
{ "field": "email", "issue": "invalid_format" }
],
"traceId": "c3f5c8b2-..."
}
Mini guía: asignación de códigos HTTP con criterio
- 400: petición inválida (forma o semántica).
- 401: no autenticado.
- 403: autenticado, pero sin permiso.
- 404: recurso no existe (o no se revela por seguridad según caso).
- 409: conflicto (estado no permite operación, duplicidad).
- 422 (opcional): validación semántica si lo adoptas de forma consistente.
- 429: rate limiting.
- 5xx: fallo del servidor (sin filtrar detalles internos).
5) Idempotencia: el seguro contra reintentos y duplicados
En APIs reales, los clientes reintentan: timeouts, redes móviles, proxies, fallos temporales. Si tu API no está preparada, duplicarás pedidos, cobros o registros.
Cuándo aplicar idempotencia: operaciones con coste o impacto (pagos, altas, integraciones, envíos).
PSEUDOCÓDIGO (modelo mental)
si request incluye Idempotency-Key:
si existe registro(key):
devolver respuesta guardada
si no existe:
ejecutar caso de uso
guardar respuesta asociada a key (con TTL)
devolver respuesta
6) Paginación, filtros y ordenación: diseño anti-abuso
“GET /todo” es una bomba de tiempo. No escala y facilita abuso.
- size max (ej. 50/100) y siempre paginado.
- sort whitelist: no aceptes cualquier campo.
- filtros explícitos (y documentados).
Offset vs cursor (cuándo cambiar)
Offset es simple, pero se degrada en tablas grandes y puede ser inconsistente si hay cambios entre páginas. Cursor pagination es más estable para listas calientes y con mucho tráfico.
7) Seguridad: si no está en el diseño, luego cuesta el doble
Seguridad mínima pero seria:
- HTTPS siempre.
- Auth por tokens (y scopes/roles claros).
- Mínimo privilegio (no “admin por defecto”).
- CORS restringido a orígenes reales.
- No filtrar datos en errores, logs o respuestas.
Mini caso: multi-tenant
Si hay tenants, el error típico es confiar en “filtros por cliente” del front. Eso es fallo crítico. El tenant debe ser parte del modelo de autorización y de las consultas en backend.
8) Anti-abuso y estabilidad: rate limiting, timeouts y límites
Una API que no limita, tarde o temprano cae (por ataque o por cliente mal implementado).
- Rate limiting por token/cliente/IP según contexto.
- Timeouts y límites de payload.
- Protección de endpoints caros (búsquedas, exportaciones, agregaciones).
9) Observabilidad: operar una API es parte del producto
Si no puedes responder “qué ha pasado” en minutos, tu MTTR se dispara.
- TraceId / CorrelationId por request (propagado a logs).
- Logs estructurados y sin secretos.
- Métricas: latencia por endpoint, 4xx/5xx, throughput, saturación.
- Alertas: picos de 5xx, latencia alta, ratio de 401/403 anómalo.
10) Versionado y deprecación: cómo evolucionar sin romper clientes
Las APIs fallan cuando el equipo no tiene política de cambio. Dos principios:
- Compatibilidad hacia atrás por defecto.
- Deprecación comunicada (plazos y alternativa).
Estrategias típicas
- Versionado en path (ej.
/v1): simple de operar. - Versionado por header: más fino, pero más complejo para clientes.
Mini guía de deprecación
- Marca el endpoint/campo como deprecado en OpenAPI.
- Introduce alternativa compatible.
- Publica fecha de retirada (y respétala).
- Monitoriza uso antes de cortar.
11) Documentación: la que se mantiene sola
Documentación que vive fuera del pipeline se pudre. Lo defendible:
- Contrato OpenAPI versionado con el código.
- Ejemplos de requests/responses por endpoint.
- Errores documentados como parte del contrato.
12) Testing y CI/CD: tu API se valida en automático o no se valida
La robustez no se discute, se instrumenta:
- Unit tests para reglas.
- Integración para fronteras (DB/serialización).
- Contrato para integraciones entre equipos.
- Smoke post-deploy para garantizar vida.
Y todo eso corre en pipeline. Si no corre en CI, se convierte en “ritual” y se abandona.
Anti-patrones de APIs (alerta roja)
- Endpoints verbosos tipo RPC por todas partes.
- Errores distintos por endpoint (o 200 con “error” dentro).
- Paginar sin límites o permitir
size=100000. - Sin idempotencia en operaciones críticas.
- Logs con PII/secretos y sin correlation id.
- Sin política de deprecación: cambios rompedores por sorpresa.
Checklist final “nivel pro”
- ¿El contrato está definido y versionado (OpenAPI)?
- ¿Hay semántica HTTP coherente (métodos, recursos, status codes)?
- ¿Errores uniformes y parseables (con errorCode + traceId)?
- ¿Idempotencia donde hay riesgo de duplicados?
- ¿Paginación con límites + filtros/sort controlados?
- ¿Seguridad explícita (auth, permisos, CORS, rate limiting)?
- ¿Observabilidad real (logs+métricas+correlación)?
- ¿Versionado y deprecación definidos?
- ¿Testing y CI/CD sostienen el contrato?
Conclusión
Una API robusta no se consigue con “un framework” ni con “seguir REST”. Se consigue con decisiones repetibles: contrato claro, coherencia, seguridad, observabilidad y política de evolución.
No necesitas convertirte en arquitecto senior para hacerlo bien. Necesitas asumir la realidad: una API es un contrato y un sistema operativo en producción, no solo un controlador que responde.