Architecture Overview
The application follows a three-layer hexagonal architecture:- core: Business logic, domain models, use cases, and repository interfaces (framework-agnostic)
- web: REST endpoints, DTOs, and mappers (driving adapter)
- data: JPA entities and repository implementations (driven adapter)
- config: Spring configuration and exception handling
Adding a New Endpoint
To add a new endpoint, you’ll work through all three layers. Let’s walk through adding a “search users by role” feature.Step 1: Define the Use Case Interface
Create a new use case interface incore/usecase/:
src/main/java/com/fbaron/user/core/usecase/FindUsersByRoleUseCase.java
Step 2: Extend the Repository Interface
Add the query method tocore/repository/UserQueryRepository.java:
src/main/java/com/fbaron/user/core/repository/UserQueryRepository.java
Step 3: Implement Business Logic
Updatecore/service/UserService.java to implement the new use case:
src/main/java/com/fbaron/user/core/service/UserService.java
Step 4: Implement Data Adapter
Updatedata/jpa/repository/UserJpaRepository.java:
src/main/java/com/fbaron/user/data/jpa/repository/UserJpaRepository.java
data/jpa/UserJpaAdapter.java:
src/main/java/com/fbaron/user/data/jpa/UserJpaAdapter.java
Step 5: Add REST Endpoint
Updateweb/rest/UserRestAdapter.java:
src/main/java/com/fbaron/user/web/rest/UserRestAdapter.java
Step 6: Wire Dependencies
No changes needed inconfig/UserBeanConfiguration.java - Spring will automatically inject the new use case since UserService implements it.
Adding a New Entity
To add a new entity (e.g.,Organization), follow this pattern:
1. Create Domain Model
src/main/java/com/fbaron/user/core/model/Organization.java
2. Create Repository Interfaces
src/main/java/com/fbaron/user/core/repository/OrganizationQueryRepository.java
src/main/java/com/fbaron/user/core/repository/OrganizationCommandRepository.java
3. Create JPA Entity
src/main/java/com/fbaron/user/data/jpa/entity/OrganizationJpaEntity.java
4. Create Database Migration
Add a new Flyway migration insrc/main/resources/db/migration/:
src/main/resources/db/migration/V2__create_organizations_table.sql
5. Create DTOs and Mappers
Follow the same pattern asUserDto, RegisterUserDto, and UserDtoMapper.
Integrating External Services
To integrate with an external API (e.g., email service):1. Define Port Interface
src/main/java/com/fbaron/user/core/port/EmailService.java
2. Create Adapter Implementation
src/main/java/com/fbaron/user/infrastructure/email/SmtpEmailAdapter.java
3. Register in Configuration
src/main/java/com/fbaron/user/config/UserBeanConfiguration.java
4. Use in Service
src/main/java/com/fbaron/user/core/service/UserService.java
Adding Custom Validation
Create custom validation annotations for business rules:src/main/java/com/fbaron/user/web/validation/ValidUsername.java
src/main/java/com/fbaron/user/web/validation/UsernameValidator.java
Adding Custom Exceptions
Create domain-specific exceptions:src/main/java/com/fbaron/user/core/exception/InvalidUserStateException.java
GlobalExceptionHandler:
src/main/java/com/fbaron/user/config/exception/GlobalExceptionHandler.java
Adding Security with Spring Security
To add authentication and authorization:1. Add Dependencies
Updatebuild.gradle:
2. Create Security Configuration
src/main/java/com/fbaron/user/config/SecurityConfig.java
Best Practices
Keep Core Framework-Agnostic
Keep Core Framework-Agnostic
The
core package should never import Spring, JPA, or web framework classes. This ensures your business logic is testable and portable.Use Interfaces for Ports
Use Interfaces for Ports
Always define interfaces for repositories and external services. This allows easy mocking in tests and swapping implementations.
Follow CQRS Pattern
Follow CQRS Pattern
Separate read operations (
UserQueryRepository) from write operations (UserCommandRepository) for better scalability and clarity.Version Your Migrations
Version Your Migrations
Always create new Flyway migrations with sequential version numbers (V1, V2, V3). Never modify existing migrations.
Use MapStruct for Mapping
Use MapStruct for Mapping
Leverage MapStruct for DTO/Entity mappings - it’s compile-time safe and generates efficient code without reflection.
Next Steps
Testing
Learn how to write unit and integration tests
API Reference
Explore the complete API documentation
Configuration
Configure the application for different environments
Troubleshooting
Common issues and solutions