diff --git a/pom.xml b/pom.xml index 508d34acc6b..87d674e9d81 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ 3.0.0-M7 0.8.8 6.3.0.202209071007-r - 0.23.1 + 1.0.0-rc1 1.6.11 0.27.0 ${problem-spring.version} diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/apidocumentation/springdocoauth/infrascture/primary/SpringdocOauth2ModuleConfiguration.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/apidocumentation/springdocoauth/infrastructure/primary/SpringdocOauth2ModuleConfiguration.java similarity index 96% rename from src/main/java/tech/jhipster/lite/generator/server/springboot/apidocumentation/springdocoauth/infrascture/primary/SpringdocOauth2ModuleConfiguration.java rename to src/main/java/tech/jhipster/lite/generator/server/springboot/apidocumentation/springdocoauth/infrastructure/primary/SpringdocOauth2ModuleConfiguration.java index ab888db2c2f..2e6106a8827 100644 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/apidocumentation/springdocoauth/infrascture/primary/SpringdocOauth2ModuleConfiguration.java +++ b/src/main/java/tech/jhipster/lite/generator/server/springboot/apidocumentation/springdocoauth/infrastructure/primary/SpringdocOauth2ModuleConfiguration.java @@ -1,4 +1,4 @@ -package tech.jhipster.lite.generator.server.springboot.apidocumentation.springdocoauth.infrascture.primary; +package tech.jhipster.lite.generator.server.springboot.apidocumentation.springdocoauth.infrastructure.primary; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/broker/hivemq/package-info.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/broker/hivemq/package-info.java deleted file mode 100644 index 6f59d221f3b..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/broker/hivemq/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -@tech.jhipster.lite.BusinessContext -package tech.jhipster.lite.generator.server.springboot.broker.hivemq; diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/cache/caffeine/package-info.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/cache/caffeine/package-info.java deleted file mode 100644 index 9694a79d99b..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/cache/caffeine/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -@tech.jhipster.lite.BusinessContext -package tech.jhipster.lite.generator.server.springboot.cache.caffeine; diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/cache/hazelcast/package-info.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/cache/hazelcast/package-info.java deleted file mode 100644 index 43355f28db4..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/cache/hazelcast/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -@tech.jhipster.lite.BusinessContext -package tech.jhipster.lite.generator.server.springboot.cache.hazelcast; diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/cache/infinispan/package-info.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/cache/infinispan/package-info.java deleted file mode 100644 index 5d75009e75e..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/cache/infinispan/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -@tech.jhipster.lite.BusinessContext -package tech.jhipster.lite.generator.server.springboot.cache.infinispan; diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/cache/memcached/package-info.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/cache/memcached/package-info.java deleted file mode 100644 index d02817b6767..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/cache/memcached/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -@tech.jhipster.lite.BusinessContext -package tech.jhipster.lite.generator.server.springboot.cache.memcached; diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/cache/redis/package-info.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/cache/redis/package-info.java deleted file mode 100644 index 86dfe957d55..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/cache/redis/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -@tech.jhipster.lite.BusinessContext -package tech.jhipster.lite.generator.server.springboot.cache.redis; diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/database/cassandra/package-info.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/database/cassandra/package-info.java deleted file mode 100644 index 087b756ad8a..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/database/cassandra/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -@tech.jhipster.lite.BusinessContext -package tech.jhipster.lite.generator.server.springboot.database.cassandra; diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/database/couchbase/package-info.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/database/couchbase/package-info.java deleted file mode 100644 index 149688bf3fc..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/database/couchbase/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -@tech.jhipster.lite.BusinessContext -package tech.jhipster.lite.generator.server.springboot.database.couchbase; diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/database/neo4j/package-info.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/database/neo4j/package-info.java deleted file mode 100644 index 9efd650f5e4..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/database/neo4j/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -@tech.jhipster.lite.BusinessContext -package tech.jhipster.lite.generator.server.springboot.database.neo4j; diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/database/oracle/package-info.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/database/oracle/package-info.java deleted file mode 100644 index 66e5e2fa070..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/database/oracle/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -@tech.jhipster.lite.BusinessContext -package tech.jhipster.lite.generator.server.springboot.database.oracle; diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/search/elasticsearch/package-info.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/search/elasticsearch/package-info.java deleted file mode 100644 index a7d9008bcef..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/search/elasticsearch/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -@tech.jhipster.lite.BusinessContext -package tech.jhipster.lite.generator.server.springboot.search.elasticsearch; diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/webflux/security/jwt/package-info.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/webflux/security/jwt/package-info.java deleted file mode 100644 index 0ca313140f3..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/webflux/security/jwt/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -@tech.jhipster.lite.BusinessContext -package tech.jhipster.lite.generator.server.springboot.webflux.security.jwt; diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/webflux/security/oauth2/package-info.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/webflux/security/oauth2/package-info.java deleted file mode 100644 index bd30551aeb0..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/webflux/security/oauth2/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -@tech.jhipster.lite.BusinessContext -package tech.jhipster.lite.generator.server.springboot.webflux.security.oauth2; diff --git a/src/main/java/tech/jhipster/lite/generator/server/springboot/webflux/springdoc/package-info.java b/src/main/java/tech/jhipster/lite/generator/server/springboot/webflux/springdoc/package-info.java deleted file mode 100644 index 81d85e49157..00000000000 --- a/src/main/java/tech/jhipster/lite/generator/server/springboot/webflux/springdoc/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -@tech.jhipster.lite.BusinessContext -package tech.jhipster.lite.generator.server.springboot.webflux.springdoc; diff --git a/src/main/resources/generator/dependencies/pom.xml b/src/main/resources/generator/dependencies/pom.xml index 432fb46ffc2..73c4ec44f2b 100644 --- a/src/main/resources/generator/dependencies/pom.xml +++ b/src/main/resources/generator/dependencies/pom.xml @@ -30,7 +30,7 @@ 3.1.0 3.3.0 5.0.41 - 0.23.1 + 1.0.0-rc1 7.8.0 7.6.1 2.10.0 diff --git a/src/main/resources/generator/server/javatool/arch/HexagonalArchTest.java.mustache b/src/main/resources/generator/server/javatool/arch/HexagonalArchTest.java.mustache index c7c1b748aa0..73c90a53c0c 100644 --- a/src/main/resources/generator/server/javatool/arch/HexagonalArchTest.java.mustache +++ b/src/main/resources/generator/server/javatool/arch/HexagonalArchTest.java.mustache @@ -5,16 +5,19 @@ import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.library.Architectures; import java.io.IOException; import java.lang.annotation.Annotation; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; import java.util.Collection; +import java.util.List; import java.util.function.Function; import java.util.stream.Stream; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.springframework.stereotype.Component; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RestController; @@ -27,24 +30,28 @@ class HexagonalArchTest { .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) .importPackages(ROOT_PACKAGE); - private static final Collection businessContexts = buildBusinessContexts(); + private static final Collection businessContexts = packagesWithAnnotation(BusinessContext.class); + private static final Collection businessContextsPackages = buildPackagesPatterns(businessContexts); - private static final Collection commonPackages = Arrays.asList("java..", "org.slf4j..", "org.apache.commons.lang3.."); + private static final Collection sharedKernels = packagesWithAnnotation(SharedKernel.class); + private static final Collection sharedKernelsPackages = buildPackagesPatterns(sharedKernels); - private static final Collection sharedKernelsPackages = buildSharedKernelsPackages(); + // the empty package is related to: https://github.com/TNG/ArchUnit/issues/191#issuecomment-507964792 + private static final Collection vanillaPackages = List.of("java..", ""); + private static final Collection commonToolsAndUtilsPackages = List.of( + "org.slf4j..", + "org.apache.commons..", + "com.google.guava.." + ); - private static Collection buildBusinessContexts() { - return getPackageAnnotatedBy(BusinessContext.class); + private static Collection buildPackagesPatterns(Collection packages) { + return packages.stream().map(path -> path + "..").toList(); } - private static Collection buildSharedKernelsPackages() { - return getPackageAnnotatedBy(SharedKernel.class).stream().map(path -> path + "..").toList(); - } - - private static Collection getPackageAnnotatedBy(Class annotationClass) throws AssertionError { + private static Collection packagesWithAnnotation(Class annotationClass) throws AssertionError { try { return Files - .walk(Paths.get("src", "main", "java", {{ packageWalkPath }})) + .walk(Paths.get("src", "main", "java", {{packageWalkPath}})) .filter(path -> path.toString().endsWith("package-info.java")) .map(toPackageName()) .map(path -> path.replaceAll("[\\/]", ".")) @@ -76,147 +83,166 @@ class HexagonalArchTest { }; } - @Test - void shouldNotHaveInfrastructureDependenciesInApplication() { - noClasses() - .that() - .resideInAnyPackage("..application..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("..infrastructure..") - .because("Application should only depend on domain, not on infrastructure") - .check(classes); - } + @Nested + class BoundedContexts { + + @Test + void shouldNotDependOnOtherBoundedContextDomains() { + Stream + .concat(businessContexts.stream(), sharedKernels.stream()) + .forEach(context -> { + noClasses() + .that() + .resideInAnyPackage(context + "..") + .should() + .dependOnClassesThat() + .resideInAnyPackage(otherBusinessContextsDomains(context)) + .because("Contexts can only depend on classes in the same context or shared kernels") + .check(classes); + }); + } - @Test - void shouldNotDependOnOtherContexts() { - businessContexts.forEach(context -> { - String[] otherContexts = otherContexts(context); + @Test + void shouldBeAnHexagonalArchitecture() { + Stream + .concat(businessContexts.stream(), sharedKernels.stream()) + .forEach(context -> Architectures + .layeredArchitecture() + .consideringOnlyDependenciesInAnyPackage(context + "..") + .withOptionalLayers(true) + .layer("domain models") + .definedBy(context + ".domain..") + .layer("domain services") + .definedBy(context + ".domain..") + .layer("application services") + .definedBy(context + ".application..") + .layer("primary adapters") + .definedBy(context + ".infrastructure.primary..") + .layer("secondary adapters") + .definedBy(context + ".infrastructure.secondary..") + .whereLayer("application services") + .mayOnlyBeAccessedByLayers("primary adapters") + .whereLayer("primary adapters") + .mayNotBeAccessedByAnyLayer() + .whereLayer("secondary adapters") + .mayNotBeAccessedByAnyLayer() + .because("Each bounded context should implement an hexagonal architecture") + .check(classes) + ); + } - noClasses() + @Test + void primaryJavaAdaptersShouldOnlyBeCalledFromSecondaries() { + classes() .that() - .resideInAnyPackage(context + "..") + .resideInAPackage("..primary..") + .and() + .areMetaAnnotatedWith(Component.class) + .and() + .haveSimpleNameStartingWith("Java") .should() - .dependOnClassesThat() - .resideInAnyPackage(otherContexts) - .because("Contexts can only depend on classes in the same context or in common") + .onlyHaveDependentClassesThat() + .resideInAPackage("..secondary..") + .because( + "To interact between two contexts, secondary from context 'A' should call a primary Java adapter (naming convention starting with 'Java') from context 'B'" + ) .check(classes); - }); - } + } - private String[] otherContexts(String context) { - return businessContexts.stream().filter(other -> !context.equals(other)).map(name -> name + ".domain..").toArray(String[]::new); + private String[] otherBusinessContextsDomains(String context) { + return businessContexts.stream().filter(other -> !context.equals(other)).map(name -> name + ".domain..").toArray(String[]::new); + } } - @Test - void shouldNotHaveFrameworkDependenciesInContext() { - businessContexts.forEach(context -> + @Nested + class Domain { + + @Test + void shouldNotDependOnOutside() { classes() .that() - .resideInAnyPackage(context + ".domain..") + .resideInAPackage(".domain..") .should() .onlyDependOnClassesThat() - .resideInAnyPackage(authorizedContextPackages(context + ".domain..")) + .resideInAnyPackage(authorizedDomainPackages()) .because("Domain model should only depend on himself and a very limited set of external dependencies") - .check(classes) - ); - } + .check(classes); + } - private String[] authorizedContextPackages(String packageName) { - return Stream.of(Arrays.asList(packageName), commonPackages, sharedKernelsPackages).flatMap(Collection::stream).toArray(String[]::new); + private String[] authorizedDomainPackages() { + return Stream + .of(List.of(".domain.."), vanillaPackages, commonToolsAndUtilsPackages, sharedKernelsPackages) + .flatMap(Collection::stream) + .toArray(String[]::new); + } } - @Test - void shouldHaveNoDependencyToApplicationFromSecondary() { - noClasses() - .that() - .resideInAnyPackage("..infrastructure.secondary..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("..application..") - .because("Secondary should not depend on application") - .check(classes); - } + @Nested + class Application { - @Test - void shouldNotHaveInfrastructureDependenciesFromDomain() { - noClasses() - .that() - .resideInAnyPackage("..domain..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("..infrastructure..") - .check(classes); + @Test + void shouldNotDependOnInfrastructure() { + noClasses() + .that() + .resideInAPackage("..application..") + .should() + .dependOnClassesThat() + .resideInAPackage("..infrastructure..") + .because("Application should only depend on domain, not on infrastructure") + .check(classes); + } } - @Test - void shouldNotHaveContextDependenciesForSharedKernels() { - sharedKernelsPackages.forEach(kernel -> + @Nested + class Primary { + + @Test + void shouldNotDependOnSecondary() { noClasses() .that() - .resideInAnyPackage(kernel) + .resideInAPackage("..primary..") .should() .dependOnClassesThat() - .resideInAnyPackage(businessContexts.stream().map(context -> context + ".domain..").toArray(String[]::new)) - .orShould() - .resideInAnyPackage(businessContexts.stream().map(context -> context + ".application..").toArray(String[]::new)) - .orShould() - .resideInAnyPackage(businessContexts.stream().map(context -> context + ".infrastructure.secondary..").toArray(String[]::new)) - .because("Shared kernels should not have dependencies to bounded contexts") - .check(classes) - ); - } + .resideInAPackage("..secondary..") + .because("Primary should not interact with secondary") + .check(classes); + } - @Test - void shouldNotHavePublicControllers() { - noClasses().that().areAnnotatedWith(RestController.class).or().areAnnotatedWith(Controller.class).should().bePublic().check(classes); + @Test + void shouldNotHavePublicControllers() { + noClasses().that().areAnnotatedWith(RestController.class).or().areAnnotatedWith(Controller.class).should().bePublic().check(classes); + } } - @Test - void domainShouldNotInteractWithOutside() { - JavaClasses importedClasses = new ClassFileImporter() - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) - .importPackages("tech.jhipster.lite"); - - noClasses() - .that() - .resideInAnyPackage("..domain..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("..application..", "..infrastructure..", "..primary..", "..secondary..") - .because("Domain should not interact with application, infrastructure, primary or secondary") - .check(importedClasses); - } + @Nested + class Secondary { - @Test - void applicationShouldNotInteractWithSecondary() { - JavaClasses importedClasses = new ClassFileImporter() - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) - .importPackages("tech.jhipster.lite"); - - noClasses() - .that() - .resideInAnyPackage("..application..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("..secondary..") - .because("Application should not interact with secondary") - .check(importedClasses); - } + @Test + void shouldNotDependOnApplication() { + noClasses() + .that() + .resideInAPackage("..infrastructure.secondary..") + .should() + .dependOnClassesThat() + .resideInAPackage("..application..") + .because("Secondary should not depend on application") + .check(classes); + } - @Test - void primaryShouldNotInteractWithSecondary() { - JavaClasses importedClasses = new ClassFileImporter() - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) - .importPackages("tech.jhipster.lite"); - - noClasses() - .that() - .resideInAnyPackage("..primary..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("..secondary..") - .because("Primary should not interact with secondary") - .check(importedClasses); + @Test + void shouldNotDependOnSameContextPrimary() { + Stream + .concat(businessContexts.stream(), sharedKernels.stream()) + .forEach(context -> { + noClasses() + .that() + .resideInAPackage(context + ".infrastructure.secondary..") + .should() + .dependOnClassesThat() + .resideInAPackage(context + ".infrastructure.primary") + .because("Secondary should not loop to its own context's primary") + .check(classes); + }); + } } } diff --git a/src/test/java/tech/jhipster/lite/HexagonalArchTest.java b/src/test/java/tech/jhipster/lite/HexagonalArchTest.java index cac6f8b0490..64732ea8fbd 100644 --- a/src/test/java/tech/jhipster/lite/HexagonalArchTest.java +++ b/src/test/java/tech/jhipster/lite/HexagonalArchTest.java @@ -1,22 +1,23 @@ package tech.jhipster.lite; -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.library.Architectures; import java.io.IOException; import java.lang.annotation.Annotation; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.function.Function; import java.util.stream.Stream; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.springframework.stereotype.Component; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RestController; @@ -29,28 +30,25 @@ class HexagonalArchTest { .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) .importPackages(ROOT_PACKAGE); - private static final Collection businessContexts = buildBusinessContexts(); + private static final Collection businessContexts = packagesWithAnnotation(BusinessContext.class); + private static final Collection businessContextsPackages = buildPackagesPatterns(businessContexts); - private static final Collection sharedKernels = buildSharedKernels(); + private static final Collection sharedKernels = packagesWithAnnotation(SharedKernel.class); + private static final Collection sharedKernelsPackages = buildPackagesPatterns(sharedKernels); // the empty package is related to: https://github.com/TNG/ArchUnit/issues/191#issuecomment-507964792 - private static final Collection commonPackages = Arrays.asList("java..", "org.slf4j..", "org.apache.commons..", ""); + private static final Collection vanillaPackages = List.of("java..", ""); + private static final Collection commonToolsAndUtilsPackages = List.of( + "org.slf4j..", + "org.apache.commons..", + "com.google.guava.." + ); - private static final Collection sharedKernelsPackages = buildSharedKernelsPackages(); - - private static Collection buildBusinessContexts() { - return getPackageAnnotatedBy(BusinessContext.class); - } - - private static Collection buildSharedKernels() { - return getPackageAnnotatedBy(SharedKernel.class); + private static Collection buildPackagesPatterns(Collection packages) { + return packages.stream().map(path -> path + "..").toList(); } - private static Collection buildSharedKernelsPackages() { - return getPackageAnnotatedBy(SharedKernel.class).stream().map(path -> path + "..").toList(); - } - - private static Collection getPackageAnnotatedBy(Class annotationClass) throws AssertionError { + private static Collection packagesWithAnnotation(Class annotationClass) throws AssertionError { try { return Files .walk(Paths.get("src", "main", "java", "tech", "jhipster", "lite")) @@ -85,175 +83,167 @@ private static Function toPackage() { }; } - @Test - void shouldNotHaveInfrastructureDependenciesInApplication() { - noClasses() - .that() - .resideInAnyPackage("..application..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("..infrastructure..") - .because("Application should only depend on domain, not on infrastructure") - .check(classes); - } + @Nested + class BoundedContexts { + + @Test + void shouldNotDependOnOtherBoundedContextDomains() { + Stream + .concat(businessContexts.stream(), sharedKernels.stream()) + .forEach(context -> { + noClasses() + .that() + .resideInAnyPackage(context + "..") + .should() + .dependOnClassesThat() + .resideInAnyPackage(otherBusinessContextsDomains(context)) + .because("Contexts can only depend on classes in the same context or shared kernels") + .check(classes); + }); + } - @Test - void shouldNotDependOnOtherContexts() { - businessContexts.forEach(context -> { - String[] otherContexts = otherContexts(context); + @Test + void shouldBeAnHexagonalArchitecture() { + Stream + .concat(businessContexts.stream(), sharedKernels.stream()) + .forEach(context -> + Architectures + .layeredArchitecture() + .consideringOnlyDependenciesInAnyPackage(context + "..") + .withOptionalLayers(true) + .layer("domain models") + .definedBy(context + ".domain..") + .layer("domain services") + .definedBy(context + ".domain..") + .layer("application services") + .definedBy(context + ".application..") + .layer("primary adapters") + .definedBy(context + ".infrastructure.primary..") + .layer("secondary adapters") + .definedBy(context + ".infrastructure.secondary..") + .whereLayer("application services") + .mayOnlyBeAccessedByLayers("primary adapters") + .whereLayer("primary adapters") + .mayNotBeAccessedByAnyLayer() + .whereLayer("secondary adapters") + .mayNotBeAccessedByAnyLayer() + .because("Each bounded context should implement an hexagonal architecture") + .check(classes) + ); + } - noClasses() + @Test + void primaryJavaAdaptersShouldOnlyBeCalledFromSecondaries() { + classes() .that() - .resideInAnyPackage(context + "..") + .resideInAPackage("..primary..") + .and() + .areMetaAnnotatedWith(Component.class) + .and() + .haveSimpleNameStartingWith("Java") .should() - .dependOnClassesThat() - .resideInAnyPackage(otherContexts) - .because("Contexts can only depend on classes in the same context or in common") + .onlyHaveDependentClassesThat() + .resideInAPackage("..secondary..") + .because( + "To interact between two contexts, secondary from context 'A' should call a primary Java adapter (naming convention starting with 'Java') from context 'B'" + ) .check(classes); - }); - } + } - private String[] otherContexts(String context) { - return businessContexts.stream().filter(other -> !context.equals(other)).map(name -> name + ".domain..").toArray(String[]::new); + private String[] otherBusinessContextsDomains(String context) { + return businessContexts.stream().filter(other -> !context.equals(other)).map(name -> name + ".domain..").toArray(String[]::new); + } } - @Test - void shouldNotHaveFrameworkDependenciesInContext() { - businessContexts.forEach(context -> - classes() - .that() - .resideInAnyPackage(context + ".domain..") - .should() - .onlyDependOnClassesThat() - .resideInAnyPackage(authorizedContextPackages(context + ".domain..")) - .because("Domain model should only depend on himself and a very limited set of external dependencies") - .check(classes) - ); - } + @Nested + class Domain { - @Test - void shouldNotHaveFrameworkDependenciesInSharedKernel() { - sharedKernels.forEach(context -> + @Test + void shouldNotDependOnOutside() { classes() .that() - .resideInAnyPackage(context + ".domain..") - .and() - .resideOutsideOfPackage("tech.jhipster.lite.common.domain") + .resideInAPackage(".domain..") .should() .onlyDependOnClassesThat() - .resideInAnyPackage(authorizedContextPackages(context + ".domain..")) + .resideInAnyPackage(authorizedDomainPackages()) .because("Domain model should only depend on himself and a very limited set of external dependencies") - .check(classes) - ); - } - - private String[] authorizedContextPackages(String packageName) { - return Stream.of(List.of(packageName), commonPackages, sharedKernelsPackages).flatMap(Collection::stream).toArray(String[]::new); - } - - @Test - void shouldHaveNoDependencyToApplicationFromSecondary() { - noClasses() - .that() - .resideInAnyPackage("..infrastructure.secondary..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("..application..") - .because("Secondary should not depend on application") - .check(classes); - } + .check(classes); + } - @Test - void shouldNotHaveInfrastructureDependenciesFromDomain() { - noClasses() - .that() - .resideInAnyPackage("..domain..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("..infrastructure..") - .check(classes); + private String[] authorizedDomainPackages() { + return Stream + .of(List.of(".domain.."), vanillaPackages, commonToolsAndUtilsPackages, sharedKernelsPackages) + .flatMap(Collection::stream) + .toArray(String[]::new); + } } - @Test - void shouldNotHaveExternalDependenciesFromDomain() { - classes() - .that() - .resideInAnyPackage("..domain..") - .should() - .onlyDependOnClassesThat() - .resideInAnyPackage("tech.jhipster.lite..", "java..", ""/* primitives */, "org.slf4j", "org.apache.commons..") - .because("Domain should not depend on external dependencies") - .check(classes); - } + @Nested + class Application { - @Test - void shouldNotHaveContextDependenciesForSharedKernels() { - sharedKernelsPackages.forEach(kernel -> + @Test + void shouldNotDependOnInfrastructure() { noClasses() .that() - .resideInAnyPackage(kernel) + .resideInAPackage("..application..") .should() .dependOnClassesThat() - .resideInAnyPackage(businessContexts.stream().map(context -> context + ".domain..").toArray(String[]::new)) - .orShould() - .resideInAnyPackage(businessContexts.stream().map(context -> context + ".application..").toArray(String[]::new)) - .orShould() - .resideInAnyPackage(businessContexts.stream().map(context -> context + ".infrastructure.secondary..").toArray(String[]::new)) - .because("Shared kernels should not have dependencies to bounded contexts") - .check(classes) - ); + .resideInAPackage("..infrastructure..") + .because("Application should only depend on domain, not on infrastructure") + .check(classes); + } } - @Test - void shouldNotHavePublicControllers() { - noClasses().that().areAnnotatedWith(RestController.class).or().areAnnotatedWith(Controller.class).should().bePublic().check(classes); - } + @Nested + class Primary { - @Test - void domainShouldNotInteractWithOutside() { - JavaClasses importedClasses = new ClassFileImporter() - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) - .importPackages("tech.jhipster.lite"); + @Test + void shouldNotDependOnSecondary() { + noClasses() + .that() + .resideInAPackage("..primary..") + .should() + .dependOnClassesThat() + .resideInAPackage("..secondary..") + .because("Primary should not interact with secondary") + .check(classes); + } - noClasses() - .that() - .resideInAnyPackage("..domain..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("..application..", "..infrastructure..", "..primary..", "..secondary..") - .because("Domain should not interact with application, infrastructure, primary or secondary") - .check(importedClasses); + @Test + void shouldNotHavePublicControllers() { + noClasses().that().areAnnotatedWith(RestController.class).or().areAnnotatedWith(Controller.class).should().bePublic().check(classes); + } } - @Test - void applicationShouldNotInteractWithSecondary() { - JavaClasses importedClasses = new ClassFileImporter() - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) - .importPackages("tech.jhipster.lite"); + @Nested + class Secondary { - noClasses() - .that() - .resideInAnyPackage("..application..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("..secondary..") - .because("Application should not interact with secondary") - .check(importedClasses); - } - - @Test - void primaryShouldNotInteractWithSecondary() { - JavaClasses importedClasses = new ClassFileImporter() - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) - .importPackages("tech.jhipster.lite"); + @Test + void shouldNotDependOnApplication() { + noClasses() + .that() + .resideInAPackage("..infrastructure.secondary..") + .should() + .dependOnClassesThat() + .resideInAPackage("..application..") + .because("Secondary should not depend on application") + .check(classes); + } - noClasses() - .that() - .resideInAnyPackage("..primary..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("..secondary..") - .because("Primary should not interact with secondary") - .check(importedClasses); + @Test + void shouldNotDependOnSameContextPrimary() { + Stream + .concat(businessContexts.stream(), sharedKernels.stream()) + .forEach(context -> { + noClasses() + .that() + .resideInAPackage(context + ".infrastructure.secondary..") + .should() + .dependOnClassesThat() + .resideInAPackage(context + ".infrastructure.primary") + .because("Secondary should not loop to its own context's primary") + .check(classes); + }); + } } }