Skip to content

Commit e3077a7

Browse files
committed
GH-862 - Trigger jMolecules architecture verifications if present on the classpath.
1 parent 9b11606 commit e3077a7

File tree

4 files changed

+71
-27
lines changed

4 files changed

+71
-27
lines changed

spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.*;
2121
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.*;
2222
import static java.util.stream.Collectors.*;
23+
import static org.springframework.modulith.core.Violations.*;
2324

2425
import java.util.*;
2526
import java.util.concurrent.ConcurrentHashMap;
@@ -34,12 +35,10 @@
3435
import org.jgrapht.graph.DefaultDirectedGraph;
3536
import org.jgrapht.graph.DefaultEdge;
3637
import org.jgrapht.traverse.TopologicalOrderIterator;
37-
import org.jmolecules.archunit.JMoleculesDddRules;
3838
import org.springframework.aot.generate.Generated;
3939
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
4040
import org.springframework.lang.Nullable;
4141
import org.springframework.modulith.core.Types.JMoleculesTypes;
42-
import org.springframework.modulith.core.Violations.Violation;
4342
import org.springframework.util.Assert;
4443
import org.springframework.util.ClassUtils;
4544
import org.springframework.util.function.SingletonSupplier;
@@ -461,24 +460,22 @@ public ApplicationModules verify() {
461460
*/
462461
public Violations detectViolations() {
463462

464-
Violations violations = rootPackages.stream() //
463+
var cycleViolations = rootPackages.stream() //
465464
.map(this::assertNoCyclesFor) //
466465
.flatMap(it -> it.getDetails().stream()) //
467-
.map(Violation::new) //
468-
.collect(Violations.toViolations());
466+
.collect(toViolations());
469467

470-
if (JMoleculesTypes.areRulesPresent()) {
468+
var jMoleculesViolations = JMoleculesTypes.getRules().stream()
469+
.map(it -> it.evaluate(allClasses))
470+
.map(EvaluationResult::getFailureReport)
471+
.flatMap(it -> it.getDetails().stream())
472+
.collect(toViolations());
471473

472-
EvaluationResult result = JMoleculesDddRules.all().evaluate(allClasses);
473-
474-
for (String message : result.getFailureReport().getDetails()) {
475-
violations = violations.and(message);
476-
}
477-
}
478-
479-
return Stream.concat(rootModules.get().stream(), modules.values().stream()) //
474+
var dependencyViolations = Stream.concat(rootModules.get().stream(), modules.values().stream()) //
480475
.map(it -> it.detectDependencies(this)) //
481-
.reduce(violations, Violations::and);
476+
.reduce(NONE, Violations::and);
477+
478+
return cycleViolations.and(jMoleculesViolations).and(dependencyViolations);
482479
}
483480

484481
/**

spring-modulith-core/src/main/java/org/springframework/modulith/core/Types.java

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@
1919
import static org.springframework.modulith.core.SyntacticSugar.*;
2020

2121
import java.lang.annotation.Annotation;
22+
import java.util.ArrayList;
23+
import java.util.Collection;
2224
import java.util.function.Predicate;
2325

26+
import org.jmolecules.archunit.JMoleculesArchitectureRules;
27+
import org.jmolecules.archunit.JMoleculesDddRules;
2428
import org.springframework.lang.Nullable;
2529
import org.springframework.modulith.PackageInfo;
2630
import org.springframework.util.Assert;
@@ -29,6 +33,7 @@
2933
import com.tngtech.archunit.base.DescribedPredicate;
3034
import com.tngtech.archunit.core.domain.JavaClass;
3135
import com.tngtech.archunit.core.domain.JavaMethod;
36+
import com.tngtech.archunit.lang.ArchRule;
3237

3338
/**
3439
* @author Oliver Drotbohm
@@ -49,9 +54,14 @@ static class JMoleculesTypes {
4954
private static final String BASE_PACKAGE = "org.jmolecules";
5055
private static final String ANNOTATION_PACKAGE = BASE_PACKAGE + ".ddd.annotation";
5156
private static final String AT_ENTITY = ANNOTATION_PACKAGE + ".Entity";
52-
private static final String ARCHUNIT_RULES = BASE_PACKAGE + ".archunit.JMoleculesDddRules";
5357
private static final String MODULE = ANNOTATION_PACKAGE + ".Module";
5458

59+
private static final String DDD_RULES = BASE_PACKAGE + ".archunit.JMoleculesDddRules";
60+
private static final String ARCHITECTURE_RULES = BASE_PACKAGE + ".archunit.JMoleculesArchitectureRules";
61+
private static final String HEXAGONAL = BASE_PACKAGE + ".architecture.hexagonal.Port";
62+
private static final String LAYERED = BASE_PACKAGE + ".architecture.layered.InfrastructureLayer";
63+
private static final String ONION = BASE_PACKAGE + ".architecture.onion.classical.InfrastructureRing";
64+
5565
private static final boolean PRESENT = ClassUtils.isPresent(AT_ENTITY, JMoleculesTypes.class.getClassLoader());
5666
private static final boolean MODULE_PRESENT = ClassUtils.isPresent(MODULE, JMoleculesTypes.class.getClassLoader());
5767

@@ -89,8 +99,38 @@ public static Class<? extends Annotation> getModuleAnnotationTypeIfPresent() {
8999
}
90100
}
91101

92-
public static boolean areRulesPresent() {
93-
return ClassUtils.isPresent(ARCHUNIT_RULES, JMoleculesTypes.class.getClassLoader());
102+
/**
103+
* Returns all architectural rules to enforce depending on the classpath arrangement.
104+
*
105+
* @return will never be {@literal null}.
106+
*/
107+
public static Collection<ArchRule> getRules() {
108+
109+
var classLoader = JMoleculesTypes.class.getClassLoader();
110+
var rules = new ArrayList<ArchRule>();
111+
112+
if (ClassUtils.isPresent(DDD_RULES, classLoader)) {
113+
rules.add(JMoleculesDddRules.all());
114+
}
115+
116+
if (!ClassUtils.isPresent(ARCHITECTURE_RULES, classLoader)) {
117+
return rules;
118+
}
119+
120+
if (ClassUtils.isPresent(HEXAGONAL, classLoader)) {
121+
rules.add(JMoleculesArchitectureRules.ensureHexagonal());
122+
}
123+
124+
if (ClassUtils.isPresent(LAYERED, classLoader)) {
125+
rules.add(JMoleculesArchitectureRules.ensureLayering());
126+
}
127+
128+
if (ClassUtils.isPresent(ONION, classLoader)) {
129+
rules.add(JMoleculesArchitectureRules.ensureOnionClassical());
130+
rules.add(JMoleculesArchitectureRules.ensureOnionSimple());
131+
}
132+
133+
return rules;
94134
}
95135
}
96136

spring-modulith-core/src/main/java/org/springframework/modulith/core/Violations.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.modulith.core;
1717

18+
import static java.util.stream.Collectors.*;
19+
1820
import java.util.ArrayList;
1921
import java.util.Collections;
2022
import java.util.List;
@@ -49,13 +51,13 @@ private Violations(List<Violation> violations) {
4951
}
5052

5153
/**
52-
* A {@link Collector} to turn a {@link java.util.stream.Stream} of {@link RuntimeException}s into a
53-
* {@link Violations} instance.
54+
* A {@link Collector} to turn a {@link java.util.stream.Stream} of {@link String}s into a {@link Violations}
55+
* instance.
5456
*
5557
* @return will never be {@literal null}.
5658
*/
57-
static Collector<Violation, ?, Violations> toViolations() {
58-
return Collectors.collectingAndThen(Collectors.toList(), Violations::new);
59+
static Collector<String, ?, Violations> toViolations() {
60+
return mapping(Violation::new, collectingAndThen(Collectors.toList(), Violations::new));
5961
}
6062

6163
/*
@@ -110,13 +112,18 @@ public void throwIfPresent() {
110112
*/
111113
Violations and(Violation violation) {
112114

113-
Assert.notNull(violation, "Exception must not be null!");
115+
Assert.notNull(violation, "Violation must not be null!");
114116

115-
return new Violations(unionByMessage(violations, List.of(violation)));
117+
return and(new Violations(List.of(violation)));
116118
}
117119

118-
Violations and(Violations other) {
119-
return new Violations(unionByMessage(violations, other.violations));
120+
Violations and(Violations that) {
121+
122+
Assert.notNull(that, "Violations must not be null!");
123+
124+
return hasViolations() || that.hasViolations()
125+
? new Violations(unionByMessage(violations, that.violations))
126+
: NONE;
120127
}
121128

122129
Violations and(String violation) {

src/docs/antora/modules/ROOT/pages/verification.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ Dependencies into internals of xref:fundamentals.adoc#modules.advanced.open[Open
2929
If those are configured, dependencies to other application modules are rejected.
3030
See xref:fundamentals.adoc#modules.explicit-dependencies[Explicit Application Module Dependencies] and xref:fundamentals.adoc#modules.named-interfaces[Named Interfaces] for details.
3131

32-
Spring Modulith optionally integrates with the jMolecules ArchUnit library and, if present, automatically triggers its Domain-Driven Design verification rules described https://github.com/xmolecules/jmolecules-integrations/tree/main/jmolecules-archunit[here].
32+
Spring Modulith optionally integrates with the jMolecules ArchUnit library and, if present, automatically triggers its Domain-Driven Design and architectural verification rules described https://github.com/xmolecules/jmolecules-integrations/tree/main/jmolecules-archunit[here].

0 commit comments

Comments
 (0)