Backend Architecture
Iquea Commerce follows a layered architecture pattern, separating concerns into distinct layers that communicate through well-defined interfaces.
Architecture Diagram
Layered Architecture Pattern
1. Controller Layer
Controllers handle HTTP requests and responses. They are responsible for:
- Receiving and validating requests
- Calling appropriate service methods
- Converting entities to DTOs (via MapStruct)
- Returning HTTP responses
Example: ProductoController
package com.edu.mcs.Iquea.controllers;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.edu.mcs.Iquea.mappers.ProductoMapper;
import com.edu.mcs.Iquea.models.Producto;
import com.edu.mcs.Iquea.models.dto.detalle.ProductoDetalleDTO;
import com.edu.mcs.Iquea.services.implementaciones.ProductoServiceImpl;
import java.util.List;
@RestController
@RequestMapping("/api/productos")
public class ProductoController {
private final ProductoServiceImpl productoService;
private final ProductoMapper productoMapper;
public ProductoController(ProductoMapper productoMapper,
ProductoServiceImpl productoService) {
this.productoMapper = productoMapper;
this.productoService = productoService;
}
@GetMapping
public ResponseEntity<List<ProductoDetalleDTO>> listarTodos() {
List<Producto> productos = productoService.obtenertodoslosproductos();
return ResponseEntity.ok(productoMapper.toDTOlist(productos));
}
@GetMapping("/{id}")
public ResponseEntity<ProductoDetalleDTO> obtenerPorId(@PathVariable Long id) {
return productoService.obtenerProductoPorId(id)
.map(p -> ResponseEntity.ok(productoMapper.toDTO(p)))
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<ProductoDetalleDTO> crear(@RequestBody ProductoDetalleDTO dto) {
Producto creado = productoService.crearProducto(dto);
return ResponseEntity.status(HttpStatus.CREATED)
.body(productoMapper.toDTO(creado));
}
@PutMapping("/sku/{sku}")
public ResponseEntity<ProductoDetalleDTO> actualizar(
@PathVariable String sku,
@RequestBody ProductoDetalleDTO dto) {
Producto actualizado = productoService.actualizarProducto(sku, dto);
return ResponseEntity.ok(productoMapper.toDTO(actualizado));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> eliminar(@PathVariable Long id) {
productoService.borrarProducto(id);
return ResponseEntity.noContent().build();
}
}
Key characteristics:
@RestController - Combines @Controller and @ResponseBody
@RequestMapping - Defines base path for all endpoints
- Constructor injection - Spring automatically injects dependencies
- DTOs instead of entities - Never expose entities directly to clients
2. Service Layer
Services contain business logic and orchestrate data operations. They:
- Implement business rules and validation
- Coordinate between multiple repositories
- Handle transactions
- Process domain logic
Interface definition:
package com.edu.mcs.Iquea.services;
import com.edu.mcs.Iquea.models.Producto;
import com.edu.mcs.Iquea.models.dto.detalle.ProductoDetalleDTO;
import java.util.List;
import java.util.Optional;
public interface IProductoService {
Producto crearProducto(ProductoDetalleDTO dto);
Optional<Producto> obtenerProductoPorId(Long id);
List<Producto> obtenertodoslosproductos();
Producto actualizarProducto(String sku, ProductoDetalleDTO dto);
void borrarProducto(Long id);
Optional<Producto> obtenerProductoPorSku(String sku);
}
Service implementation:
package com.edu.mcs.Iquea.services.implementaciones;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.edu.mcs.Iquea.mappers.ProductoMapper;
import com.edu.mcs.Iquea.models.Producto;
import com.edu.mcs.Iquea.models.dto.detalle.ProductoDetalleDTO;
import com.edu.mcs.Iquea.repositories.ProductoRepository;
import com.edu.mcs.Iquea.services.IProductoService;
import java.util.List;
import java.util.Optional;
@Service
public class ProductoServiceImpl implements IProductoService {
private final ProductoRepository productoRepository;
private final ProductoMapper productoMapper;
public ProductoServiceImpl(ProductoMapper productoMapper,
ProductoRepository productoRepository) {
this.productoMapper = productoMapper;
this.productoRepository = productoRepository;
}
@Override
@Transactional
public Producto crearProducto(ProductoDetalleDTO dto) {
if (productoRepository.existsBySku(dto.getSku())) {
throw new IllegalArgumentException(
"Ya existe un producto con el SKU: " + dto.getSku()
);
}
Producto producto = productoMapper.toEntity(dto);
return productoRepository.save(producto);
}
@Override
@Transactional(readOnly = true)
public Optional<Producto> obtenerProductoPorId(Long id) {
return productoRepository.findById(id);
}
@Override
@Transactional(readOnly = true)
public List<Producto> obtenertodoslosproductos() {
return productoRepository.findAll();
}
}
Key characteristics:
@Service - Marks as a service component
@Transactional - Manages database transactions
readOnly = true - Optimizes read-only operations
- Business validation - Checks SKU uniqueness before creating
3. Repository Layer
Repositories provide data access through Spring Data JPA. They:
- Extend
JpaRepository for CRUD operations
- Define custom queries with
@Query or method naming
- Abstract database operations
Example repository:
package com.edu.mcs.Iquea.repositories;
import com.edu.mcs.Iquea.models.Producto;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
public interface ProductoRepository extends JpaRepository<Producto, Long> {
// Method name query - Spring generates SQL automatically
boolean existsBySku(String sku);
Optional<Producto> findBySku(String sku);
// Complex method name query
List<Producto> findByNombreContainingIgnoreCaseOrDescripcionContainingIgnoreCase(
String nombre, String descripcion
);
// Custom JPQL query
@Query("SELECT p FROM Producto p WHERE p.categoria.categoria_id = :categoriaId")
List<Producto> findByCategoriaId(@Param("categoriaId") Long categoriaId);
@Query("SELECT p FROM Producto p WHERE p.es_destacado = :es_destacado")
List<Producto> findByEs_destacado(@Param("es_destacado") Boolean es_destacado);
// Query using embedded value object
List<Producto> findByPrecioCantidadBetween(
BigDecimal precioMinimo, BigDecimal precioMaximo
);
}
Key characteristics:
- No implementation needed - Spring generates it
- Type-safe queries through method names
- Support for JPQL and native SQL
- Pagination and sorting built-in
DTO Pattern with MapStruct
Why DTOs?
- Decoupling - API contracts independent of domain model
- Security - Don’t expose internal entity structure
- Flexibility - Different views of the same entity
- Performance - Send only required data
DTO Types
Detalle (Detail) DTOs - Complete entity representation:
ProductoDetalleDTO
UsuarioDetalleDTO
PedidoDetalleDTO
Resumen (Summary) DTOs - Simplified view:
ProductoResumenDTO
UsuarioResumenDTO
CategoriaResumenDTO
MapStruct Mapping
ProductoMapper example:
package com.edu.mcs.Iquea.mappers;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import com.edu.mcs.Iquea.models.Producto;
import com.edu.mcs.Iquea.models.dto.detalle.ProductoDetalleDTO;
import java.util.List;
@Mapper(componentModel = "spring", uses = {CategoriaMapperResumen.class})
public interface ProductoMapper {
// Entity to DTO - Flatten value objects
@Mapping(source = "precio.cantidad", target = "precioCantidad")
@Mapping(source = "precio.moneda", target = "precioMoneda")
@Mapping(source = "dimensiones.alto", target = "dimensionesAlto")
@Mapping(source = "dimensiones.ancho", target = "dimensionesAncho")
@Mapping(source = "dimensiones.profundidad", target = "dimensionesProfundo")
ProductoDetalleDTO toDTO(Producto producto);
// DTO to Entity - Reconstruct value objects
@Mapping(target = "precio",
expression = "java(new Precio(dto.getPrecioCantidad(), dto.getPrecioMoneda()))")
@Mapping(target = "dimensiones",
expression = "java(new Dimensiones(dto.getDimensionesAlto(), dto.getDimensionesAncho(), dto.getDimensionesProfundo()))")
Producto toEntity(ProductoDetalleDTO dto);
// Batch conversion
List<ProductoDetalleDTO> toDTOlist(List<Producto> productos);
// Update existing entity
@Mapping(target = "precio",
expression = "java(new Precio(dto.getPrecioCantidad(), dto.getPrecioMoneda()))")
void updatefromEntity(ProductoDetalleDTO dto, @MappingTarget Producto producto);
}
Exception Handling
Global exception handler:
package com.edu.mcs.Iquea.exceptions;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
// 400 — Invalid data (duplicate email, SKU, etc.)
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ApiError> handleIllegalArgument(IllegalArgumentException ex) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(new ApiError(400, ex.getMessage()));
}
// 404 — Resource not found
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ApiError> handleRuntime(RuntimeException ex) {
if (ex.getMessage() != null &&
ex.getMessage().toLowerCase().contains("no encontr")) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(new ApiError(404, ex.getMessage()));
}
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ApiError(500, "Error interno del servidor"));
}
// 500 — Any other error
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiError> handleGeneric(Exception ex) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ApiError(500, "Error interno del servidor"));
}
}
Benefits of This Architecture
Separation of Concerns
Each layer has a single, well-defined responsibility
Testability
Easy to unit test each layer independently
Maintainability
Changes in one layer don’t affect others
Scalability
Easy to add new features following the same pattern
Request Flow Example
Creating a new product:
- Client sends POST request to
/api/productos
- JwtFilter validates authentication token
- SecurityConfig checks ADMIN role authorization
- ProductoController receives request and DTO
- ProductoMapper converts DTO to Entity
- ProductoService validates business rules (SKU uniqueness)
- ProductoRepository saves to database
- Service returns saved entity
- Mapper converts entity back to DTO
- Controller returns HTTP 201 with DTO
This clean separation makes the codebase maintainable and allows developers to work on different layers simultaneously without conflicts.