AI Unified Process Spring Boot layers - service and REST API implementation and tests
94
95%
Does it follow best practices?
Impact
93%
1.03xAverage score across 4 eval scenarios
Passed
No findings from the security scan
This is a lookup used by the Spring Boot layer skills (/spring-service, /spring-rest, /spring-service-test,
/spring-rest-test) and by /jpa-persistence before writing any code. It is the only copy; the other skills
locate it by a glob for **/*spring-service/references/module-layout.md. Its job is to answer one question: does this Spring Boot backend
split hexagonal layers across separate Maven or Gradle modules, or is it one flat module? Generating flat-style
code into a hexagonal reactor (or the other way round) breaks the project's module dependency direction — for
example a JPA @Entity or a jOOQ record placed in a domain module that has no framework dependencies.
The ## Module layout section of docs/architecture/stack.md records the layout, the root package, and where each
layer's code and tests live. When it does, follow it and skip to Step 4. Run Steps 1–3 only when the section is
empty or contradicts the build files — and then say so in the report and hand off to /stack, which records
the layout; never edit docs/architecture/stack.md beyond the Markers table yourself.
Read the backend's root pom.xml (or settings.gradle(.kts)). If it is a thin aggregator —
<packaging>pom</packaging>, a <modules> list pointing at sibling directories, no Java source of its own (common
when a repository's outermost build aggregates both a frontend module and a backend directory) — descend into
whichever listed module itself declares further modules, and treat that module list as the candidate reactor.
Don't stop at the outermost aggregator if it is just a wrapper.
Strip any common project-name prefix from each module's directory name / artifactId, split the rest into tokens
on -, _, and ., and match the tokens case-insensitively against these buckets. A token must equal a keyword —
a substring is not enough, so main does not match domain, app not application, ui not build — except
infra*, which matches any token starting with infra:
| Bucket | Keywords |
|---|---|
| Domain | domain |
| Business/Application | business, application, service, core |
| Persistence adapter | postgres, jpa, jooq, persistence, db, infra*, data |
| Inbound adapter | api, web, rest, controller, ui |
| Composition root | app, bootstrap, launcher, main, runner |
When a module matches several buckets, a Domain or Business/Application match wins over Composition root
(hotel-app-core is Business/Application, not the composition root).
Classify as Hexagonal Multi-Module only if a Domain-bucket module is present and at least two of {Business/Application, Persistence adapter, Inbound adapter} are also present. Never switch pattern on a single keyword match alone.
Otherwise → Flat Single-Module. Say so explicitly ("found N modules but couldn't confidently classify them as a
layered split — implementing as a flat pattern into <module>") rather than silently picking one.
Find one already-implemented feature across the modules and copy its exact shape — don't generate purely from this heuristic in isolation:
port/port.out subpackage? Match whichever exists.@Service class directly, matching the existing asymmetric convention.XxxDto.from(domainObject)), or
a separate mapper class? Match whichever exists. Request side and response side may use different approaches —
copy each side's own convention rather than unifying them.If the reactor is Hexagonal Multi-Module but has no existing feature to copy, use this default rather than inventing textbook full hexagonal:
port subpackage).<Feature>Controller / view (inbound adapter module)
→ <Feature>Service (business module, concrete class — no interface)
→ <Feature>Repository (interface) (business module — the outbound port)
→ <Feature>RepositoryImpl (persistence adapter module, implements the port)
→ JPA entity + JpaRepository, or jOOQ DSLContext queries
→ <Feature>Response.from(...) (inbound adapter or business module — domain → response DTO)The domain module holds plain records and value types only. Never let a JPA, jOOQ, Spring, or web import leak into it — that module's whole purpose is to have zero framework dependencies.
Compile domain first, then business, then the persistence and inbound adapters (either order, they don't depend
on each other), then the composition root — following the reactor's own dependency graph rather than building
everything at once and debugging a wall of cross-module errors.
@SpringBootTest, MockMvc, a live
server — go in the composition-root module, the only module with every layer on the classpath.@DataJpaTest, @JooqTest) live in the persistence adapter module.