diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/model/SpringBean.java b/spring-modulith-core/src/main/java/org/springframework/modulith/model/SpringBean.java index 948646df..a9a466e6 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/model/SpringBean.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/model/SpringBean.java @@ -46,6 +46,16 @@ public class SpringBean { return type.getFullName(); } + /** + * Returns whether the bean is considered to be an API bean, which means it is either a public type exposed in an API + * package of the module or implements a public API interface. + * + * @return + */ + public boolean isApiBean() { + return module.isExposed(type) || !getInterfacesWithinModule().isEmpty(); + } + /** * Returns all interfaces implemented by the bean that are part of the same module. * diff --git a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java index cb087d7e..09c7887f 100644 --- a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java +++ b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Asciidoctor.java @@ -28,11 +28,11 @@ import org.springframework.lang.Nullable; import org.springframework.modulith.docs.ConfigurationProperties.ModuleProperty; import org.springframework.modulith.docs.Documenter.CanvasOptions; import org.springframework.modulith.docs.Documenter.CanvasOptions.Groupings; +import org.springframework.modulith.model.ApplicationModule; +import org.springframework.modulith.model.ApplicationModules; import org.springframework.modulith.model.ArchitecturallyEvidentType; import org.springframework.modulith.model.EventType; import org.springframework.modulith.model.FormatableJavaClass; -import org.springframework.modulith.model.ApplicationModule; -import org.springframework.modulith.model.ApplicationModules; import org.springframework.modulith.model.Source; import org.springframework.modulith.model.SpringBean; import org.springframework.util.Assert; @@ -126,7 +126,7 @@ class Asciidoctor { .map(this::toInlineCode) // .collect(Collectors.joining(", ")); - return String.format("%s implementing %s", base, interfacesAsString); + return String.format("%s (via %s)", interfacesAsString, base); } public String renderSpringBeans(CanvasOptions options, ApplicationModule module) { diff --git a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Documenter.java b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Documenter.java index 2f91bc7d..4a00b7e2 100644 --- a/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Documenter.java +++ b/spring-modulith-docs/src/main/java/org/springframework/modulith/docs/Documenter.java @@ -292,12 +292,15 @@ public class Documenter { Asciidoctor asciidoctor = Asciidoctor.withJavadocBase(modules, options.getApiBase()); Function, String> mapper = asciidoctor::typesToBulletPoints; + List aggregates = options.hideInternals + ? module.getAggregateRoots().stream().filter(module::isExposed).collect(Collectors.toList()) + : module.getAggregateRoots(); StringBuilder builder = new StringBuilder(); builder.append(startTable("%autowidth.stretch, cols=\"h,a\"")); builder.append(writeTableRow("Base package", asciidoctor.toInlineCode(module.getBasePackage().getName()))); builder.append(writeTableRow("Spring components", asciidoctor.renderSpringBeans(options, module))); - builder.append(addTableRow(module.getAggregateRoots(), "Aggregate roots", mapper)); + builder.append(addTableRow(aggregates, "Aggregate roots", mapper)); builder.append(writeTableRow("Published events", asciidoctor.renderEvents(module))); builder.append(addTableRow(module.getEventsListenedTo(modules), "Events listened to", mapper)); builder.append(writeTableRow("Properties", @@ -676,6 +679,7 @@ public class Documenter { private final List groupers; private final @With @Getter @Nullable String apiBase; private final @With @Nullable String targetFileName; + private final boolean hideInternals; public static CanvasOptions defaults() { @@ -689,7 +693,7 @@ public class Documenter { } public static CanvasOptions withoutDefaultGroupings() { - return new CanvasOptions(new ArrayList<>(), null, null); + return new CanvasOptions(new ArrayList<>(), null, null, true); } public CanvasOptions groupingBy(Grouping... groupings) { @@ -697,13 +701,22 @@ public class Documenter { List result = new ArrayList<>(groupers); result.addAll(Arrays.asList(groupings)); - return new CanvasOptions(result, apiBase, targetFileName); + return new CanvasOptions(result, apiBase, targetFileName, hideInternals); } public CanvasOptions groupingBy(String name, Predicate filter) { return groupingBy(Grouping.of(name, null, filter)); } + /** + * Enables the inclusion of internal components in the module canvas. + * + * @return will never be {@literal null}. + */ + public CanvasOptions revealInternals() { + return new CanvasOptions(groupers, apiBase, targetFileName, false); + } + Groupings groupBeans(ApplicationModule module) { List sources = new ArrayList<>(groupers); @@ -734,10 +747,11 @@ public class Documenter { return Optional.ofNullable(targetFileName); } - private static List getMatchingBeans(ApplicationModule module, Grouping filter, + private List getMatchingBeans(ApplicationModule module, Grouping filter, List alreadyMapped) { return module.getSpringBeans().stream() + .filter(it -> !hideInternals || it.isApiBean()) .filter(it -> !alreadyMapped.contains(it)) .filter(filter::matches) .collect(Collectors.toList()); diff --git a/spring-modulith-integration-test/src/test/java/org/springframework/modulith/docs/DocumenterUnitTests.java b/spring-modulith-integration-test/src/test/java/org/springframework/modulith/docs/DocumenterUnitTests.java index b359ffc5..1be68e3e 100644 --- a/spring-modulith-integration-test/src/test/java/org/springframework/modulith/docs/DocumenterUnitTests.java +++ b/spring-modulith-integration-test/src/test/java/org/springframework/modulith/docs/DocumenterUnitTests.java @@ -20,6 +20,8 @@ import static org.assertj.core.api.Assertions.*; import java.util.List; import org.junit.jupiter.api.Test; +import org.springframework.modulith.docs.Documenter.CanvasOptions; +import org.springframework.modulith.docs.Documenter.CanvasOptions.Grouping; import org.springframework.modulith.model.ApplicationModules; import org.springframework.modulith.model.SpringBean; @@ -39,13 +41,14 @@ class DocumenterUnitTests { @Test void groupsSpringBeansByArchitecturallyEvidentType() { - Documenter.CanvasOptions.Groupings result = Documenter.CanvasOptions.defaults() - .groupingBy(Documenter.CanvasOptions.Grouping.of("Representations", Documenter.CanvasOptions.Grouping.nameMatching(".*Representations"))) - .groupingBy(Documenter.CanvasOptions.Grouping.of("Interface implementations", Documenter.CanvasOptions.Grouping.subtypeOf(Stereotypes.SomeAppInterface.class))) + CanvasOptions.Groupings result = CanvasOptions.defaults() + .revealInternals() // + .groupingBy(Grouping.of("Representations", Grouping.nameMatching(".*Representations"))) + .groupingBy(Grouping.of("Interface implementations", Grouping.subtypeOf(Stereotypes.SomeAppInterface.class))) .groupBeans(modules.getModuleByName("stereotypes").orElseThrow(RuntimeException::new)); assertThat(result.keySet()) - .extracting(Documenter.CanvasOptions.Grouping::getName) + .extracting(CanvasOptions.Grouping::getName) .containsExactlyInAnyOrder("Controllers", "Services", "Repositories", "Event listeners", "Configuration properties", "Representations", "Interface implementations", "Others"); @@ -69,8 +72,8 @@ class DocumenterUnitTests { Documenter documenter = new Documenter(modules); - Documenter.CanvasOptions options = Documenter.CanvasOptions.defaults() // - .groupingBy(Documenter.CanvasOptions.Grouping.of("Representations", Documenter.CanvasOptions.Grouping.nameMatching(".*Representations"))); + CanvasOptions options = CanvasOptions.defaults() // + .groupingBy(Grouping.of("Representations", Grouping.nameMatching(".*Representations"))); assertThatNoException().isThrownBy(() -> { modules.forEach(it -> documenter.toModuleCanvas(it, options));