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 extends Annotation> annotationClass) throws AssertionError {
+ private static Collection packagesWithAnnotation(Class extends Annotation> 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 extends Annotation> annotationClass) throws AssertionError {
+ private static Collection packagesWithAnnotation(Class extends Annotation> 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);
+ });
+ }
}
}