¿Cómo crear un servicio de dominio en Spring Boot?
Construir un servicio de dominio es una parte crucial en el desarrollo de aplicaciones, dado que actúa como un intermediario vital entre el controlador de nuestra API y el repositorio. Este enfoque permite mantener una separación clara entre las responsabilidades, mejorando así el mantenimiento y la escalabilidad de tu aplicación. En este artículo, nos sumergiremos en el proceso para crear un ProductService en una aplicación utilizando Spring Boot.
¿Qué es un servicio en Spring Boot?
En el contexto de Spring Boot, un servicio es una clase que contiene la lógica de negocio específica de la aplicación. Generalmente, estas clases están decoradas con la anotación @Service, que se importa desde el paquete org.springframework.stereotype. El uso de @Service no sólo proporciona una diferenciación semántica, también facilita la gestión de la inyección de dependencia.
@ServicepublicclassProductService{privatefinalProductRepository productRepository;@AutowiredpublicProductService(ProductRepository productRepository){this.productRepository = productRepository;}// Métodos del servicio}
¿Cómo se inyecta el repositorio en un servicio?
En Spring Boot, se utiliza la anotación @Autowired para inyectar dependencias automáticamente. Cuando definimos el repositorio como un atributo de tipo interfaz en el servicio, Spring Boot se encarga de crear y asignar la implementación real del mismo.
¿Cuáles son los métodos esenciales para el servicio de producto?
Para un servicio de productos, es crucial implementar ciertos métodos básicos que gestionen las operaciones CRUD (Create, Read, Update, Delete) de manera eficiente. A continuación, un vistazo a algunos de estos métodos fundamentales:
Listar todos los productos: Este método retorna una lista de todos los productos.
¿Qué consideraciones adicionales tener al crear servicios?
Uso de Optional y Map:Optional es una excelente herramienta para manejar resultados que pueden ser nulos, promoviendo un código más seguro y limpio al evitar las excepciones de tipo NPE (Null Pointer Exception).
Anotaciones y Gestión de Dependencias: Las anotaciones en Spring como @Service y @Autowired facilitan la gestión de dependencias y el establecimiento de clases dentro del contexto de Spring.
Semántica y Buenas Prácticas: Es importante seguir las convenciones y buenas prácticas al usar anotaciones para facilitar el mantenimiento y comprensión del código.
Al entender y aplicar estos conceptos, estarás en una mejor posición para desarrollar servicios dentro de aplicaciones Spring Boot, asegurando una estructura robusta y orientada a la escalabilidad y entendimiento del negocio. ¡Continúa explorando y perfeccionando tus habilidades en Spring Boot!
Estaba un poco confundido con el concepto de Repositorio y Servicio. Así que me puse a indagar un poco y resulta que el repositorio es la capa simple de acceso o de comunicación con el sistema de almacén de datos (ojo, solo comunicación), mientras que el servicio es la lógica de acceso a estos datos desde la aplicación, así pues un ejemplo sería: Entras a un cajero automática y sacas dinero de tu cuenta, el banco es el repositorio, y el cajero es el servicio.
Que gran explicación, gracias hombre!
Muchas gracias por la explicación, excelente
Las implementaciones del método delete son buenos ejemplos de los estilos declarativo e imperativo.
El primero define el qué va a hacer el código mientras que el segundo define el cómo y por consiguiente se ven más detalles. Cabe aclarar que el estilo declarativo no puede existir sin el estilo imperativo ya que el primero se apoya en el segundo para ocultar las estructuras de control (if, else, switch, etc).
No entendi :( , como explicarlo en palabras mas simples.
Hola Johson, a lo que Manuel se refiere es:
Un código declarativo no le importa como se hará la operación solo le importa que operación es la que va a realizar:
El método delete de la clase ProductoRepository es un ejemplo, en este caso solo nos interesa el que y no el como. Asi que no se ven los detalles y se basa en otras clases que internamente harán ese proceso.
Por otro lado un estilo imperativo se preocupa del como se va a realizar la operación:
public boolean delete(int productId){returngetProduct(productId).map(product ->{ productRepository.deleteProduct(productId);returntrue;}).orElse(false);}
El método delete de la clase ProductService es un ejemplo, en este caso si nos interesa ver un poco mas a detalle como realizar la eliminación y en este caso primero se revisa si el producto realmente existe y también nos preocupa devolver una bandera que le indique al controlador si fue posible o no realizar dicha eliminación.
Otro ejemplo es cuando usas alguna librería, donde te preocupa hacer una operación pero no tanto como se hará ya que la librería se va a encargar de esto. En este caso tu código será de forma declarativa mientras la librería será imperativa.
¿Porqué es necesario verificar que el producto exista en la base de datos antes de borrarlo? El Id normalmente se obtiene de procesos anteriores como de un listado, y si queremos saber si el borrado es exitoso se podría obtener el número de fila(s) eliminada(s) ... lo digo porque esto se ve muy bonito y mantenible, pero es costoso en máquina... ¿no?
Tienes toda la razón, Luis. Lo hice así era pensando en implementar una solución más imperativa que me permita describir claramente cuando se realiza bien o no este procedimiento.
Una solución mucho más optima sería controlar esto con un try-catch, así:
A mi "EmptyResultDataAccessException e" me lo marca como error
@Service:
Intermediario entre el controlador de la API y el repositorio
👍
Creo que todo tiene un porque, al principio lo veía como algo repetitivo pero ahora tengo mas claro el porque se programa así.
Gracias profesor, excelente curso.
X2
Nuestros servicios son negociadores entre el repositorio y los controladores
Muy buen resumen
Para el que quiera entender de donde vienen estos objetos que nombra el profesor como el Optional o el método .map() ... esto hace parte de lo que es Programación Funcional (Un paradigma de programación) de la cual Java año tras año ha tratado de adaptarse a este.
Actualmente se encuentra un curso de Java Funcional en platzi y es excelente.
https://platzi.com/cursos/java-funcional/
Creo que de esta forma es más legible el método delete:
public boolean delete(long id){if(get(id).isEmpty()){returnfalse;} repository.delete(id);returntrue;}
Cuando hablamos de legibilidad todo es cuestión de gusto y costumbre.
Al final del día lo más importante es escribir código que nosotros mismos entendamos, que pasado un tiempo y al volver a verlo no digamos "WTF, ¿qué cosa hice aquí? 😵".
Si tienen errores como este tengan en cuenta no poner un "," de más, me quedé una hora buscando el error
Esta linea se puede leer de la siguiente forma?, no entendí muy bien la instrucción
Si existia el producto y fue eliminado responde true de lo contrario response false
return getProduct(productId).map(Product ->{
productoRepository.delete(producttId);
return true;
}).orElse(false);
Más o menos, aunque veo que entendiste muy bien la idea del map y el orElse.
Como el delete del ProductRepository no retorna nada entonces no puede ser una condición incluyente a como lees la instrucción.
Sería: "Sí existe el producto entonces elimínalo y retorna true; de lo contrario retorna false".
Es buena practica, agregar en nuestra clase Service las validaciones del negocio?
Las validaciones del negocio deberían ir en el servicio o en la clase del objeto de dominio.
Como se maneja la atomicidad de una transaccion que realiza multiples operaciones en la base de datos?
Para manejar la atomicidad se usa la anotación @Transactional.
Te recomiendo el artículo Managing Transactions with Spring and Spring Data JPA para aprendas más de esta anotación y transacciones ACID.
es obligatorio crear un servicio por cada entidad de repository? o un servicio puede funcionar como patron Facade y exponer todos los servicios de un módulo por ejemplo?
No es obligatorio pero si es una buena practica. El principio Single responsibility de los SOLID nos sugiere de que los componentes de nuestro sistema deberían estar diseñados de tal forma de que su responsabilidad sea única y solo se encarguen de algo especifico dentro de nuestro dominio.
Esto para buscarcomo siempre, bajo acoplamiento / alta cohesión.
Si quiero colocar una Api externa, entonces la lógica la haría en ProductRepository de la persistencia?
La respuesta correcta a esta pregunta depende de varias consideraciones y dependiendo la evaluación de las mismas será la desición correcta:
Si deseas que el acomplamiento entre el dominio de tu aplicación y la API sea fuerte (Dependan mucho) agrega el llamado en la subcapa Service. Como parte negativa es que el negocio depende mucho de la API y un cambio en ella puede afectar su funcionamiento. Como parte positiva, no se agrega mas complejidad a la infraestructura. Dejarla acá depende mucho de que tan estable es esa API en el tiempo.
Agregar una capa Aplication, esta estaría encargada de orquestar toda la interacción con la api y enviar la información entre las capas Domain y Persistence. Ventaja, desacoplas su funcionamiento, los cambios solo afectaran esa parte de tu proyecto y estan mas acordes a los principios S o de responsabilidad unica de SOLID. Desventaja, debes crear toda la infraestructura para orquestar esta función. En terminos del curso debes crear los mapper entre las capas y las reglas de funcionamiento.
En fin, es responsabilidad del programador, el arquitecto de la solución y el dinero sobre la miesa lo que dicata que camino tomar de acuerdo a la aplicación, la proyección a futuro de esta (va a crecer o es casi estatica), tiempos de entrega, recursos y compromisos con el sistema.
Hola a todos, he revisado paso a paso todas las clases hasta aquí y en el código descargado o en el que yo mismo hice, me sale este error al ejecutar:
***************************APPLICATIONFAILEDTOSTART***************************Description:Field mapper in com.platzi.market.persistence.ProductoRepository required a bean of type 'com.platzi.market.persistence.mapper.ProductMapper' that could not be found.The injection point has the following annotations:- @org.springframework.beans.factory.annotation.Autowired(required=true)Action:Consider defining a bean of type 'com.platzi.market.persistence.mapper.ProductMapper'in your configuration.```
No le he podido dar con el chiste.Gracias,Salu2.
mira lo que dice el error, dice que esta buscando un bean de tipo ProductMapper en la clase ProductoRepository y no lo encontró. Lo que puede estar pasando es que la clase ProductMapper no ha sido definida como un component de spring. Intenta anotar el mapper con @Component
Hola @feldan9119, gracias por el comentario!,
Esta es la definición de la clase ProducMapper
Según lo que entendí es que @Mapper es de Struct y para que quede enlazado como componente de Spring se usa el atributo componetModel, por lo que no hace falta colocar el @Component, sin embargo hice el ajuste, pero me dio la misma excepción.
Incluso me baje las fuentes de la clase #34 y me sale la excepción.
Gracias,
Salu2.
No hace falta usar el @Autowired en este caso porque Spring buscará la instancia de ProductRepository en su contenedor y se dará cuenta que existe creado previamente uno con el nombre productRepository, mismo nombre que el del atributo en ProductService. No obstante es buena práctica anotarlo con @Autowired
Otra opcion para el Delete, es con una lamda, queda mas prolijo me parece.
public boolean delete(int productId){returngetProduct(productId).map(product ->true).orElse(false);}```
Creo que no estarías borrando nada, solo devolviendo true y false en caso de que exista o no