Esta es una API REST genérica y reutilizable construida con Spring Boot que proporciona una arquitectura sólida y extensible para el desarrollo rápido de servicios CRUD. El proyecto implementa un enfoque basado en genéricos que permite crear nuevos endpoints para cualquier entidad con un mínimo de código repetitivo.
Acelerar el desarrollo de nuevos servicios al proporcionar una base arquitectónica robusta que incluye:
- Operaciones CRUD completas
- Manejo centralizado de excepciones
- Mapeo automático de entidades a DTOs
- Sistema de auditoría integrado
- Validación de datos
- Paginación y ordenamiento
- Controladores Genéricos:
BaseControlleryBaseControllerImplpara endpoints CRUD estándar - Servicios Genéricos:
BaseServiceyBaseServiceImplpara lógica de negocio reutilizable - Repositorios Genéricos:
BaseRepositorycon Spring Data JPA - Entidades Base:
Basecon atributos comunes (ID, estado) - DTOs Base:
BaseDTOpara transferencia de datos
- GlobalExceptionHandler: Manejo centralizado de errores
- Excepciones Personalizadas:
ResourceNotFoundException: Recurso no encontrado (404)OperationFailedException: Operación fallida (500)DuplicateResourceException: Recurso duplicado (409)ValidationException: Errores de validación (400)UnauthorizedActionException: Acción no autorizada (401)
- Respuestas de Error Estructuradas: Formato JSON consistente
- GenericMapper: Conversión bidireccional entre entidades y DTOs
- ModelMapper: Mapeo automático de propiedades
- Configuración Centralizada: Beans de mapeo en
MapperConfig
- Hibernate Envers: Seguimiento automático de cambios
- CustomRevisionListener: Listener personalizado para revisiones
- Entidad Revision: Almacenamiento de información de auditoría
- Spring Validation: Validación automática con anotaciones
- Manejo de Errores de Validación: Respuestas detalladas para errores de validación
- Spring Data Pageable: Soporte nativo para paginación
- Endpoints Paginados:
/pagedpara resultados paginados
| Tecnología | Versión | Propósito |
|---|---|---|
| Java | 21 | Lenguaje de programación |
| Spring Boot | 3.2.3 | Framework principal |
| Spring Data JPA | 3.2.3 | Persistencia de datos |
| Spring Web | 3.2.3 | API REST |
| Spring Validation | 3.2.3 | Validación de datos |
| Spring Actuator | 3.2.3 | Monitoreo y métricas |
| Hibernate Envers | 6.4.1 | Auditoría de entidades |
| MySQL Connector | Latest | Driver de base de datos |
| Lombok | Latest | Reducción de código boilerplate |
| ModelMapper | 3.1.0 | Mapeo de objetos |
| Micrometer | Latest | Métricas y observabilidad |
| Maven | 3.9.7 | Gestión de dependencias |
src/main/java/com/example/api/
├── 📁 config/ # Configuraciones
│ ├── CustomRevisionListener.java
│ └── MapperConfig.java
├── 📁 controllers/ # Controladores REST
│ ├── BaseController.java # Interfaz base
│ └── BaseControllerImpl.java # Implementación base
├── 📁 dto/ # Data Transfer Objects
│ └── BaseDTO.java
├── 📁 entities/ # Entidades JPA
│ ├── Base.java # Entidad base
│ └── audit/
│ └── Revision.java # Entidad de auditoría
├── 📁 mappers/ # Mapeadores
│ └── GenericMapper.java # Mapper genérico
├── 📁 repositories/ # Repositorios
│ └── BaseRepository.java # Repositorio base
├── 📁 services/ # Servicios de negocio
│ ├── BaseService.java # Interfaz base
│ └── BaseServiceImpl.java # Implementación base
├── 📁 utils/ # Utilidades y excepciones
│ ├── DuplicateResourceException.java
│ ├── ErrorResponse.java
│ ├── GlobalExceptionHandler.java
│ ├── OperationFailedException.java
│ ├── ResourceNotFoundException.java
│ ├── UnauthorizedActionException.java
│ └── ValidationException.java
└── ApiApplication.java # Clase principal
Configura la conexión a MySQL en src/main/resources/application.properties:
# Configuración de la base de datos
spring.datasource.url=jdbc:mysql://localhost:3306/tu_base_de_datos?createDatabaseIfNotExist=true
spring.datasource.username=tu_usuario
spring.datasource.password=tu_contraseña
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# Configuración JPA/Hibernate
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.jpa.properties.hibernate.format_sql=true
# Configuración del servidor
server.port=9000
# Configuración de Actuator
management.endpoints.web.exposure.include=health,info,metrics,prometheus
management.endpoint.health.show-details=always
# Configuración de logging
logging.level.com.example.api=DEBUG
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%nEl proyecto incluye configuración personalizada en src/main/resources/logback.xml:
<configuration>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/app.log</file>
<encoder>
<pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE"/>
</root>
</configuration>- ☕ Java 21 o superior
- 📦 Maven 3.6 o superior
- 🗄️ MySQL 8.0 o superior
- Clonar el repositorio
git clone https://github.com/JNZader/apigen.git
cd apigen- Configurar la base de datos
CREATE DATABASE api_generica;
CREATE USER 'api_user'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON api_generica.* TO 'api_user'@'localhost';
FLUSH PRIVILEGES;- Configurar application.properties
cp src/main/resources/application.properties.example src/main/resources/application.properties
# Editar con tus configuraciones de base de datos- Compilar y ejecutar
# Usando Maven Wrapper (recomendado)
./mvnw clean install
./mvnw spring-boot:run
# O usando Maven directamente
mvn clean install
mvn spring-boot:run- Verificar la instalación
# Verificar el health check
curl http://localhost:9000/actuator/health
# Debería retornar:
# {"status":"UP"}Supongamos que quieres crear endpoints CRUD básicos para gestionar productos. Sigue estos pasos:
// src/main/java/com/example/api/entities/Producto.java
package com.example.api.entities;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.hibernate.envers.Audited;
import java.math.BigDecimal;
@Entity
@Table(name = "productos")
@Audited
@Getter @Setter @NoArgsConstructor @AllArgsConstructor
@SuperBuilder
public class Producto extends Base {
@NotBlank(message = "El nombre es obligatorio")
@Size(min = 2, max = 100, message = "El nombre debe tener entre 2 y 100 caracteres")
@Column(nullable = false, length = 100)
private String nombre;
@DecimalMin(value = "0.01", message = "El precio debe ser mayor a 0")
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal precio;
@Size(max = 500, message = "La descripción no puede exceder 500 caracteres")
@Column(length = 500)
private String descripcion;
}// src/main/java/com/example/api/dto/ProductoDTO.java
package com.example.api.dto;
import jakarta.validation.constraints.*;
import lombok.*;
import lombok.experimental.SuperBuilder;
import java.math.BigDecimal;
@Getter @Setter @NoArgsConstructor @AllArgsConstructor
@SuperBuilder
public class ProductoDTO extends BaseDTO {
@NotBlank(message = "El nombre es obligatorio")
@Size(min = 2, max = 100, message = "El nombre debe tener entre 2 y 100 caracteres")
private String nombre;
@DecimalMin(value = "0.01", message = "El precio debe ser mayor a 0")
@NotNull(message = "El precio es obligatorio")
private BigDecimal precio;
@Size(max = 500, message = "La descripción no puede exceder 500 caracteres")
private String descripcion;
}// src/main/java/com/example/api/repositories/ProductoRepository.java
package com.example.api.repositories;
import com.example.api.entities.Producto;
import org.springframework.stereotype.Repository;
@Repository
public interface ProductoRepository extends BaseRepository<Producto, Long> {
// No necesitas agregar métodos adicionales
// Todos los métodos CRUD están disponibles automáticamente
}// src/main/java/com/example/api/services/ProductoService.java
package com.example.api.services;
import com.example.api.dto.ProductoDTO;
import com.example.api.entities.Producto;
public interface ProductoService extends BaseService<Producto, ProductoDTO, Long> {
// No necesitas agregar métodos adicionales
// Todos los métodos CRUD están disponibles automáticamente
}// src/main/java/com/example/api/services/ProductoServiceImpl.java
package com.example.api.services;
import com.example.api.dto.BaseDTO;
import com.example.api.dto.ProductoDTO;
import com.example.api.entities.Base;
import com.example.api.entities.Producto;
import com.example.api.mappers.GenericMapper;
import com.example.api.repositories.BaseRepository;
import com.example.api.repositories.ProductoRepository;
import jakarta.persistence.EntityManager;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ProductoServiceImpl extends BaseServiceImpl<Producto, ProductoDTO, Long>
implements ProductoService {
public ProductoServiceImpl(List<BaseRepository<?, Long>> repositories,
List<GenericMapper<? extends Base, ? extends BaseDTO>> mapperGen,
ProductoRepository productoRepository,
GenericMapper<Producto, ProductoDTO> mapper,
EntityManager entityManager) {
super(repositories, mapperGen, productoRepository, mapper, entityManager);
}
// No necesitas implementar métodos adicionales
// Todos los métodos CRUD están heredados de BaseServiceImpl
}// src/main/java/com/example/api/controllers/ProductoController.java
package com.example.api.controllers;
import com.example.api.dto.ProductoDTO;
import com.example.api.entities.Producto;
import com.example.api.services.ProductoServiceImpl;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/productos")
@CrossOrigin(origins = "*")
public class ProductoController extends BaseControllerImpl<Producto, ProductoDTO, ProductoServiceImpl> {
// No necesitas agregar métodos adicionales
// Todos los endpoints CRUD están disponibles automáticamente:
// GET /api/v1/productos - Listar todos
// GET /api/v1/productos/paged - Listar con paginación
// GET /api/v1/productos/{id} - Obtener por ID
// POST /api/v1/productos - Crear nuevo
// PUT /api/v1/productos/{id} - Actualizar
// DELETE /api/v1/productos/{id} - Eliminar
}// En src/main/java/com/example/api/config/MapperConfig.java
@Configuration
public class MapperConfig {
@Bean
public GenericMapper<Producto, ProductoDTO> productoMapper() {
return new GenericMapper<>(Producto.class, ProductoDTO.class);
}
// Otros mappers...
}Con estos 6 pasos simples, ahora tienes un conjunto completo de endpoints CRUD para la entidad Producto:
- ✅
GET /api/v1/productos- Listar todos los productos - ✅
GET /api/v1/productos/paged- Listar productos con paginación - ✅
GET /api/v1/productos/{id}- Obtener un producto por ID - ✅
POST /api/v1/productos- Crear un nuevo producto - ✅
PUT /api/v1/productos/{id}- Actualizar un producto existente - ✅
DELETE /api/v1/productos/{id}- Eliminar un producto
Todo funcionando automáticamente gracias a la arquitectura genérica de la API.
Para cualquier entidad (ej. /productos), los siguientes endpoints están disponibles automáticamente:
| Método | URL | Descripción | Respuesta |
|---|---|---|---|
GET |
/api/v1/{entidad} |
Obtiene todas las entidades | 200 OK |
GET |
/api/v1/{entidad}/paged?page=0&size=10&sort=id,asc |
Lista paginada | 200 OK |
GET |
/api/v1/{entidad}/{id} |
Obtiene por ID | 200 OK |
POST |
/api/v1/{entidad} |
Crea nueva entidad | 201 CREATED |
PUT |
/api/v1/{entidad}/{id} |
Actualiza entidad | 200 OK |
DELETE |
/api/v1/{entidad}/{id} |
Elimina entidad | 204 NO CONTENT |
GET /api/v1/productos/paged?page=0&size=10&sort=nombre,asc&sort=precio,desc
page: Número de página (base 0)size: Tamaño de páginasort: Campo y dirección de ordenamiento
| Endpoint | Descripción |
|---|---|
/actuator/health |
Estado de salud de la aplicación |
/actuator/info |
Información de la aplicación |
/actuator/metrics |
Métricas de la aplicación |
/actuator/prometheus |
Métricas en formato Prometheus |
{
"mensaje": "Descripción del error",
"codigoError": 404,
"detalles": "Información adicional del error",
"errores": ["lista", "de", "errores", "específicos"]
}{
"mensaje": "Entidad no encontrada",
"codigoError": 404,
"detalles": "El recurso solicitado no existe",
"errores": null
}{
"mensaje": "Error de validación",
"codigoError": 400,
"detalles": "Por favor, corrija los siguientes errores de validación",
"errores": [
"nombre: no debe estar vacío",
"precio: debe ser mayor que cero"
]
}{
"mensaje": "El recurso ya existe",
"codigoError": 409,
"detalles": "Ya existe un recurso con estos datos",
"errores": null
}{
"mensaje": "Error interno del servidor",
"codigoError": 500,
"detalles": "Error en ProductoServiceImpl.save() en /api/v1/productos: Error de conexión",
"errores": null
}curl -X POST http://localhost:9000/api/v1/productos \
-H "Content-Type: application/json" \
-d '{
"nombre": "Laptop Gaming",
"precio": 1299.99,
"descripcion": "Laptop para gaming con RTX 4060",
"estado": true
}'Respuesta:
{
"id": 1,
"nombre": "Laptop Gaming",
"precio": 1299.99,
"descripcion": "Laptop para gaming con RTX 4060",
"estado": true
}curl http://localhost:9000/api/v1/productoscurl "http://localhost:9000/api/v1/productos/paged?page=0&size=5&sort=nombre,asc"curl http://localhost:9000/api/v1/productos/1curl -X PUT http://localhost:9000/api/v1/productos/1 \
-H "Content-Type: application/json" \
-d '{
"nombre": "Laptop Gaming Pro",
"precio": 1399.99,
"descripcion": "Laptop para gaming profesional con RTX 4070",
"estado": true
}'curl -X DELETE http://localhost:9000/api/v1/productos/1- Descripción: Abstrae el acceso a datos
- Implementación:
BaseRepositorycon Spring Data JPA - Beneficio: Desacoplamiento entre lógica de negocio y persistencia
- Descripción: Define el esqueleto de operaciones CRUD
- Implementación:
BaseServiceImplyBaseControllerImpl - Beneficio: Reutilización de código y consistencia
- Descripción: Convierte objetos entre capas
- Implementación:
GenericMapper - Beneficio: Separación entre modelo de dominio y transferencia
- Descripción: Construcción de objetos complejos
- Implementación: Lombok
@BuilderenErrorResponse - Beneficio: Creación flexible de objetos
- Descripción: Procesamiento secuencial de excepciones
- Implementación:
GlobalExceptionHandler - Beneficio: Manejo centralizado y organizado de errores
- Descripción: Creación de objetos gestionada por Spring
- Implementación:
@BeanenMapperConfig - Beneficio: Gestión automática del ciclo de vida