← APIs REST: recursos, DTO, validación y errores

UD3 · Diseñar

Lo que debes recordar

El método

La secuencia crece otra vez. Estos son los pasos de la UD1 y la UD2 con los tres que añade el diseño:

Cómo se diseña un endpoint, versión completa
  1. ¿De qué cosa hablo? Un sustantivo, en plural si es colección. Nunca un verbo
  2. ¿Qué le hago? Eso elige el método HTTP, y con él si es idempotente
  3. ¿Qué datos necesito? Ruta si identifican, query si filtran, cuerpo si son contenido
  4. ¿Qué acepto y qué publico? Dos listas distintas, dos clases distintas
  5. ¿Qué tiene que cumplir lo que llega? Y qué digo cuando no lo cumple
  6. ¿Cómo puede terminar esto? Cada final, su código de estado
  7. ¿Cómo demuestro que sigue funcionando mañana?

El paso uno decide más que ningún otro: si eliges bien el sustantivo, los cinco métodos HTTP te dan la mitad de la API sin pensar. Si eliges un verbo, cada funcionalidad nueva te obligará a inventar una ruta.

La idea más importante

Tu API es una promesa, y las promesas se hacen a alguien que no está delante. Todo lo que obligue a preguntarte algo es un defecto de diseño, aunque funcione perfectamente.

De ahí sale la unidad entera. Por eso las rutas se nombran para que se puedan adivinar, por eso el modelo no se publica, por eso un 400 dice qué campo corregir, y por eso todos los errores tienen la misma forma: para que nadie tenga que escribirte.

Lo que cambia por dentro y lo que se promete fuera son dos cosas

Modelo, DTO y mapper existen por esa frase. El modelo cambia cuando cambia tu código; el contrato cambia cuando decides romper una promesa. Si son la misma clase, cualquier refactorización es una promesa rota sin querer.

Las decisiones que tienes que saber justificar

Decisión Lo que tienes que poder decir
La ruta lleva sustantivos La acción ya la expresa el método; con verbos, la API deja de ser adivinable
Ruta anidada o filtro Anidada si el recurso pertenece a otro; filtro si acota una colección propia
Una acción que no es CRUD Primero cambio de estado, luego recurso nuevo, y solo entonces acción explícita
Tres clases por recurso Lo que acepto, lo que manejo y lo que publico tienen motivos de cambio distintos
El DTO de entrada como lista blanca Lo que no existe en la clase no se puede asignar: previene el mass assignment
Tipos envoltorio en los DTO Un primitivo no distingue «ausente» de su valor por defecto
Un solo mapper Con dos sitios que traducen, el mismo recurso acaba publicándose de dos formas
Validar en el DTO, no en el modelo El DTO es la frontera con lo que no es de fiar; el modelo lo construye tu código
Mensajes que dicen qué se espera Un mensaje accionable se puede enseñar al usuario final sin traducirlo
Un único formato de error Quien consume escribe el tratamiento una vez y le sirve para toda la API
El 500 no cuenta nada El mensaje de una excepción puede filtrar rutas, consultas o datos ajenos
Nivel 2 y no hipermedia Coste alto y beneficio bajo con los clientes reales; es una decisión, no un olvido

Al terminar deberías poder responder

  1. ¿Por qué devolver JSON no convierte una API en REST?
  2. ¿Qué tres restricciones de REST cumples solo por usar HTTP bien?
  3. ¿Qué distingue el nivel 1 del nivel 2, y el 2 del 3?
  4. ¿Por qué una URL no debe contener un verbo?
  5. ¿Cuándo se usa ruta anidada y cuándo un filtro?
  6. Enumera las tres estrategias para exponer algo que no es CRUD, en orden.
  7. ¿Qué diferencia hay entre un recurso, un modelo y una representación?
  8. Nombra un campo que entra y no sale, y otro que sale y no entra.
  9. ¿Qué es el mass assignment y por qué un DTO de entrada lo previene?
  10. ¿Por qué un DTO de modificación usa Boolean y no boolean?
  11. ¿Qué fallo concreto produce tener la conversión repartida en dos sitios?
  12. ¿Por qué el mapper conoce el modelo y el DTO no?
  13. ¿Qué comprueba una prueba de ida y vuelta que no comprueba un código de estado?
  14. ¿Por qué la validación del navegador no protege nada?
  15. Las anotaciones de validación no hacen nada. ¿Qué compruebas primero?
  16. Diferencia entre @NotNull, @NotEmpty y @NotBlank, con un valor que las separe.
  17. ¿Por qué un validador propio debe dejar pasar el nulo?
  18. ¿Por qué un enum es mejor que validar un texto?
  19. ¿Qué reglas no puede comprobar Bean Validation, y por qué?
  20. ¿Por qué el manejador genérico no devuelve el mensaje de la excepción?
  21. ¿Cuándo se responde 409 en lugar de 400 o 404?
  22. ¿Qué tipo de fallo no detecta ninguna prueba de un endpoint aislado?

Si además puedes coger un dominio nuevo y escribir su contrato de recursos, sus DTO, sus reglas y su formato de error antes de programar nada, tienes lo que esta unidad quería darte.

El vocabulario de la unidad

Concepto Significa
REST Estilo de arquitectura, no un protocolo ni una librería ni un formato
Interfaz uniforme Que todos los recursos se identifiquen y manipulen con las mismas reglas
Niveles de madurez El mapa de 0 a 3 que sitúa cualquier API
Hipermedia El nivel 3: respuestas que incluyen los enlaces a lo que se puede hacer después
Recurso Una cosa del dominio a la que se le puede dar una dirección
Colección El conjunto de recursos de un tipo, en plural: /tareas
Ruta anidada La que expresa pertenencia: /proyectos/7/tareas
Representación El JSON concreto que viaja, con los campos que has decidido publicar
DTO Clase cuyo único trabajo es transportar datos entre dos sitios
DTO de entrada Lo que la API acepta. Funciona además como lista blanca
DTO de salida Lo que la API publica
Mass assignment Que el cliente asigne un campo que no debería poder tocar
Mapper El único sitio del proyecto que traduce entre DTO y modelo
Prueba de ida y vuelta Comprobar que un dato sobrevive al circuito completo de conversiones
Bean Validation El estándar de restricciones declarativas: @NotBlank, @Size, @Pattern
@Valid Lo que activa la comprobación de esas restricciones
Restricción propia Anotación que expresa una regla del dominio, con su validador
Regla de negocio La que necesita consultar datos para decidir. No es validación de formato
@RestControllerAdvice La clase que atiende las excepciones de todos los controladores
@ExceptionHandler El método que convierte un tipo de excepción en una respuesta
409 Conflict Petición válida que choca con el estado actual de los datos
Problem Details El estándar RFC 9457 para el cuerpo de un error, con su ProblemDetail en Spring

Comprobación final del producto

Comprobación final · con el proyecto delante

  • Ninguna ruta lleva verbos y todas las colecciones están en plural.
  • Ningún @RequestBody recibe una clase del modelo y ningún endpoint la devuelve.
  • Toda la traducción vive en el paquete mapper.
  • Un cuerpo vacío responde 400 nombrando los campos que faltan, en español.
  • Existe al menos una anotación de validación propia.
  • Los seis tipos de error tienen exactamente la misma forma.
  • Un 500 no revela nada del interior y deja la traza en la consola.
  • La auditoría de la sesión 9 y la de la sesión 14 están las dos escritas.

Resultados de la unidad

  • Distinguir una API HTTP cualquiera de una API orientada a recursos.
  • Nombrar recursos y URLs sin meter verbos ni acciones en la ruta.
  • Separar el modelo interno de lo que la API publica mediante DTO.
  • Validar la entrada antes de que llegue a la lógica y explicar qué falla.
  • Devolver errores con un formato único, predecible y útil para quien consume.

La siguiente unidad

Tres unidades, tres preguntas. Ya están las tres respondidas:

Lo que se ha preguntado hasta aquí
  1. UD1 · que responda
  2. UD2 · que responda correctamente
  3. UD3 · que esté bien diseñada

Existe una cuarta que no se ha abordado todavía:

¿Y esto quién lo mantiene?

Porque el contrato de tu API está bien, y detrás de él hay un controlador que guarda los datos, genera identificadores, busca, filtra y decide reglas de negocio. Todo junto, en una clase, sin una sola prueba que compruebe la lógica sin arrancar un servidor.

Lo que sigue mal Se arregla en
El controlador hace de todo UD4, con capas
Una regla no se puede reutilizar en dos endpoints UD4, con un service
Nada comprueba la lógica sin levantar la aplicación UD4, con los primeros tests
«El proyecto debe existir» sigue sin tener sitio UD4
Al reiniciar se pierde todo UD5, con PostgreSQL

Fíjate en la cuarta fila. Esa regla la encontraste tú en la sesión 13, viste por qué una anotación de validación no podía resolverla y la dejaste apuntada. Lleva dos sesiones esperando un sitio donde vivir, y en la unidad siguiente lo encontrará.

El trabajo de estas tres semanas demuestra aquí su utilidad: la UD4 no cambiará ninguna ruta, ni un DTO, ni un código de estado. Va a reorganizar lo que hay detrás sin tocar el contrato. Tu colección de pruebas, que ya cubre la API entera, será exactamente lo que demuestre que no has roto nada por el camino.

Ya deberías ser capaz de

  • Distinguir una API HTTP cualquiera de una API orientada a recursos.
  • Nombrar recursos y URLs sin meter verbos ni acciones en la ruta.
  • Separar el modelo interno de lo que la API publica mediante DTO.
  • Validar la entrada antes de que llegue a la lógica y explicar qué falla.
  • Devolver errores con un formato único, predecible y útil para quien consume.