GH-587 - ModulithMetadata now exposes all base packages.

Previously, we had a variety of places calculating the overall packages to inspect for types. This is now all consolidated into the ModulithMetadata abstraction exposing them directly.
This commit is contained in:
Oliver Drotbohm
2024-05-20 17:23:47 +02:00
parent 04c1203f65
commit 21bd2cef75
7 changed files with 150 additions and 184 deletions

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.modulith.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@@ -34,6 +35,7 @@ class AnnotationModulithMetadata implements ModulithMetadata {
private final Class<?> modulithType;
private final Modulithic annotation;
private final String basePackage;
/**
* Creates a new {@link AnnotationModulithMetadata} for the given type and annotation.
@@ -48,6 +50,7 @@ class AnnotationModulithMetadata implements ModulithMetadata {
this.modulithType = modulithType;
this.annotation = annotation;
this.basePackage = modulithType.getPackageName();
}
/**
@@ -123,4 +126,17 @@ class AnnotationModulithMetadata implements ModulithMetadata {
.filter(StringUtils::hasText) //
.or(() -> Optional.of(modulithType.getSimpleName()));
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.core.ModulithMetadata#getBasePackages()
*/
@Override
public List<String> getBasePackages() {
var result = new ArrayList<>(List.of(basePackage));
result.addAll(List.of(annotation.additionalPackages()));
return result;
}
}

View File

@@ -107,9 +107,40 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
private boolean verified;
/**
* Creates a new {@link ApplicationModules} instance.
*
* @param metadata must not be {@literal null}.
* @param ignored must not be {@literal null}.
* @param useFullyQualifiedModuleNames can be {@literal null}.
* @param option must not be {@literal null}.
*/
protected ApplicationModules(ModulithMetadata metadata,
DescribedPredicate<JavaClass> ignored, boolean useFullyQualifiedModuleNames, ImportOption option) {
this(metadata, metadata.getBasePackages(), ignored, useFullyQualifiedModuleNames, option);
}
/**
* Creates a new {@link ApplicationModules} instance.
*
* @param metadata must not be {@literal null}.
* @param packages must not be {@literal null}.
* @param ignored must not be {@literal null}.
* @param useFullyQualifiedModuleNames can be {@literal null}.
* @param option must not be {@literal null}.
* @deprecated since 1.2, for removal in 1.3. Use {@link ApplicationModules(ModulithMetadata, DescribedPredicate,
* boolean, ImportOption)} instead and set up {@link ModulithMetadata} to contain the packages you want to
* use.
*/
@Deprecated(forRemoval = true)
protected ApplicationModules(ModulithMetadata metadata, Collection<String> packages,
DescribedPredicate<JavaClass> ignored, boolean useFullyQualifiedModuleNames, ImportOption option) {
Assert.notNull(metadata, "ModulithMetadata must not be null!");
Assert.notNull(packages, "Base packages must not be null!");
Assert.notNull(ignored, "Ignores must not be null!");
Assert.notNull(option, "ImportOptions must not be null!");
this.metadata = metadata;
this.allClasses = new ClassFileImporter() //
.withImportOption(option) //
@@ -190,10 +221,10 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
}
/**
* Creates a new {@link ApplicationModules} relative to the given modulith type, a
* {@link ApplicationModuleDetectionStrategy} and a {@link DescribedPredicate} which types and packages to ignore.
* Will inspect the {@link org.springframework.modulith.Modulith} and {@link org.springframework.modulith.Modulithic}
* annotations on the class given for advanced customizations of the module setup.
* Creates a new {@link ApplicationModules} relative to the given modulith type, and a {@link DescribedPredicate}
* which types and packages to ignore. Will inspect the {@link org.springframework.modulith.Modulith} and
* {@link org.springframework.modulith.Modulithic} annotations on the class given for advanced customizations of the
* module setup.
*
* @param modulithType must not be {@literal null}.
* @param ignored must not be {@literal null}.
@@ -201,15 +232,22 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
*/
public static ApplicationModules of(Class<?> modulithType, DescribedPredicate<JavaClass> ignored) {
CacheKey key = new TypeKey(modulithType, ignored);
Assert.notNull(modulithType, "Modulith root type must not be null!");
Assert.notNull(ignored, "Predicate to describe ignored types must not be null!");
return CACHE.computeIfAbsent(key, it -> {
return of(CacheKey.of(modulithType, ignored, IMPORT_OPTION));
}
Assert.notNull(modulithType, "Modulith root type must not be null!");
Assert.notNull(ignored, "Predicate to describe ignored types must not be null!");
return of(key);
});
/**
* Creates a new {@link ApplicationModules} instance for the given type and {@link ImportOption}.
*
* @param modulithType must not be {@literal null}.
* @param options must not be {@literal null}.
* @return will never be {@literal null}.
* @since 1.2
*/
public static ApplicationModules of(Class<?> modulithType, ImportOption options) {
return of(CacheKey.of(modulithType, alwaysFalse(), options));
}
/**
@@ -231,15 +269,22 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
*/
public static ApplicationModules of(String javaPackage, DescribedPredicate<JavaClass> ignored) {
CacheKey key = new PackageKey(javaPackage, ignored);
Assert.hasText(javaPackage, "Base package must not be null or empty!");
Assert.notNull(ignored, "Predicate to describe ignored types must not be null!");
return CACHE.computeIfAbsent(key, it -> {
return of(CacheKey.of(javaPackage, ignored, IMPORT_OPTION));
}
Assert.hasText(javaPackage, "Base package must not be null or empty!");
Assert.notNull(ignored, "Predicate to describe ignored types must not be null!");
return of(key);
});
/**
* Creates a new {@link ApplicationModules} instance for the given package and {@link ImportOption}.
*
* @param javaPackage must not be {@literal null}.
* @param options must not be {@literal null}.
* @return will never be {@literal null}.
* @since 1.2
*/
public static ApplicationModules of(String javaPackage, ImportOption options) {
return of(CacheKey.of(javaPackage, alwaysFalse(), options));
}
/**
@@ -571,13 +616,8 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
Assert.notNull(key, "Cache key must not be null!");
var metadata = key.getMetadata();
var basePackages = new HashSet<String>();
basePackages.add(key.getBasePackage());
basePackages.addAll(metadata.getAdditionalPackages());
var modules = new ApplicationModules(metadata, basePackages, key.getIgnored(),
metadata.useFullyQualifiedModuleNames(), IMPORT_OPTION);
var modules = new ApplicationModules(metadata, key.getIgnored(),
metadata.useFullyQualifiedModuleNames(), key.getOptions());
var sharedModules = metadata.getSharedModuleNames() //
.map(modules::getRequiredModule) //
@@ -623,59 +663,39 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
}
}
private static interface CacheKey {
private static class CacheKey {
String getBasePackage();
DescribedPredicate<JavaClass> getIgnored();
ModulithMetadata getMetadata();
}
private static final class TypeKey implements CacheKey {
private final Class<?> type;
private final DescribedPredicate<JavaClass> ignored;
private final ImportOption options;
private final Supplier<ModulithMetadata> metadata;
/**
* Creates a new {@link TypeKey} for the given type and {@link DescribedPredicate} of ignored {@link JavaClass}es.
*
* @param type must not be {@literal null}.
* @param ignored must not be {@literal null}.
*/
TypeKey(Class<?> type, DescribedPredicate<JavaClass> ignored) {
public CacheKey(DescribedPredicate<JavaClass> ignored, ImportOption options, Supplier<ModulithMetadata> metadata) {
this.type = type;
this.ignored = ignored;
this.options = options;
this.metadata = SingletonSupplier.of(metadata);
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.model.Modules.CacheKey#getBasePackage()
*/
@Override
public String getBasePackage() {
return type.getPackage().getName();
static CacheKey of(String pkg, DescribedPredicate<JavaClass> ignored, ImportOption options) {
return new CacheKey(ignored, options, () -> ModulithMetadata.of(pkg));
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.model.Modules.CacheKey#getMetadata()
*/
@Override
public ModulithMetadata getMetadata() {
return ModulithMetadata.of(type);
static CacheKey of(Class<?> type, DescribedPredicate<JavaClass> ignored, ImportOption options) {
return new CacheKey(ignored, options, () -> ModulithMetadata.of(type));
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.model.ApplicationModules.CacheKey#getIgnored()
*/
@Override
public DescribedPredicate<JavaClass> getIgnored() {
DescribedPredicate<JavaClass> getIgnored() {
return ignored;
}
ModulithMetadata getMetadata() {
return metadata.get();
}
ImportOption getOptions() {
return options;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
@@ -683,98 +703,17 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
@Override
public boolean equals(Object obj) {
if (this == obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof TypeKey other)) {
if (!(obj instanceof CacheKey that)) {
return false;
}
return Objects.equals(this.type, other.type) //
&& Objects.equals(this.ignored, other.ignored);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return Objects.hash(type, ignored);
}
}
private static final class PackageKey implements CacheKey {
private final String basePackage;
private final DescribedPredicate<JavaClass> ignored;
/**
* Creates a new {@link PackageKey} for the given base package and {@link DescribedPredicate} of ignored
* {@link JavaClass}es.
*
* @param basePackage must not be {@literal null}.
* @param ignored must not be {@literal null}.
*/
PackageKey(String basePackage, DescribedPredicate<JavaClass> ignored) {
this.basePackage = basePackage;
this.ignored = ignored;
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.model.ApplicationModules.CacheKey#getBasePackage()
*/
@Override
public String getBasePackage() {
return basePackage;
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.model.ApplicationModules.CacheKey#getIgnored()
*/
public DescribedPredicate<JavaClass> getIgnored() {
return ignored;
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.model.Modules.CacheKey#getMetadata()
*/
@Override
public ModulithMetadata getMetadata() {
return ModulithMetadata.of(basePackage);
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof PackageKey that)) {
return false;
}
return Objects.equals(this.basePackage, that.basePackage) //
&& Objects.equals(this.ignored, that.ignored);
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return Objects.hash(basePackage, ignored);
return Objects.equals(this.ignored, that.ignored)
&& Objects.equals(this.options, that.options)
&& Objects.equals(this.metadata.get(), that.metadata.get());
}
}

View File

@@ -25,6 +25,11 @@ import org.springframework.modulith.Modulithic;
import org.springframework.modulith.core.Types.SpringTypes;
import org.springframework.util.Assert;
/**
* Core metadata about the modulithic application.
*
* @author Oliver Drotbohm
*/
public interface ModulithMetadata {
static final String ANNOTATION_MISSING = "Modules can only be retrieved from a root type, but %s is not annotated with either @%s, @%s or @%s!";
@@ -81,7 +86,9 @@ public interface ModulithMetadata {
* consider all direct sub-packages modules by default.
*
* @return will never be {@literal null}.
* @deprecated since 1.2, rather use {@link #getBasePackages()} that includes all packages already.
*/
@Deprecated
List<String> getAdditionalPackages();
/**
@@ -105,4 +112,12 @@ public interface ModulithMetadata {
* @return will never be {@literal null}.
*/
Optional<String> getSystemName();
/**
* Returns all base packages of the modulith.
*
* @return will never be {@literal null}.
* @since 1.2
*/
List<String> getBasePackages();
}

View File

@@ -21,17 +21,14 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.lang.NonNull;
import org.springframework.modulith.Modulith;
import org.springframework.modulith.Modulithic;
import org.springframework.modulith.core.Types.SpringTypes;
import org.springframework.util.Assert;
/**
* Creates a new {@link ModulithMetadata} representing the defaults of {@link Modulithic} but without the annotation
* present.
* Creates a new {@link ModulithMetadata} representing the defaults of {@link org.springframework.modulith.Modulithic}
* but without the annotation present.
*
* @author Oliver Drotbohm
*/
@@ -41,24 +38,29 @@ class SpringBootModulithMetadata implements ModulithMetadata {
.loadIfPresent(SpringTypes.AT_SPRING_BOOT_APPLICATION);
private final @NonNull Object source;
private final String systemName;
private final String systemName, basePackage;
/**
* Creates a new {@link SpringBootModulithMetadata} for the given source.
*
* @param source must not be {@literal null}.
* @param systemName can be {@literal null}.
* @param basePackage must not be {@literal null}.
*/
private SpringBootModulithMetadata(Object source, String systemName) {
private SpringBootModulithMetadata(Object source, String systemName, String basePackage) {
Assert.notNull(source, "Source must not be null!");
Assert.notNull(basePackage, "Base package must not be null!");
this.source = source;
this.basePackage = basePackage;
this.systemName = systemName;
}
/**
* Creates a new {@link ModulithMetadata} representing the defaults of a class annotated with
* {@link SpringBootApplication} but not customized with {@link Modulithic} or {@link Modulith}.
* {@link org.springframework.boot.autoconfigure.SpringBootApplication} but not customized with
* {@link org.springframework.modulith.Modulithic} or {@link org.springframework.modulith.Modulith}.
*
* @param annotated must not be {@literal null}.
* @return will never be {@literal null}.
@@ -69,7 +71,7 @@ class SpringBootModulithMetadata implements ModulithMetadata {
return Optional.ofNullable(AT_SPRING_BOOT_APPLICATION) //
.filter(it -> AnnotatedElementUtils.hasAnnotation(annotated, it)) //
.map(__ -> new SpringBootModulithMetadata(annotated, annotated.getSimpleName()));
.map(__ -> new SpringBootModulithMetadata(annotated, annotated.getSimpleName(), annotated.getPackageName()));
}
/**
@@ -82,7 +84,7 @@ class SpringBootModulithMetadata implements ModulithMetadata {
Assert.hasText(javaPackage, "Package name must not be null or empty!");
return new SpringBootModulithMetadata(javaPackage, null);
return new SpringBootModulithMetadata(javaPackage, null, javaPackage);
}
/*
@@ -138,4 +140,13 @@ class SpringBootModulithMetadata implements ModulithMetadata {
public Optional<String> getSystemName() {
return Optional.ofNullable(systemName);
}
/*
* (non-Javadoc)
* @see org.springframework.modulith.core.ModulithMetadata#getBasePackages()
*/
@Override
public List<String> getBasePackages() {
return List.of(basePackage);
}
}

View File

@@ -38,7 +38,7 @@ class ModulithMetadataUnitTest {
.map(ModulithMetadata::of) //
.forEach(it -> {
assertThat(it.getAdditionalPackages()).containsExactly("com.acme.foo");
assertThat(it.getBasePackages()).containsExactly("org.springframework.modulith.core", "com.acme.foo");
assertThat(it.getSharedModuleNames()).containsExactly("shared.module");
assertThat(it.getSystemName()).hasValue("systemName");
assertThat(it.useFullyQualifiedModuleNames()).isTrue();
@@ -50,7 +50,7 @@ class ModulithMetadataUnitTest {
ModulithMetadata metadata = ModulithMetadata.of(SpringBootApplicationAnnotated.class);
assertThat(metadata.getAdditionalPackages()).isEmpty();
assertThat(metadata.getBasePackages()).contains("org.springframework.modulith.core");
assertThat(metadata.getSharedModuleNames()).isEmpty();
assertThat(metadata.getSystemName()).hasValue(SpringBootApplicationAnnotated.class.getSimpleName());
assertThat(metadata.useFullyQualifiedModuleNames()).isFalse();

View File

@@ -17,7 +17,6 @@ package org.springframework.modulith.core;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*;
import java.util.List;
import java.util.function.Supplier;
import org.jmolecules.ddd.annotation.AggregateRoot;
@@ -80,7 +79,7 @@ class TestUtils {
}
public static ApplicationModules of(String basePackage, String... ignoredPackages) {
return of(ModulithMetadata.of(basePackage), basePackage, JavaClass.Predicates.resideInAnyPackage(ignoredPackages));
return of(ModulithMetadata.of(basePackage), JavaClass.Predicates.resideInAnyPackage(ignoredPackages));
}
public static ApplicationModule getApplicationModule(String packageName) {
@@ -99,9 +98,7 @@ class TestUtils {
.importPackages(packageName));
}
private static ApplicationModules of(ModulithMetadata metadata, String basePackage,
DescribedPredicate<JavaClass> ignores) {
return new ApplicationModules(metadata, List.of(basePackage), ignores, false,
new ImportOption.OnlyIncludeTests()) {};
private static ApplicationModules of(ModulithMetadata metadata, DescribedPredicate<JavaClass> ignores) {
return new ApplicationModules(metadata, ignores, false, new ImportOption.OnlyIncludeTests()) {};
}
}

View File

@@ -15,14 +15,9 @@
*/
package org.springframework.modulith.test;
import java.util.ArrayList;
import java.util.List;
import org.springframework.modulith.core.ApplicationModules;
import org.springframework.modulith.core.ApplicationModulesFactory;
import org.springframework.modulith.core.ModulithMetadata;
import com.tngtech.archunit.base.DescribedPredicate;
import com.tngtech.archunit.core.importer.ImportOption;
/**
@@ -32,6 +27,8 @@ import com.tngtech.archunit.core.importer.ImportOption;
*/
public class TestApplicationModules {
private static ImportOption ONLY_TESTS = new ImportOption.OnlyIncludeTests();
/**
* Creates an {@link ApplicationModules} instance from the given package but only inspecting the test code.
*
@@ -39,7 +36,7 @@ public class TestApplicationModules {
* @return will never be {@literal null}.
*/
public static ApplicationModules of(String basePackage) {
return of(ModulithMetadata.of(basePackage), basePackage);
return ApplicationModules.of(basePackage, ONLY_TESTS);
}
/**
@@ -50,16 +47,7 @@ public class TestApplicationModules {
* @since 1.2
*/
public static ApplicationModules of(Class<?> applicationClass) {
return of(ModulithMetadata.of(applicationClass), applicationClass.getPackageName());
}
private static ApplicationModules of(ModulithMetadata metadata, String basePackage) {
var packages = new ArrayList<>(List.of(basePackage));
packages.addAll(metadata.getAdditionalPackages());
return new ApplicationModules(metadata, packages, DescribedPredicate.alwaysFalse(), false,
new ImportOption.OnlyIncludeTests()) {};
return ApplicationModules.of(applicationClass, ONLY_TESTS);
}
/**