Skip to content

Commit 94729ab

Browse files
jeffjensenclaude
andcommitted
feat(arch): Wire presentation->services and service->integrations; refine pom output
- Each presentation module now depends on all service modules. - Each named service area depends on the integration module sharing its name; the single default 'service' module depends on every integration module. (New IT test service-integration-match covers the name-match path.) - domain-service modules describe "the service tier" (with the area name when named) rather than "the application", in both pom description and package-info. - presentation-graphql uses "Resolver" wording (pom description and main/test package-info) instead of "Request-handling". - acceptance-tests and common-testing are managed in the parent dependencyManagement TEST section with <scope>test</scope>. - Generated poms put a blank line between <dependencies> and <build>. Verify scripts updated for the reordered dependencyManagement (now a set comparison) and for the new wiring and wording. Document presentation/service wiring and DM test scope in modules.adoc. Presentation modules depend on all service modules; service modules depend on their matching integration(s) (default 'service' on all); dependencyManagement splits acceptance-tests/common-testing into a test-scoped TEST section. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01McmUDzyMrkYpbyeU5UWrot
1 parent d796d67 commit 94729ab

20 files changed

Lines changed: 168 additions & 31 deletions

File tree

src/main/resources/META-INF/archetype-post-generate.groovy

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ def modulePom = { String gId, String prefix, String ver,
122122
depsSection = "\n <dependencies>\n${section.join('\n')}\n </dependencies>\n"
123123
}
124124

125+
// #3: a blank line separates <dependencies> from <build> when both are present.
126+
def buildPart = buildSection ? "\n${buildSection}" : ''
127+
125128
"""\
126129
<?xml version="1.0" encoding="UTF-8"?>
127130
<project xmlns="http://maven.apache.org/POM/4.0.0"
@@ -139,7 +142,7 @@ def modulePom = { String gId, String prefix, String ver,
139142
140143
<artifactId>${prefix}-${aId}</artifactId>
141144
<description>${desc}</description>
142-
${depsSection}${buildSection}</project>
145+
${depsSection}${buildPart}</project>
143146
"""
144147
}
145148

@@ -327,14 +330,20 @@ public class ${className} {
327330

328331
def modulesXml = allModules.collect { " <module>../${it}</module>" }.join('\n')
329332

330-
def dmXml = allModules.collect { mod ->
333+
// #5: acceptance-tests and common-testing are test-only artifacts — managed in the TEST section
334+
// with <scope>test</scope>; every other module is managed (default/compile) in the PROD section.
335+
def DM_TEST_MODULES = ['acceptance-tests', 'common-testing']
336+
def dmEntry = { String mod ->
337+
def scopeLine = (mod in DM_TEST_MODULES) ? '\n <scope>test</scope>' : ''
331338
"""\
332339
<dependency>
333340
<groupId>${groupId}</groupId>
334341
<artifactId>${artifactId}-${mod}</artifactId>
335-
<version>\${project.version}</version>
342+
<version>\${project.version}</version>${scopeLine}
336343
</dependency>"""
337-
}.join('\n')
344+
}
345+
def dmTestXml = allModules.findAll { it in DM_TEST_MODULES }.collect(dmEntry).join('\n')
346+
def dmProdXml = allModules.findAll { !(it in DM_TEST_MODULES) }.collect(dmEntry).join('\n')
338347

339348
// ─── Parent-pom dependency blocks (instructions 4–6) ──────────────────────────
340349
// All gated on includeSpring so a Spring-off project carries no Spring artifacts.
@@ -428,9 +437,10 @@ ${modulesXml}
428437
<dependencyManagement>
429438
<dependencies>
430439
<!-- TEST -->
440+
${dmTestXml}
431441
432442
<!-- PROD -->
433-
${dmXml}${springCoreDm}
443+
${dmProdXml}${springCoreDm}
434444
</dependencies>
435445
</dependencyManagement>
436446
${parentDependencies}
@@ -661,8 +671,9 @@ serviceModules.each { svcMod ->
661671

662672
def svcDesc = svcMod == 'service' ? 'Business logic for the application.'
663673
: "Business logic for the ${label} service area."
664-
def domDesc = svcMod == 'service' ? 'Domain classes for the application.'
665-
: "Domain classes for the ${label} service area."
674+
// #4: domain-service modules describe the service tier (plus the area name when named).
675+
def domDesc = svcMod == 'service' ? 'Domain classes for the service tier.'
676+
: "Domain classes for the ${label} service tier."
666677

667678
// Domain classes owned by this service area.
668679
writeFile(projectDir, "${domMod}/pom.xml",
@@ -672,11 +683,16 @@ serviceModules.each { svcMod ->
672683
srcTree(projectDir, domMod, pkgPath, [domPkg], [domPkg], domDesc)
673684
configClass(domMod, domPkg)
674685

675-
// Business logic, depending on its own service-area domain module.
686+
// Business logic, depending on its own service-area domain module and (#2) the integration
687+
// module(s) it consumes: a named service area takes the integration sharing its name, while the
688+
// single default 'service' module takes every integration module.
689+
def svcIntegrations = svcMod == 'service'
690+
? intImplMods
691+
: integrations.findAll { it.name == label }.collect { "integration-${it.abbrev}-${it.name}" }
676692
writeFile(projectDir, "${svcMod}/pom.xml",
677693
modulePom(groupId, artifactId, version,
678694
svcMod, svcDesc,
679-
['common-domain', domMod]))
695+
['common-domain', domMod] + svcIntegrations))
680696
srcTree(projectDir, svcMod, pkgPath, [svcPkg], [svcPkg], svcDesc)
681697
configClass(svcMod, svcPkg)
682698
}
@@ -698,14 +714,21 @@ presentations.each { pType ->
698714
"Domain classes for the ${disp} presentation tier.")
699715
configClass(domMod, "domain/${pType}")
700716

717+
// #7: GraphQL presentation classes are resolvers, not request-handling controllers.
718+
def implDesc = pType == 'graphql'
719+
? "Resolver classes for the ${disp} presentation tier."
720+
: "Request-handling classes (controllers) for the ${disp} presentation tier."
721+
def implPkgDesc = pType == 'graphql'
722+
? "Resolver classes for the ${disp} presentation tier."
723+
: "Request-handling classes for the ${disp} presentation tier."
724+
// #1: every presentation module depends on all service modules.
701725
writeFile(projectDir, "${implMod}/pom.xml",
702726
modulePom(groupId, artifactId, version,
703-
implMod,
704-
"Request-handling classes (controllers) for the ${disp} presentation tier.",
705-
['common-domain', domMod] + starterDeps(presentationStarter(pType))))
727+
implMod, implDesc,
728+
['common-domain', domMod] + serviceModules + starterDeps(presentationStarter(pType))))
706729
srcTree(projectDir, implMod, pkgPath,
707730
["presentation/${pType}"], ["presentation/${pType}"],
708-
"Request-handling classes for the ${disp} presentation tier.")
731+
implPkgDesc)
709732
configClass(implMod, "presentation/${pType}")
710733
}
711734

src/site/asciidoc/modules.adoc

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ _Depends on_: `common-domain`
4141
Business logic for a service area (see the `serviceAreas` prompt).
4242
+
4343
_Package_: `{base.package}.service` or `{base.package}.service.{name}` +
44-
_Depends on_: `common-domain`, its own `domain-service` / `domain-service-{name}` module
44+
_Depends on_: `common-domain`, its own `domain-service` / `domain-service-{name}` module, and the
45+
integration module(s) it consumes — a named area takes the integration sharing its name, while the
46+
single default `service` takes every integration module
4547

4648
`domain-service` _or_ `domain-service-{name}`::
4749
Domain classes owned by a service area — one per `service` / `service-{name}` module.
@@ -103,7 +105,7 @@ Request-handling classes — controllers, resolvers, etc.
103105
(e.g. `presentation-rest`, `presentation-graphql`).
104106
+
105107
_Package_: `{base.package}.presentation.{type}` +
106-
_Depends on_: `common-domain`, `domain-{type}`
108+
_Depends on_: `common-domain`, `domain-{type}`, and all service modules
107109

108110
== Parent POM
109111

@@ -115,7 +117,9 @@ It contains:
115117
* `<parent>` — inherits `spring-boot-starter-parent` (4.1.0) via an empty `<relativePath/>`, supplying Spring dependency management and plugin defaults
116118
* `<packaging>pom</packaging>`
117119
* `<modules>` — all sibling modules referenced as `../module` relative paths, sorted alphabetically
118-
* `<dependencyManagement>` — all modules pinned to `${project.version}`, sorted alphabetically by `artifactId`
120+
* `<dependencyManagement>` — every module pinned to `${project.version}`, split into a TEST section
121+
(`acceptance-tests` and `common-testing`, at `<scope>test</scope>`) and a PROD section (all other
122+
modules, plus managed third-party dependencies such as `spring-core`)
119123
* Common properties: Java 21 via `maven.compiler.release`; UTF-8 source encoding
120124
* `<pluginManagement>` — pinned versions for `maven-compiler-plugin`, `maven-failsafe-plugin`,
121125
`maven-jar-plugin` (with `skipIfEmpty=true`), `maven-surefire-plugin`,

src/test/resources/projects/basic/verify.groovy

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ assert foundModules == moduleOrder : "parent <modules> not sorted alphabetically
109109
def dmBlock = (parentPom =~ /(?s)<dependencyManagement>.*?<\/dependencyManagement>/)[0]
110110
def dmIds = dmBlock.findAll(/<artifactId>[^<]+<\/artifactId>/).collect { it.replaceAll(/<\/?artifactId>/, '') }
111111
def dmModuleIds = dmIds.findAll { it.startsWith("${aid}-") }.collect { it.substring(aid.length() + 1) }
112-
assert dmModuleIds == modules.sort() : "parent <dependencyManagement> not sorted alphabetically"
112+
assert dmModuleIds.sort() == modules.sort() : "parent <dependencyManagement> must manage exactly the sibling modules"
113113
assert dmModuleIds.size() == modules.size() : "parent <dependencyManagement> wrong entry count: expected ${modules.size()}, got ${dmModuleIds.size()}"
114114

115115
// ── 6. Child module <parent> references parent/pom.xml ───────────────────────
@@ -260,5 +260,10 @@ check("app/src/main/java/${p}/app/config/AppServletInitializer.java")
260260
assert parentPom.contains('<artifactId>spring-boot-starter-parent</artifactId>') : 'parent must inherit spring-boot-starter-parent'
261261
assert appPom.contains('<artifactId>spring-boot-starter-web</artifactId>') : 'app must depend on spring-boot-starter-web'
262262

263+
// #1: each presentation module depends on all service modules
264+
assert text('presentation-rest/pom.xml').contains('<artifactId>service</artifactId>') : '#1: presentation-rest must depend on service'
265+
// #2: the single default 'service' module depends on every integration module
266+
assert text('service/pom.xml').contains('<artifactId>integration-db-main</artifactId>') : '#2: default service must depend on integration-db-main'
267+
263268
_buildLog.append("\n=== PASSED ===\n")
264269
true

src/test/resources/projects/blank-presentation-types/verify.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ assert moduleBlock.findAll(/<module>[^<]+<\/module>/) == moduleOrder : "parent <
6464
def dmBlock = (parentPom =~ /(?s)<dependencyManagement>.*?<\/dependencyManagement>/)[0]
6565
def dmIds = dmBlock.findAll(/<artifactId>[^<]+<\/artifactId>/).collect { it.replaceAll(/<\/?artifactId>/, '') }
6666
def dmModuleIds = dmIds.findAll { it.startsWith("${aid}-") }.collect { it.substring(aid.length() + 1) }
67-
assert dmModuleIds == modules.sort() : "parent <dependencyManagement> not sorted alphabetically"
67+
assert dmModuleIds.sort() == modules.sort() : "parent <dependencyManagement> must manage exactly the sibling modules"
6868
assert dmModuleIds.size() == modules.size() : "parent <dependencyManagement> wrong entry count: expected ${modules.size()}, got ${dmModuleIds.size()}"
6969

7070
// ── 5. Child modules reference parent/pom.xml ────────────────────────────────

src/test/resources/projects/blank-service-areas/verify.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ assert moduleBlock.findAll(/<module>[^<]+<\/module>/) == moduleOrder : "parent <
7070
def dmBlock = (parentPom =~ /(?s)<dependencyManagement>.*?<\/dependencyManagement>/)[0]
7171
def dmIds = dmBlock.findAll(/<artifactId>[^<]+<\/artifactId>/).collect { it.replaceAll(/<\/?artifactId>/, '') }
7272
def dmModuleIds = dmIds.findAll { it.startsWith("${aid}-") }.collect { it.substring(aid.length() + 1) }
73-
assert dmModuleIds == modules.sort() : "parent <dependencyManagement> not sorted alphabetically"
73+
assert dmModuleIds.sort() == modules.sort() : "parent <dependencyManagement> must manage exactly the sibling modules"
7474
assert dmModuleIds.size() == modules.size() : "parent <dependencyManagement> wrong entry count: expected ${modules.size()}, got ${dmModuleIds.size()}"
7575

7676
// ── 5. Child modules reference parent/pom.xml ────────────────────────────────

src/test/resources/projects/full/verify.groovy

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ assert moduleBlock.findAll(/<module>[^<]+<\/module>/) == moduleOrder : "parent <
7878
def dmBlock = (parentPom =~ /(?s)<dependencyManagement>.*?<\/dependencyManagement>/)[0]
7979
def dmIds = dmBlock.findAll(/<artifactId>[^<]+<\/artifactId>/).collect { it.replaceAll(/<\/?artifactId>/, '') }
8080
def dmModuleIds = dmIds.findAll { it.startsWith("${aid}-") }.collect { it.substring(aid.length() + 1) }
81-
assert dmModuleIds == modules.sort() : "parent <dependencyManagement> not sorted alphabetically"
81+
assert dmModuleIds.sort() == modules.sort() : "parent <dependencyManagement> must manage exactly the sibling modules"
8282
assert dmModuleIds.size() == modules.size() : "parent <dependencyManagement> wrong entry count: expected ${modules.size()}, got ${dmModuleIds.size()}"
8383

8484
// ── 6. Child modules reference parent/pom.xml ────────────────────────────────
@@ -317,5 +317,34 @@ assert depPom.contains('<!-- PROD -->') : 'dependency list must carry a PROD sec
317317
assert depPom.indexOf('<!-- TEST -->') < depPom.indexOf('<!-- PROD -->') : 'TEST section must come before PROD'
318318
assert depPom.indexOf('<!-- PROD -->') < depPom.indexOf('<artifactId>common-domain</artifactId>') : 'compile-scope deps must sit under PROD'
319319

320+
// ── This change set: #1 presentation→services, #2 service→integration, #4 / #5 / #7 ──
321+
322+
// #1: every presentation module depends on all service modules
323+
['presentation-rest', 'presentation-graphql'].each { pres ->
324+
['service-orders', 'service-notifications'].each { svc ->
325+
assert text("${pres}/pom.xml").contains("<artifactId>${svc}</artifactId>") : "#1: ${pres} must depend on ${svc}"
326+
}
327+
}
328+
329+
// #2: these named service areas share no name with any integration, so they take no integration dep
330+
['service-orders', 'service-notifications'].each { svc ->
331+
assert !text("${svc}/pom.xml").contains('<artifactId>integration-') : "#2: ${svc} must not take a non-matching integration"
332+
}
333+
334+
// #4: domain-service-* describe the service tier (with the area name), not "the application"
335+
assert text('domain-service-orders/pom.xml').contains('<description>Domain classes for the orders service tier.</description>') : '#4: domain-service-orders pom description'
336+
assert text("domain-service-orders/src/main/java/${p}/domain/service/orders/package-info.java").contains('Domain classes for the orders service tier.') : '#4: domain-service-orders package-info'
337+
338+
// #5: acceptance-tests and common-testing are managed in dependencyManagement at test scope
339+
['acceptance-tests', 'common-testing'].each { m ->
340+
assert dmBlock =~ /(?s)<artifactId>${aid}-${m}<\/artifactId>\s*<version>[^<]+<\/version>\s*<scope>test<\/scope>/ : "#5: ${m} must be managed at <scope>test</scope>"
341+
}
342+
343+
// #7: GraphQL presentation uses resolver wording, not request-handling
344+
assert text('presentation-graphql/pom.xml').contains('<description>Resolver classes for the GraphQL presentation tier.</description>') : '#7: presentation-graphql pom description'
345+
assert text("presentation-graphql/src/main/java/${p}/presentation/graphql/package-info.java").contains('Resolver classes for the GraphQL presentation tier.') : '#7: presentation-graphql main package-info'
346+
assert text("presentation-graphql/src/test/java/${p}/presentation/graphql/package-info.java").contains('resolver classes for the GraphQL presentation tier.') : '#7: presentation-graphql test package-info'
347+
assert !text('presentation-graphql/pom.xml').contains('Request-handling') : '#7: presentation-graphql must not use request-handling wording'
348+
320349
_buildLog.append("\n=== PASSED ===\n")
321350
true

src/test/resources/projects/malformed-integrations/verify.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ assert moduleBlock.findAll(/<module>[^<]+<\/module>/) == moduleOrder : "parent <
6969
def dmBlock = (parentPom =~ /(?s)<dependencyManagement>.*?<\/dependencyManagement>/)[0]
7070
def dmIds = dmBlock.findAll(/<artifactId>[^<]+<\/artifactId>/).collect { it.replaceAll(/<\/?artifactId>/, '') }
7171
def dmModuleIds = dmIds.findAll { it.startsWith("${aid}-") }.collect { it.substring(aid.length() + 1) }
72-
assert dmModuleIds == modules.sort() : "parent <dependencyManagement> not sorted alphabetically"
72+
assert dmModuleIds.sort() == modules.sort() : "parent <dependencyManagement> must manage exactly the sibling modules"
7373
assert dmModuleIds.size() == modules.size() : "parent <dependencyManagement> wrong entry count: expected ${modules.size()}, got ${dmModuleIds.size()}"
7474

7575
// ── 5. Child modules reference parent/pom.xml ────────────────────────────────

src/test/resources/projects/minimal/verify.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ assert moduleBlock.findAll(/<module>[^<]+<\/module>/) == moduleOrder : "parent <
6262
def dmBlock = (parentPom =~ /(?s)<dependencyManagement>.*?<\/dependencyManagement>/)[0]
6363
def dmIds = dmBlock.findAll(/<artifactId>[^<]+<\/artifactId>/).collect { it.replaceAll(/<\/?artifactId>/, '') }
6464
def dmModuleIds = dmIds.findAll { it.startsWith("${aid}-") }.collect { it.substring(aid.length() + 1) }
65-
assert dmModuleIds == modules.sort() : "parent <dependencyManagement> not sorted alphabetically"
65+
assert dmModuleIds.sort() == modules.sort() : "parent <dependencyManagement> must manage exactly the sibling modules"
6666
assert dmModuleIds.size() == modules.size() : "parent <dependencyManagement> wrong entry count: expected ${modules.size()}, got ${dmModuleIds.size()}"
6767

6868
// ── 4. Child modules reference parent/pom.xml ────────────────────────────────

src/test/resources/projects/multi-integration/verify.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ assert moduleBlock.findAll(/<module>[^<]+<\/module>/) == moduleOrder : "parent <
7575
def dmBlock = (parentPom =~ /(?s)<dependencyManagement>.*?<\/dependencyManagement>/)[0]
7676
def dmIds = dmBlock.findAll(/<artifactId>[^<]+<\/artifactId>/).collect { it.replaceAll(/<\/?artifactId>/, '') }
7777
def dmModuleIds = dmIds.findAll { it.startsWith("${aid}-") }.collect { it.substring(aid.length() + 1) }
78-
assert dmModuleIds == modules.sort() : "parent <dependencyManagement> not sorted alphabetically"
78+
assert dmModuleIds.sort() == modules.sort() : "parent <dependencyManagement> must manage exactly the sibling modules"
7979
assert dmModuleIds.size() == modules.size() : "parent <dependencyManagement> wrong entry count: expected ${modules.size()}, got ${dmModuleIds.size()}"
8080

8181
// ── 6. Child modules reference parent/pom.xml ────────────────────────────────

src/test/resources/projects/multi-service/verify.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ assert moduleBlock.findAll(/<module>[^<]+<\/module>/) == moduleOrder : "parent <
6767
def dmBlock = (parentPom =~ /(?s)<dependencyManagement>.*?<\/dependencyManagement>/)[0]
6868
def dmIds = dmBlock.findAll(/<artifactId>[^<]+<\/artifactId>/).collect { it.replaceAll(/<\/?artifactId>/, '') }
6969
def dmModuleIds = dmIds.findAll { it.startsWith("${aid}-") }.collect { it.substring(aid.length() + 1) }
70-
assert dmModuleIds == modules.sort() : "parent <dependencyManagement> not sorted alphabetically"
70+
assert dmModuleIds.sort() == modules.sort() : "parent <dependencyManagement> must manage exactly the sibling modules"
7171
assert dmModuleIds.size() == modules.size() : "parent <dependencyManagement> wrong entry count: expected ${modules.size()}, got ${dmModuleIds.size()}"
7272

7373
// ── 5. Child modules reference parent/pom.xml ────────────────────────────────

0 commit comments

Comments
 (0)