Skip to content

JNZader-Vault/apigen

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

API REST Genérica con Spring Boot

Java Spring Boot Maven MySQL License

📋 Descripción

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.

🎯 Objetivo

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

✨ Características Principales

🏗️ Arquitectura Genérica

  • Controladores Genéricos: BaseController y BaseControllerImpl para endpoints CRUD estándar
  • Servicios Genéricos: BaseService y BaseServiceImpl para lógica de negocio reutilizable
  • Repositorios Genéricos: BaseRepository con Spring Data JPA
  • Entidades Base: Base con atributos comunes (ID, estado)
  • DTOs Base: BaseDTO para transferencia de datos

🛡️ Manejo de Excepciones Robusto

  • 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

🔄 Mapeo Automático

  • GenericMapper: Conversión bidireccional entre entidades y DTOs
  • ModelMapper: Mapeo automático de propiedades
  • Configuración Centralizada: Beans de mapeo en MapperConfig

📊 Auditoría Integrada

  • Hibernate Envers: Seguimiento automático de cambios
  • CustomRevisionListener: Listener personalizado para revisiones
  • Entidad Revision: Almacenamiento de información de auditoría

✅ Validación de Datos

  • Spring Validation: Validación automática con anotaciones
  • Manejo de Errores de Validación: Respuestas detalladas para errores de validación

📄 Paginación y Ordenamiento

  • Spring Data Pageable: Soporte nativo para paginación
  • Endpoints Paginados: /paged para resultados paginados

🛠️ Tecnologías y Dependencias

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

📂 Estructura del Proyecto

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

⚙️ Configuración

🗄️ Base de Datos

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%n

📝 Configuración de Logging

El 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>

🚀 Instalación y Ejecución

Prerrequisitos

  • ☕ Java 21 o superior
  • 📦 Maven 3.6 o superior
  • 🗄️ MySQL 8.0 o superior

Pasos de Instalación

  1. Clonar el repositorio
git clone https://github.com/JNZader/apigen.git
cd apigen
  1. 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;
  1. Configurar application.properties
cp src/main/resources/application.properties.example src/main/resources/application.properties
# Editar con tus configuraciones de base de datos
  1. 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
  1. Verificar la instalación
# Verificar el health check
curl http://localhost:9000/actuator/health

# Debería retornar:
# {"status":"UP"}

🔧 Cómo Extender la API

Ejemplo Básico: Entidad Producto

Supongamos que quieres crear endpoints CRUD básicos para gestionar productos. Sigue estos pasos:

1️⃣ Crear la Entidad

// 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;
}

2️⃣ Crear el DTO

// 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;
}

3️⃣ Crear el Repositorio

// 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
}

4️⃣ Crear el Servicio

// 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
}

5️⃣ Crear el Controlador

// 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
}

6️⃣ Registrar el Mapper

// 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...
}

¡Y eso es todo! 🎉

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.

📡 Endpoints de la API

Endpoints Estándar CRUD

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

Parámetros de Paginación

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ágina
  • sort: Campo y dirección de ordenamiento

Endpoints de Monitoreo

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

🚨 Manejo de Errores

Estructura de Respuesta de Error

{
    "mensaje": "Descripción del error",
    "codigoError": 404,
    "detalles": "Información adicional del error",
    "errores": ["lista", "de", "errores", "específicos"]
}

Tipos de Errores

🔍 Recurso No Encontrado (404)

{
    "mensaje": "Entidad no encontrada",
    "codigoError": 404,
    "detalles": "El recurso solicitado no existe",
    "errores": null
}

❌ Error de Validación (400)

{
    "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"
    ]
}

⚠️ Recurso Duplicado (409)

{
    "mensaje": "El recurso ya existe",
    "codigoError": 409,
    "detalles": "Ya existe un recurso con estos datos",
    "errores": null
}

💥 Error Interno del Servidor (500)

{
    "mensaje": "Error interno del servidor",
    "codigoError": 500,
    "detalles": "Error en ProductoServiceImpl.save() en /api/v1/productos: Error de conexión",
    "errores": null
}

📚 Ejemplos de Uso

Crear un Producto

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
}

Obtener Todos los Productos

curl http://localhost:9000/api/v1/productos

Obtener Productos Paginados

curl "http://localhost:9000/api/v1/productos/paged?page=0&size=5&sort=nombre,asc"

Obtener un Producto por ID

curl http://localhost:9000/api/v1/productos/1

Actualizar un Producto

curl -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
  }'

Eliminar un Producto

curl -X DELETE http://localhost:9000/api/v1/productos/1

🏗️ Patrones de Diseño Implementados

1. Repository Pattern

  • Descripción: Abstrae el acceso a datos
  • Implementación: BaseRepository con Spring Data JPA
  • Beneficio: Desacoplamiento entre lógica de negocio y persistencia

2. Template Method Pattern

  • Descripción: Define el esqueleto de operaciones CRUD
  • Implementación: BaseServiceImpl y BaseControllerImpl
  • Beneficio: Reutilización de código y consistencia

3. Adapter Pattern

  • Descripción: Convierte objetos entre capas
  • Implementación: GenericMapper
  • Beneficio: Separación entre modelo de dominio y transferencia

4. Builder Pattern

  • Descripción: Construcción de objetos complejos
  • Implementación: Lombok @Builder en ErrorResponse
  • Beneficio: Creación flexible de objetos

5. Chain of Responsibility

  • Descripción: Procesamiento secuencial de excepciones
  • Implementación: GlobalExceptionHandler
  • Beneficio: Manejo centralizado y organizado de errores

6. Factory Method (IoC)

  • Descripción: Creación de objetos gestionada por Spring
  • Implementación: @Bean en MapperConfig
  • Beneficio: Gestión automática del ciclo de vida

About

API REST genérica y reutilizable

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages