Module Guidelines
Module Structure
Every module lives in src/Module/{ModuleName}/ and follows this structure:
src/Module/{ModuleName}/
├── Domain/
│ ├── Model/
│ │ ├── {Aggregate}.php # Aggregate root
│ │ ├── {ValueObject}.php # Value objects
│ │ └── {Entity}.php # Child entities
│ ├── Event/
│ │ ├── {Entity}Created.php # Domain events
│ │ └── {Entity}Updated.php
│ ├── Repository/
│ │ └── {Aggregate}RepositoryInterface.php
│ └── Exception/
│ └── {Aggregate}NotFoundException.php
├── Application/
│ ├── Command/
│ │ ├── Create{Entity}Command.php
│ │ └── Create{Entity}CommandHandler.php
│ ├── Query/
│ │ ├── Get{Entity}Query.php
│ │ └── Get{Entity}QueryHandler.php
│ └── EventHandler/
│ └── On{ExternalEvent}Handler.php
├── Infrastructure/
│ ├── Persistence/
│ │ ├── Doctrine{Aggregate}Repository.php
│ │ └── mapping/
│ │ └── {Aggregate}.orm.xml
│ └── Service/
│ └── {ExternalServiceAdapter}.php
├── Presentation/
│ ├── Controller/
│ │ └── {Entity}Controller.php
│ └── DTO/
│ ├── {Entity}Request.php
│ └── {Entity}Response.php
├── Contract/
│ └── {Module}ProviderInterface.php # Public API for other modules
└── config/
├── routes.yaml
└── services.yaml
Creating a New Module — Checklist
- Create the directory structure above
- Define the aggregate root in Domain/Model/
- Define domain events in Domain/Event/
- Define repository interface in Domain/Repository/
- Implement command/query handlers in Application/
- Implement repository in Infrastructure/Persistence/
- Create Doctrine mapping in Infrastructure/Persistence/mapping/
- Create controller and DTOs in Presentation/
- Define contract interface in Contract/ (if other modules need access)
- Add routes in config/routes.yaml
- Add services in config/services.yaml
- Register module in deptrac.yaml
- Update
.ai/context/MODULE_STATUS.md - Add events to
.ai/context/EVENT_CATALOG.md - Write unit tests for domain, integration tests for API
Boundary Rules
- Domain layer: zero framework dependencies
- Application layer: depends on Domain only (+ framework interfaces for dispatching)
- Infrastructure: depends on Domain + framework
- Presentation: depends on Application + framework
- Contract: pure PHP interfaces, depends on nothing
- Cross-module: only via Contract interfaces or domain events
Module Registration
Each module’s config/services.yaml registers its own services:
services:
App\Module\{ModuleName}\:
resource: '../../src/Module/{ModuleName}/'
Each module’s config/routes.yaml defines its API routes.