From 7c93b273d94915ee239e435f26cf8cc636918c75 Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Thu, 13 Oct 2022 17:15:11 +0200 Subject: [PATCH] GH-43 - Improve consistency in Documenter API. Consistent method overloads to avoid the need to create default instances of customization options. Renamed Options to DiagramOptions for consistency with CanvasOptions. --- .../modulith/docs/Documenter.java | 154 +++++++++++------- .../test/java/example/ModularityTests.java | 8 +- .../modulith/docs/DocumenterTest.java | 6 +- 3 files changed, 103 insertions(+), 65 deletions(-) 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 4d39e92a..b30ac058 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 @@ -137,25 +137,11 @@ public class Documenter { this.properties = new ConfigurationProperties(); } - private Map getComponents(Options options) { - - if (components == null) { - - this.components = modules.stream() // - .collect(Collectors.toMap(Function.identity(), - it -> container.addComponent(options.getDefaultDisplayName().apply(it), "", "Module"))); - - this.components.forEach((key, value) -> addDependencies(key, value, options)); - } - - return components; - } - /** * Customize the output folder to write the generated files to. Defaults to {@value #DEFAULT_LOCATION}. * * @param outputFolder must not be {@literal null} or empty. - * @return + * @return the current instance, will never be {@literal null}. * @see #DEFAULT_LOCATION */ public Documenter withOutputFolder(String outputFolder) { @@ -169,12 +155,27 @@ public class Documenter { *
  • Individual component diagrams per module to include all upstream modules.
  • *
  • The Module Canvas for each module.
  • * + * using {@link DiagramOptions#defaults()} and {@link CanvasOptions#defaults()}. * - * @param options must not be {@literal null}, use {@link Options#defaults()} for default. - * @param canvasOptions must not be {@literal null}, use {@link CanvasOptions#defaults()} for default. * @return the current instance, will never be {@literal null}. */ - public Documenter writeDocumentation(Options options, CanvasOptions canvasOptions) { + public Documenter writeDocumentation() { + return writeDocumentation(DiagramOptions.defaults(), CanvasOptions.defaults()); + } + + /** + * Writes all available documentation: + * + * + * @param options must not be {@literal null}. + * @param canvasOptions must not be {@literal null}. + * @return the current instance, will never be {@literal null}. + */ + public Documenter writeDocumentation(DiagramOptions options, CanvasOptions canvasOptions) { return writeModulesAsPlantUml(options) .writeIndividualModulesAsPlantUml(options) // @@ -182,11 +183,21 @@ public class Documenter { } /** - * Writes the PlantUML component diagram for all {@link ApplicationModules}. + * Writes the PlantUML component diagram for all {@link ApplicationModules} using {@link DiagramOptions#defaults()}. + * + * @return the current instance, will never be {@literal null}. + */ + public Documenter writeModulesAsPlantUml() { + return writeModulesAsPlantUml(DiagramOptions.defaults()); + } + + /** + * Writes the PlantUML component diagram for all {@link ApplicationModules} with the given {@link DiagramOptions}. * * @param options must not be {@literal null}. + * @return the current instance, will never be {@literal null}. */ - public Documenter writeModulesAsPlantUml(Options options) { + public Documenter writeModulesAsPlantUml(DiagramOptions options) { Assert.notNull(options, "Options must not be null!"); @@ -201,13 +212,19 @@ public class Documenter { return this; } + public Documenter writeIndividualModulesAsPlantUml() { + return writeIndividualModulesAsPlantUml(DiagramOptions.defaults()); + } + /** * Writes the component diagrams for all individual modules. * * @param options must not be {@literal null}. * @return the current instance, will never be {@literal null}. */ - public Documenter writeIndividualModulesAsPlantUml(Options options) { + public Documenter writeIndividualModulesAsPlantUml(DiagramOptions options) { + + Assert.notNull(options, "DiagramOptions must not be null!"); modules.forEach(it -> writeModuleAsPlantUml(it, options)); @@ -224,18 +241,18 @@ public class Documenter { Assert.notNull(module, "Module must not be null!"); - return writeModuleAsPlantUml(module, Options.defaults()); + return writeModuleAsPlantUml(module, DiagramOptions.defaults()); } /** * Writes the PlantUML component diagram for the given {@link ApplicationModule} with the given rendering - * {@link Options}. + * {@link DiagramOptions}. * * @param module must not be {@literal null}. * @param options must not be {@literal null}. * @return the current instance, will never be {@literal null}. */ - public Documenter writeModuleAsPlantUml(ApplicationModule module, Options options) { + public Documenter writeModuleAsPlantUml(ApplicationModule module, DiagramOptions options) { Assert.notNull(module, "Module must not be null!"); Assert.notNull(options, "Options must not be null!"); @@ -253,7 +270,7 @@ public class Documenter { } /** - * Writes all module canvases using {@link Options#defaults()}. + * Writes all module canvases using {@link DiagramOptions#defaults()}. * * @return the current instance, will never be {@literal null}. */ @@ -261,8 +278,16 @@ public class Documenter { return writeModuleCanvases(CanvasOptions.defaults()); } + /** + * Writes all module canvases using the given {@link DiagramOptions}. + * + * @param options must not be {@literal null}. + * @return the current instance, will never be {@literal null}. + */ public Documenter writeModuleCanvases(CanvasOptions options) { + Assert.notNull(options, "CanvasOptions must not be null!"); + modules.forEach(module -> { String filename = String.format(options.getTargetFileName().orElse("module-%s.adoc"), module.getName()); @@ -280,15 +305,15 @@ public class Documenter { return this; } - public String toModuleCanvas(ApplicationModule module) { + String toModuleCanvas(ApplicationModule module) { return toModuleCanvas(module, CanvasOptions.defaults()); } - public String toModuleCanvas(ApplicationModule module, String apiBase) { + String toModuleCanvas(ApplicationModule module, String apiBase) { return toModuleCanvas(module, CanvasOptions.defaults().withApiBase(apiBase)); } - public String toModuleCanvas(ApplicationModule module, CanvasOptions options) { + String toModuleCanvas(ApplicationModule module, CanvasOptions options) { Asciidoctor asciidoctor = Asciidoctor.withJavadocBase(modules, options.getApiBase()); Function, String> mapper = asciidoctor::typesToBulletPoints; @@ -314,11 +339,11 @@ public class Documenter { return types.isEmpty() ? "" : writeTableRow(header, mapper.apply(types)); } - public String toPlantUml() { - return createPlantUml(Options.defaults()); + String toPlantUml() { + return createPlantUml(DiagramOptions.defaults()); } - private void addDependencies(ApplicationModule module, Component component, Options options) { + private void addDependencies(ApplicationModule module, Component component, DiagramOptions options) { DEPENDENCY_DESCRIPTIONS.entrySet().stream().forEach(entry -> { @@ -339,7 +364,21 @@ public class Documenter { }); } - private void addComponentsToView(ApplicationModule module, ComponentView view, Options options) { + private Map getComponents(DiagramOptions options) { + + if (components == null) { + + this.components = modules.stream() // + .collect(Collectors.toMap(Function.identity(), + it -> container.addComponent(options.getDefaultDisplayName().apply(it), "", "Module"))); + + this.components.forEach((key, value) -> addDependencies(key, value, options)); + } + + return components; + } + + private void addComponentsToView(ApplicationModule module, ComponentView view, DiagramOptions options) { Supplier> bootstrapDependencies = () -> module.getBootstrapDependencies(modules, options.getDependencyDepth()); @@ -352,7 +391,8 @@ public class Documenter { addComponentsToView(dependencies, view, options, it -> it.add(getComponents(options).get(module))); } - private void addComponentsToView(Supplier> modules, ComponentView view, Options options, + private void addComponentsToView(Supplier> modules, ComponentView view, + DiagramOptions options, Consumer afterCleanup) { Styles styles = view.getViewSet().getConfiguration().getStyles(); @@ -409,8 +449,9 @@ public class Documenter { .findFirst().ifPresent(view::remove); } - private static Component applyBackgroundColor(ApplicationModule module, Map components, - Options options, + private static Component applyBackgroundColor(ApplicationModule module, + Map components, + DiagramOptions options, Styles styles) { Component component = components.get(module); @@ -433,7 +474,7 @@ public class Documenter { return component; } - private Documenter writeViewAsPlantUml(ComponentView view, String filename, Options options) { + private Documenter writeViewAsPlantUml(ComponentView view, String filename, DiagramOptions options) { Path file = recreateFile(filename); @@ -448,7 +489,7 @@ public class Documenter { } } - private String render(ComponentView view, Options options) { + private String render(ComponentView view, DiagramOptions options) { switch (options.style) { @@ -470,7 +511,7 @@ public class Documenter { } } - private String createPlantUml(Options options) { + private String createPlantUml(DiagramOptions options) { ComponentView componentView = createComponentView(options); componentView.setTitle(modules.getSystemName().orElse("Modules")); @@ -480,11 +521,11 @@ public class Documenter { return render(componentView, options); } - private ComponentView createComponentView(Options options) { + private ComponentView createComponentView(DiagramOptions options) { return createComponentView(options, null); } - private ComponentView createComponentView(Options options, @Nullable ApplicationModule module) { + private ComponentView createComponentView(DiagramOptions options, @Nullable ApplicationModule module) { String prefix = module == null ? "modules-" : module.getName(); @@ -533,7 +574,7 @@ public class Documenter { */ @Getter(AccessLevel.PRIVATE) @RequiredArgsConstructor(access = AccessLevel.PRIVATE) - public static class Options { + public static class DiagramOptions { private static final Set ALL_TYPES = Arrays.stream(DependencyType.values()) .collect(Collectors.toSet()); @@ -551,7 +592,8 @@ public class Documenter { private final @With Predicate exclusions; /** - * A {@link Predicate} to define which Structurizr {@link Component}s to be included in the diagram to be created. + * A {@link Predicate} to define which Structurizr {@link Component}s to be included in the diagram to be + * created. */ private final @With Predicate componentFilter; @@ -574,8 +616,8 @@ public class Documenter { private final @With Function> colorSelector; /** - * A callback to return a default display names for a given {@link ApplicationModule}. Default implementation just - * forwards to {@link ApplicationModule#getDisplayName()}. + * A callback to return a default display names for a given {@link ApplicationModule}. Default implementation + * just forwards to {@link ApplicationModule#getDisplayName()}. */ private final @With Function defaultDisplayName; @@ -587,23 +629,24 @@ public class Documenter { /** * Configuration setting to define whether modules that do not have a relationship to any other module shall be * retained in the diagrams created. The default is {@value ElementsWithoutRelationships#HIDDEN}. See - * {@link Options#withExclusions(Predicate)} for a more fine-grained way of defining which modules to exclude in - * case you flip this to {@link ElementsWithoutRelationships#VISIBLE}. + * {@link DiagramOptions#withExclusions(Predicate)} for a more fine-grained way of defining which modules to + * exclude in case you flip this to {@link ElementsWithoutRelationships#VISIBLE}. * * @see #withExclusions(Predicate) */ private final @With ElementsWithoutRelationships elementsWithoutRelationships; /** - * Creates a new default {@link Options} instance configured to use all dependency types, list immediate + * Creates a new default {@link DiagramOptions} instance configured to use all dependency types, list immediate * dependencies for individual module instances, not applying any kind of {@link ApplicationModule} or * {@link Component} filters and default file names. * * @return will never be {@literal null}. */ - public static Options defaults() { - return new Options(ALL_TYPES, DependencyDepth.IMMEDIATE, it -> false, it -> true, it -> false, null, - __ -> Optional.empty(), it -> it.getDisplayName(), DiagramStyle.C4, ElementsWithoutRelationships.HIDDEN); + public static DiagramOptions defaults() { + return new DiagramOptions(ALL_TYPES, DependencyDepth.IMMEDIATE, it -> false, it -> true, it -> false, null, + __ -> Optional.empty(), it -> it.getDisplayName(), DiagramStyle.C4, + ElementsWithoutRelationships.HIDDEN); } /** @@ -612,13 +655,14 @@ public class Documenter { * @param types must not be {@literal null}. * @return */ - public Options withDependencyTypes(DependencyType... types) { + public DiagramOptions withDependencyTypes(DependencyType... types) { Assert.notNull(types, "Dependency types must not be null!"); Set dependencyTypes = Arrays.stream(types).collect(Collectors.toSet()); - return new Options(dependencyTypes, dependencyDepth, exclusions, componentFilter, targetOnly, targetFileName, + return new DiagramOptions(dependencyTypes, dependencyDepth, exclusions, componentFilter, targetOnly, + targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships); } @@ -657,11 +701,11 @@ public class Documenter { /** * Configuration setting to define whether modules that do not have a relationship to any other module shall be * retained in the diagrams created. The default is {@value ElementsWithoutRelationships#HIDDEN}. See - * {@link Options#withExclusions(Predicate)} for a more fine-grained way of defining which modules to exclude in - * case you flip this to {@link ElementsWithoutRelationships#VISIBLE}. + * {@link DiagramOptions#withExclusions(Predicate)} for a more fine-grained way of defining which modules to + * exclude in case you flip this to {@link ElementsWithoutRelationships#VISIBLE}. * * @author Oliver Drotbohm - * @see Options#withExclusions(Predicate) + * @see DiagramOptions#withExclusions(Predicate) */ public enum ElementsWithoutRelationships { HIDDEN, VISIBLE; diff --git a/spring-modulith-example/src/test/java/example/ModularityTests.java b/spring-modulith-example/src/test/java/example/ModularityTests.java index 0bb40cbc..279e4d4b 100644 --- a/spring-modulith-example/src/test/java/example/ModularityTests.java +++ b/spring-modulith-example/src/test/java/example/ModularityTests.java @@ -17,8 +17,6 @@ package example; import org.junit.jupiter.api.Test; import org.springframework.modulith.docs.Documenter; -import org.springframework.modulith.docs.Documenter.CanvasOptions; -import org.springframework.modulith.docs.Documenter.Options; import org.springframework.modulith.model.ApplicationModules; /** @@ -37,10 +35,6 @@ class ModularityTests { @Test void createModuleDocumentation() { - - var canvasOptions = CanvasOptions.defaults(); - - new Documenter(modules) // - .writeDocumentation(Options.defaults(), canvasOptions); + new Documenter(modules).writeDocumentation(); } } diff --git a/spring-modulith-integration-test/src/test/java/org/springframework/modulith/docs/DocumenterTest.java b/spring-modulith-integration-test/src/test/java/org/springframework/modulith/docs/DocumenterTest.java index 1dba30a5..b983147f 100644 --- a/spring-modulith-integration-test/src/test/java/org/springframework/modulith/docs/DocumenterTest.java +++ b/spring-modulith-integration-test/src/test/java/org/springframework/modulith/docs/DocumenterTest.java @@ -26,7 +26,7 @@ import java.util.Comparator; import java.util.Optional; import org.junit.jupiter.api.Test; -import org.springframework.modulith.docs.Documenter.Options; +import org.springframework.modulith.docs.Documenter.DiagramOptions; import org.springframework.modulith.model.ApplicationModule; import org.springframework.modulith.model.ApplicationModule.DependencyType; @@ -52,11 +52,11 @@ class DocumenterTest { ApplicationModule module = documenter.getModules().getModuleByName("moduleB") // .orElseThrow(() -> new IllegalArgumentException()); - documenter.writeModuleAsPlantUml(module, Options.defaults() // + documenter.writeModuleAsPlantUml(module, DiagramOptions.defaults() // .withColorSelector(it -> Optional.of("#ff0000")) // .withDefaultDisplayName(it -> it.getDisplayName().toUpperCase())); - Options options = Options.defaults() // + DiagramOptions options = DiagramOptions.defaults() // .withComponentFilter(component -> component.getRelationships().stream() .anyMatch(it -> it.getTagsAsSet().contains(DependencyType.EVENT_LISTENER.toString())));