--- title: 'Guía de Desarrollo del Back' --- Guía de Desarrollo del Back === * **Coordinador Tecnológico:** Rafael Palau * **Arquitectos:** Horacio Nemeth, Marcelo Lopez * **Consultores:** Ilse Grau Julio Mello Marcos Benítez Marco Aquino Lauro Segovia ## Contenido [TOC] ## Introducción En esta guía se describen los conceptos esenciales del framework Spring/Boot y los pasos necesarios para la creación de un servicio REST de tipo Alta, Baja Modificación (ABM) en el proyecto Backend utilizando la estructura propuesta. Para el caso práctico de la presente guía se utilizará la entidad de *FRECUENCIAS*. ## Objetivo * Introducir al desarrollador en los conceptos fundamentales del framework Angular * Guiar al desarrollador en la creación de los archivos necesarios para un ABM. ## Prerequisitos * Tener instalado el IDE Eclipse/Spring Tool Suite * Tener instalado Java JDK en su versión 11 * Tener instalado Maven * Tener instalado Git ## Resultados esperados Servicio correspondiente a la tabla Frecuencias desarrollado y expuesto para ser utilizado. ## Concepto Spring --- :::info En esta sección se presentará un resumen de los conceptos fundamentales del framework Spring. Para ahondar más al respecto es conveniente consultar las documentación oficial <a href="https://spring.io/projects/spring-framework" target="_blank"> Documentación Spring</a> ::: Spring comenzó como una alternativa ligera a Java Enterprise Edition (JEE). En lugar de desarrollar componentes Enterprise complejos utilizando JavaBeans (EJBs), Spring ofrecía un enfoque más sencillo para el desarrollo de Java Enterprise, utilizando la inyección de dependencias (DI) y la programación orientada a aspectos (AOP) para conseguir prestaciones similares a los EJB empleando objetos Java simples (POJOs). Spring ofrece un conjunto de módulos que suplen el proceso de construcción de una aplicación Empresarial como ser: persistencia de datos, seguridad,microservicio, web MVC, etc. #### Módulos ![](https://i.imgur.com/3uWXkPT.png) Figura : Diagrama de los principales módulos del Spring Históricamente, la forma de configurar el contexto de la aplicación del Spring para vincular los beans se requería agregar uno o varios archivos XML que describan la relación y dependencia entre los componentes. Esto con los años fue reemplazado por las configuraciones basadas en Java (Java-based configuration). #### Principales anotaciones ##### Gestión de componentes | Anotaciones | Descripción | | -------- | -------- | | @Autowired | Se aplica a campos y métodos. La misma inyecta la dependencia del objeto implícitamente | | @Configuration | Se aplica a una clase para agregar configuración mediante clase java y tendrá métodos para crear instancias y configurar las dependencias | | @Bean | Se utiliza a nivel de método de clase, la anotación @Bean funciona con @Configuration para crear beans en el contexto de la aplicación Spring | | @Value | Se utiliza a nivel de campo, parámetro de constructor y parámetro de método. Indica una expresión que agrega valor a la clase desde una archivo de propiedades o desde el Spring Config | | @Controller | Se usa para indicar que la clase es un controlador para el Spring MVC | | @Service | Se aplica a nivel de clase, generalemente las clases anotadas con @Service realizan algún servicio, como ejecutar lógica de negocios, realizar cálculos y llamar a API externas | | @Repository | Se aplica a nivel de clase, generalemente estas clases anotadas con @Repository permiten el acceso a la base de datos | ##### Gestión de métodos HTTP | Anotaciones | Método HTTP | Uso | | -------- | -------- | -------- | | @GetMapping | HTTP GET requests | Para leer datos | | @PostMapping | HTTP POST requests | Para crear datos | | @PutMapping | HTTP PUT requests | Para actualizar datos | | @DeleteMapping | HTTP DELETE requests | Para eliminar datos | | @RestController | Anotación para gestionar las respuesta en el *body* del *response* de todos los métodos HTTP | | | @RequestMapping | Anotación de propósito general para el manejo de métodos HTTP | | | @RequestParam |Anotación que es utilizado para anotar argumentos del método del controlador, el mismo es enviado como query params del recurso solicitado | | | @PathVariable |Anotación que es utilizado para anotar argumentos del método del controlador, el mismo es enviado como parte del recurso solicitado . | | ### Spring Boot Con Spring Boot se simplifico el desarrollo utilizando Spring por las siguientes razones : * Configuración automática : Spring Boot provee configuraciones comunes y esenciales para cualquier aplicación utilizando el framework Spring. * Dependencias iniciales : Spring Boot provee funcionalidad para la construcción de proyectos base con las dependencias requeridas por las aplicaciones Spring. * Interfaz de linea de comando (CLI) : Spring Boot provee un conjunto de comandos que facilitan la creación y construcción de proyectos Spring. * Monitoreo (Actuator) : Provee servicio para consultar el estado actual de la aplicación Spring en ejecución. #### Principales anotaciones * @EnableAutoConfiguration Esta anotación se utiliza en la clase principal de la aplicación. Esta anotación le indica al Spring Boot que comience a agregar beans según la configuración de classpath. * @SpringBootApplication Esta anotación se utiliza en la clase principal de la aplicación.Esta anotación se compone de las siguientes anotaciones: * @SpringBootConfiguration * @EnableAutoConfiguration * @ComponentScan ## Estructura de proyecto El proyecto creado utiliza como gestor de dependencias el [Maven](https://maven.apache.org/), la misma se compone de un proyecto padre de y varios sub-módulos. > El módulo *siare-common* contiene todas las clases comunes que son utilizadas por los otros módulos del proyecto. * Ejemplo : ![](https://i.imgur.com/3YG6GXB.png) Figura : Estructura del proyecto Cada módulo cuenta con la siguiente estructura | Directorio/Archivo | Descripción| | -------- | -------- | | src/main/java | paquete que contiene los archivos fuentes del módulo | | src/main/resources | paquete que contiene los archivos de configuración del módulo | | src/test/java | paquete que contiene los archivos fuentes de las pruebas unitarias | | target | Directorio que contiene los archivos comprimidos generados con Maven jar,war,ear etc | | pom.xml | Archivo que contiene la definición del módulo y sus dependencias | ### Esquema de módulo ![](https://i.imgur.com/JIaywZV.png) ## Git Git es un sistema de control de versiones de código. Las características del Git son las siguientes : * Código abierto. * Gratuito. * Diseñado para manejar proyectos grandes y pequeños. * Fácil de aprender. * Ocupa poco espacio con un rendimiento ultrarrápido. ## Importación del Proyecto desde GitLab Para proceder a clonar el proyecto Backend primeramente se debe instalar git, crear una rama propia del proyecto e importarlo como proyecto maven en el STS. ### Instalación de Git Descargar el instalador del git (Windows installer). https://git-scm.com/downloads Realizar doble click sobre el instalador y seguir las instrucciones. ### Clonar el Proyecto Backend * Una vez instalado el git se procede ha ubicarse en la carpeta donde se desea clonar el proyecto y hacer **click derecho --> Git Bash Here** y despliega la ventana de comando de git. ![](https://i.imgur.com/UAxPJbI.png) **Figura.** Ejecutar git bash. ![](https://i.imgur.com/opGaVZI.png) **Figura.** Editor vim de git. * Configurar la variables globales del git :::info git config --global user.name "nombre de usuario" git config --global user.email "user@hacienda.gov.py" git config --global http.sslVerify false ::: ![](https://i.imgur.com/EUlmbkS.png) **Figura.** Configuración de variables. * Clonar proyecto. Antes de proceder a clonar el proyecto se debe acceder a GitLab del proyecto para copiar la url del proyecto. 1. Acceder al GitLab del proyecto mediante las credenciales válidas (username, password). :::info https://gitlab.siare.gov.py/ ::: ![](https://i.imgur.com/HxoM7iZ.png) **Figura.** Acceder al GitLab del proyecto. ### Flujo del Git ## Creación de ABM :::info Se asume que el proyecto fue creado y configurado ::: ### Diagrama del ABM ![](https://i.imgur.com/84hnaOM.png) Figura : Diagrama del ABM de Frecuencias > Los gráficos coloreados son los archivos requeridos para completar el proceso de desarrollo de un ABM genérico ### Paso 1 - Creación de la entidad * Ingresar al Spring Tool Suite * Ir al paquete *py.gov.siare.presupuesto.model* y crear una clase java con el nombre de *Frecuencias* esta clase debe extender de la clase base *BaseEntity*. > La clase base es utilizada para agrupar los atributos comunes * El código tendrá el siguiente contenido: ```java= package py.gov.siare.presupuesto.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.SequenceGenerator; import javax.persistence.Table; import lombok.Data; import py.gov.siare.siarecommon.model.BaseEntity; @Entity @Data @Table(name = "frecuencias") @SequenceGenerator(sequenceName = "SEQ_FRECUENCIAS_ID", allocationSize = 1, name = "baseGen") public class Frecuencias extends BaseEntity { private String nombre; private String abreviatura; } ``` > El archivo contiene una clase simple Java con anotaciones propias del JPA > Los atributos de la clase *Frecuencias* son : nombre de tipo String y abreviatura de tipo String. > Los atributos heredados mas importantes de la clase *Frecuencias*: son id, usuarioCreador, usuarioModificador, activo y borrado. Que son heredados de la clase base *BaseEntity*. > No se han agregado el constructor y los metodos setter y getter por contar con la anotacion **@Data** que es parte de la liberia [Lombok](https://projectlombok.org/), el cual crear los códigos necesarios en tiempo de compilación. > La clase también esta anotada con la anotacion **@Entity**, el cual indica al JPA que la clase Java debe tratarse como una entidad, para indicar el nombre de la tabla asociada a la entidad en la clase se utiliza la anotación **@Table**. > La clase *Frecuencias*: debe tener las siguientes anotaciones @SequenceGenerator, @Data, @Table(name = "frecuencias") para indicarle al JPA como será generado el identificador del Objeto. > Las propiedades *nombre* y *abreviatura* no cuenta con la anotación **@Column** ya que tiene el mismo nombre de la columna definida en la tabla de la base de datos. ### Paso 2 - Creación del DTO * Ir al paquete *py.gov.siare.presupuesto.dto* y crear una clase java con el nombre de *FrecuenciasDto* esta clase debe extender de la clase base *BaseDto*. > La clase base es utilizada para agrupar los atributos comunes * El código tendrá el siguiente contenido: ```java= package py.gov.siare.presupuesto.dto; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; import py.gov.siare.siarecommon.dto.BaseDto; @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class FrecuenciasDto extends BaseDto { private Long id; private String abreviatura; private String nombre; } ``` > El archivo contiene una clase simple Java con anotaciones > Los atributos de la clase *FrecuenciasDto* son : id de tipo Long, nombre de tipo String y abreviatura de tipo String. > No se han agregado el constructor y los metodos setter y getter por contar con la anotacion **@Data** que es parte de la liberia [Lombok], el cual crear los códigos necesarios en tiempo de compilación. > La clase cuenta ademas con la anotación **@JsonInclude(JsonInclude.Include.NON_NULL)**, el cual indica a la libreria jackson (procesador de Objeto/Json - Json/Objeto) que no agregue los atributos nulos al objeto generado. ### Paso 3 - Creación del repositorio * Ir al paquete *py.gov.siare.presupuesto.repository* y crear una interfaz java con el nombre de *FrecuenciasRepository*, la misma debe extender de las siguientes interfaces *JpaRepository* y *JpaSpecificationExecutor*. * El código tendrá el siguiente contenido: ```java= package py.gov.siare.presupuesto.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import py.gov.siare.presupuesto.model.Frecuencias; public interface FrecuenciasRepository extends JpaRepository<Frecuencias, Long>, JpaSpecificationExecutor<Frecuencias> { } ``` > En la interfaz se ha extendido de *JpaRepository* el cual recibe dos parámetros genéricos que son la clase entidad *Frecuencias* y el tipo de dato *Long* que coincide con el *ID* de la entidad. Así también se extiende de la interfaz *JpaSpecificationExecutor* el cual recibe el parámetro genérico de la clase entidad *Frecuencias* > Al extender de estas dos intefaces la interfaz *FrecuenciasRepository* hereda varios métodos para persitir datos de la entidad *Frecuencias*, así también los métodos de guardar, eliminar, actualizar y obtener registros. > Adicionalmente Spring Data JPA agrega métodos dinámicos de búsqueda por nombre de atributos como ser *findByNombre* o *findByAbreviatura* > Generalmente en una aplicación Java se debería definir la implementación de la interfaz *FrecuenciasRepository*, pero gracias al Spring Data JPA no es necesario ya que el Spring crea la implementación en tiempo de compilación. ### Paso 4 - Creación del servicio > Ir al paquete *py.gov.siare.presupuesto.service* y crear una interfaz java con el nombre de *FrecuenciasService*, la misma debe extender de la interfaz *ServiceBase*. * El código tendrá el siguiente contenido: ```java= package py.gov.siare.presupuesto.service; import py.gov.siare.presupuesto.dto.FrecuenciasDto; import py.gov.siare.presupuesto.model.Frecuencias; import py.gov.siare.siarecommon.service.ServiceBase; public interface FrecuenciasService extends ServiceBase<Frecuencias, Long, FrecuenciasDto>{ } ``` > En la interfaz se ha extendido de *ServiceBase* el cual recibe tres parámetros genéricos que son la clase entidad *Frecuencias* y el tipo de dato *Long* que coincide con el *ID* de la entidad y la clase DTO *FrecuenciasDto*. > La interfaz *ServiceBase* contiene los métodos comunes para un ABM simple ### Paso 5 - Creación de la implementación del servicio * Ir al paquete *py.gov.siare.presupuesto.service.impl* y crear una clase java con el nombre de *FrecuenciasServiceImpl*, la misma debe extender de la clase *ServiceBaseImpl*. * El código tendrá el siguiente contenido: ```java= package py.gov.siare.presupuesto.service.impl; import py.gov.siare.presupuesto.dto.FrecuenciasDto; import py.gov.siare.presupuesto.model.Frecuencias; import py.gov.siare.presupuesto.repository.FrecuenciasRepository; import py.gov.siare.presupuesto.service.FrecuenciasService; import py.gov.siare.siarecommon.dto.ResponseDto; import py.gov.siare.siarecommon.enumerated.Mensaje; import py.gov.siare.siarecommon.service.impl.ServiceBaseImpl; @Service // Anotación propia del Spring para los servicios public class FrecuenciasServiceImpl extends ServiceBaseImpl<Frecuencias, Long, FrecuenciasDto> implements FrecuenciasService { } ``` > Como se puede apreciar la clase esta anotada con la anotación @Service con el proposito de indicar al Spring que trate la clase como un componente de servicio al momento de escanear y construir la aplicación. > La clase extiende de *ServiceBaseImpl* la misma contiene implementación de los métodos comunes de la interfaz *ServiceBase* > La clase *ServiceBaseImpl* requiere de la redefinición de los métodos : *obtenerModelMapper*, *obtenerClaseEntidad*, *obtenerClaseDTO* y *obtenerObjectMapper* para poder realizar las operaciones comunes. Así también es necesario proveerle la instancia del repositorio *FrecuenciasRepository*. ### Paso 6 - Creación del Controller * Ir al paquete *py.gov.siare.presupuesto.controller* y crear una clase java con el nombre de *FrecuenciasController*, la misma debe extender de la clase *ControllerBase*. * El código tendrá el siguiente contenido: ```java= package py.gov.siare.presupuesto.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import py.gov.siare.presupuesto.dto.FrecuenciasDto; import py.gov.siare.presupuesto.model.Frecuencias; import py.gov.siare.siarecommon.controller.ControllerBase; @RestController @RequestMapping({ "frecuencias" }) public class FrecuenciasController extends ControllerBase<Frecuencias, Long, FrecuenciasDto>{ } ``` > Como se puede apreciar la clase esta anotada con la anotación @RestController con el proposito de indicar al Spring que trate la clase como un componente de Controller al momento de escanear y construir la aplicación. Así también cuenta con la anotación @RequestMapping con el parametro *{ "frecuencias" }* que indica al Spring el *path* del controlador a ser creado. > La clase extiende de *ControllerBase* la misma contiene implementación de los métodos comunes requeridos para un controlador. > La clase *ControllerBase* requiere de tres parámetro ya que la misma es una clase genérica por lo tanto se debe agregar la referencia de la clase *Frecuencias*, *Long* y *FrecuenciasDto*, así también es necesario redefinir el método de obtención de servicio a ser utilizado por la clase base. ### Paso 7 - Validaciones (Personalizadas) en la implementación del servicio Para realizar validaciones previamente a las operaciones de creación, actualización y eliminación (lógica) se deben reescribir los métodos de la clase *ServiceBase*: ```java= default Boolean validarCreacion(T entity, ResponseDto<E> respuesta) { return true; } default Boolean validarActualizacion(T entity, ResponseDto<E> respuesta) { return true; } default Boolean validarEliminacion(T entity, ResponseDto<E> respuesta) { return true; } ``` Estos métodos reciben como parámetros el objeto de la entidad *entity* sobre la cual se realizará la operación y una instancia de la respuesta *respuesta* que se devolverá con el resultado de la operación. A continuación se muestra un ejemplo de validación previa a la creación de un registro que verifica la existencia de registros con el mismo valor en el campo *descripción*: ```java= @Override public Boolean validarCreacion(ArrendamientoTipos entity, ResponseDto<ArrendamientoTiposDto> respuesta) { // validar descripcion duplicada if (countDescripcionDuplicada(entity) > 0) { respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR .getMensajeDetail("Descripción duplicada")); return false; } return true; } ``` Para interrumpir la operación correspondiente, en caso de incumplimento de alguna validación, el método debe ser reescrito y retornar el valor booleano *false* y registar el mensaje a desplegar en la respuesta. Dentro de estos métodos se pueden incluir validaciones que requieran consultar otras entidades, como por ejemplo, esta valiación previa a la eliminación que verifica otra entidad asociada a la misma: ```java= @Override public Boolean validarEliminacion(ArrendamientoTipos entity, ResponseDto<ArrendamientoTiposDto> respuesta) { // validar bienes asociados if (countBienesAsociados(entity) > 0) { respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR .getMensajeDetail("El Tipo de Arrendamiento posee Bien/es asociado/s")); return false; } return true; } ``` Tambien se pueden incluir multiples validaciones en serie: ```java= @Override public Boolean validarActualizacion(ArrendamientoTipos entity, ResponseDto<ArrendamientoTiposDto> respuesta) { ArrendamientoTipos entityActual = repo.getById(entity.getId()); // validar descripcion duplicada if (countDescripcionDuplicada(entity) > 0) { respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail("Descripción duplicada")); return false; } // si se inactiva registro validar bienes asociados if (!entity.getActivo() && entityActual.getActivo() && countBienesAsociados(entity) > 0) { respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR .getMensajeDetail("El Tipo de Arrendamiento posee Bien/es asociado/s")); return false; } // si se restablece registro validar descripcion duplicada if (!entity.getBorrado() && entityActual.getBorrado() && countDescripcionDuplicada(entity) > 0) { respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail("Descripción duplicada")); return false; } return true; } ``` La definición de los métodos auxiliares utilizado para la validaciones: ```java= private Long countDescripcionDuplicada(ArrendamientoTipos entity) { Specification<ArrendamientoTipos> descripcionDuplicada = new Specification<ArrendamientoTipos>() { private static final long serialVersionUID = 1L; @Override public Predicate toPredicate(Root<ArrendamientoTipos> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) { return criteriaBuilder.and(criteriaBuilder.notEqual(root.get("id"), entity.getId()), criteriaBuilder.equal(root.get("descripcion"), entity.getDescripcion().trim()), criteriaBuilder.equal(root.get("borrado"), false)); } }; return repo.count(descripcionDuplicada); } private Long countBienesAsociados(ArrendamientoTipos entity) { Bienes bien = new Bienes(); bien.setArrendamientoTipo(entity); bien.setBorrado(false); return repoBienes.count(Example.of(bien)); } ``` ### Paso 8 - Validaciones (Genéricas) vía aspectos El proceso de validación genérica esta enfocado a la verificación de atributos duplicados y eliminación lógica. Para cada verificación es utilizado anotaciones de Java y aspecto del Spring, que generalizan y externalizan la tarea de la gestión de validación a nivel de Objeto DTO y Entidad relacional (Entity). * Clases que componen la validación con aspectos * CheckValidation * ConstraintsUnique * Editar * Guardar * ValidarEliminacionLogica * EliminacionAspect * ValidadorCampoUnicoAspect #### Puntos de corte de los aspecto Los aspectos cuenta con un punto de corte que estan definidos vía anotaciones a nivel de métodos. La clase donde es definida los puntos de corte es : *ServiceBaseImpl* > Operación de **guardar** ```java @CheckValidation // punto de corte vía anotación @Guardar // anotación para determinar la operación del método public ResponseDto<E> guardar(E entidadDTO) { ResponseDto<E> respuesta = new ResponseDto<>(); Class<?> clazz = this.obtenerClaseEntidad(); T entidad = (T) this.obtenerModelMapper().map(entidadDTO, clazz); if (Boolean.TRUE.equals(validarCreacion(entidadDTO, respuesta))) { entidad = repository.save(entidad); respuesta = ResponseBuilder.crearObjetoRespuesta(entidad, this.obtenerClaseDTO()); } return respuesta; } ``` > Operación de **actualización** ```java @CheckValidation // punto de corte vía anotación @Editar // anotación para determinar la operación del método public ResponseDto<E> actualizar(K id, E entidadDTO) { AtomicReference<ResponseDto<E>> respuesta = new AtomicReference<>(); Class<E> clazzDTO = this.obtenerClaseDTO(); Optional<T> entidad = repository.findById(id); if (Boolean.TRUE.equals(validarActualizacion(entidadDTO, respuesta.get()))) { entidad.ifPresent(item -> { T entidadParaActualizar = this.actualizacionSelectivaPorCampo(item, entidadDTO); this.repository.save(entidadParaActualizar); respuesta.set(ResponseBuilder.crearObjetoRespuesta(entidadParaActualizar, clazzDTO)); }); } return respuesta.get(); } ``` > Operación de **eliminación** ```java @ValidarEliminacionLogica public ResponseDto<E> eliminarPorEntidad(E entidadDTO) { ResponseDto<E> respuesta = new ResponseDto<>(); Class<E> clazzDTO = this.obtenerClaseDTO(); Class<T> clazzEntidad = this.obtenerClaseEntidad(); if (entidadDTO != null) { T registro = this.obtenerModelMapper().map(entidadDTO, clazzEntidad); if (Boolean.TRUE.equals(validarEliminacion(registro, respuesta))) { agregarValorAEntidadPorNombre(registro, "borrado", true, clazzEntidad); this.repository.save(registro); respuesta = ResponseBuilder.crearObjetoRespuesta(entidadDTO, clazzDTO); } } return respuesta; } ``` :::info > Para la operación de actualización y guardado es necesario indicar vía anotación cuales son los atributos a ser evaluados en el proceso de validación. > Para la operación de eliminación el comportamiento esta definido por defecto. ::: #### Control de campos duplicados > La anotación a ser utilizada para el control de campos únicos es @ConstraintsUnique, está anotación es utilizada a nivel de atributo del de la clase que extiende de BaseDto.java. La anotación de unique permite definir agrupaciones por nombre para la validación de campos ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @Documented public @interface ConstraintsUnique { /** * Es el nombre del atributo a validar este valor es utilizado para generacion * del objeto CriterioFiltro es importante que este nombre coincida con el * atributo del objeto de dominio (Entity) * * @see CriterioFiltro */ String atributo() default ""; /** * * Código del mensaje definido dentro del archivo messages.properties */ String mensaje() default "{default.not.unique.message}"; /** * * valor de agrupación de campos para la validación por defecto se evalua campo * por campo */ boolean agrupar() default false; /** * * Atributo utilizado para la clasificacion de la agrupacion por atributos */ String alias() default ""; } ``` #### Paso refactorización de las clases serviceImpl con aspectos > Ejemplo : > Para el caso se toma como referencia la clase BancoSucursalesDto * Ir a la clase DTO y agregar la anotación **@ConstraintsUnique** a nivel de propieda ```java @Data @JsonInclude(JsonInclude.Include.NON_NULL) public class BancoSucursalesDto extends BaseDto implements Serializable{ private Long id; @ConstraintsUnique(mensaje = "{uniqueAgrupado.not.unique.message}", agrupar = true, alias = "codigoSiaf") private BancosDto bancos; @Size(min = 1, max = 50, message = "{default.invalid.size.message}") @NotBlank(message = "{default.blank.message}") @ConstraintsUnique(mensaje = "{default.not.unique.message}") @UpperCase private String nombre; @Size(max = 3, message = "{default.invalid.size.message}") @ConstraintsUnique(agrupar = true, mensaje = "{uniqueAgrupado.not.unique.message}") @UpperCase private String codigoBicSwift; @Size(max = 8, message = "{default.invalid.size.message}") @ConstraintsUnique(agrupar = true, mensaje = "{uniqueAgrupado.not.unique.message}") @UpperCase @NotNull(message = "{default.null.message}") private String codigoBicSwiftBanco; @ConstraintsUnique(mensaje = "{uniqueAgrupado.not.unique.message}", agrupar = true, alias = "codigoSiaf") @Min(value = 1, message = "{default.invalid.min.message}") @NotNull(message = "{default.null.message}") private Long codigoSiaf; } ``` --- * Ir a la clase BienEstadosServiceImpl y quitar la implementación de los métodos de : :::warning Estos métodos son necesarios quitarlos del servicio ya que el aspecto (@ValidarEliminacionLogica, @CheckValidation) actualmente realiza las operaciones de validación de campo/s unico y eliminación ::: > **validarCreacion** ```java @Override public Boolean validarCreacion(BienEstadosDto entityDto, ResponseDto<BienEstadosDto> respuesta) { // validar descripcion duplicada if (countNombreDuplicada(entityDto) > 0) { respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail( this.mensajes.getMessage( "bienestado.not.unique.message", new String[] { NOMBRE }, new Locale("es")))); return false; } return true; } ``` > **validarActualizacion** ```java @Override public Boolean validarActualizacion(BienEstadosDto entityDto, ResponseDto<BienEstadosDto> respuesta) { // validar descripcion duplicada if (countNombreDuplicada(entityDto) > 0) { respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail( this.mensajes.getMessage( "bienestado.not.unique.message", new String[] { NOMBRE }, new Locale("es")))); return false; } return true; } ``` > **validarEliminacion** ```java @Override public Boolean validarEliminacion(BienEstados entityActual, ResponseDto<BienEstadosDto> respuesta) { // validar estado bien asociados en comites if (countEstadoBienEnComites(entityActual) > 0) { respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(this.mensajes .getMessage(TEXTO_RELACIONADO, new String[] { INHABILITADO }, new Locale("es")))); return false; } // validar estado bien asociados en comites if (countEstadoBienEnRAF(entityActual) > 0) { respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(this.mensajes .getMessage(TEXTO_RELACIONADO, new String[] { INHABILITADO }, new Locale("es")))); return false; } // validar estado bien asociados en comites if (countEstadoBienEnRCF(entityActual) > 0) { respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(this.mensajes .getMessage(TEXTO_RELACIONADO, new String[] { INHABILITADO }, new Locale("es")))); return false; } return true; } ``` > Clase antes del cambio : ```java public class BienEstadosServiceImpl extends ServiceBaseImpl<BienEstados, Long, BienEstadosDto> implements BienEstadosService { private static final String NOMBRE = "nombre"; private static final String INHABILITADO = "inhabilitado"; private static final String TEXTO_RELACIONADO = "bienestado.related.value.message"; @Autowired private BienEstadosRepository repo; @Autowired private ComitesRepository comitesRepository; @Autowired private ActaRecepcionFirmasRepository actaRecepcionFirmasRepository; @Autowired private RecepcionComprobanteFirmasRepository recepcionComprobanteFirmas; @Autowired private MessageSource mensajes; @Autowired ModelMapper modelMapper; @Autowired ObjectMapper objectMapper; @Override public JpaRepository<BienEstados, Long> obtenerRepository() { return this.repo; } @Override public ModelMapper obtenerModelMapper() { return this.modelMapper; } @Override public Class<BienEstados> obtenerClaseEntidad() { return BienEstados.class; } @Override public Class<BienEstadosDto> obtenerClaseDTO() { return BienEstadosDto.class; } @Override public ObjectMapper obtenerObjectMapper() { return this.objectMapper; } @Override public ResponseDto<List<BienEstadosDto>> obtenerListadoBienEstadosIsNull() { List<BienEstados> listado = this.repo.findByBienEstadosIsNull(); ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>(); responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO())); responseDTO.setResultado(true); return responseDTO; } @Override public ResponseDto<List<BienEstadosDto>> obtenerListadoBienEstadosIsNotNull() { List<BienEstados> listado = this.repo.findByBienEstadosIsNotNull(); ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>(); responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO())); responseDTO.setResultado(true); return responseDTO; } public ResponseDto<List<BienEstadosDto>> obtenerListadoEstadosPorPadreId(Long id) { List<BienEstados> listado = this.repo.findAllByBienEstadosId(id); ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>(); responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO())); responseDTO.setResultado(true); return responseDTO; } public ResponseDto<List<BienEstadosDto>> obtenerListadoEstadosActivosPorPadreId(Long id) { List<BienEstados> listado = this.repo.findAllActivosNoBorradosByBienEstadosId(id); ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>(); responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO())); responseDTO.setResultado(true); return responseDTO; } @Override public Boolean validarCreacion(BienEstadosDto entityDto, ResponseDto<BienEstadosDto> respuesta) { // validar descripcion duplicada if (countNombreDuplicada(entityDto) > 0) { respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail( this.mensajes.getMessage( "bienestado.not.unique.message", new String[] { NOMBRE }, new Locale("es")))); return false; } return true; } @Override public Boolean validarActualizacion(BienEstadosDto entityDto, ResponseDto<BienEstadosDto> respuesta) { // validar descripcion duplicada if (countNombreDuplicada(entityDto) > 0) { respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail( this.mensajes.getMessage( "bienestado.not.unique.message", new String[] { NOMBRE }, new Locale("es")))); return false; } return true; } private Long countNombreDuplicada(BienEstadosDto entity) { Specification<BienEstados> descripcionDuplicada = new Specification<BienEstados>() { private static final long serialVersionUID = 1L; @Override public Predicate toPredicate(Root<BienEstados> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) { if (entity.getId() == null) return criteriaBuilder.and(criteriaBuilder.equal(root.get(NOMBRE), entity.getNombre().trim()), criteriaBuilder.equal(root.get("borrado"), false)); else return criteriaBuilder.and(criteriaBuilder.notEqual(root.get("id"), entity.getId()), criteriaBuilder.equal(root.get(NOMBRE), entity.getNombre().trim()), criteriaBuilder.equal(root.get("borrado"), false)); } }; return repo.count(descripcionDuplicada); } @Override public Boolean validarEliminacion(BienEstados entityActual, ResponseDto<BienEstadosDto> respuesta) { // validar estado bien asociados en comites if (countEstadoBienEnComites(entityActual) > 0) { respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(this.mensajes .getMessage(TEXTO_RELACIONADO, new String[] { INHABILITADO }, new Locale("es")))); return false; } // validar estado bien asociados en comites if (countEstadoBienEnRAF(entityActual) > 0) { respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(this.mensajes .getMessage(TEXTO_RELACIONADO, new String[] { INHABILITADO }, new Locale("es")))); return false; } // validar estado bien asociados en comites if (countEstadoBienEnRCF(entityActual) > 0) { respuesta.addMensaje(Mensaje.SIARE_VALIDATION_ERROR.getMensajeDetail(this.mensajes .getMessage(TEXTO_RELACIONADO, new String[] { INHABILITADO }, new Locale("es")))); return false; } return true; } /** * Obtiene una cantidad de registros de Comites que poseen un estado bien que se * provee por parametro * * @param entityActual -> Objeto BienEstado que condiciona la busqueda * @return cantidad de registros que cumplen con el criterio de busqueda */ private int countEstadoBienEnComites(BienEstados entityActual) { List<Comites> lista = comitesRepository.listarComitesByBienEstadoId(entityActual.getId()); return lista.size(); } /** * Obtiene una cantidad de registros de Recepcion_Actas_Firmas que poseen un * estado bien que se provee por parametro * * @param entityActual -> Objeto BienEstado que condiciona la busqueda * @return cantidad de registros que cumplen con el criterio de busqueda */ private int countEstadoBienEnRAF(BienEstados entityActual) { List<ActaRecepcionFirmas> lista = actaRecepcionFirmasRepository .listarActaRecepcionFirmasByBienEstadoId(entityActual.getId()); return lista.size(); } /** * Obtiene una cantidad de registros de Recepcion_Comprobante_Firmas que poseen * un estado bien que se provee por parametro * * @param entityActual -> Objeto BienEstado que condiciona la busqueda * @return cantidad de registros que cumplen con el criterio de busqueda */ private int countEstadoBienEnRCF(BienEstados entityActual) { List<RecepcionComprobanteFirmas> lista = recepcionComprobanteFirmas.listaRCFByEstadoBien(entityActual.getId()); return lista.size(); } } ``` > Clase luego del cambio ```java public class BienEstadosServiceImpl extends ServiceBaseImpl<BienEstados, Long, BienEstadosDto> implements BienEstadosService { @Override public ResponseDto<List<BienEstadosDto>> obtenerListadoBienEstadosIsNull() { List<BienEstados> listado = ((BienEstadosRepository) repository).findByBienEstadosIsNull(); ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>(); responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO())); responseDTO.setResultado(true); return responseDTO; } @Override public ResponseDto<List<BienEstadosDto>> obtenerListadoBienEstadosIsNotNull() { List<BienEstados> listado = ((BienEstadosRepository) repository).findByBienEstadosIsNotNull(); ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>(); responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO())); responseDTO.setResultado(true); return responseDTO; } public ResponseDto<List<BienEstadosDto>> obtenerListadoEstadosPorPadreId(Long id) { List<BienEstados> listado = ((BienEstadosRepository) repository).findAllByBienEstadosId(id); ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>(); responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO())); responseDTO.setResultado(true); return responseDTO; } public ResponseDto<List<BienEstadosDto>> obtenerListadoEstadosActivosPorPadreId(Long id) { List<BienEstados> listado = ((BienEstadosRepository) repository).findAllActivosNoBorradosByBienEstadosId(id); ResponseDto<List<BienEstadosDto>> responseDTO = new ResponseDto<>(); responseDTO.setRespuesta(ListaUtil.mapList(listado, this.obtenerClaseDTO())); responseDTO.setResultado(true); return responseDTO; } } ``` #### Ejemplo de request para validación : * Perfil utilizado : jduarte > Validación de nombre único : creación ```coffeescript curl --location --request POST 'http://localhost:8080/bien-estados' \ --header 'Content-Type: application/json' \ --data-raw '{ "nombre":"APROBADA", "descripcion":"TEST" }' ``` > Validación de nombre único : actualización ```coffeescript curl --location --request PUT 'http://localhost:8080/bien-estados' \ --header 'Content-Type: application/json' \ --data-raw '{ "id": 61, "nombre": "NEW RECORD", "descripcion":"NEW RECORD IV" }' ``` > Validación de eliminación ```coffeescript curl --location --request DELETE 'http://localhost:8080/bien-estados/61' ``` >