El método
Las tres unidades anteriores enseñaron a diseñar lo que se ve desde fuera. Esta enseña a colocar lo que hay detrás, y se decide con una sola pregunta repetida:
- ¿Esto habla de rutas, códigos o formato? Va al controller
- ¿Decide si algo se puede hacer, o qué valor toma? Va al service
- ¿Guarda, busca o recupera? Va al repository
- ¿Traduce entre el contrato y el modelo? Va al mapper
- Si encaja en dos sitios, es que son dos cosas: sepáralas
El paso cinco es el que hay que aplicar cuando se duda, y casi siempre acierta.
La idea más importante
Una clase debe tener un solo motivo de cambio. No «hacer una cosa», que no significa nada: un motivo de cambio, que sí se puede comprobar preguntando qué tendría que ocurrir en el mundo para tener que abrir ese archivo.
De ahí sale todo lo demás. Por eso el controller no guarda datos, por eso el service no sabe qué es un 404, por eso el repositorio es una interfaz, y por eso ahora se puede probar una regla sin arrancar un servidor.
Refactorizar es cambiar cómo, no qué
Al terminar la unidad, tu API responde exactamente lo que respondía. Si algo hubiera cambiado, no habría sido una refactorización, y la colección de la UD2 es lo que te permitió saberlo en diez segundos.
Las decisiones que tienes que saber justificar
| Decisión | Lo que tienes que poder decir |
|---|---|
| Tres capas y no dos | Cambiar el almacenamiento, cambiar una regla y cambiar el contrato son motivos distintos |
| Las dependencias van en un solo sentido | Si una capa conociera a la que la llama, no se podría cambiar sin arrastrarla |
| Los DTO no cruzan al service | El service debe poder usarse sin que exista una API web |
| El service lanza excepciones, no códigos | Dice qué ha pasado; traducirlo a HTTP es de la capa web |
| Inyección por constructor | Campos final, dependencias a la vista, y la clase se puede construir en un test |
| El repositorio es una interfaz | Permite cambiar el almacenamiento sin abrir el service, que es justo lo que hará la UD5 |
| Validación en el DTO, regla en el service | Si basta el objeto que llegó, es formato; si hay que consultar datos, es negocio |
| Un método público por caso de uso | El nombre lo tiene que entender alguien que conozca el negocio y no programe |
| Tests del service sin Spring | Prueban la lógica en milisegundos y señalan qué regla se ha roto |
| Un doble de prueba en vez del repositorio real | Para probar una clase cada vez y saber cuál falla |
Al terminar deberías poder responder
- ¿Cuál es la pregunta que revela si una clase tiene demasiadas responsabilidades?
- ¿Por qué duplicar es caro, si escribirlo dos veces cuesta poco?
- ¿Qué es una refactorización y qué la distingue de reescribir?
- Nombra dos situaciones que necesitarían tus reglas sin pasar por HTTP.
- ¿En qué dirección se permiten las dependencias entre capas?
- ¿Por qué el service no conoce los DTO?
- ¿Por qué puede el service lanzar una excepción que acabará siendo un 404?
- ¿Qué tres cosas decide una línea con
new, y cuál de ellas sí es asunto de la clase? - Da dos motivos para preferir la inyección por constructor.
- ¿Por qué un bean no debe guardar datos de una petición concreta?
- ¿Qué significan
NoSuchBeanDefinitionExceptionyNoUniqueBeanDefinitionException? - ¿Qué indica una dependencia circular sobre el diseño?
- ¿Qué pregunta separa una validación de una regla de negocio?
- ¿Qué es un caso de uso y cómo se refleja en el código?
- ¿Qué dos decisiones de la sesión 16 hacen posible probar el service?
- ¿Por qué se usa un doble y no el repositorio de memoria?
- ¿Cuáles son las tres zonas de un test?
- ¿Qué comprueba la colección que no comprueban los tests, y al revés?
- ¿Qué merece un test y qué no?
- ¿Cuántos archivos habría que tocar para cambiar de almacenamiento?
Si además puedes coger un controlador ajeno lleno de lógica y decir, método a método, qué se lleva a dónde, tienes lo que esta unidad quería darte.
El vocabulario de la unidad
| Concepto | Significa |
|---|---|
| Refactorizar | Cambiar cómo está escrito el código sin cambiar lo que hace |
| Motivo de cambio | Lo que tendría que ocurrir para tener que abrir un archivo |
| Responsabilidad única | Una clase, un motivo de cambio |
| Capa | Un grupo de clases con el mismo motivo de cambio |
| Controller | Habla HTTP: rutas, códigos, cabeceras, DTO |
| Service | Casos de uso y reglas de negocio. No sabe que existe HTTP |
| Repository | Guarda y recupera. No sabe de reglas |
| Regla de la dirección | Controller conoce al service, el service al repository, nunca al revés |
| Caso de uso | Algo completo que alguien quiere hacer con la aplicación |
| Regla de negocio | Decisión que necesita consultar datos, no solo mirar el objeto recibido |
| Inversión de control | Que no seas tú quien construye y conecta los objetos |
| Contenedor | Lo que crea los beans, los conecta y los administra |
| Bean | Un objeto gestionado por Spring |
| Estereotipo | @Service, @Repository, @Component: marcan la clase y su papel |
| Inyección por constructor | Declarar en el constructor lo que la clase necesita |
| Singleton | Una sola instancia de cada bean, compartida por toda la aplicación |
| Dependencia circular | Dos clases que se necesitan mutuamente. Spring se niega, y hace bien |
Optional |
Una caja que puede tener valor o estar vacía. Obliga a considerar el vacío |
| Test unitario | Código que ejecuta código y comprueba el resultado automáticamente |
| Doble de prueba | Implementación de mentira que controla qué datos ve lo que estás probando |
| Preparar, actuar, comprobar | Las tres zonas de todo test |
Comprobación final del producto
Comprobación final · con el proyecto delante
- Ningún controlador tiene listas, bucles de búsqueda, contadores ni reglas.
- Ningún service menciona HTTP, códigos de estado ni DTO.
- No queda ningún
newde un colaborador. - Los repositorios son interfaces con su implementación en memoria detrás.
- Cada regla de negocio tiene su test, y
./mvnw testpasa en verde. - La colección pasa en verde sin ninguna petición modificada desde la UD3.
- el análisis de problemas está tachado línea a línea.
Resultados de la unidad
- Reconocer los síntomas de un controller que acumula responsabilidades.
- Separar controller, service y repository y justificar qué va en cada capa.
- Usar inyección de dependencias en lugar de construir colaboradores a mano.
- Situar las reglas de negocio en el service y protegerlas con tests.
- Escribir tests unitarios de un service con JUnit.
La siguiente unidad
Cuatro unidades, cuatro preguntas:
- UD1 · que responda
- UD2 · que responda bien
- UD3 · que esté bien diseñada
- UD4 · que se pueda mantener
Queda la quinta, y es la que lleva cuatro unidades apareciendo al final de cada cierre:
¿Y esto dónde se guarda?
Cada vez que has reiniciado la aplicación se ha perdido todo. Lo has anotado como defecto conocido en la UD1, en la UD2 y en la UD3, y ha llegado el momento.
| Lo que sigue mal | Se arregla en |
|---|---|
| Al reiniciar se pierde todo | UD5, con PostgreSQL |
| Buscar recorre la lista entera, uno por uno | UD5, con consultas |
Las relaciones son un id suelto que nadie garantiza |
UD5, con integridad referencial |
| Dos operaciones simultáneas pueden pisarse | UD5, con transacciones |
El trabajo de estas dos semanas demuestra aquí su utilidad: cambiar de almacenamiento será un cambio localizado. Se borra la implementación en memoria, aparece una interfaz que extiende JpaRepository, y el service, el controller, los DTO, el mapper y los tests se quedan exactamente como están.
Si al terminar la UD5 has tenido que abrir el controlador, algo se colocó mal aquí.
Ya deberías ser capaz de
- Reconocer los síntomas de un controller que acumula responsabilidades.
- Separar controller, service y repository y justificar qué va en cada capa.
- Usar inyección de dependencias en lugar de construir colaboradores a mano.
- Situar las reglas de negocio en el service y protegerlas con tests.
- Escribir tests unitarios de un service con JUnit.