This repository provides a practical implementation of the principles of Clean Architecture in a Spring Boot project. While Clean Architecture offers well-defined guidelines for separation of concerns and independence of frameworks, it often leaves the details of implementation to the developer.
The goal here is to serve as a clear and organized reference for structuring a scalable and maintainable backend application following Clean Architecture concepts, adapted to the Spring ecosystem.
The project is structured into distinct layers, each with a specific responsibility, allowing for low coupling, high cohesion, and easy testability:
Contains use cases — application-specific business rules.
The core of the application, containing:
- Domain entities
- Repository interfaces
- Gateway contracts
Completely independent of frameworks.
Framework-dependent layer:
- Implementations of persistence (e.g., MongoDB)
- Spring configurations (CORS, Security)
- Controllers and DTOs
- Exception handling
Standard Spring Boot config files (application.properties, etc.).
This ensures business logic is pure and testable, with all framework dependencies pushed to the outermost layer.
Clean Architecture divides the system into concentric layers, separating business rules from implementation details. Each layer has a specific and intentional responsibility.
Responsibility: Represents the core of the system, containing only business rules and entities. This layer is completely independent of frameworks and technologies.
Why?
- Keeps business logic pure and isolated from infrastructure concerns.
- Makes the system easier to test and evolve.
In the user module:
User.java: Domain entity representing a user with fields and behaviors.IUserRepository.java: An interface (contract) for user persistence. It doesn’t know how or where the data is stored.UserGateway.java: Optional gateway for external integrations (e.g., messaging, APIs) related to users.
Responsibility: Orchestrates use cases of the system. It calls gateways and repositories to execute business flows.
Why?
- Separates the "what" (business actions) from the "how" (infrastructure).
- Keeps use case logic isolated from delivery and persistence.
In the user module:
CreateUserUseCase.java: Receives user input and triggers logic to create a user.- Calls
UserGatewayorIUserRepositoryas needed.
Responsibility: Contains implementations of interfaces from the domain layer. This includes frameworks, databases, APIs, messaging, etc.
Why?
- Keeps external systems and tools encapsulated.
- Allows changing tech (e.g., from MongoDB to PostgreSQL) without touching the core logic.
In the user module:
UserRepositoryImpl.java: ImplementsIUserRepository, uses Spring Data or raw queries.UserGatewayImpl.java: ImplementsUserGateway, interacts with external services.
Responsibility: Defines configurations like dependency injection, security, exception handling, etc.
Why?
- Keeps framework-related logic centralized.
- Decouples setup logic from business code.
Responsibility: Handles HTTP requests/responses and maps them to use cases.
Why?
- Keeps delivery logic (e.g., REST, GraphQL) separate from business logic.
- Makes it easy to swap interfaces (e.g., REST → gRPC) without affecting use cases.
In the user module:
UserController.java: Exposes endpoints likePOST /users, maps request body to DTO, and callsCreateUserUseCase.
Responsibility: Defines request/response objects for API communication.
Why?
- Protects domain models from leaking to the external world.
- Allows input validation with annotations (e.g.,
@NotNull,@Email).
In the user module:
CreateUserDto.java: Contains fields likefirstName,email, and conversion methods to domain model (toDomain()).
- Keeps business logic framework-agnostic
- Promotes testability and scalability
- Encourages separation of concerns
- Facilitates maintenance and evolution