GH-87 - Expose module structure as actuator endpoint.

Introduce Spring Modulith Actuator module to expose the application module structure as Spring Boot actuator. This required a Spring Modulith Runtime module to be extracted from the Spring Modulith Observability one. The former now contains the auto-configured ApplicationRuntime and ApplicationModulesRuntime bean instances to be able to bootstrap a ApplicationModules asynchronously on application startup. The actuator module then contains a Spring Boot @Endpoint implementation consuming the ApplicationModules to render JSON describing the application module structure.
This commit is contained in:
Oliver Drotbohm
2022-12-22 20:43:39 +01:00
parent 9dff5cc973
commit c1fc3032b7
34 changed files with 847 additions and 150 deletions

View File

@@ -119,9 +119,8 @@ public class ApplicationModule {
* @return will never be {@literal null} or empty.
*/
public String getDisplayName() {
return information.getDisplayName()
.orElseGet(() -> getName());
.orElseGet(() -> StringUtils.capitalize(basePackage.getLocalName()));
}
/**

View File

@@ -54,8 +54,8 @@ import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
public class ApplicationModules implements Iterable<ApplicationModule> {
private static final Map<CacheKey, ApplicationModules> CACHE = new HashMap<>();
private static final ApplicationModuleDetectionStrategy DETECTION_STRATEGY;
private static final ImportOption IMPORT_OPTION = new ImportOption.DoNotIncludeTests();
static {
@@ -82,13 +82,12 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
private boolean verified;
private ApplicationModules(ModulithMetadata metadata, Collection<String> packages,
DescribedPredicate<JavaClass> ignored,
boolean useFullyQualifiedModuleNames) {
protected ApplicationModules(ModulithMetadata metadata, Collection<String> packages,
DescribedPredicate<JavaClass> ignored, boolean useFullyQualifiedModuleNames, ImportOption option) {
this.metadata = metadata;
this.allClasses = new ClassFileImporter() //
.withImportOption(new ImportOption.DoNotIncludeTests()) //
.withImportOption(option) //
.importPackages(packages) //
.that(not(ignored));
@@ -189,7 +188,7 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
basePackages.addAll(metadata.getAdditionalPackages());
ApplicationModules modules = new ApplicationModules(metadata, basePackages, key.getIgnored(),
metadata.useFullyQualifiedModuleNames());
metadata.useFullyQualifiedModuleNames(), IMPORT_OPTION);
Set<ApplicationModule> sharedModules = metadata.getSharedModuleNames() //
.map(modules::getRequiredModule) //
@@ -281,7 +280,7 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
}
/**
* Execute all verifications to be applied, unless the verifcation has been executed before.
* Execute all verifications to be applied, unless the verification has been executed before.
*
* @return will never be {@literal null}.
*/

View File

@@ -25,7 +25,7 @@ import org.springframework.modulith.Modulithic;
import org.springframework.modulith.model.Types.SpringTypes;
import org.springframework.util.Assert;
interface ModulithMetadata {
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!";

View File

@@ -39,9 +39,9 @@ import com.tngtech.archunit.core.importer.ClassFileImporter;
@TestInstance(Lifecycle.PER_CLASS)
class ModuleUnitTest {
ClassFileImporter importer = new ClassFileImporter();
JavaClasses classes = importer.importPackages("com.acme.withatbean"); //
JavaPackage javaPackage = JavaPackage.of(Classes.of(classes), "");
String packageName = "com.acme.withatbean";
JavaClasses classes = new ClassFileImporter().importPackages(packageName);
JavaPackage javaPackage = JavaPackage.of(Classes.of(classes), packageName);
ApplicationModule module = new ApplicationModule(javaPackage, false);
@@ -69,4 +69,9 @@ class ModuleUnitTest {
assertThat(it.getSources()).isNotEmpty();
});
}
@Test // GH-87
void usesCapitalizedNameAsDisplayNameByDefault() {
assertThat(module.getDisplayName()).isEqualTo("Withatbean");
}
}