Ir al contenido

Release 7: Lógica de negocio y controllers

Objetivo: introducir la capa Service con las reglas de negocio del dominio, completar el CRUD en los controllers, cubrir el Service con pruebas de JUnit que corren en el pipeline y permitir asociar entidades de distintos módulos. Al terminar, la aplicación valida las reglas de negocio, expone los cinco verbos HTTP para cada entidad, y el front funciona conectado al back real.


Paso 1: Construir la capa de lógica de negocio (Service)

Sección titulada «Paso 1: Construir la capa de lógica de negocio (Service)»

El Controller ya no inyecta el Repository directamente. A partir de R7, la cadena es Controller → Service → Repository. El Service es donde viven las reglas de negocio del dominio, y se construye en este orden:

1. Precisar las reglas de negocio. Antes de escribir cualquier método, identificar, para cada entidad principal del módulo, qué reglas del enunciado del proyecto se deben validar antes de guardar o actualizar un registro (un campo obligatorio, un valor que no puede repetirse, una relación que debe existir). Esto sale del enunciado del proyecto: no se delega a la IA. El taller de reglas de negocio es el punto de partida. Las reglas se registran en la wiki del equipo, con entidad, condición y excepción que lanzan (404 o 400).

2. Definir los dos DTOs por entidad. Cada entidad tiene dos DTOs, y el Controller los usa para saber qué devolver en cada endpoint:

DTO Contenido Cuándo se usa
<Entidad>DTO Atributos básicos de la entidad. Sin colecciones. Body del POST; respuesta de endpoints de escritura (POST, PUT)
<Entidad>DTOCompleto Extiende <Entidad>DTO. Agrega las asociaciones de cardinalidad múltiple como colecciones de DTOs del tipo relacionado. Respuesta de todos los GET

Por ejemplo, ArtistaDTOCompleto extiende ArtistaDTO y agrega la lista albumes (de AlbumDTO, no de AlbumDTOCompleto: la colección anidada nunca lleva a su vez otra colección anidada). Los GET /artistas y GET /artistas/{id} responden ArtistaDTOCompleto; el POST /artistas recibe y responde ArtistaDTO.

3. Escribir los métodos del Service, empezando por create. Por cada entidad principal, crear la clase <Entidad>Service, anotada @Service, con el repositorio inyectado por constructor (no @Autowired en campo) y los métodos findAll, findById, create, update, delete:

  • findById lanza EntityNotFoundException si el id no existe.
  • create valida las reglas de negocio del punto 1 antes de guardar. Se hace primero porque es el método que fija el patrón: dónde va la validación, qué excepción lanza, y en qué momento se llama al repository.
  • update reutiliza findById (para el 404), valida las mismas reglas que create y luego guarda.
  • delete reutiliza findById y luego borra; lo asociado lo decide el mapeo.

Dos excepciones personalizadas, EntityNotFoundException (404) e IllegalOperationException (400). El Controller las traduce a su código HTTP con un @RestControllerAdvice, y el cuerpo de la respuesta lleva el mensaje de la excepción.

IA permitida aquí con restricción: la estructura del Service (anotaciones, inyección por constructor, esqueleto de los métodos, las excepciones personalizadas) es delegable. El prompt debe incluir el nombre de la entidad, el repositorio que inyecta y los métodos requeridos. Las condiciones de negocio dentro de cada validación las escribe el estudiante porque salen del enunciado. El prompt correcto: “dado este método con esta firma, agrega el manejo de excepción para cuando [condición que el estudiante redacta]. Usa la clase IllegalOperationException que ya existe.”


Paso 2: Completar los Controllers y las pruebas de Postman

Sección titulada «Paso 2: Completar los Controllers y las pruebas de Postman»

Controller. Pasa a inyectar el Service (por constructor) en vez del Repository. Expone el CRUD completo, y convierte entre entidades JPA y DTOs: nunca retorna una entidad directamente.

Método Endpoint Entrada Respuesta exitosa
findAll GET /<entidades> ninguna 200 con List<EntidadDTOCompleto>
findById GET /<entidades>/{id} @PathVariable 200 con EntidadDTOCompleto; 404 si no existe
create POST /<entidades> @RequestBody EntidadDTO 201 con EntidadDTO
update PUT /<entidades>/{id} @PathVariable + @RequestBody EntidadDTO 200 con EntidadDTO; 404 si no existe
delete DELETE /<entidades>/{id} @PathVariable 204; 404 si no existe

La conversión entre entidad y DTO la hace ModelMapper, el mismo que agregaste al proyecto en el Release 6 (ver también Implementación del API REST): modelMapper.map(entidad, EntidadDTO.class) para escritura y modelMapper.map(entidad, EntidadDTOCompleto.class) para los GET. El Controller lo recibe por constructor, igual que el Service. No se escriben métodos de conversión a mano. El DTOCompleto del álbum incluye sus canciones y se pide con GET /albumes/{id}: el tutorial de DTOs y Controller muestra el caso.

IA permitida aquí: los métodos update y delete siguen el patrón de findById y create. Se puede pedir a Gemini que los genere dado el código de create ya funcionando como ejemplo. La configuración del ModelMapper y las llamadas a map(...) también son delegables: el prompt debe incluir la clase de entidad y los dos DTOs. El estudiante verifica que los códigos HTTP son los correctos y que los atributos de la entidad y de los DTOs se llaman igual, porque ModelMapper los empareja por nombre.

Postman. Las colecciones deben cubrir el CRUD completo:

Request Verifica
GET /<entidades> 200, respuesta es array de DTOCompleto
GET /<entidades>/{id} existente 200, objeto con las relaciones anidadas
GET /<entidades>/{id} inexistente 404
POST /<entidades> válido 201, objeto creado aparece en el GET siguiente
POST /<entidades> con regla violada 400
PUT /<entidades>/{id} 200 con datos actualizados
DELETE /<entidades>/{id} 204; GET /{id} siguiente da 404

Mínimo: 7 requests por entidad × 3 entidades = 21 requests por módulo. Los requests de asociación del Paso 4 se suman a estos.

IA permitida aquí: se puede pedir a Gemini que genere los scripts de test de Postman (pestaña Tests) para verificar códigos de respuesta y estructura del body. El prompt debe incluir la estructura real del DTO. El estudiante verifica que los pm.expect corresponden a los campos que realmente devuelve el back.


Cada Service tiene pruebas con JUnit, como las de Pruebas de lógica de negocio: @DataJpaTest, @Import del Service y TestEntityManager para preparar y verificar los datos en la base de pruebas. El tutorial de pruebas del Service las construye paso a paso para ArtistaService. En sentido estricto no son pruebas unitarias, porque usan la base de datos H2, pero se escriben con JUnit y corren en el pipeline.

  • Mínimo 2 pruebas por método: un caso feliz y un caso de error (assertThrows con la excepción esperada: 404 para un id inexistente, 400 para una regla violada).
  • La primera prueba (caso feliz + primer caso de error de create) se escribe a mano. Fija el patrón para las demás.
  • Cada prueba de error corresponde a una regla real del enunciado registrada en el Paso 1.

Pruebas en el pipeline. No hay que modificar el Jenkinsfile: el que entrega el curso desde R5 ya ejecuta mvn test en el stage “Backend: build y pruebas” y publica el reporte JUnit. Lo nuevo en R7 es que ahora hay pruebas del Service que ese stage ejecuta. Un PR con pruebas en rojo hace fallar el pipeline y, como en R5, no se aprueba, aunque el código compile.

IA permitida aquí: las pruebas análogas a la primera se pueden pedir a Gemini a partir de la que se escribió a mano. El estudiante verifica que cada prueba corresponde a una regla real del enunciado y que los casos de error realmente fallan cuando se quita la validación.


Paso 4: Endpoints de asociación entre módulos

Sección titulada «Paso 4: Endpoints de asociación entre módulos»

Antes de escribir código, por cada asociación inter-módulo del diagrama de R6, el equipo registra en la wiki:

Campo Ejemplo
Recurso dueño artistas
Recurso asociado disqueras
Endpoint POST /artistas/{artistaId}/disqueras/{disqueraId}
Descripción Asocia una disquera existente a un artista existente
Respuesta exitosa 200 con ArtistaDTOCompleto
Errores posibles 404 si el artista no existe, 404 si la disquera no existe

El integrante cuyo módulo contiene el recurso dueño implementa el endpoint. La lógica de asociación vive en el Service (nunca en el Controller ni en el Repository) y las reglas sobre asociaciones identificadas en el Paso 1 (relación que ya existe, límites, entidad que debe existir) se validan allí, con las mismas excepciones. El tutorial de endpoints de asociación muestra el patrón completo (Service, Controller, pruebas) para ArtistaAlbumService.

Cada endpoint de asociación suma a Postman tres requests: éxito (200), 404 por entidad inexistente y 404 por asociado inexistente.


El front no cambia su estructura en R7. El único cambio es que el back ahora responde DTOCompleto en los GET, lo que puede añadir campos que antes no llegaban desde el Gist.

  • Verificar que las interfaces TypeScript coinciden exactamente con los campos de DTOCompleto (mayúsculas, guiones, tildes), y agregar el campo de colección que llega con DTOCompleto (por ejemplo, albumes: Album[] en la interfaz de Artista). Si hay discrepancia entre los nombres del back y los del front, corregirla acá: es la misma lección de contratos de R1, ahora con el back propio.
  • Las canciones de un álbum se quedan para el Release 8. El ArtistaDTOCompleto trae sus álbumes sin canciones (como AlbumDTO), así que el detalle del álbum muestra solo los datos del álbum, sin su lista de canciones: quita esa parte del componente o déjala vacía. Las canciones vuelven en el Release 8, cuando el detalle recibe el id por la ruta y pide GET /albumes/{id}, que responde un AlbumDTOCompleto con ellas.
  • Arrancar el back del grupo con JPA y verificar lista, detalle y formulario de crear para las tres entidades.
  • Crear un registro desde el formulario y verificar que persiste al recargar la página y al reiniciar el back (antes desaparecía).

  • El Service de una entidad no se escribe hasta que la entidad JPA y el repository de esa entidad (Release 6) estén mergeados en main.
  • Las asociaciones inter-módulo requieren que ambos módulos estén en main. No se implementa sobre el código de otro en una rama propia.
  • Los PRs se mergean uno a la vez, igual que en releases anteriores.
  • Si un PR rompe las pruebas de otro módulo, la responsabilidad es del autor del PR.
  • Si el pipeline falla después de un merge, máxima prioridad del equipo: se revierte y se investiga en rama aparte.

Back:

  • Clase <Entidad>Service con métodos findAll, findById, create, update, delete y al menos una regla de negocio por entidad principal.
  • Dos excepciones personalizadas: EntityNotFoundException (404) e IllegalOperationException (400).
  • Dos DTOs por entidad: <Entidad>DTO y <Entidad>DTOCompleto.
  • Controller actualizado: inyecta el Service, expone CRUD completo, usa DTO para escritura y DTOCompleto para lectura.
  • Pruebas del Service con JUnit: mínimo 2 por método (caso feliz + caso de error).
  • Pruebas del Service en verde en el pipeline del grupo (el Jenkinsfile del curso, sin modificar).
  • Endpoints de asociación implementados y acuerdo de endpoints publicado en la wiki.
  • Colecciones Postman con CRUD completo (mínimo 21 requests por módulo), casos de regla de negocio violada y requests de asociación.

Front:

  • Modelos TypeScript actualizados para reflejar la estructura de DTOCompleto.
  • La aplicación funciona conectada al back con JPA: lista, detalle (sin la lista de canciones del álbum, que llega en R8) y crear operativos para las tres entidades.
  • La creación de un registro persiste al reiniciar el back.