This is a simple submission system built following Clean Architecture and Domain-Driven Design (DDD) principles with Spring Boot, demonstrating layered architecture, event-driven design, and multiple infrastructure implementations.
This codebase follows Clean Architecture principles with Domain-Driven Design (DDD) patterns, implementing a layered architecture that separates concerns and maintains clear boundaries between different parts of the system.
Dependencies point inward: Infrastructure → Application → Domain. The Domain layer has no dependencies on external frameworks or infrastructure.
┌─────────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ Controllers, DTOs, Exception Handlers │
├─────────────────────────────────────────────────────────────┤
│ APPLICATION LAYER │
│ Use Cases, Commands, Queries, Application Services │
├─────────────────────────────────────────────────────────────┤
│ DOMAIN LAYER │
│ Entities, Value Objects, Domain Services, Events │
├─────────────────────────────────────────────────────────────┤
│ INFRASTRUCTURE LAYER │
│ Repositories, External Services, Messaging, Persistence │
└─────────────────────────────────────────────────────────────┘
The heart of the application containing pure business logic and rules. This layer has no dependencies on external frameworks or infrastructure.
domain/
├── submission/
│ ├── model/
│ │ ├── Submission.java # Aggregate Root
│ │ └── Approver.java # Value Object
│ ├── event/
│ │ └── SubmissionCreatedEvent.java
│ └── repository/
│ └── SubmissionRepository.java # Domain Interface
└── user/
├── model/
│ └── User.java # Aggregate Root
└── repository/
└── UserRepository.java # Domain Interface
Key Characteristics:
- Aggregate Roots:
SubmissionandUserencapsulate business rules - Value Objects:
Approverrepresents immutable concepts - Domain Events:
SubmissionCreatedEventfor loose coupling - Repository Interfaces: Define contracts without implementation details
Orchestrates domain objects to implement use cases. Contains application-specific business rules and coordinates between domain objects.
application/
├── submission/
│ ├── command/
│ │ ├── CreateSubmissionCommand.java
│ │ ├── ApproveSubmissionCommand.java
│ │ ├── RejectSubmissionCommand.java
│ │ └── UpdateSubmissionCommand.java
│ ├── query/
│ │ ├── FindSubmissionQuery.java
│ │ └── FindAllSubmissionsQuery.java
│ ├── usecase/
│ │ ├── SubmissionUseCase.java
│ │ ├── FindSubmissionUseCase.java
│ │ └── UpdateSubmissionUseCase.java
│ ├── dto/
│ │ ├── SubmissionId.java
│ │ └── PaginatedSubmissionsDto.java
│ ├── exception/
│ │ └── SubmissionApplicationException.java
│ └── SubmissionApplicationService.java
└── user/
├── command/
│ └── CreateUserCommand.java
├── query/
│ ├── FindUserQuery.java
│ ├── FindAllUsersQuery.java
│ └── FindUserByEmailQuery.java
├── usecase/
│ ├── UserUseCase.java
│ └── FindUserUseCase.java
├── dto/
│ └── UserId.java
├── exception/
│ └── UserApplicationException.java
└── UserApplicationService.java
Key Characteristics:
- Commands: Immutable objects for write operations
- Queries: Immutable objects for read operations with pagination support
- Use Cases: Interfaces defining application capabilities
- Application Services: Implement use cases and orchestrate domain objects
- DTOs: Data transfer objects for internal communication
Handles HTTP requests and responses. Converts external data formats to internal application formats.
presentation/
├── SubmissionController.java
├── UserController.java
├── exception/
│ └── GlobalExceptionHandler.java
└── dto/
├── request/
│ ├── CreateSubmissionRequest.java
│ ├── ApproveSubmissionRequest.java
│ ├── RejectSubmissionRequest.java
│ ├── UpdateSubmissionRequest.java
│ └── CreateUserRequest.java
├── response/
│ ├── SubmissionResponse.java
│ ├── UserResponse.java
│ └── PaginatedSubmissionsResponse.java
└── common/
└── ApproverDto.java
Key Characteristics:
- Controllers: Handle HTTP requests and delegate to application services
- Request DTOs: Validate and structure incoming data
- Response DTOs: Format data for client consumption with pagination metadata
- Global Exception Handler: Centralized error handling
Provides technical capabilities and external integrations. Implements interfaces defined by the domain and application layers.
infrastructure/
├── persistence/
│ ├── jdbc/
│ │ ├── submission/
│ │ │ ├── SubmissionEntity.java
│ │ │ ├── SubmissionApproverEntity.java
│ │ │ ├── JdbcSubmissionRepository.java
│ │ │ ├── SpringDataSubmissionRepository.java
│ │ │ └── SpringDataSubmissionApproverRepository.java
│ │ └── user/
│ │ ├── UserEntity.java
│ │ ├── JdbcUserRepository.java
│ │ └── SpringDataUserRepository.java
│ └── redis/
│ ├── RedisUserEntity.java
│ ├── RedisSubmissionEntity.java
│ ├── RedisUserRepository.java
│ └── RedisSubmissionRepository.java
├── external/
│ ├── document/
│ │ ├── client/
│ │ │ ├── DocumentServiceClient.java
│ │ │ └── HttpDocumentServiceClient.java
│ │ └── dto/
│ │ ├── request/
│ │ │ └── DocumentRequest.java
│ │ └── response/
│ │ └── DocumentResponse.java
│ └── metadata/
│ ├── client/
│ │ ├── MetadataServiceClient.java
│ │ └── HttpMetadataServiceClient.java
│ └── dto/
│ ├── request/
│ │ └── MetadataRequest.java
│ └── response/
│ └── MetadataResponse.java
└── messaging/
└── kafka/
├── config/
│ └── KafkaConfig.java
├── consumer/
│ └── KafkaDocumentCreatedEventConsumer.java
└── producer/
└── KafkaSubmissionEventPublisher.java
Key Characteristics:
- Persistence: Multiple repository implementations (JDBC, Redis) running simultaneously
- External Services: Clean integration with document and metadata services
- Messaging: Kafka integration for event-driven communication
- Separation of Concerns: Each external service has its own package
- Domain Layer: Defines interfaces (
SubmissionRepository,UserRepository) - Infrastructure Layer: Implements interfaces (
JdbcSubmissionRepository,RedisSubmissionRepository) - Benefits: Domain doesn't depend on infrastructure, easy to test and swap implementations
- Commands:
CreateSubmissionCommand,ApproveSubmissionCommand - Queries:
FindSubmissionQuery,FindAllSubmissionsQuerywith pagination - Benefits: Clear separation between read and write operations
- Domain Interfaces:
SubmissionRepository,UserRepository - Multiple Implementations: JDBC, Redis with
@Qualifierinjection - Benefits: Easy to switch persistence strategies
- Domain Events:
SubmissionCreatedEvent - Event Publishing:
SubmissionEventPublisher - Event Consumption: Kafka consumers for external events
- Benefits: Loose coupling between components
- Interfaces:
SubmissionUseCase,FindSubmissionUseCase - Implementation:
SubmissionApplicationService - Benefits: Clear contracts and testability
- Query Objects:
FindAllSubmissionsQuerywith pagination parameters - Response DTOs:
PaginatedSubmissionsDtowith metadata - Benefits: Scalable data retrieval
- Domain Layer: Pure Java, no framework dependencies
- Application Layer: Minimal framework coupling
- Infrastructure: Framework-specific implementations
- Benefits: Easy to change frameworks without affecting business logic
- JDBC: PostgreSQL for primary storage with complex relationships
- Redis: Caching and session storage for performance
- Qualifier Injection: Explicit repository selection with
@Qualifier - Benefits: Performance optimization and flexibility
- Clean Architecture: External services don't leak into domain
- DTO Conversion: External responses converted to internal format
- Request/Response Structure: Organized DTOs for external service contracts
- Benefits: Controlled data exposure and format independence
- Domain Events: Internal events for loose coupling
- Kafka Integration: External event publishing and consumption
- Benefits: Asynchronous processing and system integration
- Query Parameters: Page, size, sorting support
- Response Metadata: Total elements, pages, navigation info
- Benefits: Scalable data retrieval for large datasets
- OffsetDateTime: All date/time fields use
OffsetDateTimefor timezone awareness - Database Schema:
TIMESTAMP WITH TIME ZONEcolumns for proper timezone storage - Benefits: Consistent timezone handling across the application
- Clear Separation: Each layer has a specific responsibility
- Easy to Locate: Code is organized by domain and layer
- Reduced Coupling: Changes in one layer don't affect others
- Unit Tests: Each layer can be tested independently
- Mocking: Use case interfaces allow easy mocking
- Integration Tests: Repository implementations can be tested separately
- Domain Testing: Pure business logic without framework dependencies
- Multiple Persistence: Easy to add new repository implementations
- External Services: Clean integration with external systems
- Event-Driven: Asynchronous processing with Kafka
- Pagination: Efficient data retrieval for large datasets
- Microservices Ready: Each domain can become a separate service
- Technology Agnostic: Domain logic doesn't depend on frameworks
- Easy to Extend: New use cases can be added without changing existing code
- Multiple Implementations: Different persistence strategies can coexist
- Framework Independence: Easy to change frameworks without affecting business logic
- Domain-Driven: Business logic is the center of the system
- Ubiquitous Language: Code reflects business concepts
- Rich Domain Models: Business rules are encapsulated in domain objects
- Clean Separation: Business logic protected from technical concerns
- Clear Boundaries: Different teams can work on different layers
- Consistent Patterns: Standardized approach across the codebase
- Documentation: Code structure serves as documentation
- Parallel Development: Teams can work independently on different layers
1. Controller receives HTTP POST
2. Converts to CreateSubmissionCommand
3. Application Service orchestrates:
- Finds User (creator)
- Finds Users (approvers)
- Creates Submission domain object
- Saves via Repository
- Publishes SubmissionCreatedEvent
4. Returns SubmissionId
1. Controller receives HTTP GET with query parameters
2. Converts to FindAllSubmissionsQuery
3. Application Service:
- Calls Repository to get all submissions
- Applies pagination logic
- Returns PaginatedSubmissionsDto
4. Controller converts to PaginatedSubmissionsResponse
5. Returns HTTP response
1. Application Service needs document data
2. Calls DocumentServiceClient interface
3. HttpDocumentServiceClient makes HTTP call
4. Converts external response to internal DTO
5. Application Service uses data for business logic
- Java 17+
- Docker and Docker Compose
- Gradle
docker-compose up -dThis starts:
- PostgreSQL on
localhost:5432(user:postgres, password:password, db:ddd_submissions) - Redis on
localhost:6379 - Kafka on
localhost:9092
./gradlew build
./gradlew bootRun./gradlew test