GH-862 - Trigger jMolecules architecture verifications if present on the classpath.

This commit is contained in:
Oliver Drotbohm
2024-10-08 11:14:06 +02:00
parent 9b11606425
commit e3077a7abd
4 changed files with 71 additions and 27 deletions

View File

@@ -20,6 +20,7 @@ import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*;
import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.*;
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.*;
import static java.util.stream.Collectors.*;
import static org.springframework.modulith.core.Violations.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@@ -34,12 +35,10 @@ import org.jgrapht.Graph;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.traverse.TopologicalOrderIterator;
import org.jmolecules.archunit.JMoleculesDddRules;
import org.springframework.aot.generate.Generated;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.lang.Nullable;
import org.springframework.modulith.core.Types.JMoleculesTypes;
import org.springframework.modulith.core.Violations.Violation;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.function.SingletonSupplier;
@@ -461,24 +460,22 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
*/
public Violations detectViolations() {
Violations violations = rootPackages.stream() //
var cycleViolations = rootPackages.stream() //
.map(this::assertNoCyclesFor) //
.flatMap(it -> it.getDetails().stream()) //
.map(Violation::new) //
.collect(Violations.toViolations());
.collect(toViolations());
if (JMoleculesTypes.areRulesPresent()) {
var jMoleculesViolations = JMoleculesTypes.getRules().stream()
.map(it -> it.evaluate(allClasses))
.map(EvaluationResult::getFailureReport)
.flatMap(it -> it.getDetails().stream())
.collect(toViolations());
EvaluationResult result = JMoleculesDddRules.all().evaluate(allClasses);
for (String message : result.getFailureReport().getDetails()) {
violations = violations.and(message);
}
}
return Stream.concat(rootModules.get().stream(), modules.values().stream()) //
var dependencyViolations = Stream.concat(rootModules.get().stream(), modules.values().stream()) //
.map(it -> it.detectDependencies(this)) //
.reduce(violations, Violations::and);
.reduce(NONE, Violations::and);
return cycleViolations.and(jMoleculesViolations).and(dependencyViolations);
}
/**

View File

@@ -19,8 +19,12 @@ import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*;
import static org.springframework.modulith.core.SyntacticSugar.*;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Predicate;
import org.jmolecules.archunit.JMoleculesArchitectureRules;
import org.jmolecules.archunit.JMoleculesDddRules;
import org.springframework.lang.Nullable;
import org.springframework.modulith.PackageInfo;
import org.springframework.util.Assert;
@@ -29,6 +33,7 @@ import org.springframework.util.ClassUtils;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaMethod;
import com.tngtech.archunit.lang.ArchRule;
/**
* @author Oliver Drotbohm
@@ -49,9 +54,14 @@ class Types {
private static final String BASE_PACKAGE = "org.jmolecules";
private static final String ANNOTATION_PACKAGE = BASE_PACKAGE + ".ddd.annotation";
private static final String AT_ENTITY = ANNOTATION_PACKAGE + ".Entity";
private static final String ARCHUNIT_RULES = BASE_PACKAGE + ".archunit.JMoleculesDddRules";
private static final String MODULE = ANNOTATION_PACKAGE + ".Module";
private static final String DDD_RULES = BASE_PACKAGE + ".archunit.JMoleculesDddRules";
private static final String ARCHITECTURE_RULES = BASE_PACKAGE + ".archunit.JMoleculesArchitectureRules";
private static final String HEXAGONAL = BASE_PACKAGE + ".architecture.hexagonal.Port";
private static final String LAYERED = BASE_PACKAGE + ".architecture.layered.InfrastructureLayer";
private static final String ONION = BASE_PACKAGE + ".architecture.onion.classical.InfrastructureRing";
private static final boolean PRESENT = ClassUtils.isPresent(AT_ENTITY, JMoleculesTypes.class.getClassLoader());
private static final boolean MODULE_PRESENT = ClassUtils.isPresent(MODULE, JMoleculesTypes.class.getClassLoader());
@@ -89,8 +99,38 @@ class Types {
}
}
public static boolean areRulesPresent() {
return ClassUtils.isPresent(ARCHUNIT_RULES, JMoleculesTypes.class.getClassLoader());
/**
* Returns all architectural rules to enforce depending on the classpath arrangement.
*
* @return will never be {@literal null}.
*/
public static Collection<ArchRule> getRules() {
var classLoader = JMoleculesTypes.class.getClassLoader();
var rules = new ArrayList<ArchRule>();
if (ClassUtils.isPresent(DDD_RULES, classLoader)) {
rules.add(JMoleculesDddRules.all());
}
if (!ClassUtils.isPresent(ARCHITECTURE_RULES, classLoader)) {
return rules;
}
if (ClassUtils.isPresent(HEXAGONAL, classLoader)) {
rules.add(JMoleculesArchitectureRules.ensureHexagonal());
}
if (ClassUtils.isPresent(LAYERED, classLoader)) {
rules.add(JMoleculesArchitectureRules.ensureLayering());
}
if (ClassUtils.isPresent(ONION, classLoader)) {
rules.add(JMoleculesArchitectureRules.ensureOnionClassical());
rules.add(JMoleculesArchitectureRules.ensureOnionSimple());
}
return rules;
}
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.modulith.core;
import static java.util.stream.Collectors.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -49,13 +51,13 @@ public class Violations extends RuntimeException {
}
/**
* A {@link Collector} to turn a {@link java.util.stream.Stream} of {@link RuntimeException}s into a
* {@link Violations} instance.
* A {@link Collector} to turn a {@link java.util.stream.Stream} of {@link String}s into a {@link Violations}
* instance.
*
* @return will never be {@literal null}.
*/
static Collector<Violation, ?, Violations> toViolations() {
return Collectors.collectingAndThen(Collectors.toList(), Violations::new);
static Collector<String, ?, Violations> toViolations() {
return mapping(Violation::new, collectingAndThen(Collectors.toList(), Violations::new));
}
/*
@@ -110,13 +112,18 @@ public class Violations extends RuntimeException {
*/
Violations and(Violation violation) {
Assert.notNull(violation, "Exception must not be null!");
Assert.notNull(violation, "Violation must not be null!");
return new Violations(unionByMessage(violations, List.of(violation)));
return and(new Violations(List.of(violation)));
}
Violations and(Violations other) {
return new Violations(unionByMessage(violations, other.violations));
Violations and(Violations that) {
Assert.notNull(that, "Violations must not be null!");
return hasViolations() || that.hasViolations()
? new Violations(unionByMessage(violations, that.violations))
: NONE;
}
Violations and(String violation) {

View File

@@ -29,4 +29,4 @@ Dependencies into internals of xref:fundamentals.adoc#modules.advanced.open[Open
If those are configured, dependencies to other application modules are rejected.
See xref:fundamentals.adoc#modules.explicit-dependencies[Explicit Application Module Dependencies] and xref:fundamentals.adoc#modules.named-interfaces[Named Interfaces] for details.
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].
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].