This guide helps agentic coding agents work effectively in this Spring Java DDD project.
- Build:
./gradlew buildormake build - Test:
./gradlew testormake test(requires MySQL instance running) - Run:
./gradlew :runormake run - Single Test:
./gradlew test --tests "ClassName.methodName" - View Module Paths:
./gradlew view_paths
Tests and application require MySQL. Use Docker:
docker run --name mysql-ddd -p3306:3306 -e MYSQL_ROOT_PASSWORD=yourpassword -d mysql:8.0.31Or use docker-compose: docker compose up
Update database configuration in app/main/resources/application.properties.
This is a Domain-Driven Design (DDD) project with hexagonal architecture implementing a multi-module Spring Boot application:
shared: Common domain building blocks (Value Objects, Aggregate Root, Domain Events, Service annotation)mooc: MOOC bounded context (courses and course counter aggregates)backoffice: Backoffice bounded contextapp: Application entry point that wires all modules together (main class:ec.solmedia.app.Starter)
- Package declaration first
- Static imports at the top (before other imports)
- Project imports grouped first, then standard java imports
- No blank lines within import groups
- 2-space indentation
- Opening braces on same line
- Blank line between method definitions
- Private final fields at class level
- Constructor injection pattern only
finalkeyword for immutable domain classes- Empty line after opening brace for complex initialization blocks
- Classes: PascalCase
- Methods: camelCase
- Value Objects: Type + descriptor (CourseName, CourseDuration, CourseId)
- Aggregates: Entity name (Course, CoursesCounter)
- Domain Events:
{Entity}{Action}DomainEvent(CourseCreatedDomainEvent) - Commands:
{Action}{Entity}Command(CourseCreateCommand) - Queries:
{Action}{Entity}Query(FindCoursesCounterQuery) - Handlers:
{Command/Query}Handler(CourseCreateCommandHandler) - Use Cases:
{Entity}{Action}or(CourseCreator, CoursesCounterIncrementer) - Repositories: Interface (CourseRepository), Implementation (MySqlCourseRepository)
- Mappers:
{Type}Mapper(CourseMapper) - Entities:
{Type}Entity(CourseEntity, CourseIdEntity) - Test Mothers:
{Type}Mother(CourseMother, CourseCreateCommandMother)
- All domain classes are
finaland immutable where possible - Value Objects extend
StringValueObjectorIntValueObjectfrom shared module - Aggregates extend
AggregateRootand record domain events viarecord()method - Domain Events extend
DomainEventand remain infrastructure-free - Application Services use
@Servicefrom shared module (NOT Spring's) - Repository implementations use
@Primaryannotation and composition pattern
Value Objects:
public final class CourseName extends StringValueObject {
public CourseName(String value) { super(value); }
}Aggregates:
public final class Course extends AggregateRoot {
private final CourseId id;
private final CourseName name;
private final CourseDuration duration;
public Course(CourseId id, CourseName name, CourseDuration duration) {
this.id = id;
this.name = name;
this.duration = duration;
this.record(new CourseCreatedDomainEvent(id.value(), name.value(), duration.value()));
}
// Provide both value() and getValue() methods for compatibility
}Application Services:
- Use
@Serviceannotation from shared module - Constructor injection with
private finalfields - Publish domain events via
eventBus.publish(aggregate.pullDomainEvents())
Infrastructure Layer:
- JPA Entities: Use
@Entity,@EmbeddedId,@Embeddedfor value objects - Mappers: MapStruct with
@Mapper(componentModel = "spring") - Repository Implementation: Use
@Primaryand composition with Spring Data JPA
- Unit tests extend module-specific base classes (e.g.,
CoursesModuleUnitTestCase) - Infrastructure tests extend
InfrastructureTestCasewhich uses@SpringBootTest - Use object mothers for test data (e.g.,
CourseMother,CourseCreateCommandMother) - Use Datafaker library for random test data generation
- Test methods use
@DisplayNamewith Given-When-Then format - Verify domain events were published using
shouldHavePublished()
- Domain-specific exceptions extend from appropriate base classes
- Use Optional for repository search methods that may not find results
src/{context}/main/ec/solmedia/{context}/
├── {aggregate}/
│ ├── application/ # Use cases, command handlers, query handlers
│ ├── domain/ # Entities, value objects, repositories, domain events
│ └── infrastructure/ # JPA entities, repositories, mappers, controllers
- Component Scanning: Custom
@Serviceannotation filter inStarter.java - Entity Scanning:
@EntityScanand@EnableJpaRepositorieswith basePackageClasses - Database: Auto-created schema via
spring.jpa.hibernate.ddl-auto=create-drop - Virtual Threads: Enabled with
spring.threads.virtual.enabled=true - Java Toolchain: Java 24
GitHub Issues. See docs/agents/issue-tracker.md.
Uses the five canonical labels: needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix. See docs/agents/triage-labels.md.
Single-context — one CONTEXT.md at repo root. See docs/agents/domain.md.