# Project Structure for Java DDD This guide covers organizing a Java project following Clean Architecture and Domain-Driven Design principles. ## Maven Structure Standard Maven project organization: ``` project-root/ ├── pom.xml ├── src/ │ ├── main/ │ │ └── java/ │ │ └── com/example/ │ │ ├── domain/ # Domain layer │ │ ├── application/ # Application layer │ │ ├── infrastructure/ # Infrastructure layer │ │ └── shared/ # Shared utilities │ └── test/ │ └── java/ │ └── com/example/ │ ├── domain/ │ ├── application/ │ ├── infrastructure/ │ └── shared/ ├── README.md └── .gitignore ``` ### Maven Dependencies Structure ```xml 4.0.0 com.example banking-system 1.0.0 21 UTF-8 junit junit-jupiter 5.10.0 test org.slf4j slf4j-api 2.0.7 org.postgresql postgresql 42.6.0 org.apache.maven.plugins maven-compiler-plugin 3.11.0 21 ``` ## Gradle Structure Gradle-based alternative: ```gradle plugins { id 'java' } java { sourceCompatibility = '21' targetCompatibility = '21' } repositories { mavenCentral() } dependencies { // Testing testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' // Logging implementation 'org.slf4j:slf4j-api:2.0.7' runtimeOnly 'org.slf4j:slf4j-simple:2.0.7' // Database implementation 'org.postgresql:postgresql:42.6.0' } test { useJUnitPlatform() } ``` ## Complete Directory Structure ### Domain Layer ``` src/main/java/com/example/domain/ ├── account/ # Bounded context: Account │ ├── Account.java # Aggregate root │ ├── AccountId.java # Value object (ID) │ ├── AccountStatus.java # Value object (enum-like) │ ├── Money.java # Value object (shared across contexts) │ ├── AccountError.java # Sealed interface for errors │ ├── AccountRepository.java # Repository interface (domain contract) │ └── DomainEventPublisher.java # Event publishing interface (optional) │ ├── transfer/ # Bounded context: Transfer │ ├── Transfer.java # Aggregate root │ ├── TransferId.java # Value object │ ├── TransferStatus.java # Status enum │ ├── TransferError.java # Errors │ ├── TransferRepository.java # Repository interface │ └── TransferService.java # Domain service (if needed) │ ├── shared/ │ ├── result/ │ │ ├── Result.java # Result interface │ │ ├── Success.java # Success case │ │ └── Failure.java # Failure case │ └── DomainEvent.java # Base domain event ``` ### Application Layer ``` src/main/java/com/example/application/ ├── account/ │ ├── OpenAccountUseCase.java # One use case per file │ ├── DepositMoneyUseCase.java │ ├── WithdrawMoneyUseCase.java │ ├── GetAccountBalanceUseCase.java │ ├── dto/ │ │ ├── OpenAccountRequest.java │ │ ├── OpenAccountResponse.java │ │ ├── DepositRequest.java │ │ └── DepositResponse.java │ └── AccountApplicationError.java # App-specific errors │ ├── transfer/ │ ├── TransferMoneyUseCase.java │ ├── GetTransferStatusUseCase.java │ ├── dto/ │ │ ├── TransferRequest.java │ │ └── TransferResponse.java │ └── TransferApplicationError.java │ ├── shared/ │ ├── UseCase.java # Interface/base class for use cases │ └── UnitOfWork.java # Transaction management interface ``` ### Infrastructure Layer ``` src/main/java/com/example/infrastructure/ ├── persistence/ │ ├── account/ │ │ ├── JdbcAccountRepository.java # Implements AccountRepository │ │ ├── AccountRowMapper.java # Database row mapping │ │ └── AccountQueries.java # SQL constants │ ├── transfer/ │ │ ├── JdbcTransferRepository.java │ │ └── TransferRowMapper.java │ ├── connection/ │ │ ├── ConnectionPool.java │ │ └── DataSourceFactory.java │ └── transaction/ │ └── JdbcUnitOfWork.java # Transaction coordinator │ ├── http/ │ ├── handler/ │ │ ├── account/ │ │ │ ├── OpenAccountHandler.java │ │ │ ├── WithdrawHandler.java │ │ │ └── GetBalanceHandler.java │ │ └── transfer/ │ │ ├── TransferHandler.java │ │ └── GetTransferStatusHandler.java │ ├── router/ │ │ └── ApiRouter.java # Route definition │ ├── response/ │ │ ├── SuccessResponse.java │ │ └── ErrorResponse.java │ └── middleware/ │ ├── ErrorHandlingMiddleware.java │ └── LoggingMiddleware.java │ ├── event/ │ ├── DomainEventPublisherImpl.java # Publishes domain events │ ├── event-handlers/ │ │ ├── AccountCreatedEventHandler.java │ │ └── TransferCompletedEventHandler.java │ └── EventDispatcher.java │ ├── config/ │ └── AppConfiguration.java # Dependency injection setup │ └── persistence/ └── migrations/ ├── V001__CreateAccountsTable.sql └── V002__CreateTransfersTable.sql ``` ### Test Structure ``` src/test/java/com/example/ ├── domain/ │ ├── account/ │ │ ├── AccountTest.java # Unit tests for Account │ │ ├── MoneyTest.java │ │ └── AccountRepositoryTest.java # Contract tests │ └── transfer/ │ ├── TransferTest.java │ └── TransferRepositoryTest.java │ ├── application/ │ ├── account/ │ │ ├── OpenAccountUseCaseTest.java │ │ ├── WithdrawMoneyUseCaseTest.java │ │ └── fixtures/ │ │ ├── AccountFixture.java # Test data builders │ │ └── MoneyFixture.java │ └── transfer/ │ └── TransferMoneyUseCaseTest.java │ ├── infrastructure/ │ ├── persistence/ │ │ ├── JdbcAccountRepositoryTest.java # Integration tests │ │ └── JdbcTransferRepositoryTest.java │ └── http/ │ └── OpenAccountHandlerTest.java │ └── acceptance/ └── OpenAccountAcceptanceTest.java # End-to-end tests ``` ## Three Organizational Approaches ### Approach 1: BC-First (Recommended for Most Projects) Organize around Bounded Contexts: ``` src/main/java/com/example/ ├── account/ # BC 1 │ ├── domain/ │ │ ├── Account.java │ │ ├── AccountError.java │ │ └── AccountRepository.java │ ├── application/ │ │ ├── OpenAccountUseCase.java │ │ └── dto/ │ └── infrastructure/ │ ├── persistence/ │ │ └── JdbcAccountRepository.java │ └── http/ │ └── OpenAccountHandler.java │ ├── transfer/ # BC 2 │ ├── domain/ │ ├── application/ │ └── infrastructure/ │ └── shared/ # Shared across BCs ├── result/ │ └── Result.java └── events/ ``` **Pros:** - Clear BC boundaries - Easy to navigate between layers within a context - Natural place for context-specific configuration - Facilitates team ownership per BC **Cons:** - Duplication across contexts - More code organization overhead ### Approach 2: Tech-First (Better for Microservices) Organize by technical layer: ``` src/main/java/com/example/ ├── domain/ │ ├── account/ │ │ ├── Account.java │ │ ├── AccountRepository.java │ │ └── AccountError.java │ └── transfer/ │ ├── application/ │ ├── account/ │ │ ├── OpenAccountUseCase.java │ │ └── dto/ │ └── transfer/ │ ├── infrastructure/ │ ├── persistence/ │ │ ├── account/ │ │ │ └── JdbcAccountRepository.java │ │ └── transfer/ │ ├── http/ │ │ ├── account/ │ │ └── transfer/ │ └── config/ │ └── shared/ ``` **Pros:** - Clear layer separation - Easy to review layer architecture - Good for enforcing dependency rules **Cons:** - Scattered BC concepts across files - Harder to find all code for one feature ### Approach 3: Hybrid (Best for Large Projects) Combine both approaches strategically: ``` src/main/java/com/example/ ├── domain/ # All domain objects, shared across project │ ├── account/ │ ├── transfer/ │ └── shared/ │ ├── application/ # All application services │ ├── account/ │ └── transfer/ │ ├── infrastructure/ # Infrastructure organized by BC │ ├── account/ │ │ ├── persistence/ │ │ └── http/ │ ├── transfer/ │ ├── config/ │ └── shared/ ``` **Pros:** - Emphasizes domain independence - Clear infrastructure layer separation - Good for large teams **Cons:** - Two different organizational styles - Requires discipline to maintain ## One Use Case Per File ### Recommended Structure ``` src/main/java/com/example/application/account/ ├── OpenAccountUseCase.java ├── DepositMoneyUseCase.java ├── WithdrawMoneyUseCase.java ├── GetAccountBalanceUseCase.java ├── CloseAccountUseCase.java └── dto/ ├── OpenAccountRequest.java ├── OpenAccountResponse.java ├── DepositRequest.java └── DepositResponse.java ``` ### Use Case File Template ```java package com.example.application.account; import com.example.domain.account.*; import com.example.shared.result.Result; import static com.example.shared.result.Result.success; import static com.example.shared.result.Result.failure; /** * OpenAccountUseCase - one file, one use case. * * Coordinates the opening of a new account: * 1. Create Account aggregate * 2. Persist via repository * 3. Publish domain events */ public class OpenAccountUseCase { private final AccountRepository accountRepository; private final AccountIdGenerator idGenerator; private final UnitOfWork unitOfWork; public OpenAccountUseCase( AccountRepository accountRepository, AccountIdGenerator idGenerator, UnitOfWork unitOfWork ) { this.accountRepository = accountRepository; this.idGenerator = idGenerator; this.unitOfWork = unitOfWork; } /** * Execute the use case. * * @param request containing account opening parameters * @return success with account ID, or failure with reason */ public Result execute( OpenAccountRequest request ) { try { // Phase 1: Create aggregate AccountId accountId = idGenerator.generate(); Result accountResult = Account.create( accountId, request.initialBalance(), request.accountHolder() ); if (accountResult instanceof Failure f) { return failure(mapError(f.error())); } Account account = ((Success) accountResult).value(); // Phase 2: Persist return unitOfWork.withTransaction(() -> { accountRepository.save(account); // Phase 3: Publish events account.publishedEvents().forEach(event -> eventPublisher.publish(event) ); return success(new OpenAccountResponse( account.id().value(), account.balance() )); }); } catch (Exception e) { logger.error("Unexpected error opening account", e); return failure(new OpenAccountError.RepositoryError("Failed to save account")); } } private OpenAccountError mapError(AccountError error) { return switch (error) { case InvalidAmountError e -> new OpenAccountError.InvalidInitialBalance(e.message()); case InvalidAccountHolderError e -> new OpenAccountError.InvalidHolder(e.message()); default -> new OpenAccountError.UnexpectedError(error.message()); }; } } /** * OpenAccountRequest - input DTO. */ public record OpenAccountRequest( Money initialBalance, String accountHolder ) { public OpenAccountRequest { if (initialBalance == null) { throw new IllegalArgumentException("Initial balance required"); } if (accountHolder == null || accountHolder.isBlank()) { throw new IllegalArgumentException("Account holder name required"); } } } /** * OpenAccountResponse - output DTO. */ public record OpenAccountResponse( String accountId, Money balance ) {} /** * OpenAccountError - use case specific errors. */ public sealed interface OpenAccountError permits InvalidInitialBalance, InvalidHolder, RepositoryError, UnexpectedError { String message(); } public record InvalidInitialBalance(String reason) implements OpenAccountError { @Override public String message() { return "Invalid initial balance: " + reason; } } // ... other error implementations ``` ## Package Naming ``` com.example # Root ├── domain # Domain layer │ ├── account # BC 1 domain │ │ └── Account.java │ └── transfer # BC 2 domain │ └── Transfer.java ├── application # Application layer │ ├── account # BC 1 use cases │ │ └── OpenAccountUseCase.java │ └── transfer # BC 2 use cases │ └── TransferMoneyUseCase.java ├── infrastructure # Infrastructure layer │ ├── persistence # Persistence adapters │ │ ├── account # BC 1 persistence │ │ └── transfer # BC 2 persistence │ ├── http # HTTP adapters │ │ ├── account # BC 1 handlers │ │ └── transfer # BC 2 handlers │ └── config # Configuration │ └── AppConfiguration.java └── shared # Shared across layers ├── result # Result ├── events # Domain events └── exceptions # Shared exceptions (use sparingly) ``` ## Example: Account BC Structure Complete example of one bounded context: ``` com/example/account/ ├── domain/ │ ├── Account.java # Aggregate root │ ├── AccountId.java # ID value object │ ├── AccountStatus.java # Status value object │ ├── AccountError.java # Sealed error interface │ ├── AccountRepository.java # Repository interface │ ├── DomainEvents.java # Domain events (AccountCreated, etc.) │ └── AccountIdGenerator.java # Generator interface │ ├── application/ │ ├── OpenAccountUseCase.java │ ├── DepositMoneyUseCase.java │ ├── WithdrawMoneyUseCase.java │ ├── GetAccountBalanceUseCase.java │ ├── ApplicationError.java # App-level errors │ ├── dto/ │ │ ├── OpenAccountRequest.java │ │ ├── OpenAccountResponse.java │ │ ├── DepositRequest.java │ │ └── DepositResponse.java │ └── fixtures/ (test directory) │ └── AccountFixture.java │ └── infrastructure/ ├── persistence/ │ ├── JdbcAccountRepository.java │ ├── AccountRowMapper.java │ └── AccountQueries.java ├── http/ │ ├── OpenAccountHandler.java │ ├── DepositHandler.java │ ├── WithdrawHandler.java │ └── GetBalanceHandler.java └── events/ └── AccountEventHandlers.java ``` ## Configuration Example ```java package com.example.infrastructure.config; public class AppConfiguration { private final DataSource dataSource; private final DomainEventPublisher eventPublisher; public AppConfiguration() { this.dataSource = createDataSource(); this.eventPublisher = createEventPublisher(); } // Account BC dependencies public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } public AccountIdGenerator accountIdGenerator() { return new UuidAccountIdGenerator(); } public OpenAccountUseCase openAccountUseCase() { return new OpenAccountUseCase( accountRepository(), accountIdGenerator(), unitOfWork() ); } // Transfer BC dependencies public TransferRepository transferRepository() { return new JdbcTransferRepository(dataSource); } public TransferMoneyUseCase transferMoneyUseCase() { return new TransferMoneyUseCase( accountRepository(), transferRepository(), unitOfWork() ); } // Shared infrastructure public UnitOfWork unitOfWork() { return new JdbcUnitOfWork(dataSource); } public DomainEventPublisher eventPublisher() { return eventPublisher; } private DataSource createDataSource() { // Database pool configuration return new HikariDataSource(); } private DomainEventPublisher createEventPublisher() { return new SimpleEventPublisher(); } } ``` ## Best Practices 1. **Organize by BC first** when starting a project 2. **One use case per file** in application layer 3. **Keep test directory structure** parallel to main 4. **Place DTOs near their use cases** (not in separate folder) 5. **Shared code in `shared` package** (Result, base classes) 6. **Database migrations** in dedicated folder 7. **Configuration at root** of infrastructure layer 8. **HTTP handlers** group by BC 9. **Repository implementations** group by BC 10. **No circular package dependencies** - enforce with checkstyle