Skip to content

Commit d796d67

Browse files
jeffjensenclaude
andcommitted
feat(arch): Prefix module artifactIds and add shared Spring dependencies
Generated module artifactIds are now "{artifactId}-{dir}" (the parent is "{artifactId}-parent"), with every parent, dependencyManagement, and inter-module dependency reference updated to match. Parent POM additions, all gated on includeSpring: - dependencyManagement: spring-core, excluding commons-logging - dependencies (test scope, all modules): spring-boot-starter-test, excluding commons-logging - dependencies (compile scope, all modules): spring-boot-starter-logging, jspecify - common-testing: spring-boot-starter-test at compile scope Also: - acceptance-tests package-info: the test package now describes the functional tests directly (no "Tests for " prefix); the main package describes supporting infrastructure for those tests - *ComponentScan Javadoc reworded to "Anchor class for basePackageClasses component scanning." - modules.adoc: document the module artifactId naming pattern Updated all 15 integration-test verify scripts for the prefixed coordinates and the new dependencies. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01McmUDzyMrkYpbyeU5UWrot
1 parent b9fa00e commit d796d67

17 files changed

Lines changed: 392 additions & 136 deletions

File tree

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

Lines changed: 94 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ def writeFile = { File base, String rel, String text ->
3333
}
3434

3535
def srcTree = { File base, String module, String pkgPath,
36-
List<String> mainSubs, List<String> testSubs, String description ->
36+
List<String> mainSubs, List<String> testSubs, String description,
37+
String testDescription = null ->
3738
ensureDir(base, "${module}/src/main/java")
3839
mainSubs.each { sub ->
3940
def fullPkg = (pkgPath + '/' + sub).replace('/', '.')
@@ -48,8 +49,10 @@ package ${fullPkg};
4849
}
4950
ensureDir(base, "${module}/src/main/resources")
5051
ensureDir(base, "${module}/src/test/java")
51-
// Test-package Javadoc reuses the main wording, prefixed to mark it as the tests for that code.
52-
def testDesc = 'Tests for ' + lcFirst(description.replaceAll(/\.\s*$/, '')) + '.'
52+
// Test-package Javadoc reuses the main wording, prefixed to mark it as the tests for that code,
53+
// unless the caller supplies an explicit testDescription (e.g. acceptance-tests, whose test
54+
// package holds the functional tests themselves rather than tests *for* the main package).
55+
def testDesc = testDescription ?: ('Tests for ' + lcFirst(description.replaceAll(/\.\s*$/, '')) + '.')
5356
testSubs.each { sub ->
5457
def fullPkg = (pkgPath + '/' + sub).replace('/', '.')
5558
def dir = "${module}/src/test/java/${pkgPath}/${sub}"
@@ -75,23 +78,37 @@ def failsafeSection = """\
7578
</build>
7679
"""
7780

78-
/** Builds a child-module pom.xml string (2-space indent).
79-
* A dependency entry is either a String (an internal module artifactId, inheriting this project's
80-
* groupId and BOM-managed version) or a Map [groupId:…, artifactId:…, scope:…] for an external
81+
/** Builds a child-module pom.xml string (2-space indent). `prefix` is the project artifactId;
82+
* every module's artifactId is rendered as "{prefix}-{aId}" and the inherited parent as
83+
* "{prefix}-parent".
84+
* A dependency entry is either a String (an internal module's bare name, rendered as
85+
* "{prefix}-{name}" with this project's groupId and BOM-managed version) or a Map
86+
* [groupId:…, artifactId:…, scope:…, exclusions:[[groupId:…, artifactId:…], …]] for an external
8187
* dependency. Dependencies are grouped into a "<!-- TEST -->" section (scope 'test', emitted first)
8288
* and a "<!-- PROD -->" section (everything else); both headers are always present. */
83-
def modulePom = { String gId, String parentAId, String ver,
89+
def modulePom = { String gId, String prefix, String ver,
8490
String aId, String desc, List deps = [], String buildSection = '' ->
8591
def depsSection = ''
8692
if (deps) {
8793
def renderDep = { dep ->
8894
def dGroup = (dep instanceof Map) ? dep.groupId : gId
89-
def dArtifact = (dep instanceof Map) ? dep.artifactId : dep
95+
def dArtifact = (dep instanceof Map) ? dep.artifactId : "${prefix}-${dep}"
9096
def dScope = (dep instanceof Map && dep.scope) ? "\n <scope>${dep.scope}</scope>" : ''
97+
def dExclusions = ''
98+
if (dep instanceof Map && dep.exclusions) {
99+
def exLines = dep.exclusions.collect { ex ->
100+
"""\
101+
<exclusion>
102+
<groupId>${ex.groupId}</groupId>
103+
<artifactId>${ex.artifactId}</artifactId>
104+
</exclusion>"""
105+
}.join('\n')
106+
dExclusions = "\n <exclusions>\n${exLines}\n </exclusions>"
107+
}
91108
"""\
92109
<dependency>
93110
<groupId>${dGroup}</groupId>
94-
<artifactId>${dArtifact}</artifactId>${dScope}
111+
<artifactId>${dArtifact}</artifactId>${dScope}${dExclusions}
95112
</dependency>"""
96113
}
97114
def isTest = { it instanceof Map && it.scope == 'test' }
@@ -115,12 +132,12 @@ def modulePom = { String gId, String parentAId, String ver,
115132
116133
<parent>
117134
<groupId>${gId}</groupId>
118-
<artifactId>${parentAId}</artifactId>
135+
<artifactId>${prefix}-parent</artifactId>
119136
<version>${ver}</version>
120137
<relativePath>../parent/pom.xml</relativePath>
121138
</parent>
122139
123-
<artifactId>${aId}</artifactId>
140+
<artifactId>${prefix}-${aId}</artifactId>
124141
<description>${desc}</description>
125142
${depsSection}${buildSection}</project>
126143
"""
@@ -272,7 +289,7 @@ def configClass = { String module, String mainSub ->
272289
package ${rootPkgFq};
273290
274291
/**
275-
* Anchor class for based package class scanning.
292+
* Anchor class for basePackageClasses component scanning.
276293
*/
277294
public interface ${scanName} {
278295
}
@@ -314,11 +331,56 @@ def dmXml = allModules.collect { mod ->
314331
"""\
315332
<dependency>
316333
<groupId>${groupId}</groupId>
317-
<artifactId>${mod}</artifactId>
334+
<artifactId>${artifactId}-${mod}</artifactId>
318335
<version>\${project.version}</version>
319336
</dependency>"""
320337
}.join('\n')
321338

339+
// ─── Parent-pom dependency blocks (instructions 4–6) ──────────────────────────
340+
// All gated on includeSpring so a Spring-off project carries no Spring artifacts.
341+
// #4 dependencyManagement PROD: manage spring-core, excluding commons-logging.
342+
// #5 dependencies TEST: spring-boot-starter-test (test scope, all submodules), no commons-logging.
343+
// #6 dependencies PROD: spring-boot-starter-logging + jspecify (compile scope, all submodules).
344+
def springCoreDm = !includeSpring ? '' : '\n' + [
345+
' <dependency>',
346+
' <groupId>org.springframework</groupId>',
347+
' <artifactId>spring-core</artifactId>',
348+
' <exclusions>',
349+
' <exclusion>',
350+
' <groupId>commons-logging</groupId>',
351+
' <artifactId>commons-logging</artifactId>',
352+
' </exclusion>',
353+
' </exclusions>',
354+
' </dependency>',
355+
].join('\n')
356+
357+
def parentDependencies = !includeSpring ? '' : '\n' + [
358+
' <dependencies>',
359+
' <!-- TEST -->',
360+
' <dependency>',
361+
' <groupId>org.springframework.boot</groupId>',
362+
' <artifactId>spring-boot-starter-test</artifactId>',
363+
' <scope>test</scope>',
364+
' <exclusions>',
365+
' <exclusion>',
366+
' <groupId>commons-logging</groupId>',
367+
' <artifactId>commons-logging</artifactId>',
368+
' </exclusion>',
369+
' </exclusions>',
370+
' </dependency>',
371+
'',
372+
' <!-- PROD -->',
373+
' <dependency>',
374+
' <groupId>org.springframework.boot</groupId>',
375+
' <artifactId>spring-boot-starter-logging</artifactId>',
376+
' </dependency>',
377+
' <dependency>',
378+
' <groupId>org.jspecify</groupId>',
379+
' <artifactId>jspecify</artifactId>',
380+
' </dependency>',
381+
' </dependencies>',
382+
].join('\n') + '\n'
383+
322384
def parentInherit = includeSpring ? """\
323385
<parent>
324386
<groupId>org.springframework.boot</groupId>
@@ -338,11 +400,11 @@ def parentPom = """\
338400
<modelVersion>4.0.0</modelVersion>
339401
340402
${parentInherit} <groupId>${groupId}</groupId>
341-
<artifactId>${artifactId}</artifactId>
403+
<artifactId>${artifactId}-parent</artifactId>
342404
<version>${version}</version>
343405
<packaging>pom</packaging>
344406
345-
<name>${artifactId}</name>
407+
<name>${artifactId}-parent</name>
346408
347409
<modules>
348410
${modulesXml}
@@ -365,10 +427,13 @@ ${modulesXml}
365427
366428
<dependencyManagement>
367429
<dependencies>
368-
${dmXml}
430+
<!-- TEST -->
431+
432+
<!-- PROD -->
433+
${dmXml}${springCoreDm}
369434
</dependencies>
370435
</dependencyManagement>
371-
436+
${parentDependencies}
372437
<build>
373438
<pluginManagement>
374439
<plugins>
@@ -537,11 +602,19 @@ configClass('common-domain', 'common/domain')
537602

538603
// ─── common-testing ───────────────────────────────────────────────────────────
539604

605+
// common-testing carries spring-boot-starter-test at compile scope (it is test-support code that
606+
// other modules compile their tests against), overriding the test-scoped copy every module inherits
607+
// from the parent (#5). No commons-logging exclusion is needed here — the parent already excludes it.
608+
def commonTestingDeps = ['common-domain']
609+
if (includeSpring) {
610+
commonTestingDeps << [groupId: 'org.springframework.boot', artifactId: 'spring-boot-starter-test',
611+
scope: 'compile']
612+
}
540613
writeFile(projectDir, 'common-testing/pom.xml',
541614
modulePom(groupId, artifactId, version,
542615
'common-testing',
543616
'Testing infrastructure common to all test modules.',
544-
['common-domain']))
617+
commonTestingDeps))
545618
srcTree(projectDir, 'common-testing', pkgPath,
546619
['common/testing'], ['common/testing'],
547620
'Test infrastructure common to all modules.')
@@ -725,5 +798,8 @@ writeFile(projectDir, 'acceptance-tests/pom.xml',
725798
'Functional acceptance tests for the application.',
726799
atDeps,
727800
failsafeSection))
801+
// The test package holds the functional tests themselves (no "Tests for " prefix); the rarely-used
802+
// main package holds supporting classes / infrastructure for those tests.
728803
srcTree(projectDir, 'acceptance-tests', pkgPath, ['at'], ['at'],
804+
'Supporting classes and infrastructure for the functional acceptance tests.',
729805
'Functional acceptance tests for the application.')

src/site/asciidoc/modules.adoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ Every module that the `app` composes also carries a Spring `@Configuration` clas
1212
`.config` sub-package; the `app` module's `Application` class `@Import`s them all so the
1313
whole tree assembles into a single Spring context.
1414

15+
== Module Naming
16+
17+
Throughout this page each module is referred to by its directory name (e.g. `common-domain`).
18+
A module's Maven `artifactId` is the project `artifactId`, followed by `-`, followed by that
19+
directory name. For a project generated with `artifactId=my-service`, the `common-domain`
20+
module's `artifactId` is `my-service-common-domain`, and the aggregator/BOM under `parent/`
21+
is `my-service-parent`. Every module inherits its `groupId` and `version` from `parent/`.
22+
This prefix applies to `artifactId`s only — the directory names and Java package names listed
23+
below are unaffected.
24+
1525
== Fixed Modules
1626

1727
These modules are always generated regardless of input.

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

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,18 @@ def check = { String rel ->
1515
assert new File(basedir, rel).exists() : "Missing: $rel"
1616
}
1717

18-
def text = { String rel ->
18+
// Instruction 2: each module's artifactId is "<artifactId property>-<dir name>". The inter-module
19+
// wiring assertions below are written in terms of bare module names, so text() strips the "<aid>-"
20+
// prefix from <artifactId> tags; the prefix itself is verified explicitly via raw() (each module's
21+
// own artifactId, the parent dependencyManagement, and the parent artifactId), and the generated
22+
// project's real Maven build — run before this script — would fail on any inconsistent reference.
23+
def aid = 'basic-test'
24+
def raw = { String rel ->
1925
new File(basedir, rel).text
2026
}
27+
def text = { String rel ->
28+
raw(rel).replace("<artifactId>${aid}-", '<artifactId>')
29+
}
2130

2231
// ── 1. parent/ module exists; no root pom.xml ────────────────────────────────
2332

@@ -41,15 +50,15 @@ assert !new File(basedir, 'integration-database-main').exists(): "'database' mus
4150

4251
// ── 4. parent/pom.xml lists every module with "../" relative paths ───────────
4352

44-
def parentPom = text('parent/pom.xml')
53+
def parentPom = raw('parent/pom.xml')
4554
modules.each { m ->
46-
assert parentPom.contains("<module>../${m}</module>") : "parent <modules> missing: ../$m"
47-
assert parentPom.contains("<artifactId>${m}</artifactId>") : "parent <dependencyManagement> missing: $m"
55+
assert parentPom.contains("<module>../${m}</module>") : "parent <modules> missing: ../$m"
56+
assert parentPom.contains("<artifactId>${aid}-${m}</artifactId>") : "parent <dependencyManagement> missing: $m"
4857
}
4958
assert parentPom.contains('<packaging>pom</packaging>') : "parent must have pom packaging"
5059
assert parentPom.contains('${project.version}') : "dependencyManagement must use \${project.version}"
5160
assert parentPom.contains('<groupId>com.example</groupId>') : 'parent must have correct groupId'
52-
assert parentPom.contains('<artifactId>basic-test</artifactId>') : 'parent must have correct artifactId'
61+
assert parentPom.contains('<artifactId>basic-test-parent</artifactId>') : 'parent must have correct artifactId'
5362
assert parentPom.contains('<version>1.0.0-SNAPSHOT</version>') : 'parent must have correct version'
5463
assert parentPom.contains('<java.version>21</java.version>') : 'parent must declare java.version=21'
5564
assert parentPom.contains('<maven.compiler.release>${java.version}</maven.compiler.release>') \
@@ -99,8 +108,9 @@ assert foundModules == moduleOrder : "parent <modules> not sorted alphabetically
99108

100109
def dmBlock = (parentPom =~ /(?s)<dependencyManagement>.*?<\/dependencyManagement>/)[0]
101110
def dmIds = dmBlock.findAll(/<artifactId>[^<]+<\/artifactId>/).collect { it.replaceAll(/<\/?artifactId>/, '') }
102-
assert dmIds == modules.sort() : "parent <dependencyManagement> not sorted alphabetically"
103-
assert dmIds.size() == modules.size() : "parent <dependencyManagement> wrong entry count: expected ${modules.size()}, got ${dmIds.size()}"
111+
def dmModuleIds = dmIds.findAll { it.startsWith("${aid}-") }.collect { it.substring(aid.length() + 1) }
112+
assert dmModuleIds == modules.sort() : "parent <dependencyManagement> not sorted alphabetically"
113+
assert dmModuleIds.size() == modules.size() : "parent <dependencyManagement> wrong entry count: expected ${modules.size()}, got ${dmModuleIds.size()}"
104114

105115
// ── 6. Child module <parent> references parent/pom.xml ───────────────────────
106116

@@ -111,8 +121,8 @@ modules.each { m ->
111121
}
112122

113123
modules.each { m ->
114-
assert text("${m}/pom.xml").contains("<artifactId>${m}</artifactId>") \
115-
: "${m}/pom.xml must declare its own artifactId as '${m}'"
124+
assert raw("${m}/pom.xml").contains("<artifactId>${aid}-${m}</artifactId>") \
125+
: "${m}/pom.xml must declare its own artifactId as '${aid}-${m}'"
116126
}
117127

118128
// ── 7. Source directory skeletons ────────────────────────────────────────────

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

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,17 @@ def check = { String rel ->
1717
assert new File(basedir, rel).exists() : "Missing: $rel"
1818
}
1919

20-
def text = { String rel ->
20+
// Instruction 2: each module's artifactId is "<artifactId property>-<dir name>". The wiring
21+
// assertions below use bare module names, so text() strips the "<aid>-" prefix from <artifactId>
22+
// tags; the prefix is verified explicitly via raw() (module's own artifactId + parent DM), and the
23+
// generated project's real Maven build — run before this script — fails on any inconsistent reference.
24+
def aid = 'blank-pres-test'
25+
def raw = { String rel ->
2126
new File(basedir, rel).text
2227
}
28+
def text = { String rel ->
29+
raw(rel).replace("<artifactId>${aid}-", '<artifactId>')
30+
}
2331

2432
// ── 1. parent/ module exists; no root pom.xml ────────────────────────────────
2533

@@ -42,10 +50,10 @@ assert !new File(basedir, 'presentation-rest').exists(): "Blank presentation ent
4250

4351
// ── 4. parent/pom.xml completeness, packaging, sort order ────────────────────
4452

45-
def parentPom = text('parent/pom.xml')
53+
def parentPom = raw('parent/pom.xml')
4654
modules.each { m ->
47-
assert parentPom.contains("<module>../${m}</module>") : "parent <modules> missing: ../$m"
48-
assert parentPom.contains("<artifactId>${m}</artifactId>") : "parent <dependencyManagement> missing: $m"
55+
assert parentPom.contains("<module>../${m}</module>") : "parent <modules> missing: ../$m"
56+
assert parentPom.contains("<artifactId>${aid}-${m}</artifactId>") : "parent <dependencyManagement> missing: $m"
4957
}
5058
assert parentPom.contains('<packaging>pom</packaging>') : "parent must have pom packaging"
5159

@@ -55,8 +63,9 @@ assert moduleBlock.findAll(/<module>[^<]+<\/module>/) == moduleOrder : "parent <
5563

5664
def dmBlock = (parentPom =~ /(?s)<dependencyManagement>.*?<\/dependencyManagement>/)[0]
5765
def dmIds = dmBlock.findAll(/<artifactId>[^<]+<\/artifactId>/).collect { it.replaceAll(/<\/?artifactId>/, '') }
58-
assert dmIds == modules.sort() : "parent <dependencyManagement> not sorted alphabetically"
59-
assert dmIds.size() == modules.size() : "parent <dependencyManagement> wrong entry count: expected ${modules.size()}, got ${dmIds.size()}"
66+
def dmModuleIds = dmIds.findAll { it.startsWith("${aid}-") }.collect { it.substring(aid.length() + 1) }
67+
assert dmModuleIds == modules.sort() : "parent <dependencyManagement> not sorted alphabetically"
68+
assert dmModuleIds.size() == modules.size() : "parent <dependencyManagement> wrong entry count: expected ${modules.size()}, got ${dmModuleIds.size()}"
6069

6170
// ── 5. Child modules reference parent/pom.xml ────────────────────────────────
6271

@@ -66,8 +75,8 @@ modules.each { m ->
6675
}
6776

6877
modules.each { m ->
69-
assert text("${m}/pom.xml").contains("<artifactId>${m}</artifactId>") \
70-
: "${m}/pom.xml must declare its own artifactId as '${m}'"
78+
assert raw("${m}/pom.xml").contains("<artifactId>${aid}-${m}</artifactId>") \
79+
: "${m}/pom.xml must declare its own artifactId as '${aid}-${m}'"
7180
}
7281

7382
// ── 6. Source skeletons ───────────────────────────────────────────────────────

0 commit comments

Comments
 (0)