0012. Clean Architecture Package Structure Standards
Status: Accepted Date: 2025-12-10 Context: Standardize Java package structure across modules using Clean Architecture principles.
Context
The codebase had inconsistent package structures across modules:
- Some services had REST resources at root level, others in sub-packages
- Inconsistent naming conventions (Resource vs Controller)
- Infrastructure concerns mixed with domain logic
- Duplicate DTOs across modules
- No clear separation between domain, infrastructure, and presentation layers
This inconsistency made the codebase harder to navigate, understand, and maintain. New developers had to learn different patterns for each module.
Decision
We will adopt Clean Architecture package structure standards across all modules. All services, applications, and libraries must follow these standards.
Core Principles
- Separation of Concerns: Clear boundaries between domain, infrastructure, and presentation layers
- Consistent Structure: Same package structure across all modules of the same type
- Naming Conventions: Consistent naming patterns for resources, services, and repositories
- Single Source of Truth: DTOs defined once in
libs/domain-dtos, not duplicated in services
Package Structure Standards
Services (services/{service-name})
services/{service-name})io.forge.services.{service}/
├── domain/ # Business logic
│ ├── dto/ # Domain DTOs (if service-specific)
│ ├── exception/ # Domain exceptions
│ └── [Domain services/interfaces]
├── infrastructure/ # External concerns
│ ├── config/ # Configuration producers
│ ├── persistence/ # Database repositories
│ ├── mapper/ # Entity/DTO mappers
│ └── [Other infrastructure]
├── presentation/ # HTTP/REST layer
│ └── rest/ # JAX-RS resources
│ ├── exception/ # Exception mappers
│ └── [REST resources]
├── adapters/ # External system adapters (optional)
│ └── [Adapter implementations]
└── runtime/ # Runtime utilities
└── StartupBanner.java
Applications (application/{app-name})
application/{app-name})io.forge.application.{app}/
├── presentation/ # HTTP/REST layer
│ └── rest/ # JAX-RS controllers
│ └── [REST controllers]
└── runtime/ # Runtime utilities
└── StartupBanner.java
Infrastructure Libraries (libs/{library-name})
libs/{library-name})For libraries that provide infrastructure concerns (aws-api):
io.forge.{library}/
├── infrastructure/
│ ├── config/ # Client producers
│ ├── client/ # Client implementations
│ └── persistence/ # Repository implementations
└── domain/ # Interfaces (if any)
└── [Domain interfaces]
Client Libraries (libs/domain-clients)
libs/domain-clients)For REST client interfaces:
io.forge.client.{service}/
└── {Service}Client.java
Domain Libraries (libs/domain-*)
libs/domain-*)For shared domain concerns:
io.forge.domain.{concern}/
└── [Domain types, DTOs, interfaces]
Naming Conventions
REST Resources/Controllers
- Services: Use
{Service}Resource.javasuffix, place inpresentation/rest/ - Applications: Use
{Feature}Controller.javasuffix, place inpresentation/rest/ - Exception: Protocol-specific resources (OIDC) can stay in protocol packages (
oidc/)
Domain Services
- Use descriptive names:
{Purpose}Service.java - Example:
CandidateService,DocumentService
Repositories
- Use
{Entity}Repository.java - Example:
CandidateRepository,ResumeRepository
Client Producers
- Use
{Service}ClientProducer.java - Example:
S3ClientProducer,DynamoDbClientProducer
Mappers
- Use
{Entity}Mapper.java - Example:
CandidateMapper,ResumeMapper
DTO Standards
Shared DTOs
All shared DTOs must be defined in libs/domain-dtos:
io.forge.domain.dto.auth.*- Authentication DTOsio.forge.domain.dto.user.*- User DTOsio.forge.domain.dto.document.*- Document DTOs
Service-Specific DTOs
Service-specific DTOs can be defined in services/{service}/domain/dto/:
- Example:
services/document-service/domain/dto/ParseJobResponseWrapper.java
Rule: If a DTO is used by multiple services or the frontend, it must be in libs/domain-dtos.
Examples
Good Structure: actor-service
io.forge.services.actor/
├── domain/
│ └── ActorService.java
├── infrastructure/
│ └── persistence/
│ └── ActorRepository.java
└── presentation/
└── rest/
└── ActorResource.java
Good Structure: security library
io.forge.security/
├── domain/
│ ├── dto/
│ ├── exception/
│ ├── TokenValidator.java
│ └── UserAuthenticationProvider.java
├── infrastructure/
│ └── config/
│ └── CognitoClientProducer.java
├── presentation/
│ └── rest/
│ ├── exception/
│ ├── Secured.java
│ └── [Other presentation classes]
└── adapters/
└── cognito/
└── CognitoUserAuthenticationProvider.java
Consequences
Positive:
- Consistency: Same structure across all modules makes codebase predictable
- Maintainability: Clear separation of concerns reduces cognitive load
- Onboarding: New developers learn one pattern, not many
- Testability: Clean separation enables easier unit and integration testing
- Scalability: Structure supports growth without refactoring
Implementation
New Modules
All new modules must follow these standards from creation.
Existing Modules
Existing modules should be refactored to follow these standards when:
- Major feature work is being done
- Significant refactoring is already planned
- Code review identifies structure issues
Note: Not all modules need immediate refactoring. Standards apply to new code and major changes.
Validation
Code reviews should verify:
- REST resources/controllers are in
presentation/rest/ - Domain logic is in
domain/ - Infrastructure concerns are in
infrastructure/ - DTOs are not duplicated across modules
- Naming conventions are followed
Decision Owner: Architecture Team
Review Cycle: Review annually or when new module types are introduced
Updated 10 days ago