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:
findByIdlanzaEntityNotFoundExceptionsi el id no existe.createvalida 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.updatereutilizafindById(para el404), valida las mismas reglas quecreatey luego guarda.deletereutilizafindByIdy 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
IllegalOperationExceptionque 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
updateydeletesiguen el patrón defindByIdycreate. Se puede pedir a Gemini que los genere dado el código decreateya funcionando como ejemplo. La configuración delModelMappery las llamadas amap(...)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, porqueModelMapperlos 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.expectcorresponden a los campos que realmente devuelve el back.
Paso 3: Pruebas del Service y pipeline
Sección titulada «Paso 3: Pruebas del Service y pipeline»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 (
assertThrowscon 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.
Paso 5: Actualizar el front
Sección titulada «Paso 5: Actualizar el front»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 conDTOCompleto(por ejemplo,albumes: Album[]en la interfaz deArtista). 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
ArtistaDTOCompletotrae sus álbumes sin canciones (comoAlbumDTO), 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 pideGET /albumes/{id}, que responde unAlbumDTOCompletocon 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).
Coordinación del equipo
Sección titulada «Coordinación del equipo»- 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.
Entregable R7
Sección titulada «Entregable R7»Back:
- Clase
<Entidad>Servicecon métodosfindAll,findById,create,update,deletey al menos una regla de negocio por entidad principal. - Dos excepciones personalizadas:
EntityNotFoundException(404) eIllegalOperationException(400). - Dos DTOs por entidad:
<Entidad>DTOy<Entidad>DTOCompleto. - Controller actualizado: inyecta el Service, expone CRUD completo, usa DTO para escritura y
DTOCompletopara 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
Jenkinsfiledel 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.