From e3077a7abd256170ba5f841835d59e1a0a1db505 Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Tue, 8 Oct 2024 11:14:06 +0200 Subject: [PATCH] GH-862 - Trigger jMolecules architecture verifications if present on the classpath. --- .../modulith/core/ApplicationModules.java | 27 +++++------ .../springframework/modulith/core/Types.java | 46 +++++++++++++++++-- .../modulith/core/Violations.java | 23 ++++++---- .../modules/ROOT/pages/verification.adoc | 2 +- 4 files changed, 71 insertions(+), 27 deletions(-) diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java index 359cd010..2871f943 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/ApplicationModules.java @@ -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 { */ 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); } /** diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/Types.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/Types.java index dba02611..f5bd6586 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/Types.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/Types.java @@ -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 getRules() { + + var classLoader = JMoleculesTypes.class.getClassLoader(); + var rules = new ArrayList(); + + 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; } } diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/Violations.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/Violations.java index 0c05178d..258c005b 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/Violations.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/Violations.java @@ -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 toViolations() { - return Collectors.collectingAndThen(Collectors.toList(), Violations::new); + static Collector 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) { diff --git a/src/docs/antora/modules/ROOT/pages/verification.adoc b/src/docs/antora/modules/ROOT/pages/verification.adoc index ba6dc418..52e396ca 100644 --- a/src/docs/antora/modules/ROOT/pages/verification.adoc +++ b/src/docs/antora/modules/ROOT/pages/verification.adoc @@ -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].