Architectural Style
The application follows Hexagonal Architecture (also known as Ports and Adapters), combined with Domain-Driven Design principles and CQRS pattern for data access optimization.Hexagonal architecture ensures that business logic remains independent of frameworks, databases, and external services, making the system highly testable and adaptable to change.
Module Structure
The codebase is organized into four main modules, each with distinct responsibilities:Core Module (core/)
The heart of the application containing pure business logic:
model/- Domain entities and value objects (e.g.,User,UserRole)usecase/- Use case interfaces defining application capabilitiesservice/- Business logic implementationsrepository/- Repository port interfaces (abstractions)exception/- Domain-specific exceptions
src/main/java/com/fbaron/user/core/
Web Module (web/)
The driving adapter that exposes REST endpoints:
rest/- REST controllers/adapters (e.g.,UserRestAdapter)dto/- Data Transfer Objects for HTTP requests/responsesmapper/- Mappers between DTOs and domain models
src/main/java/com/fbaron/user/web/rest/UserRestAdapter.java:33
Data Module (data/)
The driven adapter implementing persistence:
jpa/- JPA implementation of repository portsUserJpaAdapter- Implements both query and command repositoriesentity/- JPA entities (e.g.,UserJpaEntity)mapper/- Mappers between JPA entities and domain modelsrepository/- Spring Data JPA repositories
src/main/java/com/fbaron/user/data/jpa/UserJpaAdapter.java:26
Configuration Module (config/)
Spring configuration and cross-cutting concerns:
UserBeanConfiguration- Wires hexagonal architecture componentsexception/GlobalExceptionHandler- Centralized error handlingopenapi/OpenApiConfig- Swagger/OpenAPI configuration
src/main/java/com/fbaron/user/config/UserBeanConfiguration.java:28
Design Patterns
The architecture employs multiple complementary patterns:Hexagonal Architecture
Ports and adapters pattern for clean separation between core and infrastructure
CQRS Pattern
Command/Query separation for optimized read and write operations
Domain-Driven Design
Rich domain models with business logic and domain exceptions
Use Case Pattern
Explicit interfaces defining application capabilities (e.g.,
RegisterUserUseCase)Additional Patterns
Repository Pattern Abstract data access through interfaces defined in the core module:UserQueryRepository- Read operationsUserCommandRepository- Write operations
UserDtoMapper- Web layer ↔ Domain modelUserJpaMapper- Domain model ↔ JPA entity
@Builder for immutable object construction:
Request Flow
Here’s how a typical request flows through the architecture:Dependency Flow
The architecture follows the Dependency Inversion Principle (DIP): Key points:- Web layer depends on use case interfaces (not implementations)
- Core service depends on repository interfaces (not implementations)
- Data layer implements repository interfaces
- All dependencies point toward abstractions
Benefits of This Architecture
Testability
Business logic can be tested with simple mocks, no framework required
Maintainability
Clear separation of concerns makes code easy to understand and modify
Flexibility
Infrastructure can be changed without affecting business rules (swap databases, add GraphQL, etc.)
Framework Independence
Core domain has no Spring, JPA, or web framework dependencies
Technology Stack
- Framework: Spring Boot 3.x
- Language: Java 21
- Database: PostgreSQL with Spring Data JPA
- API Documentation: OpenAPI 3.0 (Swagger)
- Migration: Flyway
- Build Tool: Gradle
Next Steps
Explore the detailed architecture patterns:Hexagonal Architecture
Learn about ports, adapters, and the core domain
CQRS Pattern
Understand command/query separation
Domain-Driven Design
Explore domain models and business rules