Overview
This E-Commerce Backend API is built using Clean Architecture principles, which organize code into concentric layers with clear boundaries and dependencies flowing inward. This architectural pattern ensures that business logic remains independent of frameworks, databases, and external systems.
Dependency Rule : Source code dependencies can only point inward. Inner layers know nothing about outer layers.
Architecture Layers
The codebase is organized into three main layers within each module:
Domain Layer Core business logic and rules
Application Layer Use cases and orchestration
Infrastructure Layer External concerns and frameworks
Layer Structure
Each module (e.g., usuario, producto) follows this structure:
com.example.demo.usuario/
├── domain/
│ ├── models/ # Business entities (POJOs)
│ ├── vo/ # Value Objects
│ ├── repositories/ # Repository interfaces
│ └── exceptions/ # Domain-specific exceptions
├── application/
│ ├── commands/ # Write operations
│ ├── querys/ # Read operations
│ └── usecases/ # Business use cases
└── infrastructure/
├── controllers/ # REST API endpoints
├── entities/ # JPA entities
├── repositories/ # Repository implementations
└── config/ # Framework configuration
Domain Layer
The Domain Layer contains the core business logic and is completely independent of any framework or external system.
Domain Models
Domain models represent business entities using POJOs (Plain Old Java Objects):
src/main/java/com/example/demo/usuario/domain/models/UsuarioPOJO.java
package com.example.demo.usuario.domain.models;
import com.example.demo.usuario.domain.vo.ContrasenaVO;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@ AllArgsConstructor
@ NoArgsConstructor
@ Getter
@ Setter
public class UsuarioPOJO {
private Long id ;
private String usuario ;
private ContrasenaVO contrasena ;
}
Domain models use Value Objects for complex types, ensuring validation and business rules are encapsulated.
Repository Interfaces
The domain defines repository contracts without knowing how they’re implemented:
src/main/java/com/example/demo/usuario/domain/repositories/UsuarioRepository.java
package com.example.demo.usuario.domain.repositories;
import com.example.demo.usuario.domain.models.UsuarioPOJO;
public interface UsuarioRepository {
UsuarioPOJO findUsuarioByUsuarioYContrasena ( String usuario , String contrasena );
UsuarioPOJO saveUsuario ( String usuario , String contrasena );
}
Application Layer
The Application Layer orchestrates business use cases and coordinates between the domain and infrastructure layers.
Commands and Queries (CQRS)
This layer implements the CQRS pattern with separate handlers:
src/main/java/com/example/demo/usuario/application/commands/handlers/RegistrarUsuarioHandler.java
package com.example.demo.usuario.application.commands.handlers;
import org.springframework.stereotype.Service;
import com.example.demo.usuario.application.commands.RegistrarUsuarioCommand;
import com.example.demo.usuario.domain.models.UsuarioPOJO;
import com.example.demo.usuario.domain.repositories.UsuarioRepository;
@ Service
public class RegistrarUsuarioHandler {
private final UsuarioRepository usuarioRepository ;
public RegistrarUsuarioHandler ( UsuarioRepository usuarioRepository ) {
this . usuarioRepository = usuarioRepository;
}
public UsuarioPOJO handle ( RegistrarUsuarioCommand command ) {
return usuarioRepository . saveUsuario (
command . getUsuario (),
command . getContrasena (). getValue ()
);
}
}
Handlers depend on domain repository interfaces , not concrete implementations. This maintains the dependency inversion principle.
Infrastructure Layer
The Infrastructure Layer contains all external concerns and framework-specific code.
JPA Entities
Infrastructure entities handle database persistence:
src/main/java/com/example/demo/usuario/infrastructure/entities/UsuarioEntity.java
@ Entity
@ Table ( name = "Usuario" )
public class UsuarioEntity {
@ Id
@ GeneratedValue ( strategy = GenerationType . IDENTITY )
private Long idUsuario ;
@ Column ( name = "usuario" )
private String usuario ;
@ Column ( name = "contrasena" )
private String contrasena ;
public UsuarioPOJO toDomain () {
return new UsuarioPOJO (
this . idUsuario ,
this . usuario ,
new ContrasenaVO ( this . contrasena )
);
}
public static UsuarioEntity fromDomain ( UsuarioPOJO usuario ) {
return new UsuarioEntity (
usuario . getId (),
usuario . getUsuario (),
usuario . getContrasena (). getValue ()
);
}
}
Mapper Methods : toDomain() and fromDomain() convert between infrastructure entities and domain models, maintaining separation of concerns.
Repository Adapters
Adapters implement domain repository interfaces using Spring Data JPA:
src/main/java/com/example/demo/usuario/infrastructure/repositories/UsuarioAdapterRepository.java
@ Repository
public class UsuarioAdapterRepository implements UsuarioRepository {
private final UsuarioJPARepository usuarioJPARepository ;
public UsuarioAdapterRepository ( UsuarioJPARepository usuarioJPARepository ) {
this . usuarioJPARepository = usuarioJPARepository;
}
@ Override
public UsuarioPOJO findUsuarioByUsuarioYContrasena ( String usuario , String contrasena ) {
return usuarioJPARepository
. findByUsuarioAndContrasena (usuario, contrasena)
. toDomain ();
}
@ Override
public UsuarioPOJO saveUsuario ( String usuario , String contrasena ) {
UsuarioEntity usuarioEntity = new UsuarioEntity (usuario, contrasena);
return usuarioJPARepository . save (usuarioEntity). toDomain ();
}
}
REST Controllers
Controllers handle HTTP requests and delegate to application handlers:
src/main/java/com/example/demo/usuario/infrastructure/controllers/UsuarioRestController.java
@ RestController
public class UsuarioRestController {
private final RegistrarUsuarioHandler registrarUsuarioHandler ;
private final LoginUsuarioHandler loginUsuarioHandler ;
public UsuarioRestController (
RegistrarUsuarioHandler registrarUsuarioHandler ,
LoginUsuarioHandler loginUsuarioHandler
) {
this . registrarUsuarioHandler = registrarUsuarioHandler;
this . loginUsuarioHandler = loginUsuarioHandler;
}
@ PostMapping ( "/registro" )
public UsuarioPOJO registroUsuario (@ RequestBody RegistrarUsuarioRequest request ) {
RegistrarUsuarioCommand command = new RegistrarUsuarioCommand (
request . usuario (),
new ContrasenaVO ( request . contrasena ())
);
return registrarUsuarioHandler . handle (command);
}
@ GetMapping ( "/login/{usuario}/{contrasena}" )
public UsuarioPOJO loginUsuario (
@ PathVariable String usuario ,
@ PathVariable String contrasena
) {
LoginUsuarioQuery query = new LoginUsuarioQuery (usuario, contrasena);
return loginUsuarioHandler . handle (query);
}
}
Benefits
Independence from Frameworks
Business logic doesn’t depend on Spring, JPA, or any other framework. You can change frameworks without rewriting business rules.
Domain and application layers can be tested without databases, web frameworks, or external systems.
Teams can work on different layers simultaneously with minimal conflicts.
Infrastructure can be swapped (e.g., PostgreSQL to MongoDB) without affecting business logic.
Dependency Flow
The Domain Layer has zero dependencies on outer layers, making it the most stable and reusable part of the system.
CQRS Pattern Learn how Commands and Queries separate write and read operations
Value Objects Understand how VOs encapsulate validation and business rules