From 5f4c8acbef8a758b95c0297cd5b5d7cde779319b Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 10:51:24 +0400 Subject: [PATCH 01/20] chore: ignore prompts for AI --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4b9c4e7..07b4fb6 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,4 @@ out/ gradle-plugin-publishing.md sedr-library-maven-central-publishing.md +/**/req_prompts.md \ No newline at end of file From 28092f4fa7b3ccbe68ddacbb1b1124aaa93318b2 Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 11:08:18 +0400 Subject: [PATCH 02/20] fix(hexagonal-spring-rules): fail fast on missing architecture configuration What changed: - Added RulePackConfiguration.requireConfigured() to validate required Architecture Validator properties. - Enforced the validation from basePackage() so all rule classes fail early on misconfiguration. - Added RulePackConfigurationTest with requiredArchitecturePropertiesShouldBeConfigured(). - Updated hexagonal-spring-rules/README.adoc to document RulePackConfigurationTest in the rule table. Why: - Misconfigured consumers could previously import zero classes and pass architecture rules vacuously, producing false green builds. How it works: - Throws AssertionError with explicit remediation guidance when architectureValidator.basePackage is blank. - Throws AssertionError when architectureValidator.inPorts, architectureValidator.outPorts, and architectureValidator.domainModel are all empty. - Kept existing packages()/property() helper behavior unchanged; the change is additive. Side effects and caveats: - Consumers now fail immediately with a clear message instead of silently passing when configuration is missing/blank. - Verification builds were run with --no-configuration-cache due to a pre-existing project issue with external process execution during configuration. Outcomes: - Verified hexagonal-spring-rules build succeeds. - Verified direct helper behavior with a temporary src/test/java test (fail when missing, pass when configured), then removed the temporary test. - Verified end-to-end in the example project with intentional misconfiguration and restored the example files afterward; baseline intentional failures remain. --- hexagonal-spring-rules/README.adoc | 3 +++ .../spring/RulePackConfiguration.java | 13 ++++++++++ .../spring/RulePackConfigurationTest.java | 24 +++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfigurationTest.java diff --git a/hexagonal-spring-rules/README.adoc b/hexagonal-spring-rules/README.adoc index 123b7a3..892f99a 100644 --- a/hexagonal-spring-rules/README.adoc +++ b/hexagonal-spring-rules/README.adoc @@ -81,6 +81,9 @@ The default rules target Spring-specific architectural boundaries, including dep |`PortContractTest` |In-ports and out-ports are interfaces, and port signatures reference only Java core and domain model types with no framework leakage into port contracts. + +|`RulePackConfigurationTest` +|Fails fast when required Architecture Validator properties are missing so the rule pack cannot pass with empty class imports. |=== == Architecture Validator Properties diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java index 31ed607..693bb3f 100644 --- a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java @@ -31,6 +31,7 @@ private RulePackConfiguration() { } static String basePackage() { + requireConfigured(); return property("architectureValidator.basePackage", ""); } @@ -54,6 +55,18 @@ static String[] applicationServices() { return packages("architectureValidator.applicationServices"); } + static void requireConfigured() { + String configuredBasePackage = property("architectureValidator.basePackage", "").trim(); + + if (configuredBasePackage.isEmpty()) { + throw new AssertionError("architectureValidator.basePackage is not set. Configure it via the Architecture Validator plugin's architectureValidator { basePackage = 'com.example.myapp' } extension."); + } + + if (inPorts().length == 0 && outPorts().length == 0 && domainModel().length == 0) { + throw new AssertionError("architectureValidator.inPorts, architectureValidator.outPorts, and architectureValidator.domainModel are all empty. Configure at least one of these via the Architecture Validator plugin's architectureValidator { inPorts = 'com.example.myapp.application.port.in'; outPorts = 'com.example.myapp.application.port.out'; domainModel = 'com.example.myapp.domain.model' } extension."); + } + } + static String[] merge(String[] dynamicPackages, String... fixedPackages) { return Stream.concat(Arrays.stream(fixedPackages), Arrays.stream(dynamicPackages)) .toArray(String[]::new); diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfigurationTest.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfigurationTest.java new file mode 100644 index 0000000..5043068 --- /dev/null +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfigurationTest.java @@ -0,0 +1,24 @@ +package com.arc_e_tect.gradle.architecture.spring; + +import org.junit.jupiter.api.Test; + +/** + * Fail-fast configuration validation for the Spring rule pack. + * + *

This rule ensures the consuming project configures the minimum + * Architecture Validator properties so architecture checks do not pass + * vacuously with empty class imports. + * + * @see RulePackConfiguration + * @since 1.0.0 + */ +class RulePackConfigurationTest { + + /** + * Validates that required architecture properties are configured. + */ + @Test + void requiredArchitecturePropertiesShouldBeConfigured() { + RulePackConfiguration.requireConfigured(); + } +} From 35caf8f6c54405b319a267e631f818ee5f76e749 Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 15:09:37 +0400 Subject: [PATCH 03/20] feat(hexagonal-spring-rules): add JPA and web DTO leakage rules What changed: - Added new rule class TypeLeakageTest with three additive architecture rules: - portsShouldNotExposeJpaEntities() - domainModelShouldNotReferenceJpaEntities() - portsShouldNotExposeWebDtos() - Implemented signature-focused leakage detection for port methods (return and parameter types) and direct dependency checks for domain model classes. - Covered both jakarta.persistence.Entity and javax.persistence.Entity annotation names for compatibility. - Updated hexagonal-spring-rules/README.adoc rule table to document TypeLeakageTest. Why: - Existing rules prevented broad framework leakage but did not specifically guard against common hexagonal boundary violations where JPA entity types or web DTO transport types leak into ports and core domain code. - This could allow persistence or transport concerns to couple with application contracts and domain model types. How: - Uses ArchUnit rule style consistent with existing rule pack structure (ClassFileImporter, base package import, because clauses, allowEmptyShould(true), check(classes)). - Applies method-signature conditions to in-port and out-port methods for explicit contract-level detection. - Applies domain dependency condition to detect JPA entity references from domain model packages. Side effects and caveats: - The example project was temporarily wired to a local rule-pack jar and temporary fixtures were introduced only for smoke verification, then fully restored. - No existing rule class source files were modified; this is additive via one new class plus README documentation. Outcomes: - Verified ./gradlew clean build --no-configuration-cache succeeds in hexagonal-spring-rules. - Verified independent smoke failures for each new rule in the example project: - TypeLeakageTest.portsShouldNotExposeJpaEntities - TypeLeakageTest.domainModelShouldNotReferenceJpaEntities - TypeLeakageTest.portsShouldNotExposeWebDtos - Verified positive case with no leakage: TypeLeakageTest XML reported tests=3, failures=0. - Verified example project behavior returns to its original intentional failure pattern after restoration. --- hexagonal-spring-rules/README.adoc | 3 + .../architecture/spring/TypeLeakageTest.java | 166 ++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/TypeLeakageTest.java diff --git a/hexagonal-spring-rules/README.adoc b/hexagonal-spring-rules/README.adoc index 892f99a..64ecafa 100644 --- a/hexagonal-spring-rules/README.adoc +++ b/hexagonal-spring-rules/README.adoc @@ -84,6 +84,9 @@ The default rules target Spring-specific architectural boundaries, including dep |`RulePackConfigurationTest` |Fails fast when required Architecture Validator properties are missing so the rule pack cannot pass with empty class imports. + +|`TypeLeakageTest` +|Ports must not expose JPA entities or web DTO packages, and the domain model must not reference JPA entities. |=== == Architecture Validator Properties diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/TypeLeakageTest.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/TypeLeakageTest.java new file mode 100644 index 0000000..b32cc44 --- /dev/null +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/TypeLeakageTest.java @@ -0,0 +1,166 @@ +package com.arc_e_tect.gradle.architecture.spring; + +import com.tngtech.archunit.core.domain.Dependency; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; +import org.junit.jupiter.api.Test; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods; + +/** + * Type leakage validation rules for Spring Hexagonal architecture. + * + *

This rule class enforces that persistence and web transport details do not leak + * into ports or the domain model. Ports and the core model must depend on stable + * business types, not JPA entities or web DTOs. + * + *

Rules validate: + *

+ * + *

This class is discovered and included in the rule-pack suite by the Architecture Validator + * plugin. Each test method defines a separate validation rule. + * + * @see RulePackConfiguration + * @since 1.0.0 + */ +class TypeLeakageTest { + + private static final String JAKARTA_ENTITY = "jakarta.persistence.Entity"; + private static final String JAVAX_ENTITY = "javax.persistence.Entity"; + + private final JavaClasses classes = new ClassFileImporter() + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) + .importPackages(RulePackConfiguration.basePackage()); + + /** + * Validates that ports do not expose JPA entity types. + * + *

Ports define application contracts and must stay persistence-agnostic. + * Referencing JPA entities in port contracts leaks adapter concerns into + * the application boundary and couples core use cases to persistence models. + */ + @Test + void portsShouldNotExposeJpaEntities() { + methods() + .that().areDeclaredInClassesThat().resideInAnyPackage(RulePackConfiguration.inPorts()) + .or().areDeclaredInClassesThat().resideInAnyPackage(RulePackConfiguration.outPorts()) + .should(notExposeJpaEntityTypesInMethodSignatures()) + .because("Ports must not expose JPA entity types; use domain model contracts instead") + .allowEmptyShould(true) + .check(classes); + } + + /** + * Validates that domain model types do not reference JPA entities. + * + *

The domain model must remain persistence-independent so business + * behavior can evolve independently from ORM mappings and storage concerns. + */ + @Test + void domainModelShouldNotReferenceJpaEntities() { + classes() + .that().resideInAnyPackage(RulePackConfiguration.domainModel()) + .should(notDependOnJpaEntityTypes()) + .because("Domain model must not depend on JPA entity types") + .allowEmptyShould(true) + .check(classes); + } + + /** + * Validates that ports do not expose web DTO/request/response transport types. + * + *

Port contracts should model business interactions, not HTTP transport payloads. + * Using web DTO packages in ports leaks adapter concerns into the application core. + */ + @Test + void portsShouldNotExposeWebDtos() { + methods() + .that().areDeclaredInClassesThat().resideInAnyPackage(RulePackConfiguration.inPorts()) + .or().areDeclaredInClassesThat().resideInAnyPackage(RulePackConfiguration.outPorts()) + .should(notExposeWebDtoTypesInMethodSignatures()) + .because("Ports must not expose web transport DTOs; map them at adapter boundaries") + .allowEmptyShould(true) + .check(classes); + } + + private static ArchCondition notExposeJpaEntityTypesInMethodSignatures() { + return new ArchCondition<>("not expose JPA entity types in method signatures") { + @Override + public void check(JavaMethod method, ConditionEvents events) { + verifySignatureTypeIsNotJpaEntity(method, method.getRawReturnType(), "return type", events); + method.getRawParameterTypes().forEach(parameterType -> + verifySignatureTypeIsNotJpaEntity(method, parameterType, "parameter type", events)); + } + }; + } + + private static ArchCondition notDependOnJpaEntityTypes() { + return new ArchCondition<>("not depend on JPA entity types") { + @Override + public void check(JavaClass javaClass, ConditionEvents events) { + for (Dependency dependency : javaClass.getDirectDependenciesFromSelf()) { + JavaClass targetClass = dependency.getTargetClass(); + if (isJpaEntityType(targetClass)) { + events.add(SimpleConditionEvent.violated( + javaClass, + javaClass.getName() + " depends on JPA entity type " + targetClass.getName())); + } + } + } + }; + } + + private static ArchCondition notExposeWebDtoTypesInMethodSignatures() { + return new ArchCondition<>("not expose web DTO types in method signatures") { + @Override + public void check(JavaMethod method, ConditionEvents events) { + verifySignatureTypeIsNotWebDto(method, method.getRawReturnType(), "return type", events); + method.getRawParameterTypes().forEach(parameterType -> + verifySignatureTypeIsNotWebDto(method, parameterType, "parameter type", events)); + } + }; + } + + private static void verifySignatureTypeIsNotJpaEntity(JavaMethod method, JavaClass signatureType, String role, ConditionEvents events) { + if (isJpaEntityType(signatureType)) { + events.add(SimpleConditionEvent.violated( + method, + method.getOwner().getName() + "#" + method.getName() + " exposes JPA entity " + + signatureType.getName() + " as " + role)); + } + } + + private static void verifySignatureTypeIsNotWebDto(JavaMethod method, JavaClass signatureType, String role, ConditionEvents events) { + if (isWebDtoType(signatureType)) { + events.add(SimpleConditionEvent.violated( + method, + method.getOwner().getName() + "#" + method.getName() + " exposes web DTO type " + + signatureType.getName() + " as " + role)); + } + } + + private static boolean isJpaEntityType(JavaClass javaClass) { + return javaClass.getAnnotations().stream() + .map(annotation -> annotation.getRawType().getName()) + .anyMatch(annotationName -> JAKARTA_ENTITY.equals(annotationName) || JAVAX_ENTITY.equals(annotationName)); + } + + private static boolean isWebDtoType(JavaClass javaClass) { + String typeName = javaClass.getName().toLowerCase(); + return typeName.contains(".web.") + && (typeName.contains(".dto.") + || typeName.contains(".request.") + || typeName.contains(".response.")); + } +} From b2ec7bef9e551d64b632cae7bc49a745f957c094 Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 15:14:42 +0400 Subject: [PATCH 04/20] feat(hexagonal-spring-rules): add package cycle detection rules What changed: - Added CycleFreedomTest with two ArchUnit slice-based cycle rules: - adapterPackagesShouldBeFreeOfCycles() - domainModelShouldBeFreeOfCycles() - Implemented per-root slice pattern normalization to apply SlicesRuleDefinition.slices().matching(...).should().beFreeOfCycles() across configured package roots. - Added graceful no-op behavior when adapters/domainModel package arrays are empty to avoid false failures in partially configured projects. - Updated README.adoc rule table with CycleFreedomTest coverage. Why: - Existing layer-boundary rules do not detect intra-layer package cycles. - Cycles inside adapter or domain package trees increase coupling and reduce maintainability, so they need explicit enforcement. How it works: - Imports classes from RulePackConfiguration.basePackage() with test classes excluded, consistent with existing rule-pack style. - Builds slice patterns per configured package root using the required (**)-style matching and checks each slice graph for cycles. - Uses allowEmptyShould(true) on cycle rules and early return when no target roots are configured. Side effects and caveats: - Verification used temporary example fixtures to create adapter and domain cycles, then removed them fully; no fixture code is retained. - Example project was temporarily wired to the local rule-pack jar for end-to-end validation, then restored to its original dependency setup. Outcomes: - Verified hexagonal-spring-rules build succeeds (./gradlew clean build --no-configuration-cache). - Verified adapter cycle scenario fails at CycleFreedomTest.adapterPackagesShouldBeFreeOfCycles(). - Verified domain cycle scenario fails at CycleFreedomTest.domainModelShouldBeFreeOfCycles(). - Verified no-cycle baseline and restored example both keep CycleFreedomTest green (tests=2, failures=0) and preserve original intentional example failures. --- hexagonal-spring-rules/README.adoc | 3 + .../architecture/spring/CycleFreedomTest.java | 83 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/CycleFreedomTest.java diff --git a/hexagonal-spring-rules/README.adoc b/hexagonal-spring-rules/README.adoc index 64ecafa..cba8459 100644 --- a/hexagonal-spring-rules/README.adoc +++ b/hexagonal-spring-rules/README.adoc @@ -87,6 +87,9 @@ The default rules target Spring-specific architectural boundaries, including dep |`TypeLeakageTest` |Ports must not expose JPA entities or web DTO packages, and the domain model must not reference JPA entities. + +|`CycleFreedomTest` +|Adapter and domain-model package trees must be free of circular package dependencies. |=== == Architecture Validator Properties diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/CycleFreedomTest.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/CycleFreedomTest.java new file mode 100644 index 0000000..ddd1c0f --- /dev/null +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/CycleFreedomTest.java @@ -0,0 +1,83 @@ +package com.arc_e_tect.gradle.architecture.spring; + +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.core.importer.ImportOption; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; + +/** + * Package cycle validation rules for Hexagonal architecture. + * + *

This rule class enforces cycle freedom inside configured adapter and domain-model + * package roots. Layer boundary checks can miss cycles inside a single layer, so these + * rules prevent tightly coupled package graphs that are hard to evolve and test. + * + *

This class is discovered and included in the rule-pack suite by the Architecture Validator + * plugin. Each test method defines a separate validation rule. + * + * @see RulePackConfiguration + * @since 1.0.0 + */ +class CycleFreedomTest { + + private final JavaClasses classes = new ClassFileImporter() + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) + .importPackages(RulePackConfiguration.basePackage()); + + /** + * Validates that configured adapter package trees are free of package cycles. + */ + @Test + void adapterPackagesShouldBeFreeOfCycles() { + assertConfiguredSlicesAreCycleFree( + RulePackConfiguration.adapters(), + "Adapter packages should remain acyclic to keep infrastructure boundaries maintainable"); + } + + /** + * Validates that configured domain model package trees are free of package cycles. + */ + @Test + void domainModelShouldBeFreeOfCycles() { + assertConfiguredSlicesAreCycleFree( + RulePackConfiguration.domainModel(), + "Domain model packages should remain acyclic to preserve a clear core model structure"); + } + + private void assertConfiguredSlicesAreCycleFree(String[] packageRoots, String reason) { + if (packageRoots.length == 0) { + return; + } + + Arrays.stream(packageRoots) + .map(CycleFreedomTest::toSlicePattern) + .forEach(slicePattern -> slices() + .matching(slicePattern) + .should().beFreeOfCycles() + .because(reason) + .allowEmptyShould(true) + .check(classes)); + } + + private static String toSlicePattern(String packageRoot) { + String normalized = packageRoot.trim(); + + while (normalized.startsWith("..")) { + normalized = normalized.substring(2); + } + + while (normalized.endsWith("..")) { + normalized = normalized.substring(0, normalized.length() - 2); + } + + if (normalized.endsWith(".")) { + normalized = normalized.substring(0, normalized.length() - 1); + } + + return normalized.isEmpty() ? "(**)" : normalized + ".(**)"; + } +} From 30bc0997fa3ef1eeb0f901ebc681c15c274b648c Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 16:16:19 +0400 Subject: [PATCH 05/20] test(hexagonal-spring-rules): add self-test fixtures for rule-pack coverage What changed: - Enabled java-test-fixtures in hexagonal-spring-rules/build.gradle and wired test fixtures into test classpath. - Added RulePackSelfTest under src/test/java to execute all rule methods against compliant and targeted violating fixtures. - Added compliant fixture package tree under src/testFixtures/java/com/arc_e_tect/fixtures/compliant. - Added violating fixture scenarios under src/testFixtures/java/com/arc_e_tect/fixtures/violating, one scenario per rule expectation across SpringHexagonalArchitectureTest, DomainIsolationTest, DependencyDirectionTest, and PortContractTest. Why: - The rule pack previously compiled but had no in-repo runtime proof that each rule actually fires on violations and stays silent on compliant structures. - This change closes that regression gap by making rule behavior executable and repeatable inside the module build. How: - RulePackSelfTest sets architectureValidator.* system properties per scenario, instantiates rule classes, and asserts pass/fail outcomes with JUnit. - Fixture sources are isolated in testFixtures so they are available to tests but excluded from the main published jar. - Scenario design keeps unrelated rules green while intentionally failing only the targeted rule per case. Side effects and caveats: - Verification commands use --no-configuration-cache due existing build script version-resolution behavior invoking gh during configuration time. - No example-project source files were modified. Outcomes: - ./gradlew clean build --no-configuration-cache succeeded in hexagonal-spring-rules. - ./gradlew test --tests '*RulePackSelfTest*' --no-configuration-cache succeeded. - RulePackSelfTest report shows tests=14, failures=0, errors=0, skipped=0. - Main binary jar contains only main rule classes; fixture and self-test classes are absent. --- hexagonal-spring-rules/build.gradle | 4 + .../architecture/spring/RulePackSelfTest.java | 383 ++++++++++++++++++ .../OrderStoreRepositoryAdapter.java | 12 + .../adapters/web/CreateOrderController.java | 18 + .../port/in/CreateOrderUseCase.java | 5 + .../service/CreateOrderService.java | 18 + .../ApplicationConfiguration.java | 11 + .../compliant/domain/model/Order.java | 16 + .../service/adapter/AdapterCallsService.java | 16 + .../domain/service/impl/AppServiceImpl.java | 7 + .../adapters/persistence/AdapterThing.java | 7 + .../domain/model/CoreUsesAdapter.java | 16 + .../application/service/AppService.java | 7 + .../domain/model/DomainCallsService.java | 16 + .../application/service/AnnotatedService.java | 10 + .../domain/model/GoodDomain.java | 14 + .../domain/model/BadDomain.java | 16 + .../framework/FrameworkBoundDomain.java | 17 + .../domain/model/GoodDomain.java | 14 + .../port/in/NotInterfaceInputPort.java | 10 + .../domain/model/PortDomain.java | 14 + .../application/port/in/GoodInputPort.java | 7 + .../domain/model/PortDomain.java | 14 + .../application/port/in/LeakyInputPort.java | 7 + .../domain/model/PortDomain.java | 14 + .../spring/components/misc/BadComponent.java | 7 + .../adapters/web/BadController.java | 18 + .../application/service/ConcreteService.java | 7 + .../adapters/persistence/BadRepository.java | 10 + .../service/RepositoryConsumer.java | 16 + .../adapters/persistence/BadRepository.java | 10 + .../services/adapters/service/BadService.java | 18 + 32 files changed, 759 insertions(+) create mode 100644 hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/persistence/OrderStoreRepositoryAdapter.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/web/CreateOrderController.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/in/CreateOrderUseCase.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/service/CreateOrderService.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/configuration/ApplicationConfiguration.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/domain/model/Order.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/adapterDependsOnService/application/domain/service/adapter/AdapterCallsService.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/adapterDependsOnService/application/domain/service/impl/AppServiceImpl.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/coreDependsOnAdapter/adapters/persistence/AdapterThing.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/coreDependsOnAdapter/domain/model/CoreUsesAdapter.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/nonConfigDependsOnService/application/service/AppService.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/nonConfigDependsOnService/domain/model/DomainCallsService.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/applicationServiceStereotype/application/service/AnnotatedService.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/applicationServiceStereotype/domain/model/GoodDomain.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/domain/model/BadDomain.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/frameworkDependency/domain/framework/FrameworkBoundDomain.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/frameworkDependency/domain/model/GoodDomain.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/in/NotInterfaceInputPort.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/domain/model/PortDomain.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/in/GoodInputPort.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/domain/model/PortDomain.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/in/LeakyInputPort.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/domain/model/PortDomain.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/components/misc/BadComponent.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/controllers/adapters/web/BadController.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/controllers/application/service/ConcreteService.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/repositories/adapters/persistence/BadRepository.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/repositories/application/service/RepositoryConsumer.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/services/adapters/persistence/BadRepository.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/services/adapters/service/BadService.java diff --git a/hexagonal-spring-rules/build.gradle b/hexagonal-spring-rules/build.gradle index 7ad04cf..0a75663 100644 --- a/hexagonal-spring-rules/build.gradle +++ b/hexagonal-spring-rules/build.gradle @@ -7,6 +7,7 @@ plugins { id 'jacoco' id 'java-library' + id 'java-test-fixtures' id 'maven-publish' alias(libs.plugins.owasp.dependency.check.iff) alias(libs.plugins.jreleaser.iff) @@ -92,7 +93,10 @@ dependencies { compileOnly libs.spring.context.iff compileOnly libs.junit.jupiter.iff + testFixturesImplementation libs.spring.context.iff + testImplementation libs.junit.jupiter.iff + testImplementation(testFixtures(project)) testRuntimeOnly libs.junit.platform.launcher.iff } diff --git a/hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java b/hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java new file mode 100644 index 0000000..9b1d4d8 --- /dev/null +++ b/hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java @@ -0,0 +1,383 @@ +package com.arc_e_tect.gradle.architecture.spring; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Self-tests for the Spring Hexagonal rule pack. + * + *

This test suite executes rule classes against in-repo fixture packages to prove + * that compliant structures pass while targeted violations fail. + * + * @since 0.4.0 + */ +class RulePackSelfTest { + + private static final String BASE_PACKAGE_KEY = "architectureValidator.basePackage"; + private static final String IN_PORTS_KEY = "architectureValidator.inPorts"; + private static final String OUT_PORTS_KEY = "architectureValidator.outPorts"; + private static final String DOMAIN_MODEL_KEY = "architectureValidator.domainModel"; + private static final String ADAPTERS_KEY = "architectureValidator.adapters"; + private static final String APPLICATION_SERVICES_KEY = "architectureValidator.applicationServices"; + + private static final String COMPLIANT_BASE = "com.arc_e_tect.fixtures.compliant"; + + private static final String SPRING_CONTROLLERS_BASE = "com.arc_e_tect.fixtures.violating.spring.controllers"; + private static final String SPRING_SERVICES_BASE = "com.arc_e_tect.fixtures.violating.spring.services"; + private static final String SPRING_REPOSITORIES_BASE = "com.arc_e_tect.fixtures.violating.spring.repositories"; + private static final String SPRING_COMPONENTS_BASE = "com.arc_e_tect.fixtures.violating.spring.components"; + + private static final String DOMAIN_DEPENDENCY_BASE = "com.arc_e_tect.fixtures.violating.domain.domainDependency"; + private static final String DOMAIN_FRAMEWORK_BASE = "com.arc_e_tect.fixtures.violating.domain.frameworkDependency"; + private static final String DOMAIN_SERVICE_STEREOTYPE_BASE = "com.arc_e_tect.fixtures.violating.domain.applicationServiceStereotype"; + + private static final String DEPENDENCY_CORE_BASE = "com.arc_e_tect.fixtures.violating.dependency.coreDependsOnAdapter"; + private static final String DEPENDENCY_ADAPTER_BASE = "com.arc_e_tect.fixtures.violating.dependency.adapterDependsOnService"; + private static final String DEPENDENCY_NON_CONFIG_BASE = "com.arc_e_tect.fixtures.violating.dependency.nonConfigDependsOnService"; + + private static final String PORT_INPUT_BASE = "com.arc_e_tect.fixtures.violating.port.inputNotInterface"; + private static final String PORT_OUTPUT_BASE = "com.arc_e_tect.fixtures.violating.port.outputNotInterface"; + private static final String PORT_SIGNATURE_BASE = "com.arc_e_tect.fixtures.violating.port.signatureLeak"; + + private final Map originalProperties = new HashMap<>(); + + @BeforeEach + void captureOriginalArchitectureProperties() { + capture(BASE_PACKAGE_KEY); + capture(IN_PORTS_KEY); + capture(OUT_PORTS_KEY); + capture(DOMAIN_MODEL_KEY); + capture(ADAPTERS_KEY); + capture(APPLICATION_SERVICES_KEY); + } + + @AfterEach + void restoreOriginalArchitectureProperties() { + restore(BASE_PACKAGE_KEY); + restore(IN_PORTS_KEY); + restore(OUT_PORTS_KEY); + restore(DOMAIN_MODEL_KEY); + restore(ADAPTERS_KEY); + restore(APPLICATION_SERVICES_KEY); + } + + @Test + void rulePackShouldPassAllCoreRulesWhenFixturesAreCompliant() { + configure( + COMPLIANT_BASE, + COMPLIANT_BASE + ".application.port.in..", + COMPLIANT_BASE + ".application.port.out..", + COMPLIANT_BASE + ".domain.model..", + COMPLIANT_BASE + ".adapters.web..," + COMPLIANT_BASE + ".adapters.persistence..", + COMPLIANT_BASE + ".application.service.." + ); + + SpringHexagonalArchitectureTest springRules = new SpringHexagonalArchitectureTest(); + DomainIsolationTest domainRules = new DomainIsolationTest(); + DependencyDirectionTest dependencyRules = new DependencyDirectionTest(); + PortContractTest portRules = new PortContractTest(); + + assertAll( + () -> assertDoesNotThrow(springRules::controllersShouldOnlyCallInPorts), + () -> assertDoesNotThrow(springRules::servicesShouldNotAccessRepositoriesDirectly), + () -> assertDoesNotThrow(springRules::repositoriesShouldOnlyBeAccessedViaOutPorts), + () -> assertDoesNotThrow(springRules::springComponentsShouldFollowHexagonalLayers), + () -> assertDoesNotThrow(domainRules::domainModelShouldOnlyDependOnJavaCoreAndDomainModel), + () -> assertDoesNotThrow(domainRules::coreApplicationLayerShouldHaveNoFrameworkDependencies), + () -> assertDoesNotThrow(domainRules::applicationServicesShouldNotCarrySpringStereotypes), + () -> assertDoesNotThrow(dependencyRules::coreApplicationLayerShouldNotDependOnAdapters), + () -> assertDoesNotThrow(dependencyRules::adaptersShouldNotDependOnServiceImplementations), + () -> assertDoesNotThrow(dependencyRules::onlyConfigurationMayDependOnServiceImplementations), + () -> assertDoesNotThrow(portRules::inputPortsShouldBeInterfaces), + () -> assertDoesNotThrow(portRules::outputPortsShouldBeInterfaces), + () -> assertDoesNotThrow(portRules::portsShouldOnlyDependOnJavaCoreAndDomainModel) + ); + } + + @Test + void springHexagonalArchitectureShouldFailControllersRuleWhenControllerDependsOnService() { + configure( + SPRING_CONTROLLERS_BASE, + SPRING_CONTROLLERS_BASE + ".application.port.in..", + SPRING_CONTROLLERS_BASE + ".application.port.out..", + SPRING_CONTROLLERS_BASE + ".domain.model..", + SPRING_CONTROLLERS_BASE + ".adapters..", + SPRING_CONTROLLERS_BASE + ".application.service.." + ); + + SpringHexagonalArchitectureTest rules = new SpringHexagonalArchitectureTest(); + + assertThrows(AssertionError.class, rules::controllersShouldOnlyCallInPorts); + assertDoesNotThrow(rules::servicesShouldNotAccessRepositoriesDirectly); + assertDoesNotThrow(rules::repositoriesShouldOnlyBeAccessedViaOutPorts); + assertDoesNotThrow(rules::springComponentsShouldFollowHexagonalLayers); + } + + @Test + void springHexagonalArchitectureShouldFailServicesRuleWhenServiceDependsOnRepository() { + configure( + SPRING_SERVICES_BASE, + SPRING_SERVICES_BASE + ".application.port.in..", + SPRING_SERVICES_BASE + ".application.port.out..", + SPRING_SERVICES_BASE + ".domain.model..", + SPRING_SERVICES_BASE + ".adapters..", + SPRING_SERVICES_BASE + ".application.service.." + ); + + SpringHexagonalArchitectureTest rules = new SpringHexagonalArchitectureTest(); + + assertDoesNotThrow(rules::controllersShouldOnlyCallInPorts); + assertThrows(AssertionError.class, rules::servicesShouldNotAccessRepositoriesDirectly); + assertDoesNotThrow(rules::repositoriesShouldOnlyBeAccessedViaOutPorts); + assertDoesNotThrow(rules::springComponentsShouldFollowHexagonalLayers); + } + + @Test + void springHexagonalArchitectureShouldFailRepositoryAccessRuleWhenRepositoryIsUsedOutsideAllowedLayers() { + configure( + SPRING_REPOSITORIES_BASE, + SPRING_REPOSITORIES_BASE + ".application.port.in..", + SPRING_REPOSITORIES_BASE + ".application.port.out..", + SPRING_REPOSITORIES_BASE + ".domain.model..", + SPRING_REPOSITORIES_BASE + ".adapters..", + SPRING_REPOSITORIES_BASE + ".application.service.." + ); + + SpringHexagonalArchitectureTest rules = new SpringHexagonalArchitectureTest(); + + assertDoesNotThrow(rules::controllersShouldOnlyCallInPorts); + assertDoesNotThrow(rules::servicesShouldNotAccessRepositoriesDirectly); + assertThrows(AssertionError.class, rules::repositoriesShouldOnlyBeAccessedViaOutPorts); + assertDoesNotThrow(rules::springComponentsShouldFollowHexagonalLayers); + } + + @Test + void springHexagonalArchitectureShouldFailComponentLayerRuleWhenComponentIsOutsideHexagonalPackages() { + configure( + SPRING_COMPONENTS_BASE, + SPRING_COMPONENTS_BASE + ".application.port.in..", + SPRING_COMPONENTS_BASE + ".application.port.out..", + SPRING_COMPONENTS_BASE + ".domain.model..", + SPRING_COMPONENTS_BASE + ".adapters..", + SPRING_COMPONENTS_BASE + ".application.service.." + ); + + SpringHexagonalArchitectureTest rules = new SpringHexagonalArchitectureTest(); + + assertDoesNotThrow(rules::controllersShouldOnlyCallInPorts); + assertDoesNotThrow(rules::servicesShouldNotAccessRepositoriesDirectly); + assertDoesNotThrow(rules::repositoriesShouldOnlyBeAccessedViaOutPorts); + assertThrows(AssertionError.class, rules::springComponentsShouldFollowHexagonalLayers); + } + + @Test + void domainIsolationShouldFailDomainDependencyRuleWhenDomainDependsOnPortContract() { + configure( + DOMAIN_DEPENDENCY_BASE, + DOMAIN_DEPENDENCY_BASE + ".application.port.in..", + DOMAIN_DEPENDENCY_BASE + ".application.port.out..", + DOMAIN_DEPENDENCY_BASE + ".domain.model..", + DOMAIN_DEPENDENCY_BASE + ".adapters..", + DOMAIN_DEPENDENCY_BASE + ".application.service.." + ); + + DomainIsolationTest rules = new DomainIsolationTest(); + + assertThrows(AssertionError.class, rules::domainModelShouldOnlyDependOnJavaCoreAndDomainModel); + assertDoesNotThrow(rules::coreApplicationLayerShouldHaveNoFrameworkDependencies); + assertDoesNotThrow(rules::applicationServicesShouldNotCarrySpringStereotypes); + } + + @Test + void domainIsolationShouldFailFrameworkDependencyRuleWhenDomainUsesSpringTypes() { + configure( + DOMAIN_FRAMEWORK_BASE, + DOMAIN_FRAMEWORK_BASE + ".application.port.in..", + DOMAIN_FRAMEWORK_BASE + ".application.port.out..", + DOMAIN_FRAMEWORK_BASE + ".domain.model..", + DOMAIN_FRAMEWORK_BASE + ".adapters..", + DOMAIN_FRAMEWORK_BASE + ".application.service.." + ); + + DomainIsolationTest baselineRules = new DomainIsolationTest(); + assertDoesNotThrow(baselineRules::domainModelShouldOnlyDependOnJavaCoreAndDomainModel); + assertDoesNotThrow(baselineRules::applicationServicesShouldNotCarrySpringStereotypes); + + configure( + DOMAIN_FRAMEWORK_BASE, + DOMAIN_FRAMEWORK_BASE + ".application.port.in..", + DOMAIN_FRAMEWORK_BASE + ".application.port.out..", + DOMAIN_FRAMEWORK_BASE + ".domain.framework..", + DOMAIN_FRAMEWORK_BASE + ".adapters..", + DOMAIN_FRAMEWORK_BASE + ".application.service.." + ); + + DomainIsolationTest violatingRules = new DomainIsolationTest(); + + assertThrows(AssertionError.class, violatingRules::coreApplicationLayerShouldHaveNoFrameworkDependencies); + assertDoesNotThrow(violatingRules::applicationServicesShouldNotCarrySpringStereotypes); + } + + @Test + void domainIsolationShouldFailServiceStereotypeRuleWhenApplicationServiceIsAnnotatedWithService() { + configure( + DOMAIN_SERVICE_STEREOTYPE_BASE, + DOMAIN_SERVICE_STEREOTYPE_BASE + ".application.port.in..", + DOMAIN_SERVICE_STEREOTYPE_BASE + ".application.port.out..", + DOMAIN_SERVICE_STEREOTYPE_BASE + ".domain.model..", + DOMAIN_SERVICE_STEREOTYPE_BASE + ".adapters..", + DOMAIN_SERVICE_STEREOTYPE_BASE + ".application.service.." + ); + + DomainIsolationTest rules = new DomainIsolationTest(); + + assertDoesNotThrow(rules::domainModelShouldOnlyDependOnJavaCoreAndDomainModel); + assertDoesNotThrow(rules::coreApplicationLayerShouldHaveNoFrameworkDependencies); + assertThrows(AssertionError.class, rules::applicationServicesShouldNotCarrySpringStereotypes); + } + + @Test + void dependencyDirectionShouldFailCoreRuleWhenCoreDependsOnAdapters() { + configure( + DEPENDENCY_CORE_BASE, + DEPENDENCY_CORE_BASE + ".application.port.in..", + DEPENDENCY_CORE_BASE + ".application.port.out..", + DEPENDENCY_CORE_BASE + ".domain.model..", + DEPENDENCY_CORE_BASE + ".adapters..", + DEPENDENCY_CORE_BASE + ".application.service.." + ); + + DependencyDirectionTest rules = new DependencyDirectionTest(); + + assertThrows(AssertionError.class, rules::coreApplicationLayerShouldNotDependOnAdapters); + assertDoesNotThrow(rules::adaptersShouldNotDependOnServiceImplementations); + assertDoesNotThrow(rules::onlyConfigurationMayDependOnServiceImplementations); + } + + @Test + void dependencyDirectionShouldFailAdapterRuleWhenAdapterDependsOnServiceImplementation() { + configure( + DEPENDENCY_ADAPTER_BASE, + DEPENDENCY_ADAPTER_BASE + ".application.port.in..", + DEPENDENCY_ADAPTER_BASE + ".application.port.out..", + DEPENDENCY_ADAPTER_BASE + ".domain.model..", + DEPENDENCY_ADAPTER_BASE + ".application.domain.service.adapter..", + DEPENDENCY_ADAPTER_BASE + ".application.domain.service.impl.." + ); + + DependencyDirectionTest rules = new DependencyDirectionTest(); + + assertDoesNotThrow(rules::coreApplicationLayerShouldNotDependOnAdapters); + assertThrows(AssertionError.class, rules::adaptersShouldNotDependOnServiceImplementations); + assertDoesNotThrow(rules::onlyConfigurationMayDependOnServiceImplementations); + } + + @Test + void dependencyDirectionShouldFailConfigurationRuleWhenNonConfigurationDependsOnServiceImplementation() { + configure( + DEPENDENCY_NON_CONFIG_BASE, + DEPENDENCY_NON_CONFIG_BASE + ".application.port.in..", + DEPENDENCY_NON_CONFIG_BASE + ".application.port.out..", + DEPENDENCY_NON_CONFIG_BASE + ".domain.model..", + DEPENDENCY_NON_CONFIG_BASE + ".adapters..", + DEPENDENCY_NON_CONFIG_BASE + ".application.service.." + ); + + DependencyDirectionTest rules = new DependencyDirectionTest(); + + assertDoesNotThrow(rules::coreApplicationLayerShouldNotDependOnAdapters); + assertDoesNotThrow(rules::adaptersShouldNotDependOnServiceImplementations); + assertThrows(AssertionError.class, rules::onlyConfigurationMayDependOnServiceImplementations); + } + + @Test + void portContractShouldFailInputPortRuleWhenInputPortIsAConcreteClass() { + configure( + PORT_INPUT_BASE, + PORT_INPUT_BASE + ".application.port.in..", + PORT_INPUT_BASE + ".application.port.out..", + PORT_INPUT_BASE + ".domain.model..", + PORT_INPUT_BASE + ".adapters..", + PORT_INPUT_BASE + ".application.service.." + ); + + PortContractTest rules = new PortContractTest(); + + assertThrows(AssertionError.class, rules::inputPortsShouldBeInterfaces); + assertDoesNotThrow(rules::outputPortsShouldBeInterfaces); + assertDoesNotThrow(rules::portsShouldOnlyDependOnJavaCoreAndDomainModel); + } + + @Test + void portContractShouldFailOutputPortRuleWhenOutputPortIsAConcreteClass() { + configure( + PORT_OUTPUT_BASE, + PORT_OUTPUT_BASE + ".application.port.in..", + PORT_OUTPUT_BASE + ".application.port.out..", + PORT_OUTPUT_BASE + ".domain.model..", + PORT_OUTPUT_BASE + ".adapters..", + PORT_OUTPUT_BASE + ".application.service.." + ); + + PortContractTest rules = new PortContractTest(); + + assertDoesNotThrow(rules::inputPortsShouldBeInterfaces); + assertThrows(AssertionError.class, rules::outputPortsShouldBeInterfaces); + assertDoesNotThrow(rules::portsShouldOnlyDependOnJavaCoreAndDomainModel); + } + + @Test + void portContractShouldFailSignatureRuleWhenPortExposesFrameworkType() { + configure( + PORT_SIGNATURE_BASE, + PORT_SIGNATURE_BASE + ".application.port.in..", + PORT_SIGNATURE_BASE + ".application.port.out..", + PORT_SIGNATURE_BASE + ".domain.model..", + PORT_SIGNATURE_BASE + ".adapters..", + PORT_SIGNATURE_BASE + ".application.service.." + ); + + PortContractTest rules = new PortContractTest(); + + assertDoesNotThrow(rules::inputPortsShouldBeInterfaces); + assertDoesNotThrow(rules::outputPortsShouldBeInterfaces); + assertThrows(AssertionError.class, rules::portsShouldOnlyDependOnJavaCoreAndDomainModel); + } + + private void capture(String key) { + originalProperties.put(key, System.getProperty(key)); + } + + private void restore(String key) { + String value = originalProperties.get(key); + if (value == null) { + System.clearProperty(key); + return; + } + System.setProperty(key, value); + } + + private void configure( + String basePackage, + String inPorts, + String outPorts, + String domainModel, + String adapters, + String applicationServices + ) { + System.setProperty(BASE_PACKAGE_KEY, basePackage); + System.setProperty(IN_PORTS_KEY, inPorts); + System.setProperty(OUT_PORTS_KEY, outPorts); + System.setProperty(DOMAIN_MODEL_KEY, domainModel); + System.setProperty(ADAPTERS_KEY, adapters); + System.setProperty(APPLICATION_SERVICES_KEY, applicationServices); + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/persistence/OrderStoreRepositoryAdapter.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/persistence/OrderStoreRepositoryAdapter.java new file mode 100644 index 0000000..2872e7f --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/persistence/OrderStoreRepositoryAdapter.java @@ -0,0 +1,12 @@ +package com.arc_e_tect.fixtures.compliant.adapters.persistence; + +import com.arc_e_tect.fixtures.compliant.application.port.out.OrderStorePort; +import org.springframework.stereotype.Repository; + +@Repository +public class OrderStoreRepositoryAdapter implements OrderStorePort { + + @Override + public void save() { + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/web/CreateOrderController.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/web/CreateOrderController.java new file mode 100644 index 0000000..ce65d62 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/web/CreateOrderController.java @@ -0,0 +1,18 @@ +package com.arc_e_tect.fixtures.compliant.adapters.web; + +import com.arc_e_tect.fixtures.compliant.application.port.in.CreateOrderUseCase; +import org.springframework.stereotype.Controller; + +@Controller +public class CreateOrderController { + + private final CreateOrderUseCase createOrderUseCase; + + public CreateOrderController(CreateOrderUseCase createOrderUseCase) { + this.createOrderUseCase = createOrderUseCase; + } + + public void create() { + createOrderUseCase.create(); + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/in/CreateOrderUseCase.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/in/CreateOrderUseCase.java new file mode 100644 index 0000000..6f2607f --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/in/CreateOrderUseCase.java @@ -0,0 +1,5 @@ +package com.arc_e_tect.fixtures.compliant.application.port.in; + +public interface CreateOrderUseCase { + void create(); +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/service/CreateOrderService.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/service/CreateOrderService.java new file mode 100644 index 0000000..422f889 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/service/CreateOrderService.java @@ -0,0 +1,18 @@ +package com.arc_e_tect.fixtures.compliant.application.service; + +import com.arc_e_tect.fixtures.compliant.application.port.in.CreateOrderUseCase; +import com.arc_e_tect.fixtures.compliant.application.port.out.OrderStorePort; + +public class CreateOrderService implements CreateOrderUseCase { + + private final OrderStorePort orderStorePort; + + public CreateOrderService(OrderStorePort orderStorePort) { + this.orderStorePort = orderStorePort; + } + + @Override + public void create() { + orderStorePort.save(); + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/configuration/ApplicationConfiguration.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/configuration/ApplicationConfiguration.java new file mode 100644 index 0000000..3f3dd78 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/configuration/ApplicationConfiguration.java @@ -0,0 +1,11 @@ +package com.arc_e_tect.fixtures.compliant.configuration; + +import com.arc_e_tect.fixtures.compliant.application.port.out.OrderStorePort; +import com.arc_e_tect.fixtures.compliant.application.service.CreateOrderService; + +public class ApplicationConfiguration { + + public CreateOrderService createOrderService(OrderStorePort orderStorePort) { + return new CreateOrderService(orderStorePort); + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/domain/model/Order.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/domain/model/Order.java new file mode 100644 index 0000000..71ba8f0 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/domain/model/Order.java @@ -0,0 +1,16 @@ +package com.arc_e_tect.fixtures.compliant.domain.model; + +import java.util.UUID; + +public class Order { + + private final UUID id; + + public Order(UUID id) { + this.id = id; + } + + public UUID id() { + return id; + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/adapterDependsOnService/application/domain/service/adapter/AdapterCallsService.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/adapterDependsOnService/application/domain/service/adapter/AdapterCallsService.java new file mode 100644 index 0000000..f3805bb --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/adapterDependsOnService/application/domain/service/adapter/AdapterCallsService.java @@ -0,0 +1,16 @@ +package com.arc_e_tect.fixtures.violating.dependency.adapterDependsOnService.application.domain.service.adapter; + +import com.arc_e_tect.fixtures.violating.dependency.adapterDependsOnService.application.domain.service.impl.AppServiceImpl; + +public class AdapterCallsService { + + private final AppServiceImpl appService; + + public AdapterCallsService(AppServiceImpl appService) { + this.appService = appService; + } + + public void execute() { + appService.execute(); + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/adapterDependsOnService/application/domain/service/impl/AppServiceImpl.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/adapterDependsOnService/application/domain/service/impl/AppServiceImpl.java new file mode 100644 index 0000000..5557f50 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/adapterDependsOnService/application/domain/service/impl/AppServiceImpl.java @@ -0,0 +1,7 @@ +package com.arc_e_tect.fixtures.violating.dependency.adapterDependsOnService.application.domain.service.impl; + +public class AppServiceImpl { + + public void execute() { + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/coreDependsOnAdapter/adapters/persistence/AdapterThing.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/coreDependsOnAdapter/adapters/persistence/AdapterThing.java new file mode 100644 index 0000000..3d3524c --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/coreDependsOnAdapter/adapters/persistence/AdapterThing.java @@ -0,0 +1,7 @@ +package com.arc_e_tect.fixtures.violating.dependency.coreDependsOnAdapter.adapters.persistence; + +public class AdapterThing { + + public void execute() { + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/coreDependsOnAdapter/domain/model/CoreUsesAdapter.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/coreDependsOnAdapter/domain/model/CoreUsesAdapter.java new file mode 100644 index 0000000..6df8df9 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/coreDependsOnAdapter/domain/model/CoreUsesAdapter.java @@ -0,0 +1,16 @@ +package com.arc_e_tect.fixtures.violating.dependency.coreDependsOnAdapter.domain.model; + +import com.arc_e_tect.fixtures.violating.dependency.coreDependsOnAdapter.adapters.persistence.AdapterThing; + +public class CoreUsesAdapter { + + private final AdapterThing adapterThing; + + public CoreUsesAdapter(AdapterThing adapterThing) { + this.adapterThing = adapterThing; + } + + public void execute() { + adapterThing.execute(); + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/nonConfigDependsOnService/application/service/AppService.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/nonConfigDependsOnService/application/service/AppService.java new file mode 100644 index 0000000..10490c2 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/nonConfigDependsOnService/application/service/AppService.java @@ -0,0 +1,7 @@ +package com.arc_e_tect.fixtures.violating.dependency.nonConfigDependsOnService.application.service; + +public class AppService { + + public void execute() { + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/nonConfigDependsOnService/domain/model/DomainCallsService.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/nonConfigDependsOnService/domain/model/DomainCallsService.java new file mode 100644 index 0000000..b0bd7d1 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/dependency/nonConfigDependsOnService/domain/model/DomainCallsService.java @@ -0,0 +1,16 @@ +package com.arc_e_tect.fixtures.violating.dependency.nonConfigDependsOnService.domain.model; + +import com.arc_e_tect.fixtures.violating.dependency.nonConfigDependsOnService.application.service.AppService; + +public class DomainCallsService { + + private final AppService appService; + + public DomainCallsService(AppService appService) { + this.appService = appService; + } + + public void execute() { + appService.execute(); + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/applicationServiceStereotype/application/service/AnnotatedService.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/applicationServiceStereotype/application/service/AnnotatedService.java new file mode 100644 index 0000000..8dc118d --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/applicationServiceStereotype/application/service/AnnotatedService.java @@ -0,0 +1,10 @@ +package com.arc_e_tect.fixtures.violating.domain.applicationServiceStereotype.application.service; + +import org.springframework.stereotype.Service; + +@Service +public class AnnotatedService { + + public void execute() { + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/applicationServiceStereotype/domain/model/GoodDomain.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/applicationServiceStereotype/domain/model/GoodDomain.java new file mode 100644 index 0000000..84ab553 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/applicationServiceStereotype/domain/model/GoodDomain.java @@ -0,0 +1,14 @@ +package com.arc_e_tect.fixtures.violating.domain.applicationServiceStereotype.domain.model; + +public class GoodDomain { + + private final String value; + + public GoodDomain(String value) { + this.value = value; + } + + public String value() { + return value; + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/domain/model/BadDomain.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/domain/model/BadDomain.java new file mode 100644 index 0000000..c4e6986 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/domain/model/BadDomain.java @@ -0,0 +1,16 @@ +package com.arc_e_tect.fixtures.violating.domain.domainDependency.domain.model; + +import com.arc_e_tect.fixtures.violating.domain.domainDependency.application.port.out.ForbiddenPort; + +public class BadDomain { + + private final ForbiddenPort forbiddenPort; + + public BadDomain(ForbiddenPort forbiddenPort) { + this.forbiddenPort = forbiddenPort; + } + + public void execute() { + forbiddenPort.send(); + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/frameworkDependency/domain/framework/FrameworkBoundDomain.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/frameworkDependency/domain/framework/FrameworkBoundDomain.java new file mode 100644 index 0000000..de813c7 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/frameworkDependency/domain/framework/FrameworkBoundDomain.java @@ -0,0 +1,17 @@ +package com.arc_e_tect.fixtures.violating.domain.frameworkDependency.domain.framework; + +import org.springframework.util.Assert; + +public class FrameworkBoundDomain { + + private final String value; + + public FrameworkBoundDomain(String value) { + Assert.hasText(value, "value must not be empty"); + this.value = value; + } + + public String value() { + return value; + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/frameworkDependency/domain/model/GoodDomain.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/frameworkDependency/domain/model/GoodDomain.java new file mode 100644 index 0000000..4cc58b5 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/frameworkDependency/domain/model/GoodDomain.java @@ -0,0 +1,14 @@ +package com.arc_e_tect.fixtures.violating.domain.frameworkDependency.domain.model; + +public class GoodDomain { + + private final String value; + + public GoodDomain(String value) { + this.value = value; + } + + public String value() { + return value; + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/in/NotInterfaceInputPort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/in/NotInterfaceInputPort.java new file mode 100644 index 0000000..adeece5 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/in/NotInterfaceInputPort.java @@ -0,0 +1,10 @@ +package com.arc_e_tect.fixtures.violating.port.inputNotInterface.application.port.in; + +import com.arc_e_tect.fixtures.violating.port.inputNotInterface.domain.model.PortDomain; + +public class NotInterfaceInputPort { + + public PortDomain execute(PortDomain domain) { + return domain; + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/domain/model/PortDomain.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/domain/model/PortDomain.java new file mode 100644 index 0000000..98f2c1e --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/domain/model/PortDomain.java @@ -0,0 +1,14 @@ +package com.arc_e_tect.fixtures.violating.port.inputNotInterface.domain.model; + +public class PortDomain { + + private final String value; + + public PortDomain(String value) { + this.value = value; + } + + public String value() { + return value; + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/in/GoodInputPort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/in/GoodInputPort.java new file mode 100644 index 0000000..df03f0b --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/in/GoodInputPort.java @@ -0,0 +1,7 @@ +package com.arc_e_tect.fixtures.violating.port.outputNotInterface.application.port.in; + +import com.arc_e_tect.fixtures.violating.port.outputNotInterface.domain.model.PortDomain; + +public interface GoodInputPort { + PortDomain execute(PortDomain domain); +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/domain/model/PortDomain.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/domain/model/PortDomain.java new file mode 100644 index 0000000..990aecf --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/domain/model/PortDomain.java @@ -0,0 +1,14 @@ +package com.arc_e_tect.fixtures.violating.port.outputNotInterface.domain.model; + +public class PortDomain { + + private final String value; + + public PortDomain(String value) { + this.value = value; + } + + public String value() { + return value; + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/in/LeakyInputPort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/in/LeakyInputPort.java new file mode 100644 index 0000000..e230132 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/in/LeakyInputPort.java @@ -0,0 +1,7 @@ +package com.arc_e_tect.fixtures.violating.port.signatureLeak.application.port.in; + +import org.springframework.context.ApplicationContext; + +public interface LeakyInputPort { + void execute(ApplicationContext context); +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/domain/model/PortDomain.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/domain/model/PortDomain.java new file mode 100644 index 0000000..0cf18b2 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/domain/model/PortDomain.java @@ -0,0 +1,14 @@ +package com.arc_e_tect.fixtures.violating.port.signatureLeak.domain.model; + +public class PortDomain { + + private final String value; + + public PortDomain(String value) { + this.value = value; + } + + public String value() { + return value; + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/components/misc/BadComponent.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/components/misc/BadComponent.java new file mode 100644 index 0000000..0763ade --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/components/misc/BadComponent.java @@ -0,0 +1,7 @@ +package com.arc_e_tect.fixtures.violating.spring.components.misc; + +import org.springframework.stereotype.Component; + +@Component +public class BadComponent { +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/controllers/adapters/web/BadController.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/controllers/adapters/web/BadController.java new file mode 100644 index 0000000..30d7be0 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/controllers/adapters/web/BadController.java @@ -0,0 +1,18 @@ +package com.arc_e_tect.fixtures.violating.spring.controllers.adapters.web; + +import com.arc_e_tect.fixtures.violating.spring.controllers.application.service.ConcreteService; +import org.springframework.stereotype.Controller; + +@Controller +public class BadController { + + private final ConcreteService concreteService; + + public BadController(ConcreteService concreteService) { + this.concreteService = concreteService; + } + + public void execute() { + concreteService.execute(); + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/controllers/application/service/ConcreteService.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/controllers/application/service/ConcreteService.java new file mode 100644 index 0000000..32b77fd --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/controllers/application/service/ConcreteService.java @@ -0,0 +1,7 @@ +package com.arc_e_tect.fixtures.violating.spring.controllers.application.service; + +public class ConcreteService { + + public void execute() { + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/repositories/adapters/persistence/BadRepository.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/repositories/adapters/persistence/BadRepository.java new file mode 100644 index 0000000..6c587ff --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/repositories/adapters/persistence/BadRepository.java @@ -0,0 +1,10 @@ +package com.arc_e_tect.fixtures.violating.spring.repositories.adapters.persistence; + +import org.springframework.stereotype.Repository; + +@Repository +public class BadRepository { + + public void save() { + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/repositories/application/service/RepositoryConsumer.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/repositories/application/service/RepositoryConsumer.java new file mode 100644 index 0000000..876dcb2 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/repositories/application/service/RepositoryConsumer.java @@ -0,0 +1,16 @@ +package com.arc_e_tect.fixtures.violating.spring.repositories.application.service; + +import com.arc_e_tect.fixtures.violating.spring.repositories.adapters.persistence.BadRepository; + +public class RepositoryConsumer { + + private final BadRepository badRepository; + + public RepositoryConsumer(BadRepository badRepository) { + this.badRepository = badRepository; + } + + public void execute() { + badRepository.save(); + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/services/adapters/persistence/BadRepository.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/services/adapters/persistence/BadRepository.java new file mode 100644 index 0000000..37a7ef3 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/services/adapters/persistence/BadRepository.java @@ -0,0 +1,10 @@ +package com.arc_e_tect.fixtures.violating.spring.services.adapters.persistence; + +import org.springframework.stereotype.Repository; + +@Repository +public class BadRepository { + + public void save() { + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/services/adapters/service/BadService.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/services/adapters/service/BadService.java new file mode 100644 index 0000000..28d051b --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/spring/services/adapters/service/BadService.java @@ -0,0 +1,18 @@ +package com.arc_e_tect.fixtures.violating.spring.services.adapters.service; + +import com.arc_e_tect.fixtures.violating.spring.services.adapters.persistence.BadRepository; +import org.springframework.stereotype.Service; + +@Service +public class BadService { + + private final BadRepository badRepository; + + public BadService(BadRepository badRepository) { + this.badRepository = badRepository; + } + + public void execute() { + badRepository.save(); + } +} From 6cbfac0bd44c0cbcdd08fa4b37ef8ae725e2cbc6 Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 18:39:24 +0400 Subject: [PATCH 06/20] feat(hexagonal-spring-rules): add per-rule opt-out configuration What changed: - Added RulePackConfiguration.isRuleDisabled(String ruleId) and parsing support for architectureValidator.rules.disabled as a comma-separated list of rule identifiers. - Added JUnit 5 Assumptions.assumeFalse guards at the top of every @Test method in SpringHexagonalArchitectureTest, DomainIsolationTest, DependencyDirectionTest, and PortContractTest. - Used stable rule identifiers in ClassSimpleName.methodName format for all guarded methods. - Updated README.adoc Architecture Validator Properties table with architectureValidator.rules.disabled, including identifier format and example value. Why: - Consumers needed a way to disable a single rule without dropping the entire rule pack dependency. - Without targeted opt-out, teams with intentional exceptions had to accept all-or-nothing rule enforcement. How it works: - RulePackConfiguration reads architectureValidator.rules.disabled from system properties, trims and filters values, and exposes a boolean lookup per rule id. - Each affected rule method now short-circuits via assumeFalse when its identifier is present, causing JUnit to mark the test as SKIPPED instead of PASSED or FAILED. - Existing architectural assertions were left unchanged; only the precondition guard was introduced. Side effects and caveats: - End-to-end verification required temporary forwarding of archRulesDisabled in the example project's testArchitecture task because Gradle Test workers do not inherit CLI JVM -D properties by default. - Temporary example wiring and local-jar substitution used for verification were fully reverted; only hexagonal-spring-rules files are committed. Outcomes: - hexagonal-spring-rules build succeeded with the new guards enabled. - Baseline example run (no disabled ids) retained prior failing behavior with no skipped external rule-pack tests. - Positive run with SpringHexagonalArchitectureTest.servicesShouldNotAccessRepositoriesDirectly produced a targeted skip and reduced failing count accordingly. - Negative run with SomeClass.nonexistentMethod produced no skips and matched baseline behavior. --- hexagonal-spring-rules/README.adoc | 4 ++++ .../spring/DependencyDirectionTest.java | 13 +++++++++++++ .../spring/DomainIsolationTest.java | 13 +++++++++++++ .../architecture/spring/PortContractTest.java | 13 +++++++++++++ .../spring/RulePackConfiguration.java | 13 +++++++++++++ .../spring/SpringHexagonalArchitectureTest.java | 17 +++++++++++++++++ 6 files changed, 73 insertions(+) diff --git a/hexagonal-spring-rules/README.adoc b/hexagonal-spring-rules/README.adoc index cba8459..137298e 100644 --- a/hexagonal-spring-rules/README.adoc +++ b/hexagonal-spring-rules/README.adoc @@ -117,6 +117,10 @@ The rules read package boundaries from system properties set by the Architecture |`architectureValidator.applicationServices` |Comma-separated application service packages. + +|`architectureValidator.rules.disabled` +|Comma-separated rule identifiers to skip in the format `ClassSimpleName.methodName`. +Example: `DomainIsolationTest.applicationServicesShouldNotCarrySpringStereotypes`. |=== == Example Project diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DependencyDirectionTest.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DependencyDirectionTest.java index 88b747b..2d82e0f 100644 --- a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DependencyDirectionTest.java +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DependencyDirectionTest.java @@ -3,6 +3,7 @@ import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; @@ -43,6 +44,10 @@ class DependencyDirectionTest { */ @Test void coreApplicationLayerShouldNotDependOnAdapters() { + Assumptions.assumeFalse( + RulePackConfiguration.isRuleDisabled("DependencyDirectionTest.coreApplicationLayerShouldNotDependOnAdapters"), + "Rule disabled via architectureValidator.rules.disabled" + ); noClasses() .that().resideOutsideOfPackage("..adapter..") .and().resideOutsideOfPackage("..adapters..") @@ -60,6 +65,10 @@ void coreApplicationLayerShouldNotDependOnAdapters() { */ @Test void adaptersShouldNotDependOnServiceImplementations() { + Assumptions.assumeFalse( + RulePackConfiguration.isRuleDisabled("DependencyDirectionTest.adaptersShouldNotDependOnServiceImplementations"), + "Rule disabled via architectureValidator.rules.disabled" + ); noClasses() .that().resideInAnyPackage(RulePackConfiguration.adapters()) .should().dependOnClassesThat().resideInAnyPackage(RulePackConfiguration.applicationServices()) @@ -78,6 +87,10 @@ void adaptersShouldNotDependOnServiceImplementations() { */ @Test void onlyConfigurationMayDependOnServiceImplementations() { + Assumptions.assumeFalse( + RulePackConfiguration.isRuleDisabled("DependencyDirectionTest.onlyConfigurationMayDependOnServiceImplementations"), + "Rule disabled via architectureValidator.rules.disabled" + ); noClasses() .that().resideOutsideOfPackage("..configuration..") .and().resideOutsideOfPackage("..application.domain.service..") diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DomainIsolationTest.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DomainIsolationTest.java index e6430e1..8b2c65f 100644 --- a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DomainIsolationTest.java +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DomainIsolationTest.java @@ -3,6 +3,7 @@ import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; @@ -43,6 +44,10 @@ class DomainIsolationTest { */ @Test void domainModelShouldOnlyDependOnJavaCoreAndDomainModel() { + Assumptions.assumeFalse( + RulePackConfiguration.isRuleDisabled("DomainIsolationTest.domainModelShouldOnlyDependOnJavaCoreAndDomainModel"), + "Rule disabled via architectureValidator.rules.disabled" + ); classes() .that().resideInAnyPackage(RulePackConfiguration.domainModel()) .should().onlyDependOnClassesThat() @@ -65,6 +70,10 @@ void domainModelShouldOnlyDependOnJavaCoreAndDomainModel() { @Test void coreApplicationLayerShouldHaveNoFrameworkDependencies() { + Assumptions.assumeFalse( + RulePackConfiguration.isRuleDisabled("DomainIsolationTest.coreApplicationLayerShouldHaveNoFrameworkDependencies"), + "Rule disabled via architectureValidator.rules.disabled" + ); noClasses() .that().resideInAnyPackage(RulePackConfiguration.domainModel()) .should().dependOnClassesThat() @@ -89,6 +98,10 @@ void coreApplicationLayerShouldHaveNoFrameworkDependencies() { */ @Test void applicationServicesShouldNotCarrySpringStereotypes() { + Assumptions.assumeFalse( + RulePackConfiguration.isRuleDisabled("DomainIsolationTest.applicationServicesShouldNotCarrySpringStereotypes"), + "Rule disabled via architectureValidator.rules.disabled" + ); noClasses() .that().resideInAnyPackage(RulePackConfiguration.applicationServices()) .should().beAnnotatedWith("org.springframework.stereotype.Service") diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/PortContractTest.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/PortContractTest.java index 49062a6..0daf099 100644 --- a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/PortContractTest.java +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/PortContractTest.java @@ -3,6 +3,7 @@ import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; @@ -43,6 +44,10 @@ class PortContractTest { */ @Test void inputPortsShouldBeInterfaces() { + Assumptions.assumeFalse( + RulePackConfiguration.isRuleDisabled("PortContractTest.inputPortsShouldBeInterfaces"), + "Rule disabled via architectureValidator.rules.disabled" + ); classes() .that().resideInAnyPackage(RulePackConfiguration.inPorts()) .should().beInterfaces() @@ -61,6 +66,10 @@ void inputPortsShouldBeInterfaces() { */ @Test void outputPortsShouldBeInterfaces() { + Assumptions.assumeFalse( + RulePackConfiguration.isRuleDisabled("PortContractTest.outputPortsShouldBeInterfaces"), + "Rule disabled via architectureValidator.rules.disabled" + ); classes() .that().resideInAnyPackage(RulePackConfiguration.outPorts()) .should().beInterfaces() @@ -79,6 +88,10 @@ void outputPortsShouldBeInterfaces() { */ @Test void portsShouldOnlyDependOnJavaCoreAndDomainModel() { + Assumptions.assumeFalse( + RulePackConfiguration.isRuleDisabled("PortContractTest.portsShouldOnlyDependOnJavaCoreAndDomainModel"), + "Rule disabled via architectureValidator.rules.disabled" + ); classes() .that().resideInAnyPackage(RulePackConfiguration.inPorts()) .or().resideInAnyPackage(RulePackConfiguration.outPorts()) diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java index 693bb3f..a8b1c51 100644 --- a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java @@ -2,6 +2,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -21,6 +22,7 @@ *

  • {@code architectureValidator.domainModel} - Comma-separated domain model packages
  • *
  • {@code architectureValidator.adapters} - Comma-separated adapter packages
  • *
  • {@code architectureValidator.applicationServices} - Comma-separated application service packages
  • + *
  • {@code architectureValidator.rules.disabled} - Comma-separated rule identifiers to skip
  • * * * @since 0.4.0 @@ -55,6 +57,10 @@ static String[] applicationServices() { return packages("architectureValidator.applicationServices"); } + static boolean isRuleDisabled(String ruleId) { + return disabledRules().contains(ruleId); + } + static void requireConfigured() { String configuredBasePackage = property("architectureValidator.basePackage", "").trim(); @@ -83,4 +89,11 @@ private static String[] packages(String key) { .collect(Collectors.toList()); return values.toArray(new String[0]); } + + private static Set disabledRules() { + return Arrays.stream(property("architectureValidator.rules.disabled", "").split(",")) + .map(String::trim) + .filter(value -> !value.isEmpty()) + .collect(Collectors.toSet()); + } } \ No newline at end of file diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/SpringHexagonalArchitectureTest.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/SpringHexagonalArchitectureTest.java index 43a5ef9..ee2bc76 100644 --- a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/SpringHexagonalArchitectureTest.java +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/SpringHexagonalArchitectureTest.java @@ -3,6 +3,7 @@ import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; @@ -56,6 +57,10 @@ private String[] mergeAll(String[] first, String[] second, String... fixed) { */ @Test void controllersShouldOnlyCallInPorts() { + Assumptions.assumeFalse( + RulePackConfiguration.isRuleDisabled("SpringHexagonalArchitectureTest.controllersShouldOnlyCallInPorts"), + "Rule disabled via architectureValidator.rules.disabled" + ); classes() .that().areAnnotatedWith("org.springframework.stereotype.Controller") .or().areAnnotatedWith("org.springframework.web.bind.annotation.RestController") @@ -78,6 +83,10 @@ void controllersShouldOnlyCallInPorts() { */ @Test void servicesShouldNotAccessRepositoriesDirectly() { + Assumptions.assumeFalse( + RulePackConfiguration.isRuleDisabled("SpringHexagonalArchitectureTest.servicesShouldNotAccessRepositoriesDirectly"), + "Rule disabled via architectureValidator.rules.disabled" + ); classes() .that().areAnnotatedWith("org.springframework.stereotype.Service") .should().onlyDependOnClassesThat() @@ -101,6 +110,10 @@ void servicesShouldNotAccessRepositoriesDirectly() { */ @Test void repositoriesShouldOnlyBeAccessedViaOutPorts() { + Assumptions.assumeFalse( + RulePackConfiguration.isRuleDisabled("SpringHexagonalArchitectureTest.repositoriesShouldOnlyBeAccessedViaOutPorts"), + "Rule disabled via architectureValidator.rules.disabled" + ); classes() .that().areAnnotatedWith("org.springframework.stereotype.Repository") .should().onlyBeAccessed().byClassesThat() @@ -122,6 +135,10 @@ void repositoriesShouldOnlyBeAccessedViaOutPorts() { */ @Test void springComponentsShouldFollowHexagonalLayers() { + Assumptions.assumeFalse( + RulePackConfiguration.isRuleDisabled("SpringHexagonalArchitectureTest.springComponentsShouldFollowHexagonalLayers"), + "Rule disabled via architectureValidator.rules.disabled" + ); classes() .that().areAnnotatedWith("org.springframework.stereotype.Component") .or().areAnnotatedWith("org.springframework.stereotype.Service") From 45f85d09f840c0e04587238098fd5586792eaa01 Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 18:43:54 +0400 Subject: [PATCH 07/20] feat(hexagonal-spring-rules): add no-field-injection architecture rule What changed: - Added DependencyInjectionStyleTest with fieldsShouldNotBeAutowired() in the Spring rule pack. - Implemented a noFields() ArchUnit rule that targets fields declared in classes from configured adapters, applicationServices, and domainModel packages. - Enforced prohibition of org.springframework.beans.factory.annotation.Autowired on fields with allowEmptyShould(true) and a constructor-injection rationale. - Updated README.adoc rule table to document DependencyInjectionStyleTest coverage. Why: - Field injection hides required dependencies and weakens testability and architectural clarity in Hexagonal Spring applications. - The existing rule pack enforced layer boundaries but did not enforce dependency-wiring style. How it works: - The rule imports configured application classes via ClassFileImporter and evaluates fields by declaring-class package residency. - Any field-level @Autowired in targeted layers triggers an ArchUnit assertion failure; compliant constructor-injected classes pass unchanged. Side effects and caveats: - Verification used temporary example-project wiring to the locally built rule-pack jar and a temporary @Autowired annotation for smoke testing; both were fully reverted. - No persistent changes were made outside hexagonal-spring-rules for this task. Outcomes: - hexagonal-spring-rules build succeeded after adding DependencyInjectionStyleTest. - Baseline example run with unmodified code showed DependencyInjectionStyleTest present and passing. - Smoke test with temporary field @Autowired failed at DependencyInjectionStyleTest.fieldsShouldNotBeAutowired() as expected. - Post-revert example run returned to its original intentional failure profile. --- hexagonal-spring-rules/README.adoc | 3 + .../spring/DependencyInjectionStyleTest.java | 57 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DependencyInjectionStyleTest.java diff --git a/hexagonal-spring-rules/README.adoc b/hexagonal-spring-rules/README.adoc index 137298e..adc157d 100644 --- a/hexagonal-spring-rules/README.adoc +++ b/hexagonal-spring-rules/README.adoc @@ -90,6 +90,9 @@ The default rules target Spring-specific architectural boundaries, including dep |`CycleFreedomTest` |Adapter and domain-model package trees must be free of circular package dependencies. + +|`DependencyInjectionStyleTest` +|Classes in adapters, application services, and domain model must not use field injection via `@Autowired`. |=== == Architecture Validator Properties diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DependencyInjectionStyleTest.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DependencyInjectionStyleTest.java new file mode 100644 index 0000000..0f5ebde --- /dev/null +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DependencyInjectionStyleTest.java @@ -0,0 +1,57 @@ +package com.arc_e_tect.gradle.architecture.spring; + +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.core.importer.ImportOption; +import org.junit.jupiter.api.Test; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noFields; + +/** + * Dependency injection style validation rules for Hexagonal architecture. + * + *

    This rule class enforces constructor-injection-friendly design by forbidding + * Spring field injection annotations in core application and adapter layers. + * + *

    Rules validate: + *

      + *
    • Fields in adapters, application services, and domain model are not annotated with {@code @Autowired}
    • + *
    + * + *

    This class is discovered and included in the rule-pack suite by the Architecture Validator + * plugin. Each test method defines a separate validation rule. + * + * @see RulePackConfiguration + * @since 0.4.0 + */ +class DependencyInjectionStyleTest { + + private final JavaClasses classes = new ClassFileImporter() + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) + .importPackages(RulePackConfiguration.basePackage()); + + private String[] targetPackages() { + return RulePackConfiguration.merge( + RulePackConfiguration.domainModel(), + RulePackConfiguration.merge( + RulePackConfiguration.adapters(), + RulePackConfiguration.applicationServices()) + ); + } + + /** + * Validates that classes in core and adapter layers do not use field injection. + * + *

    Field injection hides required dependencies and makes classes harder to test in isolation. + * Constructor injection keeps dependencies explicit and supports framework-agnostic design. + */ + @Test + void fieldsShouldNotBeAutowired() { + noFields() + .that().areDeclaredInClassesThat().resideInAnyPackage(targetPackages()) + .should().beAnnotatedWith("org.springframework.beans.factory.annotation.Autowired") + .because("Constructor injection keeps dependencies explicit and testable without a Spring context") + .allowEmptyShould(true) + .check(classes); + } +} From 8d5aef96c3750422f838814f2730bd25645ffed6 Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 18:51:07 +0400 Subject: [PATCH 08/20] feat(hexagonal-spring-rules): add opt-in naming convention architecture rules What changed: - Added NamingConventionTest with three opt-in ArchUnit rules: - inputPortsShouldHaveConsistentSuffix() accepts UseCase or Port suffixes. - outputPortsShouldHaveConsistentSuffix() enforces Port suffix. - adaptersShouldHaveConsistentSuffix() enforces Repository/Adapter for @Repository and Controller for @Controller/@RestController. - Added RulePackConfiguration.namingConventionsEnabled() reading architectureValidator.namingConventions.enabled with default false. - Documented NamingConventionTest in README 'What This Rule Pack Checks' and added architectureValidator.namingConventions.enabled to the properties table. Why: - Naming conventions are a common companion to Hexagonal rules because they make architectural roles explicit during code review and IDE navigation. - This check is intentionally opt-in because naming policies are team-specific and more opinionated than boundary and dependency rules. How: - Implemented the naming checks in a dedicated rule class (separate from existing rule classes) using ArchUnit class predicates with allowEmptyShould(true). - Guarded every test method with assumeTrue so disabled-by-default behavior is explicit and reported as SKIPPED when not enabled. - Kept existing rule logic unchanged and additive-only. Verification: - ./gradlew build succeeded in hexagonal-spring-rules. - Default-off behavior showed NamingConventionTest tests as SKIPPED in architecture XML reports. - With opt-in enabled, each naming rule was validated by introducing one temporary violation at a time and observing targeted failure messages. - Negative case with opt-in enabled and unmodified example classes showed NamingConventionTest with 0 failures. - Temporary example wiring/fixtures were fully reverted; only rule-pack and README changes remain. --- hexagonal-spring-rules/README.adoc | 6 + .../spring/NamingConventionTest.java | 108 ++++++++++++++++++ .../spring/RulePackConfiguration.java | 5 + 3 files changed, 119 insertions(+) create mode 100644 hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/NamingConventionTest.java diff --git a/hexagonal-spring-rules/README.adoc b/hexagonal-spring-rules/README.adoc index adc157d..54e3806 100644 --- a/hexagonal-spring-rules/README.adoc +++ b/hexagonal-spring-rules/README.adoc @@ -93,6 +93,9 @@ The default rules target Spring-specific architectural boundaries, including dep |`DependencyInjectionStyleTest` |Classes in adapters, application services, and domain model must not use field injection via `@Autowired`. + +|`NamingConventionTest` +|Optional naming checks enforce consistent suffixes for in-ports, out-ports, repositories, and web controllers. |=== == Architecture Validator Properties @@ -124,6 +127,9 @@ The rules read package boundaries from system properties set by the Architecture |`architectureValidator.rules.disabled` |Comma-separated rule identifiers to skip in the format `ClassSimpleName.methodName`. Example: `DomainIsolationTest.applicationServicesShouldNotCarrySpringStereotypes`. + +|`architectureValidator.namingConventions.enabled` +|Boolean flag for optional naming convention rules; defaults to `false` and must be explicitly set to `true`. |=== == Example Project diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/NamingConventionTest.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/NamingConventionTest.java new file mode 100644 index 0000000..0b2d905 --- /dev/null +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/NamingConventionTest.java @@ -0,0 +1,108 @@ +package com.arc_e_tect.gradle.architecture.spring; + +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.core.importer.ImportOption; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; + +/** + * Optional naming convention validation rules for Spring Hexagonal architecture. + * + *

    This rule class enforces naming suffix conventions for ports and adapters to keep + * architectural roles obvious in code review and IDE navigation. + * + *

    Rules validate: + *

      + *
    • In-ports end with {@code UseCase} or {@code Port}
    • + *
    • Out-ports end with {@code Port}
    • + *
    • Repository and web adapter stereotypes use consistent suffixes
    • + *
    + * + *

    This class is discovered and included in the rule-pack suite by the Architecture Validator + * plugin. Each test method defines a separate validation rule. + * + * @see RulePackConfiguration + * @since 0.4.0 + */ +class NamingConventionTest { + + private static final String OPT_IN_MESSAGE = "Naming convention rules are opt-in; set architectureValidator.namingConventions.enabled=true to activate"; + + private final JavaClasses classes = new ClassFileImporter() + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) + .importPackages(RulePackConfiguration.basePackage()); + + /** + * Validates that in-port types use a consistent role suffix. + * + *

    Using {@code UseCase} or {@code Port} suffixes makes inbound application + * contracts instantly recognizable and reinforces Hexagonal boundaries. + */ + @Test + void inputPortsShouldHaveConsistentSuffix() { + Assumptions.assumeTrue(RulePackConfiguration.namingConventionsEnabled(), OPT_IN_MESSAGE); + classes() + .that().resideInAnyPackage(RulePackConfiguration.inPorts()) + .should().haveNameMatching(".*UseCase$") + .orShould().haveNameMatching(".*Port$") + .because("Consistent in-port naming makes application entry-point contracts explicit") + .allowEmptyShould(true) + .check(classes); + } + + /** + * Validates that out-port types end with {@code Port}. + * + *

    A stable suffix for outbound contracts clarifies that adapter implementations + * are behind an abstraction owned by the application core. + */ + @Test + void outputPortsShouldHaveConsistentSuffix() { + Assumptions.assumeTrue(RulePackConfiguration.namingConventionsEnabled(), OPT_IN_MESSAGE); + classes() + .that().resideInAnyPackage(RulePackConfiguration.outPorts()) + .should().haveSimpleNameEndingWith("Port") + .because("Consistent out-port naming keeps infrastructure boundaries self-documenting") + .allowEmptyShould(true) + .check(classes); + } + + /** + * Validates that adapter stereotypes use role-aligned suffixes. + * + *

    Repository stereotypes should read as repository/adapter implementations, + * and web controllers should read as controller entry points. + */ + @Test + void adaptersShouldHaveConsistentSuffix() { + Assumptions.assumeTrue(RulePackConfiguration.namingConventionsEnabled(), OPT_IN_MESSAGE); + + classes() + .that().resideInAnyPackage(RulePackConfiguration.adapters()) + .and().areAnnotatedWith("org.springframework.stereotype.Repository") + .should().haveSimpleNameEndingWith("Repository") + .orShould().haveSimpleNameEndingWith("Adapter") + .because("Repository adapters should use repository-oriented suffixes to signal their role") + .allowEmptyShould(true) + .check(classes); + + classes() + .that().resideInAnyPackage(RulePackConfiguration.adapters()) + .and().areAnnotatedWith("org.springframework.web.bind.annotation.RestController") + .should().haveSimpleNameEndingWith("Controller") + .because("Web adapters should use Controller suffixes to make request-entry components obvious") + .allowEmptyShould(true) + .check(classes); + + classes() + .that().resideInAnyPackage(RulePackConfiguration.adapters()) + .and().areAnnotatedWith("org.springframework.stereotype.Controller") + .should().haveSimpleNameEndingWith("Controller") + .because("Web adapters should use Controller suffixes to make request-entry components obvious") + .allowEmptyShould(true) + .check(classes); + } +} diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java index a8b1c51..64fd1f0 100644 --- a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java @@ -23,6 +23,7 @@ *

  • {@code architectureValidator.adapters} - Comma-separated adapter packages
  • *
  • {@code architectureValidator.applicationServices} - Comma-separated application service packages
  • *
  • {@code architectureValidator.rules.disabled} - Comma-separated rule identifiers to skip
  • + *
  • {@code architectureValidator.namingConventions.enabled} - Enables optional naming convention rules
  • * * * @since 0.4.0 @@ -61,6 +62,10 @@ static boolean isRuleDisabled(String ruleId) { return disabledRules().contains(ruleId); } + static boolean namingConventionsEnabled() { + return Boolean.parseBoolean(property("architectureValidator.namingConventions.enabled", "false")); + } + static void requireConfigured() { String configuredBasePackage = property("architectureValidator.basePackage", "").trim(); From 70919c40806c264e2c61662d2bd7612487861516 Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 18:56:15 +0400 Subject: [PATCH 09/20] docs(hexagonal-spring-rules): move misplaced DomainIsolationTest Javadoc What changed: - Moved the Javadoc block describing coreApplicationLayerShouldHaveNoFrameworkDependencies() out of domainModelShouldOnlyDependOnJavaCoreAndDomainModel(). - Repositioned that block directly above the coreApplicationLayerShouldHaveNoFrameworkDependencies() @Test method where it belongs. - Left all executable rule logic, assumptions, annotations, and assertions unchanged. Why: - The comment was previously embedded inside a different method body, which was syntactically valid but semantically misleading during maintenance and review. - Placing the Javadoc above the intended method restores accurate API documentation and prevents confusion about rule intent. How: - Performed a comment-only relocation in DomainIsolationTest.java without modifying method behavior. Side effects and constraints: - No architecture rule behavior changes were introduced. - No additional files were changed. Outcomes: - ./gradlew build in hexagonal-spring-rules succeeded after the change. - git diff for DomainIsolationTest.java shows only comment movement. - ./gradlew testArchitecture in examples/architecture-validator/single-module-spring kept the same intentional failure profile as before. --- .../architecture/spring/DomainIsolationTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DomainIsolationTest.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DomainIsolationTest.java index 8b2c65f..8ee464f 100644 --- a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DomainIsolationTest.java +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/DomainIsolationTest.java @@ -53,6 +53,13 @@ void domainModelShouldOnlyDependOnJavaCoreAndDomainModel() { .should().onlyDependOnClassesThat() .resideInAnyPackage(RulePackConfiguration.merge( RulePackConfiguration.domainModel(), + "java.util..", + "java.lang..", + "java.time..")) + .because("Domain model classes must stay framework free") + .check(classes); + } + /** * Validates that the core application layer has no Spring or persistence framework dependencies. * @@ -61,13 +68,6 @@ void domainModelShouldOnlyDependOnJavaCoreAndDomainModel() { * This ensures the business logic remains framework-agnostic and can be evolved * independently of infrastructure choices. */ - "java.util..", - "java.lang..", - "java.time..")) - .because("Domain model classes must stay framework free") - .check(classes); - } - @Test void coreApplicationLayerShouldHaveNoFrameworkDependencies() { Assumptions.assumeFalse( From 0fe1d2ac2e9a9330641b667ff576f7d4c76ac2b1 Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 21:15:37 +0400 Subject: [PATCH 10/20] chore(gitignore): broaden prompts file ignore pattern What changed:\n- Updated .gitignore pattern from /**/req_prompts.md to /**/*prompts.md.\n\nWhy:\n- The repository now uses multiple prompt specification files (for example req_prompts.md and example_prompts.md), and only ignoring one filename left similar prompt artifacts tracked by mistake.\n\nHow:\n- Replaced the single-file ignore with a suffix-based prompts pattern that matches all prompt markdown variants in nested directories.\n\nSide effects and constraints:\n- This change affects only ignore behavior for untracked files and does not alter any source code or build behavior.\n\nOutcomes:\n- Prompt markdown helper files are now consistently excluded from version control noise. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 07b4fb6..8365f9e 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,4 @@ out/ gradle-plugin-publishing.md sedr-library-maven-central-publishing.md -/**/req_prompts.md \ No newline at end of file +/**/*prompts.md \ No newline at end of file From a804dc9355a05cf65828d2d469cca28589786604 Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 21:15:46 +0400 Subject: [PATCH 11/20] fix(hexagonal-spring-rules): detect nested adapter cycles reliably What changed:\n- Updated CycleFreedomTest.toSlicePattern to preserve a leading .. wildcard when present in configured package roots.\n- Kept trailing normalization while returning ...(**) for wildcard-prefixed roots so ArchUnit slice matching works for nested base packages.\n- Added a regression self-test in RulePackSelfTest that asserts adapter cycle detection fails on a deep fixture package graph.\n- Added dedicated cycle fixture classes under src/testFixtures for the self-test scenario.\n- Refined RulePackConfiguration guidance text to reference inbound/outbound package names used by current defaults.\n\nWhy:\n- The previous slice pattern transformation stripped the leading wildcard, anchoring matches to the beginning of class names and allowing real nested package cycles to pass vacuously.\n\nHow:\n- Captured whether the configured root starts with .., normalized only redundant prefixes/suffixes, then rebuilt the slice pattern with the leading wildcard when required.\n- Added fixture-backed regression coverage so future refactors cannot reintroduce vacuous cycle passes.\n\nSide effects and constraints:\n- Cycle rules now evaluate real nested packages instead of empty sets, so consumers with actual cycles will correctly fail architecture checks.\n- No plugin API changes were introduced.\n\nOutcomes:\n- Adapter cycle detection behavior now matches rule intent across realistic package layouts and is protected by regression tests. --- .../architecture/spring/CycleFreedomTest.java | 13 +++++-- .../spring/RulePackConfiguration.java | 2 +- .../architecture/spring/RulePackSelfTest.java | 34 ++++++++++++++++++- .../notification/OrderNotifierAdapter.java | 12 +++++++ .../persistence/OrderRepositoryAdapter.java | 12 +++++++ 5 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/cycle/adapters/notification/OrderNotifierAdapter.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/cycle/adapters/persistence/OrderRepositoryAdapter.java diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/CycleFreedomTest.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/CycleFreedomTest.java index ddd1c0f..1afdf91 100644 --- a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/CycleFreedomTest.java +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/CycleFreedomTest.java @@ -64,8 +64,10 @@ private void assertConfiguredSlicesAreCycleFree(String[] packageRoots, String re } private static String toSlicePattern(String packageRoot) { - String normalized = packageRoot.trim(); + String trimmed = packageRoot.trim(); + boolean matchesAnyPrefix = trimmed.startsWith(".."); + String normalized = trimmed; while (normalized.startsWith("..")) { normalized = normalized.substring(2); } @@ -78,6 +80,13 @@ private static String toSlicePattern(String packageRoot) { normalized = normalized.substring(0, normalized.length() - 1); } - return normalized.isEmpty() ? "(**)" : normalized + ".(**)"; + if (normalized.isEmpty()) { + return "(**)"; + } + + // ArchUnit slice patterns anchor to the start of the fully-qualified class name unless + // prefixed with "..", so a leading ".." in the configured package root must be preserved, + // otherwise nested packages (the normal case) never match and the check passes vacuously. + return (matchesAnyPrefix ? ".." : "") + normalized + ".(**)"; } } diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java index 64fd1f0..5c7c794 100644 --- a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java @@ -74,7 +74,7 @@ static void requireConfigured() { } if (inPorts().length == 0 && outPorts().length == 0 && domainModel().length == 0) { - throw new AssertionError("architectureValidator.inPorts, architectureValidator.outPorts, and architectureValidator.domainModel are all empty. Configure at least one of these via the Architecture Validator plugin's architectureValidator { inPorts = 'com.example.myapp.application.port.in'; outPorts = 'com.example.myapp.application.port.out'; domainModel = 'com.example.myapp.domain.model' } extension."); + throw new AssertionError("architectureValidator.inPorts, architectureValidator.outPorts, and architectureValidator.domainModel are all empty. Configure at least one of these via the Architecture Validator plugin's architectureValidator { inPorts = 'com.example.myapp.application.port.inbound'; outPorts = 'com.example.myapp.application.port.outbound'; domainModel = 'com.example.myapp.domain.model' } extension."); } } diff --git a/hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java b/hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java index 9b1d4d8..ba14d3d 100644 --- a/hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java +++ b/hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java @@ -47,6 +47,8 @@ class RulePackSelfTest { private static final String PORT_OUTPUT_BASE = "com.arc_e_tect.fixtures.violating.port.outputNotInterface"; private static final String PORT_SIGNATURE_BASE = "com.arc_e_tect.fixtures.violating.port.signatureLeak"; + private static final String CYCLE_BASE = "com.arc_e_tect.fixtures.violating.cycle"; + private final Map originalProperties = new HashMap<>(); @BeforeEach @@ -85,6 +87,8 @@ void rulePackShouldPassAllCoreRulesWhenFixturesAreCompliant() { DependencyDirectionTest dependencyRules = new DependencyDirectionTest(); PortContractTest portRules = new PortContractTest(); + CycleFreedomTest cycleRules = new CycleFreedomTest(); + assertAll( () -> assertDoesNotThrow(springRules::controllersShouldOnlyCallInPorts), () -> assertDoesNotThrow(springRules::servicesShouldNotAccessRepositoriesDirectly), @@ -98,10 +102,38 @@ void rulePackShouldPassAllCoreRulesWhenFixturesAreCompliant() { () -> assertDoesNotThrow(dependencyRules::onlyConfigurationMayDependOnServiceImplementations), () -> assertDoesNotThrow(portRules::inputPortsShouldBeInterfaces), () -> assertDoesNotThrow(portRules::outputPortsShouldBeInterfaces), - () -> assertDoesNotThrow(portRules::portsShouldOnlyDependOnJavaCoreAndDomainModel) + () -> assertDoesNotThrow(portRules::portsShouldOnlyDependOnJavaCoreAndDomainModel), + () -> assertDoesNotThrow(cycleRules::adapterPackagesShouldBeFreeOfCycles), + () -> assertDoesNotThrow(cycleRules::domainModelShouldBeFreeOfCycles) ); } + @Test + void cycleFreedomTestShouldFailAdapterRuleWhenAdapterPackagesFormACycle() { + configure( + CYCLE_BASE, + CYCLE_BASE + ".application.port.in..", + CYCLE_BASE + ".application.port.out..", + CYCLE_BASE + ".domain.model..", + // Deliberately a floating "..X.." wildcard, not anchored to CYCLE_BASE like the + // other fixture configuration in this file: this matches how the real Architecture + // Validator plugin sends its default adapters pattern (e.g. "..adapter..", + // "..adapters.."). An anchored, fully-qualified value would not exercise the + // leading-".." handling this test is targeting. + "..adapters..", + CYCLE_BASE + ".application.service.." + ); + + CycleFreedomTest rules = new CycleFreedomTest(); + + // Regression test for a bug where CycleFreedomTest stripped the leading ".." from + // configured package roots before building the ArchUnit slice pattern, anchoring it to + // the start of the fully-qualified class name so nested packages (the normal case, as + // exercised here by a base package several segments deep) never matched and the check + // passed vacuously regardless of real cycles. + assertThrows(AssertionError.class, rules::adapterPackagesShouldBeFreeOfCycles); + } + @Test void springHexagonalArchitectureShouldFailControllersRuleWhenControllerDependsOnService() { configure( diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/cycle/adapters/notification/OrderNotifierAdapter.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/cycle/adapters/notification/OrderNotifierAdapter.java new file mode 100644 index 0000000..66554d5 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/cycle/adapters/notification/OrderNotifierAdapter.java @@ -0,0 +1,12 @@ +package com.arc_e_tect.fixtures.violating.cycle.adapters.notification; + +import com.arc_e_tect.fixtures.violating.cycle.adapters.persistence.OrderRepositoryAdapter; + +public class OrderNotifierAdapter { + + private final OrderRepositoryAdapter repository; + + public OrderNotifierAdapter(OrderRepositoryAdapter repository) { + this.repository = repository; + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/cycle/adapters/persistence/OrderRepositoryAdapter.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/cycle/adapters/persistence/OrderRepositoryAdapter.java new file mode 100644 index 0000000..6c0c396 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/cycle/adapters/persistence/OrderRepositoryAdapter.java @@ -0,0 +1,12 @@ +package com.arc_e_tect.fixtures.violating.cycle.adapters.persistence; + +import com.arc_e_tect.fixtures.violating.cycle.adapters.notification.OrderNotifierAdapter; + +public class OrderRepositoryAdapter { + + private final OrderNotifierAdapter notifier; + + public OrderRepositoryAdapter(OrderNotifierAdapter notifier) { + this.notifier = notifier; + } +} From 634d854a4d6b6b26a3d99f19f49b69a32e4308ef Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 21:15:57 +0400 Subject: [PATCH 12/20] refactor(single-module-spring): align fixture with inbound-port conventions What changed:\n- Renamed the inbound port package path from application.port.in to application.port.inbound and updated OrderUseCase package declaration/imports accordingly.\n- Updated OrderController to depend on the inbound port interface instead of a concrete OrderService type.\n- Removed @Service from OrderService to keep the application service as a plain class, while preserving the intentional repository coupling violation.\n- Refreshed the single-module example README to describe the updated fixture shape and the exact expected failing rules.\n\nWhy:\n- The rule pack expects inbound/outbound package conventions that use inbound/outbound naming.\n- The external rule set also requires application services to remain framework-agnostic plain classes, so annotating the service with @Service added unintended behavior.\n\nHow:\n- Applied package and import updates across the fixture classes and synchronized documentation to the observed rule outcomes.\n\nSide effects and constraints:\n- The example remains intentionally non-compliant for architecture validation demonstrations, but now fails for the intended reasons without conflicting stereotype assumptions.\n\nOutcomes:\n- The baseline single-module example now matches current rule-pack semantics and documentation expectations. --- .../single-module-spring/README.adoc | 18 ++++++++++++------ .../spring/adapter/web/OrderController.java | 6 +++--- .../port/{in => inbound}/OrderUseCase.java | 2 +- .../application/service/OrderService.java | 4 +--- 4 files changed, 17 insertions(+), 13 deletions(-) rename examples/architecture-validator/single-module-spring/src/main/java/com/arc_e_tect/example/spring/application/port/{in => inbound}/OrderUseCase.java (51%) diff --git a/examples/architecture-validator/single-module-spring/README.adoc b/examples/architecture-validator/single-module-spring/README.adoc index 58d25b2..6b7bad7 100644 --- a/examples/architecture-validator/single-module-spring/README.adoc +++ b/examples/architecture-validator/single-module-spring/README.adoc @@ -3,7 +3,7 @@ // tag::root[] This example enables the companion Spring rule pack. -It demonstrates a direct dependency from a Spring service to a Spring repository in the adapter layer. +It demonstrates a direct dependency from an application service to a Spring repository adapter, bypassing the outbound port. == Files and Purpose @@ -12,7 +12,7 @@ It demonstrates a direct dependency from a Spring service to a Spring repository |File |Purpose |`.../application/service/OrderService.java` -|A Spring `@Service` that depends directly on `OrderRepository` and triggers the rule violation under test. +|A plain application service (deliberately not annotated `@Service` — see `DomainIsolationTest.applicationServicesShouldNotCarrySpringStereotypes`) that depends directly on `OrderRepository`, triggering the rule violations under test. |`.../adapter/persistence/OrderRepository.java` |Adapter-layer Spring repository reached directly by the service instead of through an out-port. @@ -20,7 +20,7 @@ It demonstrates a direct dependency from a Spring service to a Spring repository |`.../adapter/web/OrderController.java` |Inbound web adapter calling the use case. -|`.../application/port/in/OrderUseCase.java` +|`.../application/port/inbound/OrderUseCase.java` |Inbound port interface. |`.../application/domain/model/Order.java` @@ -44,8 +44,14 @@ The current sample is intentionally non-compliant, so both commands are expected == Expected violations -`OrderService` is a Spring `@Service` that depends directly on `OrderRepository`. -That violates the Spring-flavoured Hexagonal rules because the service bypasses an outbound port and reaches into the adapter layer directly. +`OrderService` depends directly on `OrderRepository`, an adapter-layer Spring `@Repository`, instead of going through an outbound port. +That single design mistake trips several rules from different angles: + +* `DependencyDirectionTest.coreApplicationLayerShouldNotDependOnAdapters` — the application core must not depend on adapter-layer classes. +* `SpringHexagonalArchitectureTest.repositoriesShouldOnlyBeAccessedViaOutPorts` — `@Repository` classes may only be reached through an outbound port, an adapter, or configuration. +* The plugin's built-in generated `HexagonalArchitectureTest.application_services_must_not_depend_on_adapters` rule, which checks the same thing independently of the external rule pack. + +Note: `SpringHexagonalArchitectureTest.servicesShouldNotAccessRepositoriesDirectly` does not fire here because it only inspects `@Service`-annotated classes, and this rule pack expects application services to stay plain (see `DomainIsolationTest.applicationServicesShouldNotCarrySpringStereotypes`) — so `OrderService` intentionally carries no Spring stereotype. == Reports @@ -54,7 +60,7 @@ The XML report is under `build/reports/architecture-validator/xml/`. == Fix -Introduce an outbound port in `application.port.out`. +Introduce an outbound port in `application.port.outbound`. Make the service depend on that port. Move the repository implementation behind an adapter that satisfies the port contract. // end::root[] \ No newline at end of file diff --git a/examples/architecture-validator/single-module-spring/src/main/java/com/arc_e_tect/example/spring/adapter/web/OrderController.java b/examples/architecture-validator/single-module-spring/src/main/java/com/arc_e_tect/example/spring/adapter/web/OrderController.java index edcacdf..4fe9da6 100644 --- a/examples/architecture-validator/single-module-spring/src/main/java/com/arc_e_tect/example/spring/adapter/web/OrderController.java +++ b/examples/architecture-validator/single-module-spring/src/main/java/com/arc_e_tect/example/spring/adapter/web/OrderController.java @@ -1,14 +1,14 @@ package com.arc_e_tect.example.spring.adapter.web; -import com.arc_e_tect.example.spring.application.service.OrderService; +import com.arc_e_tect.example.spring.application.port.inbound.OrderUseCase; import org.springframework.stereotype.Controller; @Controller public class OrderController { - private final OrderService orderService; + private final OrderUseCase orderService; - public OrderController(OrderService orderService) { + public OrderController(OrderUseCase orderService) { this.orderService = orderService; } diff --git a/examples/architecture-validator/single-module-spring/src/main/java/com/arc_e_tect/example/spring/application/port/in/OrderUseCase.java b/examples/architecture-validator/single-module-spring/src/main/java/com/arc_e_tect/example/spring/application/port/inbound/OrderUseCase.java similarity index 51% rename from examples/architecture-validator/single-module-spring/src/main/java/com/arc_e_tect/example/spring/application/port/in/OrderUseCase.java rename to examples/architecture-validator/single-module-spring/src/main/java/com/arc_e_tect/example/spring/application/port/inbound/OrderUseCase.java index 174a72c..5967a05 100644 --- a/examples/architecture-validator/single-module-spring/src/main/java/com/arc_e_tect/example/spring/application/port/in/OrderUseCase.java +++ b/examples/architecture-validator/single-module-spring/src/main/java/com/arc_e_tect/example/spring/application/port/inbound/OrderUseCase.java @@ -1,4 +1,4 @@ -package com.arc_e_tect.example.spring.application.port.in; +package com.arc_e_tect.example.spring.application.port.inbound; public interface OrderUseCase { diff --git a/examples/architecture-validator/single-module-spring/src/main/java/com/arc_e_tect/example/spring/application/service/OrderService.java b/examples/architecture-validator/single-module-spring/src/main/java/com/arc_e_tect/example/spring/application/service/OrderService.java index c86b132..4b31f84 100644 --- a/examples/architecture-validator/single-module-spring/src/main/java/com/arc_e_tect/example/spring/application/service/OrderService.java +++ b/examples/architecture-validator/single-module-spring/src/main/java/com/arc_e_tect/example/spring/application/service/OrderService.java @@ -2,10 +2,8 @@ import com.arc_e_tect.example.spring.adapter.persistence.OrderRepository; import com.arc_e_tect.example.spring.application.domain.model.Order; -import com.arc_e_tect.example.spring.application.port.in.OrderUseCase; -import org.springframework.stereotype.Service; +import com.arc_e_tect.example.spring.application.port.inbound.OrderUseCase; -@Service public class OrderService implements OrderUseCase { private final OrderRepository repository; From decb7f466ae84c91dcccb68ae8fa866ea37de188 Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 21:16:24 +0400 Subject: [PATCH 13/20] fix(hexagonal-spring-rules): narrow service rule to adapter dependencies What changed:\n- Reworked SpringHexagonalArchitectureTest.servicesShouldNotAccessRepositoriesDirectly from an allow-list dependency rule to a targeted deny-list rule.\n- The rule now asserts that @Service classes must not depend on adapter packages, using noClasses().should().dependOnClassesThat().resideInAnyPackage(adapters).\n- Added clarifying Javadoc that explains why the deny-list model is correct for Hexagonal services that implement in-port interfaces.\n\nWhy:\n- The previous allow-list approach incorrectly penalized valid service-to-inport relationships because ArchUnit observes the implemented interface dependency, creating false positives.\n\nHow:\n- Switched from classes().should().onlyDependOnClassesThat(...) to noClasses().should().dependOnClassesThat(...adapters).\n- Kept the existing rule id and disabled-rule wiring unchanged to preserve configuration compatibility.\n\nSide effects and constraints:\n- The rule remains focused on preventing direct service access to adapters/repositories while allowing normal in-port implementation patterns.\n\nOutcomes:\n- Services now fail only for the architectural violation the rule is intended to catch, reducing noise in example and consumer projects. --- .../SpringHexagonalArchitectureTest.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/SpringHexagonalArchitectureTest.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/SpringHexagonalArchitectureTest.java index ee2bc76..8618355 100644 --- a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/SpringHexagonalArchitectureTest.java +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/SpringHexagonalArchitectureTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; /** * Spring Hexagonal architecture validation rules. @@ -76,10 +77,16 @@ void controllersShouldOnlyCallInPorts() { /** * Validates that Spring services do not directly access repositories or adapters. - * + * *

    Services must communicate with external systems through out-ports only. * Direct repository access couples the business logic to persistence details * and prevents flexibility in choosing adapter implementations. + * + *

    This is a deny-list check on the adapter packages specifically, rather than an + * allow-list of every legitimate dependency: a {@code @Service} implementing its own + * in-port (the standard Hexagonal pattern) is a real dependency ArchUnit can see, and an + * allow-list would have to special-case it. The actual concern is adapter/repository + * access, so only that is checked. */ @Test void servicesShouldNotAccessRepositoriesDirectly() { @@ -87,15 +94,10 @@ void servicesShouldNotAccessRepositoriesDirectly() { RulePackConfiguration.isRuleDisabled("SpringHexagonalArchitectureTest.servicesShouldNotAccessRepositoriesDirectly"), "Rule disabled via architectureValidator.rules.disabled" ); - classes() + noClasses() .that().areAnnotatedWith("org.springframework.stereotype.Service") - .should().onlyDependOnClassesThat() - .resideInAnyPackage(mergeAll( - RulePackConfiguration.outPorts(), - RulePackConfiguration.applicationServices(), - RulePackConfiguration.domainModel(), - "java..", - "org.springframework..")) + .should().dependOnClassesThat() + .resideInAnyPackage(RulePackConfiguration.adapters()) .because("Spring services should not access repositories directly; use out-ports instead") .allowEmptyShould(true) .check(classes); From 46d12e9c233767291cc352e40cee4c6e58f35ce5 Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 21:16:31 +0400 Subject: [PATCH 14/20] feat(architecture-examples): add six spring rule-pack example projects What changed:\n- Added six new Gradle example projects under examples/architecture-validator/:\n - hexagonal-spring-misconfigured\n - hexagonal-spring-type-leakage\n - hexagonal-spring-cycle-detection\n - hexagonal-spring-rules-disabled\n - hexagonal-spring-field-injection\n - hexagonal-spring-naming-conventions\n- Each project includes full scaffolding (wrapper, settings, build script, version catalog, source fixture, and README).\n- Wired all new examples to resolve plugin and external rule-pack artifacts from mavenLocal first.\n- Documented each fixture's observed expected violations and remediation guidance in project READMEs.\n- Updated hexagonal-spring-rules README example section to link all seven available example projects (existing single-module plus six new ones).\n\nWhy:\n- The rule-pack now includes multiple new rules and DSL options that need runnable, focused demonstrations for users and maintainers.\n\nHow:\n- Created one fixture per feature area, then iterated against architecture test output to keep each example aligned to its intended rule behavior.\n- Captured required sanity checks in docs, including opt-in and selective-disable behavior where applicable.\n\nSide effects and constraints:\n- These projects are intentionally non-compliant by design so architecture checks fail in specific, teachable ways.\n- The type-leakage fixture disables PortContractTest.portsShouldOnlyDependOnJavaCoreAndDomainModel to isolate TypeLeakageTest output in that scenario.\n\nOutcomes:\n- The repository now has a complete example suite covering fail-fast configuration, type leakage, cycle detection, per-rule opt-out, field injection, and naming conventions. --- .../README.adoc | 44 ++++ .../build.gradle | 45 ++++ .../gradle/libs.versions.toml | 16 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48462 bytes .../gradle/wrapper/gradle-wrapper.properties | 9 + .../hexagonal-spring-cycle-detection/gradlew | 248 ++++++++++++++++++ .../gradlew.bat | 82 ++++++ .../settings.gradle | 19 ++ .../adapter/notification/OrderNotifier.java | 18 ++ .../persistence/OrderRepositoryAdapter.java | 25 ++ .../adapter/web/OrderController.java | 18 ++ .../application/domain/Order.java | 4 + .../port/inbound/OrderUseCase.java | 6 + .../application/port/outbound/OrderPort.java | 8 + .../application/service/OrderService.java | 19 ++ .../versions.properties | 10 + .../README.adoc | 44 ++++ .../build.gradle | 45 ++++ .../gradle/libs.versions.toml | 16 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48462 bytes .../gradle/wrapper/gradle-wrapper.properties | 9 + .../hexagonal-spring-field-injection/gradlew | 248 ++++++++++++++++++ .../gradlew.bat | 82 ++++++ .../settings.gradle | 19 ++ .../persistence/OrderRepositoryAdapter.java | 18 ++ .../persistence/PersistenceGateway.java | 10 + .../adapter/web/OrderController.java | 18 ++ .../application/domain/Order.java | 4 + .../port/inbound/OrderUseCase.java | 6 + .../application/port/outbound/OrderPort.java | 8 + .../application/service/OrderService.java | 19 ++ .../versions.properties | 10 + .../README.adoc | 44 ++++ .../build.gradle | 45 ++++ .../gradle/libs.versions.toml | 16 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48462 bytes .../gradle/wrapper/gradle-wrapper.properties | 9 + .../hexagonal-spring-misconfigured/gradlew | 248 ++++++++++++++++++ .../gradlew.bat | 82 ++++++ .../settings.gradle | 19 ++ .../spring/misconfigured/SampleDomain.java | 4 + .../versions.properties | 10 + .../README.adoc | 46 ++++ .../build.gradle | 48 ++++ .../gradle/libs.versions.toml | 16 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48462 bytes .../gradle/wrapper/gradle-wrapper.properties | 9 + .../gradlew | 248 ++++++++++++++++++ .../gradlew.bat | 82 ++++++ .../settings.gradle | 19 ++ .../adapter/persistence/OrderPersistence.java | 14 + .../adapter/web/OrderController.java | 18 ++ .../application/domain/Order.java | 4 + .../port/inbound/OrderCommands.java | 6 + .../application/port/outbound/OrderStore.java | 8 + .../application/service/OrderService.java | 19 ++ .../versions.properties | 10 + .../README.adoc | 45 ++++ .../build.gradle | 46 ++++ .../gradle/libs.versions.toml | 16 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48462 bytes .../gradle/wrapper/gradle-wrapper.properties | 9 + .../hexagonal-spring-rules-disabled/gradlew | 248 ++++++++++++++++++ .../gradlew.bat | 82 ++++++ .../settings.gradle | 19 ++ .../adapter/persistence/OrderRepository.java | 12 + .../adapter/web/OrderController.java | 18 ++ .../application/domain/Order.java | 4 + .../port/inbound/OrderUseCase.java | 6 + .../service/adapter/OrderService.java | 21 ++ .../versions.properties | 10 + .../hexagonal-spring-type-leakage/README.adoc | 49 ++++ .../build.gradle | 47 ++++ .../gradle/libs.versions.toml | 18 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48462 bytes .../gradle/wrapper/gradle-wrapper.properties | 9 + .../hexagonal-spring-type-leakage/gradlew | 248 ++++++++++++++++++ .../hexagonal-spring-type-leakage/gradlew.bat | 82 ++++++ .../settings.gradle | 19 ++ .../persistence/OrderRepositoryAdapter.java | 16 ++ .../adapter/web/OrderController.java | 18 ++ .../typeleakage/application/domain/Order.java | 4 + .../port/inbound/OrderUseCase.java | 6 + .../application/port/outbound/OrderPort.java | 8 + .../application/service/OrderService.java | 18 ++ .../typeleakage/persistence/OrderEntity.java | 17 ++ .../versions.properties | 10 + hexagonal-spring-rules/README.adoc | 13 +- 88 files changed, 3267 insertions(+), 2 deletions(-) create mode 100644 examples/architecture-validator/hexagonal-spring-cycle-detection/README.adoc create mode 100644 examples/architecture-validator/hexagonal-spring-cycle-detection/build.gradle create mode 100644 examples/architecture-validator/hexagonal-spring-cycle-detection/gradle/libs.versions.toml create mode 100644 examples/architecture-validator/hexagonal-spring-cycle-detection/gradle/wrapper/gradle-wrapper.jar create mode 100644 examples/architecture-validator/hexagonal-spring-cycle-detection/gradle/wrapper/gradle-wrapper.properties create mode 100755 examples/architecture-validator/hexagonal-spring-cycle-detection/gradlew create mode 100644 examples/architecture-validator/hexagonal-spring-cycle-detection/gradlew.bat create mode 100644 examples/architecture-validator/hexagonal-spring-cycle-detection/settings.gradle create mode 100644 examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/notification/OrderNotifier.java create mode 100644 examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/persistence/OrderRepositoryAdapter.java create mode 100644 examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/web/OrderController.java create mode 100644 examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/domain/Order.java create mode 100644 examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/port/inbound/OrderUseCase.java create mode 100644 examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/port/outbound/OrderPort.java create mode 100644 examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/service/OrderService.java create mode 100644 examples/architecture-validator/hexagonal-spring-cycle-detection/versions.properties create mode 100644 examples/architecture-validator/hexagonal-spring-field-injection/README.adoc create mode 100644 examples/architecture-validator/hexagonal-spring-field-injection/build.gradle create mode 100644 examples/architecture-validator/hexagonal-spring-field-injection/gradle/libs.versions.toml create mode 100644 examples/architecture-validator/hexagonal-spring-field-injection/gradle/wrapper/gradle-wrapper.jar create mode 100644 examples/architecture-validator/hexagonal-spring-field-injection/gradle/wrapper/gradle-wrapper.properties create mode 100755 examples/architecture-validator/hexagonal-spring-field-injection/gradlew create mode 100644 examples/architecture-validator/hexagonal-spring-field-injection/gradlew.bat create mode 100644 examples/architecture-validator/hexagonal-spring-field-injection/settings.gradle create mode 100644 examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/persistence/OrderRepositoryAdapter.java create mode 100644 examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/persistence/PersistenceGateway.java create mode 100644 examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/web/OrderController.java create mode 100644 examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/domain/Order.java create mode 100644 examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/port/inbound/OrderUseCase.java create mode 100644 examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/port/outbound/OrderPort.java create mode 100644 examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/service/OrderService.java create mode 100644 examples/architecture-validator/hexagonal-spring-field-injection/versions.properties create mode 100644 examples/architecture-validator/hexagonal-spring-misconfigured/README.adoc create mode 100644 examples/architecture-validator/hexagonal-spring-misconfigured/build.gradle create mode 100644 examples/architecture-validator/hexagonal-spring-misconfigured/gradle/libs.versions.toml create mode 100644 examples/architecture-validator/hexagonal-spring-misconfigured/gradle/wrapper/gradle-wrapper.jar create mode 100644 examples/architecture-validator/hexagonal-spring-misconfigured/gradle/wrapper/gradle-wrapper.properties create mode 100755 examples/architecture-validator/hexagonal-spring-misconfigured/gradlew create mode 100644 examples/architecture-validator/hexagonal-spring-misconfigured/gradlew.bat create mode 100644 examples/architecture-validator/hexagonal-spring-misconfigured/settings.gradle create mode 100644 examples/architecture-validator/hexagonal-spring-misconfigured/src/main/java/com/arc_e_tect/example/spring/misconfigured/SampleDomain.java create mode 100644 examples/architecture-validator/hexagonal-spring-misconfigured/versions.properties create mode 100644 examples/architecture-validator/hexagonal-spring-naming-conventions/README.adoc create mode 100644 examples/architecture-validator/hexagonal-spring-naming-conventions/build.gradle create mode 100644 examples/architecture-validator/hexagonal-spring-naming-conventions/gradle/libs.versions.toml create mode 100644 examples/architecture-validator/hexagonal-spring-naming-conventions/gradle/wrapper/gradle-wrapper.jar create mode 100644 examples/architecture-validator/hexagonal-spring-naming-conventions/gradle/wrapper/gradle-wrapper.properties create mode 100755 examples/architecture-validator/hexagonal-spring-naming-conventions/gradlew create mode 100644 examples/architecture-validator/hexagonal-spring-naming-conventions/gradlew.bat create mode 100644 examples/architecture-validator/hexagonal-spring-naming-conventions/settings.gradle create mode 100644 examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/adapter/persistence/OrderPersistence.java create mode 100644 examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/adapter/web/OrderController.java create mode 100644 examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/domain/Order.java create mode 100644 examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/port/inbound/OrderCommands.java create mode 100644 examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/port/outbound/OrderStore.java create mode 100644 examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/service/OrderService.java create mode 100644 examples/architecture-validator/hexagonal-spring-naming-conventions/versions.properties create mode 100644 examples/architecture-validator/hexagonal-spring-rules-disabled/README.adoc create mode 100644 examples/architecture-validator/hexagonal-spring-rules-disabled/build.gradle create mode 100644 examples/architecture-validator/hexagonal-spring-rules-disabled/gradle/libs.versions.toml create mode 100644 examples/architecture-validator/hexagonal-spring-rules-disabled/gradle/wrapper/gradle-wrapper.jar create mode 100644 examples/architecture-validator/hexagonal-spring-rules-disabled/gradle/wrapper/gradle-wrapper.properties create mode 100755 examples/architecture-validator/hexagonal-spring-rules-disabled/gradlew create mode 100644 examples/architecture-validator/hexagonal-spring-rules-disabled/gradlew.bat create mode 100644 examples/architecture-validator/hexagonal-spring-rules-disabled/settings.gradle create mode 100644 examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/adapter/persistence/OrderRepository.java create mode 100644 examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/adapter/web/OrderController.java create mode 100644 examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/application/domain/Order.java create mode 100644 examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/application/port/inbound/OrderUseCase.java create mode 100644 examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/application/service/adapter/OrderService.java create mode 100644 examples/architecture-validator/hexagonal-spring-rules-disabled/versions.properties create mode 100644 examples/architecture-validator/hexagonal-spring-type-leakage/README.adoc create mode 100644 examples/architecture-validator/hexagonal-spring-type-leakage/build.gradle create mode 100644 examples/architecture-validator/hexagonal-spring-type-leakage/gradle/libs.versions.toml create mode 100644 examples/architecture-validator/hexagonal-spring-type-leakage/gradle/wrapper/gradle-wrapper.jar create mode 100644 examples/architecture-validator/hexagonal-spring-type-leakage/gradle/wrapper/gradle-wrapper.properties create mode 100755 examples/architecture-validator/hexagonal-spring-type-leakage/gradlew create mode 100644 examples/architecture-validator/hexagonal-spring-type-leakage/gradlew.bat create mode 100644 examples/architecture-validator/hexagonal-spring-type-leakage/settings.gradle create mode 100644 examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/adapter/persistence/OrderRepositoryAdapter.java create mode 100644 examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/adapter/web/OrderController.java create mode 100644 examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/domain/Order.java create mode 100644 examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/port/inbound/OrderUseCase.java create mode 100644 examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/port/outbound/OrderPort.java create mode 100644 examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/service/OrderService.java create mode 100644 examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/persistence/OrderEntity.java create mode 100644 examples/architecture-validator/hexagonal-spring-type-leakage/versions.properties diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/README.adoc b/examples/architecture-validator/hexagonal-spring-cycle-detection/README.adoc new file mode 100644 index 0000000..8940833 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/README.adoc @@ -0,0 +1,44 @@ += Hexagonal Spring Cycle Detection Example +:toc: left + +This example demonstrates package cycle detection in adapter packages. +It also documents the CycleFreedomTest slice-pattern bug fix that was required to make this check effective in nested base packages. + +== Files and Purpose + +[cols="1,2"] +|=== +|File |Purpose + +|src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/persistence/OrderRepositoryAdapter.java +|Adapter package A that depends on adapter.notification.OrderNotifier. + +|src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/notification/OrderNotifier.java +|Adapter package B that depends back on OrderRepositoryAdapter, creating a package cycle. + +|src/main/java/com/arc_e_tect/example/spring/cycledetection/application/service/OrderService.java +|Plain application service that depends only on an outbound port. +|=== + +== Run + +[source,bash] +---- +./gradlew build +./gradlew testArchitecture +---- + +== Expected violations + +CycleFreedomTest.adapterPackagesShouldBeFreeOfCycles fails and reports the cycle between adapter.persistence and adapter.notification. +Other rule classes should pass. + +== Reports + +The Gradle HTML report is under build/reports/architecture-validator/html/. +The XML report is under build/reports/architecture-validator/xml/. + +== Fix + +Break the dependency loop so adapter.notification no longer depends on adapter.persistence. +Keep adapter package dependencies acyclic to preserve maintainable boundaries. diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/build.gradle b/examples/architecture-validator/hexagonal-spring-cycle-detection/build.gradle new file mode 100644 index 0000000..352bccf --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/build.gradle @@ -0,0 +1,45 @@ +plugins { + id 'jacoco' + id 'java' + alias(libs.plugins.architecture.validator.iff) +} + +group = 'com.arc-e-tect.example.spring' +version = '0.0.1' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation libs.spring.context.iff + testArchitectureImplementation libs.architecture.validator.hexagonal.spring.rules.iff +} + +architectureValidator { + basePackage = 'com.arc_e_tect.example.spring.cycledetection' + useBuiltInHexagonalRulePack = false + +} + +tasks.named('jacocoTestReport') { + reports { + xml.required = true + html.required = true + } +} + +tasks.named('testArchitecture', Test) { + ignoreFailures = false +} + +tasks.named('check') { + dependsOn tasks.named('testArchitecture') +} diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/gradle/libs.versions.toml b/examples/architecture-validator/hexagonal-spring-cycle-detection/gradle/libs.versions.toml new file mode 100644 index 0000000..58fd4a4 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/gradle/libs.versions.toml @@ -0,0 +1,16 @@ +## Generated by $ ./gradlew refreshVersionsCatalog + +[plugins] + +architecture-validator-iff = { id = "com.arc-e-tect.architecture-validator", version.ref = "architecture-validator-iff" } + +[versions] + +architecture-validator-iff = "0.0.1" +architecture-validator-hexagonal-spring-rules-iff = "1.0.0-PRERELEASE" +spring-context-iff = "7.0.8" + +[libraries] + +architecture-validator-hexagonal-spring-rules-iff = { module = "com.arc-e-tect.architecture-validator:architecture-validator-hexagonal-spring-rules", version.ref = "architecture-validator-hexagonal-spring-rules-iff" } +spring-context-iff = { module = "org.springframework:spring-context", version.ref = "spring-context-iff" } diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/gradle/wrapper/gradle-wrapper.jar b/examples/architecture-validator/hexagonal-spring-cycle-detection/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..b1b8ef56b44f16b14dc800fa8103a6d89abb526f GIT binary patch literal 48462 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 R2np~f1o-$>Q}y+){|A@R9n$~+ literal 0 HcmV?d00001 diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/gradle/wrapper/gradle-wrapper.properties b/examples/architecture-validator/hexagonal-spring-cycle-detection/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a9db115 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,9 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/gradlew b/examples/architecture-validator/hexagonal-spring-cycle-detection/gradlew new file mode 100755 index 0000000..249efbb --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/gradlew.bat b/examples/architecture-validator/hexagonal-spring-cycle-detection/gradlew.bat new file mode 100644 index 0000000..a51ec4f --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/settings.gradle b/examples/architecture-validator/hexagonal-spring-cycle-detection/settings.gradle new file mode 100644 index 0000000..08c02b7 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/settings.gradle @@ -0,0 +1,19 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' + id 'de.fayard.refreshVersions' version '0.60.6' +} + +refreshVersions { + rejectVersionIf { + candidate.stabilityLevel.isLessStableThan(current.stabilityLevel) + } +} + +rootProject.name = 'architecture-validator-hexagonal-spring-cycle-detection' diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/notification/OrderNotifier.java b/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/notification/OrderNotifier.java new file mode 100644 index 0000000..0e7332e --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/notification/OrderNotifier.java @@ -0,0 +1,18 @@ +package com.arc_e_tect.example.spring.cycledetection.adapter.notification; + +import com.arc_e_tect.example.spring.cycledetection.adapter.persistence.OrderRepositoryAdapter; +import org.springframework.stereotype.Component; + +@Component +public class OrderNotifier { + + private final OrderRepositoryAdapter repositoryAdapter; + + public OrderNotifier(OrderRepositoryAdapter repositoryAdapter) { + this.repositoryAdapter = repositoryAdapter; + } + + public void notifySaved(String id) { + repositoryAdapter.recordNotification(id); + } +} diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/persistence/OrderRepositoryAdapter.java b/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/persistence/OrderRepositoryAdapter.java new file mode 100644 index 0000000..a5e922d --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/persistence/OrderRepositoryAdapter.java @@ -0,0 +1,25 @@ +package com.arc_e_tect.example.spring.cycledetection.adapter.persistence; + +import com.arc_e_tect.example.spring.cycledetection.adapter.notification.OrderNotifier; +import com.arc_e_tect.example.spring.cycledetection.application.domain.Order; +import com.arc_e_tect.example.spring.cycledetection.application.port.outbound.OrderPort; +import org.springframework.stereotype.Repository; + +@Repository +public class OrderRepositoryAdapter implements OrderPort { + + private final OrderNotifier notifier; + + public OrderRepositoryAdapter(OrderNotifier notifier) { + this.notifier = notifier; + } + + @Override + public void save(Order order) { + notifier.notifySaved(order.id()); + } + + public void recordNotification(String id) { + // Example back-reference target used to complete the package cycle. + } +} diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/web/OrderController.java b/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/web/OrderController.java new file mode 100644 index 0000000..3d1827f --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/web/OrderController.java @@ -0,0 +1,18 @@ +package com.arc_e_tect.example.spring.cycledetection.adapter.web; + +import com.arc_e_tect.example.spring.cycledetection.application.port.inbound.OrderUseCase; +import org.springframework.stereotype.Controller; + +@Controller +public class OrderController { + + private final OrderUseCase orderUseCase; + + public OrderController(OrderUseCase orderUseCase) { + this.orderUseCase = orderUseCase; + } + + public void submit(String id) { + orderUseCase.createOrder(id); + } +} diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/domain/Order.java b/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/domain/Order.java new file mode 100644 index 0000000..9160648 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/domain/Order.java @@ -0,0 +1,4 @@ +package com.arc_e_tect.example.spring.cycledetection.application.domain; + +public record Order(String id) { +} diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/port/inbound/OrderUseCase.java b/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/port/inbound/OrderUseCase.java new file mode 100644 index 0000000..132aa9a --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/port/inbound/OrderUseCase.java @@ -0,0 +1,6 @@ +package com.arc_e_tect.example.spring.cycledetection.application.port.inbound; + +public interface OrderUseCase { + + void createOrder(String id); +} diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/port/outbound/OrderPort.java b/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/port/outbound/OrderPort.java new file mode 100644 index 0000000..918a34a --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/port/outbound/OrderPort.java @@ -0,0 +1,8 @@ +package com.arc_e_tect.example.spring.cycledetection.application.port.outbound; + +import com.arc_e_tect.example.spring.cycledetection.application.domain.Order; + +public interface OrderPort { + + void save(Order order); +} diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/service/OrderService.java b/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/service/OrderService.java new file mode 100644 index 0000000..c5d7ed4 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/src/main/java/com/arc_e_tect/example/spring/cycledetection/application/service/OrderService.java @@ -0,0 +1,19 @@ +package com.arc_e_tect.example.spring.cycledetection.application.service; + +import com.arc_e_tect.example.spring.cycledetection.application.domain.Order; +import com.arc_e_tect.example.spring.cycledetection.application.port.inbound.OrderUseCase; +import com.arc_e_tect.example.spring.cycledetection.application.port.outbound.OrderPort; + +public class OrderService implements OrderUseCase { + + private final OrderPort orderPort; + + public OrderService(OrderPort orderPort) { + this.orderPort = orderPort; + } + + @Override + public void createOrder(String id) { + orderPort.save(new Order(id)); + } +} diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/versions.properties b/examples/architecture-validator/hexagonal-spring-cycle-detection/versions.properties new file mode 100644 index 0000000..a07c552 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/versions.properties @@ -0,0 +1,10 @@ +#### Dependencies and Plugin versions with their available updates. +#### Generated by `./gradlew refreshVersions` version 0.60.6 +#### +#### Don't manually edit or split the comments that start with four hashtags (####), +#### they will be overwritten by refreshVersions. +#### +#### suppress inspection "SpellCheckingInspection" for whole file +#### suppress inspection "UnusedProperty" for whole file +#### +#### NOTE: Some versions are filtered by the rejectVersionIf predicate. See the settings.gradle.kts file. diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/README.adoc b/examples/architecture-validator/hexagonal-spring-field-injection/README.adoc new file mode 100644 index 0000000..516ccea --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-field-injection/README.adoc @@ -0,0 +1,44 @@ += Hexagonal Spring Field Injection Example +:toc: left + +This example demonstrates the no-field-injection rule from DependencyInjectionStyleTest. +It intentionally uses @Autowired on a field in an adapter class. + +== Files and Purpose + +[cols="1,2"] +|=== +|File |Purpose + +|src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/persistence/OrderRepositoryAdapter.java +|Repository adapter with an @Autowired field to trigger DependencyInjectionStyleTest.fieldsShouldNotBeAutowired. + +|src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/service/OrderService.java +|Plain application service that depends only on an outbound port. + +|src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/web/OrderController.java +|Inbound controller that depends only on the inbound port. +|=== + +== Run + +[source,bash] +---- +./gradlew build +./gradlew testArchitecture +---- + +== Expected violations + +DependencyInjectionStyleTest.fieldsShouldNotBeAutowired fails because OrderRepositoryAdapter uses field injection. +The failure names the specific class and field. + +== Reports + +The Gradle HTML report is under build/reports/architecture-validator/html/. +The XML report is under build/reports/architecture-validator/xml/. + +== Fix + +Replace the @Autowired field with constructor injection. +Keep dependencies explicit and testable without a Spring context. diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/build.gradle b/examples/architecture-validator/hexagonal-spring-field-injection/build.gradle new file mode 100644 index 0000000..bff31e1 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-field-injection/build.gradle @@ -0,0 +1,45 @@ +plugins { + id 'jacoco' + id 'java' + alias(libs.plugins.architecture.validator.iff) +} + +group = 'com.arc-e-tect.example.spring' +version = '0.0.1' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation libs.spring.context.iff + testArchitectureImplementation libs.architecture.validator.hexagonal.spring.rules.iff +} + +architectureValidator { + basePackage = 'com.arc_e_tect.example.spring.fieldinjection' + useBuiltInHexagonalRulePack = false + +} + +tasks.named('jacocoTestReport') { + reports { + xml.required = true + html.required = true + } +} + +tasks.named('testArchitecture', Test) { + ignoreFailures = false +} + +tasks.named('check') { + dependsOn tasks.named('testArchitecture') +} diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/gradle/libs.versions.toml b/examples/architecture-validator/hexagonal-spring-field-injection/gradle/libs.versions.toml new file mode 100644 index 0000000..58fd4a4 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-field-injection/gradle/libs.versions.toml @@ -0,0 +1,16 @@ +## Generated by $ ./gradlew refreshVersionsCatalog + +[plugins] + +architecture-validator-iff = { id = "com.arc-e-tect.architecture-validator", version.ref = "architecture-validator-iff" } + +[versions] + +architecture-validator-iff = "0.0.1" +architecture-validator-hexagonal-spring-rules-iff = "1.0.0-PRERELEASE" +spring-context-iff = "7.0.8" + +[libraries] + +architecture-validator-hexagonal-spring-rules-iff = { module = "com.arc-e-tect.architecture-validator:architecture-validator-hexagonal-spring-rules", version.ref = "architecture-validator-hexagonal-spring-rules-iff" } +spring-context-iff = { module = "org.springframework:spring-context", version.ref = "spring-context-iff" } diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/gradle/wrapper/gradle-wrapper.jar b/examples/architecture-validator/hexagonal-spring-field-injection/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..b1b8ef56b44f16b14dc800fa8103a6d89abb526f GIT binary patch literal 48462 zcma&NV{|3jwk;gnwr$(CRk3Z`Sy9Ed?Nn^ruGlsztklcC=e7I2x9>aqJFB(1eyu-q z%|3b`eLzVT6buar3JMAc2#EOW{C^)LAZQ?YaW!FjX$1*JIcZUG1yyl%HEd!6f#E+}*Jo*NafvM<-FbE0;-_L#rp}qdn%JEoAVNlEB#J^Oq`mU_#*ev4HLmc> zjXz_hFft^><#omb;Zer-%wm4hxo!wjuX3hBldg(^-RiOleKin`>KHfL3P*{k?(rji(#j2Cc0K509#>qu=-T&B!-5EBi(+ zIuTD-qfcAYgS@`Fb2^-p)4#o6A3z0&fp?~cV=CRsAeCmO4ZQ5kKgC%0el=Q&Rhd#k zaGmAbUW8uKC}-C0s~2);d{;mpsNBx9rn__66W{AhaSvJEK+c0b6ARO+l(CI7E|S5x zhaYP--@F<|99X&)9`q^2(^-Zu^Tzfm)v|gkTJHQ!G*zIg5hzoygeXZoYUEJ;iFkE# zq^r$*c|>Hmn3GapzcDYnjgSFiO^NFyTR5AH#mh%zRToMpEi(r)1$5)h455DuV}0al z!*psWuL@Ke-2gvftfMEGf9YEi^<{B@qru zINgo+YsE&LN?)1qItJoNhISp-fZ86`XR#*6xcvM~_7=JHUX;K9*=Gu5X~ zix|O2d=&C#u_w{=B$eCpJ4L*6i7={j+{Og~`Emz@&98}6s<-p^)`0fXE4cJBP{>)Ltb>JwcqI>yz z0-r-SEhC@p)XOoh|1|XgjFaREHfsu4dAGVz*k#m+V<4 zHqvlud6=;#QWHUoTR_a8Y8+heN?M%n1@0YLiaN@GuOPNd26tik7eKulTx?mM-R!1H znB6+H{^krFXg_b{y=QeCT~qR3T4}l+b!Oz9;~|3*6F<3?#|DYYW&1RtFE)ILZ!`85 zVmvrZkLTzf31unH7Cc5E0iFShqlBE9hgEnRJH1juII*vyp&xd!g`q}X_6WT6E$hhQ`Vdp9k^<)VS?lj!cTh z7FQcQAVA@jL^cXod8cnhKG2TS9+;QU6Kq>}UOY3&TL9gXbl{Fv8@WsF=z7>X0To@$ zY@Oi1uc|MdJ$>Kn{@!g_e`-I&Tpwfg9cr>(iakDX1qciCG_1y!Di#4_)lE!bWJbrp z5aUonb6m-?tiQyR_`P#~SOu+tb_ev6JO>EbEhHK@KbeT0_FDo>dl9bMg)>xmCNB*g zG5NC8ABavuTEZVGW6jP*nAqRt3W?7Iigc-EE~zpNJXRAE z>`~RO9$892j&I1kV;9U)xT8^}IeV`n{}QDtj2o-RBt`DGZUOO;O*lFCb_vpyGh*;95PfeGu!dyrmZ9VJ3Z*upg z6R-3Lr%_55$Hw1^{+KWx0#z`T7O6sXo1h;m?B_ur`X2bFz-SzDrL zpk^@B<+I6imc@7vip za%1jMB7q@1j# zz{u?YojZMW{5j$@h=v4iu2mTu7IzI|)Sxn!74=*J>1a&?Xjt z2%JhSi#4huEcD9qdR9Lj4vwmfnL{%+vQ{f-KgYeqin(OPd8+(g*Uq#TLxQjD4 zLCL%ul(V&PAPlAx8D`@K8Rc`{GPecQ<)d=KWel0ejFeeXGQ6o7601B!!I@RY&eDriADD6wP6DcFKDLZ|lO#YwnrNCZ)zRJpdxX_nPZa4j#$j6v!h|6p!dH}MY6#B`@%6=) z-HigguDACKBULnon^FKzazF|Y1{t(U5rUGnEU|}djVsWT-F>@@mNx?_$kF51QF4C5 zStKR$^3(fw85(4HGs9{mUTtn1)3PwxTN?6}j;32&vJ^BiPHfndLkdU5sOemXKGyCZ z@<7j(k>DNeo~QXyJkFWk!7(y1SB%nA3{v~P2c8ooKa4auM!el!Q_=;lJ$c5ADqE+^ zX8*|A99v;jWPrm(8=h;2ZAj|(vVbx~wQ{N%v;eYLD_BB2LAEWCs@xauyBDl(_HIBvA(XJ7B1E;O zJYCJ8xFJh7f5sr;Y#Wp_`$4Z_H4e9bGiBp?Qu&2!@%Bl2dT5evfFO*^hLDiBu2%Jl z*WAlL5PaQ7skJa(qVysky}DQquZ8U?2@UyJ8zB#=U_E>MgE%XA$CtfL31m$rATJvC zs@!crc0=128PM=Zp zW_5Czv9))n_8Ru?{pxM2F8^r%*O41}RnONbSj*piG%`nyF>6ky=|;B&k8iot(J=kyoU3p<_zaAX(1ijzf*uXA zZ_5jeC{Lks+&QeFIlmzZi3+fsF4fNW^~kvC4Q*T-vrNP!x9xnen12lZQM=1_MdW76LKX(GuW`%T~dM^YX6+ras|Xy4Qhfcq=D+z-P-ea z`T;^gj3+grr3^hwqcNTJErl$z+k>{bYFm6QV%7Opth?9+>|Dn)O@`7F@=j-XSqGPW zjUAu%b3Er@;j1%RZxVDhI3sakg-gvTLOSV7;FV6ED=(5;UG??=WADZw^=$4AyFh#}VMe3afM^pF zFa}-nM8X=K?Jy02*o02@6k{ z%O!hBhjXlXKdhy3A{xGB<##e|j3^dFv~~%v2_H{t(mN7NVeS~51?D&Ozbxa`qwZ_4 z;C#Q#fL1sua%ggucgIEHZtcY=Ag&GgE|h7Q{77D!WUq`;SSGEE0pU;aoj<7-JCAvf zduN=(tx3Mb+EUXKoax|v;8b@#HJ&Q|!g4ryrl|R>WlAv?IH`bk)I24;eE4NIq@SLK31LD4+w~#3iN{=<`<1R!t^$@K5>U6%W=%8_ANuR5 zs(IDuI18ftirTDARnGmF%;iz+4{MlMihJw_l!0Y)NttXC_t+s)V<EY>=Xin*nGX79k6vQ?beRk zy_J>@YSC_gMIG$yjO-y&o>S6xtfT27aSs>e|`x(f2R1bM}*518~%x>1Yct=18b&Z>GiS*>VB$+i2876zL)1cT zN33g=g|>xWE2)dds5m2+8Vy)m-u@NHOlGYxxjam21r1;xWtT0TgqKZrl}*LSkqFt4 zNTI1=3o%C*!-i;iWnlca$stRdwITA1?#fD~5OIqIQAM18BwO_u>hqL&OAANiF|8rG z_IZ9mp?FA-{Gq9+Ky<#NgL1gWJixfO0ziP$4T4G>vsvqC-NQh+A64F4! z-(t<=AbPSG%`mTl6BJtH~3RmvPhQlE-EUkEoBIP(_WMN zK~Fe!siee{M*ns1hkp5(2}vX#%u+T!Abh=<_gEx_QW?h4V@B>uOCEetEe01tl)^`V z(=cOLmuOB;8&&m%_6pcyrt83UXkJ`f9I&0KxY09}RTTs!l^_7~8$tPA%Hm#&$k0;# zF;O0zCGo0IN)X~SyKDoY1DW{Ulce|V9w=ld;U`z$t$>8U!Gu8V?_LAJAudt3eI#*! z2i9~F=kP5m>!bmb%1e~b1!1gz01Py(Yw5gOsFN#o1a&d|=PpgN(#UVreY9^99I0iG zaYE@>(C^V7pnoB~#w$2C1_TIb1N5Je&iao?S2A*TF>@vpHg`31{uk<9{zf_}s&z%dL-Fo)C$yl$%pAdqU!HJgp zh_{m1imk{&{ScyeuziqZHu5cto0{S}^BlXu% z0~;>_yHGd#?Kt8ErxK)z6ojj5SacQobw)-8`c!$HOI*V6eyqou{1Upm%_p!BY^t(D zDtn(oQ!jff`ddGSD;P8Hes!v)OKW-*>mS&#i0ow87;h>(=Cu0>b4)|=EegbN5=Xkh z9Ge13=3z#sk+fT<)PuUUf_%Nx@l!P?t*mni^94p^Ax6b2SVL5U>9dHH!H4DL4}@?@ z?Gpq$C**OmWliYA{5s<|EZ@QI2{-K#brFxfA~AIqq&-WSALHWQ8}%mvaNFasrtnE{ zg=sB4-RF!?)nf{>Wo~kNFgYefoFHBcSr*;iF9B!R=5Np|jv>Uf+mcarG-XGy*kP{z zISVyoPcl_9cOg-@613Qx16OGF#sH&2NTHDa_}vyidmxS~pMfY#AeQvu?AXpWNzi7A z*6&7a7!C9HRU+N{>WYTh0GXoBnXw{lQby^XShgDOw@e8TP}9Y*oFV4MVF#@Ds2A+A zXBEt3a@-IIl)TOcXx;0P;|ihR%Tq@DXeG5p-O{!T7Sg$s1 z8OA4iOx-!>6eK^x{jU-0SvByimK|nZik5zKIvvWVGE)4=x^&5Nx%Qgje!k3VoizaB zip#?$u(R8u{wUFC>tVR8oA%7fs?xEu(gYn>y6BB%vwPR9&RoZE%%RK! zl#Qnkl^+Y*Y4L{Xk(YX&aGj|zSpqO_;C3CTepA!L#4EXO|(eA`Fi+2EQ3!C zo^SpVP?{chQ3uaxu7y>w213e22cdA#l-M2kStPE%sq6vE4M*?3At!S7tIp(tQg(Ml zECjeJw8)*#LYYk_+Txv3rxsH9jJZBRrHp29yJ(^;_PEdn%#U1q`r89}38;XeF{ee& zsZEsUbJ{LtwOjU{vjL(Wvs2!Bx;#^Mzld&TjS@oo3kk=0P36MC-Ie6eHNN&{8b^s z0@jcbdejrrj!>r#Wu=3H1dgjeOI}NkhmE}K+UK&M>%7b!n&{0Zixk%^)6#@=V~IZN zxG>9kl&STQth}qScidfg58d2dF|v_U<@+V^eE@$4x;7oS3)MvWusA?9+%rN>aY#eA_6 zic@S(@e9$9tQM-&-7>X8~#n{5G}nuOu=dSyN+b~jA;_SExZ1H9Q1A}}Rz;XtXUIOP0~ zZzS|~T+%de-nGI$s?wxaJoe+99vmo%xm8o8SNEsAqAE)4LNvHc-1AX24C4k4u3vZmov^_VcxgGxapV(8)_K(^8= z2d{xCrmk(x&514Ly?e{Mf6}h3=oeP7+ZE{%B^c-kK8g0W{tYw3q%zty_Rd@1nbnyHMwabNp-sSyzpV4v>QsnKcQjF67%g~n&3t^1MesVxCzfJ5b=SOI#YfPP^^JGQw=9L1RCMFbrU{8O0LWOUdBK#j&{`tzXX zpe2_{+-8$a+o#%8MUlL4$yK`*--z&3{@Y?jP!m{g5nM+Ht=bD3o}Ok~sBQ_!^!->! z?NDVtyLXzmGYCEmjSCDK*q?Aq1;8fz9l9|z@~l{)R6GfKELc^(nV+TjjI^n0M+S0i z@YOu*Tk>|M6a0_n$(E;#^1Zgif<-CpYiMvyT+Y*9Z?&~IKSwsLa5Q#p_?FqK3lKIw zlp6Hk%lio6)yq>m-`QT2Nj-q!aX7~Hlm^Xh6FNbw z$#ri(Kk*GUHXORu@`aYQU@ zB~S-oIO^~abRPocemkm!W73dbb!j^_xgo_@#W#6p12>w^{){VfeX?U71Xyn9&E zHa1#*!4c;?r}jv7dMN`g#&R_S215)dccDOJr=uz%LIz@zia+LIFjRakROr?P zQ|Xw0Pa8o7&W=fw17`+SqepsQ-Os5v3ncD5|N?N(AHH&`>hLY+CLOluJ z_ErpaT49zK(UcdNmQ%iA-`jS`A_1c|$W86{d_T_T2V-HH3xUqpX0QJSH%i>1i>#vK z&y{;5)^pMB=u;&_DEWakQU>j&+opIrBf~2GUh{`kG{|Z&2Z}5dwG}>Y{W_uQHaR$_ zYH%}$c`CGC-FGCetRdQ@RZ2-%ucC_|R?mHzYEnqC%u9zRBH8wx7po`=EVPMpq+hL2 zTdjVhQn$)++17^cn;<3=bxJy0Z$U;i3AqJMPJO&SuieU&0eVX?eLEEI7Av@#PV_ZQ zsa>I>B5HE996O$z6HyJfhEt^aC><@AnzeN`xs@lv>^pPFtcodrcGyqPSB?#C`Piu0 zh5=hAW|OtT9hs*G?7}@*mG_f7ae@-Nz4{qvne66kco^uD$(JbCo2ttqUm-SMy@kx% z!eDt?5>w5)M!E#C!b#Iu9GqyhUs|QoYWHtR{4espRS-LUt=viY2iygF=-j3kcU#uF z{ka2=zsOuLR}s;&PbbrB`zty&NfZpV*Y;~i*W$EH0JOGS&FMS%VK@)f*%OOrcU3P9 zq4zjhMpx}oc`PWtP!o5Bdlp=(A***TZwVwuZbuB1Pibv5uiHvW{PsE-k5IfCgUz~l z0nMeZU0R>(ajoQ0G%Il)z0BgRR*bsdz5NcqJ<)niF6|PUO0i}<4)q>6wx4K(5>Y_I z4$WMkbCOQFs(krBnl zx85i0*7%Zm(&nKNP?AQ}d~6@?D9dO%@}ouN2paSR;zyUqJuw)1SRy=g%o;g(BD|Bh ztnKV(4fcBgDJ~M@%}n-6ow3xOhnC>C^d?PbS(9=TnO)k5p+W;pu2F4eiG7ts zJVL4M(NiZPQDy*9`H>-P0GWY#=UTnh8feiNF}hCs`8^ZDKy;XIL^9K4Ps&y^#DQSE z-?J z@YOQ9NQi>ZP>^ix5K`R07kWj?`R(B?E*OyR1$Vd;8p%2Y2zEYt4CJM~gVX%MO(E1B zzXhsHn~R1ifq9~dtzuH!*3&W;r`D(Sjrc)m#EI%`Car;CMWcU0c+0r?O!)HpjEvyP zb^;pO-Bn6e-+>dS^o{q&8yEH9v}vuXX`W;NPRlwJdX|59`z?~z{pFE!^u{3k{KkJ55^ zD;F0ldy9W*`d5YP|0(E6|K%}9|D^SIq>wO)4^cJ+yCa&xl*3}hpvcQ1eP_k;@>tz= zOZnw)#fxHc81jPcTM#)jgy|0?n0(jd3IPu-lJ&Tm`#F1)o$GTwYp@dlqy-qiHFCHS zKgikMUx|%x=_%B)>n_y^+HvD2=nP`}-G_0A7)I$yc4`tXS-On8qOkNp>Q^$|Ew%Jm zYx34*(*Z3SF}xw$CA?nG9O3ZH7l)@Dp4EyH>8eXDb}AFz)k*T53iA~gRu&e15u@|% z9Rw?69nQOeJhv^^unjd-VGFwbDzf9K{i(U{xxHyM@-aI+0qP{TU0G~w+Fs>taL#Ik z4+92(Z7n%+okd478;__0GkE`&(C`k8h@?UNnM=F%A~2|TKo)q9F<5`s)KwxJRw~k; z4giS~|8AIVG;rde6I^W6m9fliR^7YT*>&x7wv^?xu(5p45n{|2F>x%?9Jq+~Tqo9# zChbeGm@9!(s;uIKae_4h@`~yIj`Tqct+-M>d>~2PCiQ?UmFUioyy&~h_DTBQ--W|q zqA^UaJMTz4tEggQ*_cQ_LA7j7bLyz8#cpGggy;YBVk!%oSdufoh5-FYAQ)v=d$Bi`G$^~ zm!O;En#M9uCykPzLZ5SHa%?hDHP5P;T4HN0L6J*r9DAvC1WWPOrd{*obfr3yJ?Kl3 z^_6dnXRoi4<$Tr!=4mhHg6ig~BatHR zv%ZMJr-`8w_JyFEzUSQdp0HT>|9QQG?IXj$7Rbx4E)%HauDyY!tedHP ztIbq;D)ckd-eirAHOG7icBH23*ApHA@nG*Jdh}~G?L5C^Xw^+nLWG+>hRi&(fnpY5 z?^hj4si6I{m1u^%i_yk$tco}28X8|}g5*tAEZYF37$f(+xT%XvO^`i^Ig}%cydrwF zlpL!xdO->&@q|8MiJrAxt;z2CP*a+EvV`_2& z<1=p{zjhmmYVkpx#RV=#zuy&7^2Trn=H$nT{OBVF*0z|QH!NxBF%gbqT!BEx zKB!SsSUwSo1Zr?kMM%N)@hG=&m`vRQ6QK6=oIvnUI+|C)dGKM@jNwqG2Xi8;YCUHYRh? zbl@DN-za)+0F9kw>Yv=ioL)01uFp7@AVEB0AH-nmB%j$RC_totFy4BKd;OPCMUMBb zu3oUUK`|{AvkM+@KPZD4Tn$(VlQi&aWV*Uf@DO|FQjLOoVw&C@z~Um*h%Ka-C=n4H z@(Lf&MDJXNS{3Hs@J)11(zo9tGp>wS^b9{Q1WN=Ktn>ZieRZS?k`gb7P4n?cl^7^* zG5-oARAG#i<*z`J0ski%;QCLD-T$AbOHq<{KxIb4=QJRn@MGj=ns0WhZX+uX z=oTjz`o-VviMt1mB0W1vA*7oq1ENz{<*-EU)U;r*ODfV!G-?hdnzhM@rRZ=|qaFTN zX*t~$gc-)M7GS{#34R-n`B)eAPfebN46~61R?j^(Pg3TXR1PyQrO7Mf@xf<3VL0`4 zh(i?-SktJu8Oj?KIy4p@%5ZH;P&p5LB8 z^}7P)9h}vUP+1Hd3nNzNcbR`%1>dSZbWhiXe-CcB+s9e)_w<{bypZ(@cQT`P@ch=d zSOPhExgI31MVFPsClEXe>$~qYQ+d}7(!BE*9y%AjQ47BMDt=#>`1ie)|ES{pFFdHa zI)CK`f3x>)DtZnm!f5=e@g;3iK^jf!RU6hpjYu^V#q0uWLuJ-6={Ua3gDi9#*P7;- z`rm*5)n{2QE{UZ01PVy@_9(amogzzOwYcVgp2>LsJ(}hKbX_!ayZ7=U{!p{BHussVj(W z2z3$zu7h$KK<%}P0YBJ+)0unV*xD&6GusXqs=M=Cl&fP@Ttzfq?>H9TW#qDId+C7? zhD;;HOxDJR4dc_xI7-b6N6nZ@bUWueDk<_9Rju2I*o(i)M0&~%C^ zc)a<25M<^NrsjAccydV2HJu_-1W>b;xrB~Mi@c7FrW-94$-GnKXvF7( zA68!d!gkIo8(URS{(u{zRtrF}B$9@*)KH9POqOW-B$za4Sg-A&PM*on$>$o#L7pH~ z&YW8oJX3T!!@2r4Rr6ac0ZDbtB1b5yc$5}7oZSDvGF0FWTpZ#r7@GfM^MmC-p{9Qj z_JmmlTxO(^(NHqBc$ECU$jQp^;)%xnyr$qvNTd`R@j$8JppDCGQAHQ7?fja9McCUZ^;``VW$1+G#=<;K{_OfH- z_$fp~S3K`;jPNNZnkB@=DFQy3{6+Bq9nOf3~dr4q8zD_t{P4-^%<4kj!U z0aj`=#@G*w?!4fpM? z8Pwb15(Ka*TtDN-2aWK>*hh{R_C}*e*vSTkHdM(ETM!JrJ=1h?(_WL}2p#QXjrKZ_ z0k_yu^;~)#*r>sQP7d_4VBRvWJCzw#TxA{*hktwQI3ST{8{>3$KHJIgMGK6I!d}Q zinmfq&RLRxX8P)_@@vVr0gPu7*)uU<%xS{|Eg;*w1}2=C&?7B zSX?OLt-gZO+<4@tLeF+K0~*|xwMD__KxWgGfsUpj)KyeCM3J-f*uxe|xk;Dlqq%1< zL(PaY@U(>Z#k!C!B45JlmE^~wHSH;r1c^kWTG9_VT~1LN6$a6Yg@kNF?&b0hs+5Dw=0j zR(wcEYmdfgojx+Hzu89*C}4$I7^?^vYKhF(`>=MC)VeeFR}}?j#XeLnp8OhW9%9ND zt6utD8DHnQj5@YJv+$USdN{8apQir2)Z{8_s!BABmG2O#pz5lSh|gf#CI8X4I|U4g zhQwk=VEV+j+-KNxuIk96Bi%^(Sf9}A7o$zHJ5mV~)qP))QQY&^>9}z9z9)PWpw>8T z7#NWNEtnUoUl{DP5(lmy<3;tpLJ3hG|;CGB`3**uH0tf9>;7w;Aq9SRVg1FDpI5y~rY#B|eCNpAXD z9692@_%$t2^nu&4lU~(~_iVf|Cs|mXs-xKlY$-~FZB$!oDK#)JgHZCG)ySDURM=@(i zCpd{Er89|l&)(&5>L6LuWY3yC6)`jPz(Po8pY=AYIBnx3y2Qx6*sT42mpR$zwx!!< zHHCc~tbF^-bje?bo#~Q59Dmw_-VcliCn^FfI*EV)U1NkNA`6Cm=^%j`%M?1Zxa=1U zn#DPNc32&XHHfUfmPx*J+3_GA&g-_pd#wO=Q^5bdhzmm)>s@yO0q|>ROV(hkhJWf@ zqWjI#+9Wx%C+!kp&kxX|XPS5m9CBC&3r>}SwdFd#YF_W78A*CN6mFC)qzOjM);Z&v z#MjdXXMw63v*tbvY+$tDmuHNFunOlRM#qe|eV&|$98!xy{n)-=N?lrkr0_}U^sz|x zs0y);(2Dooa;(9zHzRi=I{GSVcv!6jl%ck@)>JODfR? z%aI)0HvbhzY9K7eYsntq#JvWzj$WCuoyGoPY7;LSPfZlFiWU)X?(-p}s4FXQcpIp00;%Jv;k0t@2vBu4i;rh-?{z}cHTLL9Rz zT8r(1Ws*H~EyH+adP$cGv|7HkeS9p6eOEI*`idH3twkEJ*72|ey4JgISglGV0Vo@qe#)f-=|g%l$S&Onwl@mmdn|sjXXYaQ4MlfzjiK1* zY&hWQyc9?G2}2s1fYnQ}LXpq{!&Kr97d?=a?_xXAU0SXrZE?T+=9os2*v9%Csph*M zW{}m4+PIRmHEI;<=c5$PMrfg#MTs);4Tb_0**o}*cimSWRcxo(;G&&NV+-?W7v*%4ACG#t5J zQP=$g-(mN*;B6s)d9JNkF0#Zz_WA>J;{=2a!IJsiqCV!YLjJ(wUJ`3b$>qcZ!HjDT z2xm;fMSbtJ|3o~tc!jJ+U8a)vX@NcxU8y#u!Puq%R~{sps0msRFO2!GM4}786S7* zxgNmf{q@|Sdnf6_he>gEGX7Hn)uih5nL&&t4`O{?V;;bdl1U~9RAnjNmt~1UPC3mh zrR8ZtHzz1(yOYSK$OjKf;InJ+7mH$WfqI^OG3dhA+S!YmIgRv>2H78?<6A=~%E{ug^P+^b*+f=j32&Nv&Ypq?DcH&Busg^AUDE|p; z8(tQxZs1+0gUX<5~Ah zT0cGckI5%nM~d`uaMJ$o%2bt^##I0UdaQ2>-bpsP4P1Vk8r7EOSr+a!D*Z4shiKFL z35Lvs^i;#;G{%ksUUo8(Nj2DY?u5->J8kqS_#{B`HqS(UkzR|K5&6XI_#FH4?$ znMXeTb$nmr1`|{n*#5H1T%vtU4-H)vrtAchme!ZG#@c+Hrf4uxx$;VU(Dr~N-ich4 zMKpdwot^bPY#kBILFgi?i3W_kV%vn2J+%R5x}TL8I?B~o#VXlmr?i=y`yJi-><;X* zPCDrsU51x;mkr+t18lPs=6)r^gEh2$saaA!qv_< zKQP13J}ptHaUjT_(*x+P}wfV-}57aU3rp#3AB&~e3%y}0ju#22u5@mUIT!GA{* zd%-e2DTmr#$(P6^$&N0oCgR)F9IPR~!Q!x6YI*7dx6LR6n8tj(#1~!0rofeMtT#g* zW%-p@V09>&o>iz0j66K^soJWg(o9#T(8Xx-P3?;J|t~nIDSGPq(?-B zOoNnc5HZhsW(m6!J+yj~kjmjV6GKvhO>%^v5`O2I@4B$Z!~DgelYWdC4P>YfmI$TR zq`atDEhIt5ua)PS;Yz1`FX@3Na6j^uBx_rNKTmgboWGwE6O5;iQiN6Q8>ZX%ApVJS zTEf6oj=@?7klS(JaijG|(gO@dTgxB3#H)4&?+@VWkTc)dl;qK|uv;WRI*cG2`6PiF z4+svy+Bfn&Fs57Jz6i!C(w$w@VWPAbRGak~oN>3vUg|Mmk0NpfURt0*DSJ_e*Gi8I zqshW4F}L&aS8x~4*#{4vOc`gKW99cx*L^69fgPj#?++q9LidItd}<@&#E{ZGz7g|c zFX$uKJ;Qv^NpN*e&EL;l@1br8j8oxO3e`g<911L_jr~Xb0)t$x$A~dFay9(}gt4&L zyb=1<`|)_7(!^xJ14xLBGKXO3`R^_;F01 zG70TiF<5(=pRsJYj!^XjLl_vFJOQPhN#Pkr#G0-m#xG>q)GAHjE4WFhe7Zi83;gte zdDv6+)qrgh3F0}$gPmtb9-Ff1m|xDD$6jX)Dcd5Ms-(@nKM_3)2+hfh6@Cs@-=%Z_ zIinf|ck6rN{EOadGmJ-rzvxZnAL)(mf108HL2v&m)%=a*?3CnX2ZfOQY?ha_11m@UzRqlkhrVbQ@0M(tSSTerx}IH@Dn2={w$iGqU#`v}PuV7I&A9JYNP%sqMn z1bTq*Ok{V>SlVH8H*4X-lO?VzaDQzAaLvc1tTL+To)YOuj^V8mQ?)K-FT(s_!ds-O zeb$rKRR-~g^+_aiGtH6kbJ)!K^ie;ipJ8e;>iy2}73i(1RY-~!(tk2zPj;pwB4k1a zVa~7lF^EE`UH=#eb**88zBH%!WkO0S?_Zu0KpRtXN+XMsAwfT56IZI}&cs+R5N~p3 zlQH7o$(zsQQBPIRmD)i>TfdcgCSKbVVD;VCmO3l1VNbV&rWc9o>Pk>ex!)Nap%NtP z&kKIFMm@k9-HeXj2$((SmG+a-dXvl7q(7n=8)cELHf!@Le+X)=++(}pKC*dcns?>G zVa*fV{2FDIJNaK_jq)WE9MvxiTm6sI%YUn|S=oP0Z`vE#GMZa`4V5byxmv0@8@Zb~ zyBOJuTAG>Im^uIL@!ZrWJy6xL{%n;pEwY87Y^xYSfmmgRcgcEDfz4TJ#{;n|g>8(> zv$(RLnp4oD1Mj>H@ar|0RCy}E{GwvuKOf1FS}O&z-Q)MmCVEK{p~b2xFj@lTn}#s4xg7h+r;n$TZDlT2AXAv z7R^$J?R|*xL^>7HI}e>7{HszA#Y_e8=~8*3zy_J$ejuhByeI0I!w-&%MW7Q-FGMKU z8qPm&IdU3w#^#`d%Vcn&q^w;EEr|w2F@ax^`R;a@p>l`U-T%~f&^`#zG}qdSV)A<0 z^*U=#=#o&gd{o+*s#j$xf+2y^t1Wj9_h}(DNi^aK#jI}z)v1rk-H)gocbgc`wB*?$ zfg~22r!^VEN+n>U8|3{Ebe#!9k|dF8lV*9c&9H~&g|$Ymc-2O^j9w$Q^I)ldd}5zv zQkBFDS2TxDn`p}-{-`br?tUCgyfr0Wbf3QeATbp=9sN|e90U^eVOu0~VT$1A5))@C zPcwzUn7bP^Gd~hLA@8EwiklMmlc^(;uPE%tLecC-iZ$_~jNJnZYn1A%r}=VE(-LG; znh6Q+b;zKz_N7)0SH7t~u#)e>Pr194w7xp;V&CpmJw5j6zBO%yB zjVf*iveYaWlrE~+p8YYym=-QmTd_F!`)ATishn6(oD}hTE2AqnVPF_os`ca^ET@@Z zoo~4YJASOBn<;8#(#3G>n1E)&@JA^3LV7mK^kaJ$((~ASWup3G(%#8O%xFX8XSiN~ zUF0&gDyT`FzIjtA`<-+9RXEKbwu%RtcrG!#-aoN0aj)i z(G|=#b_!z{o1}cIyw#n=j~Ac|NnR@<-CW$c%JFBFTi5JW0BX#4k2o2w{L0EglSN7E zFUcmFVF&U6NBA7!t`Lut>faDk>pW>Lz9BSzsqWvnI<+L#wg=zw+aeL6=70S773#Rq zG@fVM9=1ZibB`>L>hKz>rHG}`pX;dZD>I!_x~u>jsx3;0d$`Q%t7d<8^lkl8w0WZ3 z(HGiok6h^#G2EzIH}G*;!U8FW>@|C+wE+z{@e{wwWEkzUEiT0aDJo2JwZR{zcX$Bz ze2pzE&vKCc6@vE*GIv1LZ=qSg~HR)Jf|ljt#^m2hZF4z|32*7{hd|u`C7{C zjG>}`{SC3Dnc~5%D4yBa!V@}xSBtQ$ZWY^qs3)9jTuIXYMgPF5E0*&A0B(=JEntcVgC%ZO4UKHyuzuSblKNHWJ}OzVpeS z?8|{P8FtkJ=~%YMf1h*@o-YsZkLVQU!43cY~nWEmBt#&Ar%7WClZK8 zSe-!M)B8((tj^wSIm3?e5oe&mQs6BAE#Y7K*^boU^Z#aITL%-H zul5Gx*FKM}n~RnE*Ko3}nXrk8nTw0Ok-d?{|KMda<$n9cFHzkfb4wa&Dp0x>XjayP zg-KZ^Ayey*gb`NecHls@$a-2|Z!Xe^@P`uYYo`Q*jKzDQGPFf^GDQ5rd(-X3n)&f|bD>?`-DktKL<0hWK!cPS>L^@|VH6## zG*0#NtGfzpZpt+e{yL@K$|Lg*JfO%I+hp&kR;NxOJ+y2H49xZA7=^RKObPZi6 zL&R70!l_{PTFcxI#h+WsO^Y<`hE*z1vg9n7nG-6n0xBU8F8yDd}=?${Kl$qim3(S98@^W*vvSs{l zU}!oUIXap-i#nT`er(?avm4Q4-snuM&-cwu#-M{K8n;l1gP$ z3sw?`ls1z%eb%&mNBvLuEci8}-Q`|kUw6;F0-pHb?+A)+BLSn7_@my}6u%J=Ub~(* zU1n~wcfO|73IBZF;|Bhy$0FeO^>lmmZz?ZuZC8$p6<>B{Lsp-*mS05IVU00ergKWv z(LIsLS=?(>QLLQQ?bdTpyO?iiEL`;>(XJw^lA*7FCd|$g@c3VRy#tUf-Lfs*_HNs@ zZQC|>+qT`k+qP}nwz1o`ZNC1_y*J{2=fCentcZ%LwW?M`<;L&dcdwa@4GT@LCkltq=Xfy+OasOLT!lXrqy` zEW9YuDcfQtJ$oJ|Ln|b|q*_a|YPgCbBBfQ|5;-1(P3R`sK~3T`TtVV6yrtDbioJKI zPDV1BAaj#O~V^ll>$# zNC?nv_r5RiH^A2t<)qzcvns9Qd$_UU$`jN;KUSNqMCQiCFCi3A$*D#(v=FXCqz$SB zyC8vjHyJhMy$5kCi}FBy0NdSCJa6{q(|*9I^zwX1NHX*dHOIDB8bsI3_{(*-kkQV@ng|lWd*nWx!(xQ1stGMcRDjH=YUQvY2^uCZuO%-0Jw5az*F1nW_|h zR~z5DT4j&Z7527|#z9b}pmRW}p^|OrU(TWox^&Kn>YUn%%JlZJ^16vzy|O|GnZsf3 zSXEMjOhuYZlh*ikE0&zHt5va@6&GI{1&D+NPop@Tss&f!V4;}nqX@iOvdonoDa}J_ zE-u%qrrUpYVYSGU5NeXJr?#B#3dkObD8uk*U|u*zS;T2YgAk;_kdF0s4A6A*YGO4)#dKwYLQi+*i=C3N85d93 zAe#Lng7EX?@}-FPvIdp0y!`J@^1tg|IHwZ=C-i6LW7u!d>#==7<(?=6?caFCo;)AM zwwV6XHIU7}%D3 z75#&7SiVq=f6k4N*gy{?o~K9`+fsId8Co*62ksPHLm=SB>G)@44I(Fbs1stfE==|e z5WM)k7Hs~OwT#*$%<~0|BEb_6HV0F0=kYy;P zdAZbN(@{*9FL}4bSi-&#J^2;N`G{J?KFD@i^8BEXQq3$Q#~shvw_cx5r%ZlgHz2&Y z*cU<9UD1(G6qg=Yx{LRix``xh^Yi7@j|r7hm00t{(0ei78ZQbt`JV={$XlXvX91YH zxbI<;-YQG@9xrY>Ar~yWklR>hQ-X6TUxD-S!;~b9lu;Tu@f59S=euifnkTO2C*G;S z@TJZ5{$VG<^ThBbq_74=9q9r7DxC6VBngr@olJ}~W87-NEagn(;M*)7Oj2!(TG+}U zsLu!TV4B7DH{}gtanAHawLkpH5_$jk$0~;0`rM1Hjkl;4D-KsjXTl<*z|E`_8Nlb6 zroi&vNu(socja8wZ}9J>;D}esqgs4BR?_u7ZyELz2k%GQjtG%Vx+yeS&QI*AK1Q~e z;1-8)WjT?WqB>et(n%42u5UPI+!F^B7Hx#oW{i;??}{9#vpvk}lwvHPB$=-+pnIAL zGBd3sTO%TRGFw?`Nh>DzU#VeO7C?`w!-QT4ZgBE!WsS1clJ&i=m$ zHn^;?BNx^_wESMCsSKfxi542WFvUJUh%GpT-JP-b+D|wh`H$h4?*AT6uKyK)=>%&^oOXr5Al10+ld z9x<66pEk?hlV|$s!otJ~_Kz3DcB~XFzWq<@HMwvNFc2}VQuS$6g{U$+nN4G0`E zua0)-H1D8k;mm6E{(!pNomCz*qxv$pI3NvG>(+Q4AcJvK#K8 zb9SOKS@GC!pN|JW#<}*37GFj>D1wi~_)k#-N5izNy0%(q7hMm?oL_Ju8jMFGA9bKb zv$!gbC9lC0>Unx?+*3GF(6ZZH<(4j|5-Om02Y2z2IG_&xn+2Z`6;N1An(~^lQwwUQ zOiKj)?fuj7EGlb8nv@wDs4us&o=Bt%l*TAhB{h=R+Pddpm83-ms{V0T&ofYt=D7dS=Kr=V{~wzR|1=j_+3Fh+3mcp0J6k#Z&$+yVt*OJ$s$BYK zRx!5u|IH#%N;9@dV#r@$o(;Dy3GBon{2-)SK+R!>`0yL(nq~lFeelQy_)_BZt2i}m z8rSXb0|MpaMQpG<_IaUCD@=+=`KtLmC}H1)-vV;8Y!fw&`K2B6oou$QOj%XL`Ye$dX*5~GV? zjoCc8{4m*B_lFn=K@#mp@(*Vga>;sjA3Ds|(a_aGGbuFi)9-z>)&hY^h=PM>jvvAt z$Q7Zfbr%lPeu2OFHW3uNyavs`ezAXnB`OuCGx+U1e%!gwF?S3T3XLaG+BzOfiLB-f zLsTI!R2nT{#3)Z+EHpqiKXE$CK-~2S!*Tvgi)l{*o7SZiuHQf&N=jK$gt6|+nF)`Gm z!Txq?dNfctW^}=z-436nDud8w974=Iuf~cqED93ykXqf1w8FZK9fiO>iyHhGH6`Xa zy99CYP)x3@)FSqPdVt-Br1$H%x6;EwpuBzZ?#_D^RUI0KPMzf^_Q2rPhK)0jFB8Xm zlV*;2seylEHqM|s4!E5>k-zx$17R0R2*LcwM(ea^%K>Rf92id$mc6SChy+Lhh?+zh zvO6({dx7GOFjsuW1#TIks9C3Y1NS^K;IL#Bmt5WRAnNcc>QhlO{Vj2vmon)s*asQd z33&IEDekAAXHibwHHW4Kjin6FB;UgbL))#+*%fRgjq!Uy)J$xt^A4P* z=wpGU$DPMXW)DL%DW!nu39E+G5tKB@YM$r#?rOf~PwEaIWOZ?-rZteokPGZsqWYS4;B z|0LjjIbp)2Q9#;HApIi0rAAv&MKYgXU3KhsoOYe|YT)zr{({<}EXL67@nFgE$g8n) zlwsHK7H3m?1l)9j7MVEeKIFU&$Urel=||l_I+%2%vpEWGJ4%Ae=4~9emV-GN((dey zu%{X&7)-JZ@$2L0Yqtni7;-H%fWs%8= z=kT2S6oOA<-_q!hTShh=6tYB`my{cf^+Lx>yzS~3hAy^=8Fn4^M9*a;F$7-pPb`5WTTi>BH<(hQt<2d>L}bEO@qeR~R5CV6M#}U~hOs$t?sI z7o&N-naKA!$TJ z>&^XTo(>zGjv|b*XTI$ut5?7&&KtRH*Xif1`>gBEp7*Joo(B{{&6%EYr?;2euFLC6 zyxINGDCvA&Z9Ke6+p?I9Q!BMcUI`b0h}(?yqWH@VsM zQOR!?^5j*fLK3_B=$34i3+r{u7IgD)M~W2q7y3L-307k;BupXtBuqlRxD3=-rhwa9 z?bS^@iS*Hnd^;p2cOp}nC~VDSN?;3$3z!yI^$)`1W?UAhtCjjqn>M&ph0;8EaiL{z zu|C4KQm1Ko&6~iXk*x&^ph_a+*qDsevtmcT;T0k>1Tvc@2_|YU#phijBjGm~(FAS> zlUlF>J!lV+cX^mbgNt|q+%c)}o#I2L8tL)BII4PpHABevx1oqq4Fk=enLf)lPJppehzt;iO9UQ2qK{ycJZ}25$Em8#QCj@IGeY)Ih;t1C_j5#Indn9> z?q%Mr*&t<`FGYDnXUw!Q9F(&(vc=j2NyA|}`{O%(aBk4&ic|F*CyG^zcJTh7Jbkku znj-MdZ0aPz3?=kXncCW=-<;dP;J9T1y-C;{aJj^)J(P2N6H-0wO?ZvS=U!GHKVCK< z=aWv?u%5>H&8MwXa49`eLmGW<%;nt}*#2=)K*`axE(dLvH|fGa6F34#8tRY?cr_y0 ze3Ys0rp;JgADiP65s|!r+v;Bhhv}`Vm{n>M24Hc%zOJ&UhG2A;(vSJbsM4>fU{u2_ z-6VIhEcV`qxROML_k8tmxBr)-{ z0Nki4Ka!>@`U^UZ)eJ*+dVEKh%hU52puWKbEG44AD>zWsBPQobQCa)OTlz41wS`U5 zA(_e!#MIkQ_D?<^L@2G~TpSiQGc{2i*D?M}9=ed6<%52)rPN_&_Zz}kJyQ*xrss+n z+*}R)Uzw_8MN}8>Nin$jkrHrz;R3n*HT*JD&M9fIRS?wRHq#A#i(f4q5+z;_5Ij)k z55fi>(u^$A=GCiS!o_k6hWVWf;@9>(C^LB-^lw%JYn+7v`}UC04jw=#dbI?>PxGb< z^hYM;a|^$Xv8HwRyEFBlC0EGDeVFD zsI=F15ChE=aHP6tL~Ao9#WHh`H@ZcicgWiJi5Wg12JkaFg6%fLuw^#2^+FGSBYJC) zcLQaBfXhJJeIf<*h>U>kVP9*cRCfKc<$@qO~wd*)<>-)SK6P zJ@I^4#us1Hf$yt#&=?VaIkhDY^^W;!&OFd#L5S3wEK(42b#OVRSI3Yn=DLC>djb3m zOx*FMX7ymI4;B56>=L7Cv?Opmx_j#kUAIX{b-S2c8Z$v=gOMvo?-ij^Qg7+-IsiMdRFM)v7G{O9O zb{zD!lmDA*H)}70ZFQ4xTkLM$F*jknM@CK!9fA;1rEyA1T;kT|rRhl7MQ@3Z8K3<$ zthbXo^c6w1sy3usEhrD|+wtJ{DqW>!SzzMAYG&n5P_48!FI7^!mt^UsJ=Ii%VFz|f zC`{_0n8zVxPB%8P&U9wpG3=awF3lq(pY)ZY+X0iPX>u?nXvOVKqHlZ!kPr!p?==9sB_~DS`Wz) z-C{l?ZU7>v`xhem*b=STWhZXwe7a@WUN>CeYu(sj2^yMe+X__p(O0XKfx z%AXEQxVFsfTzy)ozm#eCQhr*;4iF$jVCn@40VgXeH%1E z29UQ3y$aVZ3TOp-E~*g`Gz^slv`Lf|RO$MFBa@P)tKRuI=cc?XxIqzmXgmw~OWv_3 z79M~sk*g{jtNxD4ShkFGO@d3`N{)-(L`+B$P3o{T)|L%BE`c71nj=koezdtBY4~a%t^5r3-m!3Kj%V`9dB?v%w?BxOI$&~!jUNWa z@o8Q~I6n%f3*aDLLYK<|4FU2X@*``7jnlDRq5+VebLwb4vJVL_1XDYFTUc;$dW3relP0}p?81NZ&{!uRJU{&9)O%uEL4Mkts~ z&T=;)Kjl_c^Tc3YX*8y9Lb`*cpyU^wFHkn{Z--k1SA~|n0bO2_YwyEVv91paW(>>D z5A?fn$`0!!94mEWTUFmE5+yocu&wZDj;aE3+jOFJ95*T%`pKWaqKNiaixt!T^#`@p zHlA$6Fj^5&7!Hb19 zHyE9zQWe<12XmH)8IDIOtwPeM zHRd&LKn-qMRQRtyy5LYzR9#*8JDBD2K-E^^INa=#S{XA+rW5XKtg>7Nn^Of&Vhir! z+P>KycTUF|e~Hw_vAX%ap<+u9o9)jcAVaw~|4zkmS zZa8>nl~i|D8zjQ^%<{;ZR6cbVD>%?nlBzUD&(9h}VOpBkVW!AuVW!MGuz;OfTWE_| z{yi!0mE#74$DH%4$iv357s-5PS(g3aXJUS?=I-+Jz4Y{Czu2{VMepL1!wV0l8b0k) zSH~&|HJ~YYm{WKY&gKO*WNzB=l|JE3C?T`VIh$Fi$wHFx68QWYRy%ziF%z4Zc<{>B zjkGSyv*i{+F*O@tKQ!EDM%7xw!z{Yx)~Woo$kr{Z7+t7ve;X$MoE{R-LVe22TZY;% zOIFYRqSw}4;Mcno^z?O*G8Q`&wbgNV%>E*DX{fnqK*lP#K0dvcU3endLW%GugLOH< z>Y{oG#ECe$UPvO#$t@?@GA5JFE*6oY@?+$jRxnx(BiZ8q{AuRkwymR+;{*D6-bh*) z-5@PC8lo`?K**Ec9*n$U>OJRjK0H$J@vnMoQZa4ti zMegzJ2oft=1Y+aEG$4JE9{t_I{tH*SwKVixk$IyL|hvQq*qu&_4C6X zp>36)v+qAXl|OfXL8koN-RrhNjjA36)N;pjmTkOO>jg}c>35j<2gH)fb7QYv#8VV2-AXJ1-O{Vpi$uIz3lMp3dl`?Wwpp>|6_$}|ROmbQ- z+O3VID2pdMNR%dc(_#%+-P-%bNIb5Irk&d>rOY(_mq8%P;dkWuH0mR4vhl=r?rV5g z%=n2Yz2%@f5#I6!(KxF>D%1-3IyJU|VW-!(l$}cWBQtobb>#9D+>HlD>@kp+qgiCj zU_Y+2nP+9m^gw~vIRygs?R~aXBZ*Vk8cFZj_&b8(pTaY{Y}cTT z*fRuKeL3=89rk16#2TNQ%KL}Ryx)%5M0MHy=A(uL9M*f_;^wBL-FO~J+@|(7I)GQF zGxu8y$fzRDE)xoI0MCR3S^FKd3Mzir$&35HZu)9V$~5*Kk^r{%vt!7ISD#%fswRS1 z7x8ugQ&u(usOPXbN5Z5URhEFc|NLc;g}f4JzVjlUxu&$T#yH-Omy4s=$~b=B<)v}= z;R7RHY}oe#TExRVjM2_)jF*Q3%G{)3ZZqgSTa^}wnjk_InITrx)tW> zN_A5pLZ9CogVv`5^1_9Jm_n4I&Od-1kC6YSPp-Oxyt0!D zIplg&zC_?4NKvoQui_?BUY3EYOP5n0W0#hYf21a%4Fg1xeEs;w-CE2d_X6pd9A`2e zuiIRY)}Lqe0J(eXdpq{`UG}5w@h=I2qwDlnybY&n3-F)3(mWK*z~Y1=sqQ352UCF4 zQlI=T^y5Lp>gG~>1T94`()}Z4=w<|*zIWTL=+#(!PT$k6nPOoI-RVk#s?iWB=$tTc z;v`#9_oLoCy7W1j8Mn^hfr?}kDKcERb3jxH4>hafqve(?N%m6{o48;*Aj`VQb5)Ul zHK-31_Fm*+OH8EXSzh8{$7fljqN=ahTv<75(Rp-SR$Zz#EMGFOcXfT5%J^HHx8x@r zP2)nIWHes~>%OVy%4>O3(0{X?N*ukyQv5>kKb>M|32-D&p%1(V8j7s?3w|Lp63nOV z937ts^a~AioVI92W$?353}~XMK~{A}5JkKH5b=n9Ciq@IDBAB;Z!IUAV+ciiDvH*j zMD^3Dk+a${QM5$azio{#f^OHOx>LnJ+5kbRm4^N`5ii4(4>XD|b?3s1jrWv1Z}MFy zT9v+!?Ds9SiLUpcRnr?JG+C=^SKkC=BwXt~F8Tyir)=)czcAl$Z)2R5pR!H;e=OVl z8*}D=$~ONscK(|=^G~^sSitaqkw<2U?vov$hY7)fa=I8~62|7IuK10w(qZq9BnSjK zt$S9yI^QU{77(-&cteiu27n8-8*tNC&-dMPS#upD2hi$Q=J$O0#Os?xwTN{WtSzZC zp0+5nsTrDO-C3RykP7Y)6z8U{uiQ@973Pg|STBrbPO4R4VU>jA3ZJD%OK)mD`u%Bq zjUA|-$B9L(11X}nY*naJ%@8ESe`WsFWU8vR= z2;2}9@)$?_zbc_riw26%Kg!e8Kd<=z-OEDxpIr0*^LqcyFQ+uzy_6rD_)MF*+Au)L zK+sV!gc8RX!}1A93BeHY86igj>{s@tCS@2Inb@Wg|3Ir$G(TxPHZ`*>y-_zsskEEv zlcqu`YL%;Yn6XuOyEIg6vQ;HLymz>grb&Gw(*q#A5?6USh=@|D2=%(`I*cmsk7f^9^}}P? z?OW5EW$5ivagZURMyiQ!)dSTd0?Cq6Pu{r&OKRfiuu+&nj(M|bhppFk4ze_}sSz1;);PvKNiaE=q^G|5w^Vy2SN zBs0Xts91C^d0dq<=JmXesd8D;1K5UvF9?WTYl6d%lJqXxN`Pj}5LxPgSRE$%)Se9Nn;^;MLmXCiH$)23AiNRlj3 zB5S`@U11=y{xj(rqgS3zSUD^dhUILAwb|IZt>UN#gv=Rm63ig{MK*6HQPQQC{?1ODO*flB7}Q(AO3hFI}(g&O+0tS_v* zssss=fjAF6c7M%h{bJFcbm>-<=R>Xa4X{qGb3|a97zk+R8pO+p(k2^QM<;%(sz0y~ zRB?%#!Lct8vXEtAzqvF2#xo$NsieLB9TCSs^E_?X{@2BD7<@uv#vvJzQhJD^v3!dT zl|$vIA|g+p5nMz|Au5{UAyp|$2kfI)S~hhN0%yOnr(#(o-&bKg$Y+VeF{*sx3Du~N znZWwrE{QHx{GA?2J*uLTQ+AKA)Nbt+N2AXvftlF`pev3SOJ$4`MSDf=HiGkA5i0UO zd~$T7PLbVXMt2^U57wmD5}@X1U>&QO#B&jZ0J18_+exP+Z@5Me9xd0Jbq&L^e7(>X zNNZ(5fx4(0i?cEE=!j+2!b@EfJXIo&j};GwfS*019h#N=Yt|*|0J4`!D5 zN_q7;3^d-)FNmK&7&H^rwGK+yh}q{Hpt?|PFC?Fm#mlG5xknmlrQ>IgB05c3KF~=a zh6K*nAvP~CiOXlXY$wlxYQ8_)WN;>NeiQS5Mb-&Nuox?GER-8$-`li(QhmzUy}Keq zW@+_RPM`C|bx|r{2{VLpv4kQKehI>QOprT%3zknCxVb_F`5u!3W#trOn>06Z6D*XH z=M)M2!jWK4RGLfuttE%E2P@F6hVZljI&jmjn43^ zPJ~{D)br75_H1XB8(ej-Emk3-$#Qk8x9>hEB<9vjxJQ=EG&)&*v=3TD&pvVnxeR-) z?Lb+YlOky39f%jYERz8;%h7@zQH?O%8>!r^nUZ(>IPqq+lbCHA8Ax24#IZ@dwzGe_ zNr{+ocSoD-L2*Xdg%@t^OiJbgq#@1W&4(>T_SLJKpM5HrJSQaRRfbG&uyI9+T~>My zyWR{C12~~%bhg$$vJk%xRx<*^v~v)B^3%hV33i~-tUvA5Sfb|5i=rmc9n>)2!GqKa z^P&<_F>DtK$|77CJ5xuKX-Q%!OtxP3n%EsDQrn82M%6F*?l55XtzSVcMPQG0ZuQjl zmq*Ic&aackwk$S6PqbQ!TT;VJDSX~x&h0RoXfrD8&a{@qUZfVn6$ilU9V(GVzCpk^ zP$Zf;Ui%dnVGK2;ueF6kZ zFhW{mY7j^Tftei%owFtP`AO&4M?tOT( z;Htw$hS6rDA9#f<0l{2DA~U)NOfScqg!^m^q#5Caibizsnh)JfGIIAiSiC=S%J|_X-AWeS|ich7A5v3!>zaS0qG@+}6 zF+61ADkXR}zFbZ1mX?PdOp=@C9DI^|;2Tz^0qedK3>_4z?WYMY85qL(rt=Zq14q`G zmX)L~hGa0K_F1zeK5O`YjYkt&x-#C=rX%}-v%xC}Z95zssU#Mk{YR8Je z@U4Wha=tl!xo6aPg=VsfWT-Uw*s!bATd!Jrcam6JES#?b>09?3j3HtW9zjdZo{@vm z;Qsw!K~TU*LK!uvRJbS;OkNH2Wt%Y^x3I4&v!zodO!!r6#`%hm7yl~tBXG|sE%(t= zztYj^vC$ivB^+7S$l7s@do8-L_omu&g;hi4Q7^#p%DB);DAqKLC_yf{M--fbVCW4Q zpLSAJpyR=Jw|FpZ7!OY9&`o&H;FE5C-006%H7z?V^+c?EUl19l4m+%pxM%W-d$e~- zt(|&Ex@CFK^ihfbnmM|@OUuO+x=YOaa6Up`MZSv=z+ zj&v;Xfs>|(JoZyyf*n#2H&qEvkEBqz1th01TIY?cy1siJEZd%upf04|88q_e^UcqIJI$qO^tX{0Q=;ytn*d0;d>W zpbMg2hvsXQ_P18QOkwPq?4dM+V|(uRBPZ<<$bpw08v0vS$9$VUpbm=Fv(IMqMe~ij zM>0rOq>iZMoC}d%y?jB;97(AMLyv&6Zzi(5LIvB?<#Ywf0)mZ_~Rdangdl z&@8jcCHuwoEo63_;{rqY2HFx=n@YZylX9a} zl&P9Yv{)Lgc|b3Q1o2l|SANshLidoYfmF5?I`bsF`E$9kGP};}K?$qva#L^~CH` z!TFGfb4WF(Bq_ENC#V_OREgx>tR!Qa(Jg2?b%7g;M5AE-&>&(JHfZkcmN2s4eJeN!nCrcl9Way`gTk=o|nGo|BD1pGHLvB0ih$H-WM^@K##RBrgEQ`4$CSNzg z8QjInTy|bpvXE2PqeM9*$mGvZ!Ps7Fn?$@*V_0OIlsGq$7xq#m0A&oC)8WX5OB{I{& z&m4D92ULj=J&5P>4A>lRn(KPS@|aiq-&TfHnOC`uYpkgbZ!za!sgrKX&HmC&DR$Qw znLUwmqe#(ab!;OBsne)NG--Cm>qV#<+25uf(vCyt?AGIMoJse#4t}n3bFn42(girok)X zsLlF0m3f3uPV@^VjN3J zs7vW$dREOUH=t;vnxK-_6qp*ejG&zM*m*>v9wu&xniWe@+eJ-67VZtoVET-b0X5{6 zr(c*Y=7z@KB`=B#zMR8)M_(&sn@t?LtNkyD`lrk0nJapT+`Ued`PVEyOY{v7f2Alh zxP{mY>C3kmqt~@Sx9=weAH3PUD&9e;-4Z?DM%u2JrA~7?nOo3Fg!@?ilHRb~Q9Vh0 zS~k)vttP$Xy9A>{?$-j{oKIM^!~^qOk9nFfO9U;uX<{Z}MGPU&T0}pPw4d7EHF*^c z(1Qo888T#p5hW(|Q-(yg#r6vVzhg0gpd>56bb9oH0wu}%3M)p2fxFLEy>QG4R_-h8 zU+Al?!eBv?3%sHzLA?4>j0E@%7$S|RYf_S$ylY+ z4n%*ot_mG#p83HvVERPUjJRH!Ay-9T%yQe2biJr+b%|?XeE(`??bZyWEqp{h5`F<$ z|26&q>X&o$0crC>TI-zNN~}*w7-kFnefLs z2fQs{{%-wM-9ryBgJ*Iuv&{5yuKy+Eoc^si>??Jju|gyAn_Uf`ajXB1%g`EBtwiQ1 zx^awk%lc*V?-yf2mx&<2oHk?3d{TaxpMu&Sc>d+t2h>+*DNg;iw%P+Pbq56MHt1{8 zuC!j;1YlpBL2hXi-rks7|L=db0Mz7?nWiEF08stMZRP$Sn6!kAqm#as74d%`|J5u1 zZ`hY{-1iNNl z1=2bj@r1^~3~TeQTAAId%fY2ha|!FRU6VMpiAkkk@VViqVwhBxz8SBI0v70InyyD6 z3Bn|Jj3nVomoatTh{xa7jx;yvi_UnW_#l*M<|9E)rOc4j#iVycL>cKHTtp3#k-nKL z+7?|mS#aSINetxl?nE8)%Zyk>!C1k`<{`huyPwZD2`YbK4!99|Okznl56^r1}88nU&cpyn*~f zRP2FGaX0@#FpvKuii!WfqnQ6~#DBA2l_uoxjK6W&?wmdns)%IKg2?m;9KE4d3H+J4 z{P-@21_oU4WQ76zv4`7rf2c8VBqkLlTWX8sn;VP7*r9$|Zvr<123Vyh&st-dNnOt) zxtL4AjW-w3bde9fPrdt&)f0toUJ2&UdD?Duy5Ap7dEF=0V82i93p+Kxkri{*^!QAa z`)V#?MO?Egc}EaN?0rV`N9>*U}noU~6E-WouZiR;Mgh z;i}OVBurvrDpRj7!i%ICbMj)VT&(w5JB7dEWs8$MSfbZaa1D^jw$rlh41JSI!*+g5 zc`HjldKt~dEdKiq-t`OW#SHiFi#h4kU3|pR`S;CF5SvpIp|Cl8#>|qEO zL6o_yj`uN0$wSqXQfj)_qWIKrnS3$j-u8y`GrF8k5xy*m3E_xC>4xG+3@28lsi2dl zG->G?bNPxG)$u+RlKOK*4722EnDvKFTfCP}MVn#i1AP7T_HVVXeMTs4JO zpT_!OPG@)cEQ+es9a7Q~8ZJxuwg`RN6PqI_ZGrR{=g#vc28nWQy+I8dcb5dFR^-u; z&&P%sTVJJ;F`R;9s*$hDbF31St>mkHWdp=P*}5fF!x?lQhPw$TMi}e=#xDm^PWJok zBklIX+F!cN8)z!@No~Er@9ywmEwj?-&7I}xh?Aw0SPtK(3EQ+5LHqwwu+}k1p;#vH zrvh`dw3QgL-4@kIQ!Av--?{@#~s8|+dQ;(;Mo#ndpY6spn{3TJBv8{Ee0%vgX2)N zCCV1=Y(p9TH+hpYR^mG9QF6nF>tHb9wDPpXRlL7F+QvVV*IK(W=+D|wiR-*I;elS7 zY`O=x^{a5b-2CDtug6c%+y!Jb>;Y$1|5k+KbP-$ndnLz+PK~0IJ6_kenCmP!NG!nT z0oX@l4sD#DBU$@kjnc{sh4baeOf!mqY{x0?+@X-P%tFTkGt+fK8Xnl}SW!g#bX7&^ z+2;eo?q}&im*rirs}E*eubvzp8ZZ##(eDL0O^$sfaX!0;rmj^d#vG<0v5$vbadqkM z;c@S>jXq)Rz%lvuo_XtEk0U!0-X%0LG%_Oo&y;sC!y!Vzbv!1e%gjo7+E(!P5CXQg zglw~&%zv|GAITU4^EUXYL*ba5L|+fG{n2f#<$P`;XXQzw!rFG>1xIQtjYXPCx$0Tg z_y1H9*k8*NMu;cG(T9I5k|_z+!6-KvLctWLG?awCF`Wto6>5{_B*kX_J!#TlRfW|Q zTxT2;H#0}=YR;55U1N;$dTp5H%;k}GCmbbyfA00QK5!SnK;wWT_=y7G3YX(F_2ej zekKG-;-FFYlnsInfBS-ue-l(=JyzlnCV;dv+bFa!pd>$1xZyr37BgGGzr|0+^O~0j z15^}t&e-E6dU|#)QNVmuka5beLq1^$=n5hx6Mg@fLV!rjf(f07zjUyE!{MRr^$O81 z9c&-SdtEZ{pn(T}h6ZnUS7wPMBn?d!5HMe!BHRBbb05=@24O?2h_`+1 zSkky=Y6p<;hK&MFs_UV3Pi4-ZFlQ5qOdAaJ4>=1O04Q<~*!bCF?FPS~o{er4?b z@BAktYAQF=_~SF#TF%vAsN~HdgBetV+7Sn}tl<@KS7SOg0f&fC(;da%oL1YWSL+*m zGM#5P_te#*^#`lcd2E#Bzrd<*Ozyihcs6GM{UIN@;iOnS-MRs~qr?3IfIIow<-ibm z1axfeXk3WdOtrvL9~RrkL@RPE27Wm{vO5xg=Y{Si6xRMyB}nHWVL(7VUs(tiyCf+=eFX z^v*e{k1Tj6MkZdZ0LiaYY^zFpCUo+Dxx=bBlNeU*IS#VeeOAzI)Vt^$zh$j^EZMHM z**h+Kz~xZ6N@mz-#ETTbxO`K|Nr-N;@=2jQ#7ZgkFx(W;GWygjB|Jx@jU+qS`t!IrL_@Mh#X_TZx%@ z^4p_*L+-*ol_Bw(5gpCY^}j0qLkVl4eKqJivQEuSwK~_wQU=a?(Pr}B&EB% zySux)K|s1&x?55}O1is2>5>k~O$h(?yyyFj*W>Z~9|mI&_Fz2Mnsd!nbFSyU4NmP* zk_r34gxePNOJ$h6cykvyCw$qW0>}3|r&9U*AFcQWu@^Z90;YM#zVCO^+rx zNH@pXoqevqr|SqP@$wvXr8J@&d_JP>=uXmMSW8G@sN0shx}NXhJ^U;k3^P3*Y9*{X zT_){Q>`WUL%w79gi?=u4Dq=QB^rnC>Qexc!1mCKET58qi_4>ylhJterN@VVP&{9R} zf`VGjgzL=<92XlYXsi4V{!C1%tpasaKFas6LJV)K-=vfm;P_v(pq!FX4Y?&YsVKhO zR%%faHzRDbQ!M3E;64T2WnRzcuczPxKYjJ4E?oK+r6|}!&xa}zY4)CB2A?|sZ9Z0a z|7}5bo3I!eu5axh5J}j*49lzaa_Zc8rw3g>pdb(cSDK@($H8DyJ~4-_*`cwZ$s? ze5h6-?o%Yb`5-tXa|0?FF6Y2tk6?PhbB~VSfa6cTW01)6;9^4dE+jka44m<(+qOx| zS7+%A4{cV1vYAlL_6DE@7TAVxXLfPEJy)0APHnPc=nL6sYxCkc(#=FY#J=VU)@bgA z0_~_L;7&Dz1PtGWxfn&<4}Ma94p>_udw=f*7k4kv58VQ0lC!J^kehlmGtWV4Mi6UiYHz1L*lE`k@;g5_yK$-= zZtu<-NFGqxlm4JpB#T7g%Ex-iNmQO!&y7g$cHfwbO|=&7md}4l4Mn9|n24rEQ^>Ux zYO+gTedMAD(2~_1Q6k*FOpy38A*yn7gLcbXj?+s+U;2tl$BG4xn$@hHmfNzSfuA*V zDR8OI{FbT?yi6r34Q}@hSTAGKo2ggB19-#DmV2x|Zadz2|rHCQV8f=qYq3S-XQKr)V!L{fbjC(JB{i1oZ ziF#JsGKmxT>@0|5a3}*}b2#dWUIr!i`8n>4;r7E*)&qvB!SvEbZkC%_T$i>HF_iTK znSw(apn9nYdcK)KaXd!E__$?es}T}>(H*ztldjGo3~FxJOQHIwDEbA;V7L2u0y+iR zI z`Ta|+1SVzj1fro-ACvhOxw!`lkeVnt+5zUv+2Q>l6W3DEHS!?GkLeUc=jF=*DYi;4 zgAmXvqwtL98S&@oBP*(OL2;6Q!{jJ!x!SIzc(UKP=n25KVnzea3MJKb=3u8Cm>iLlc zo>?@$-95+WQf~)EAZt_5R=Kx&-+eesXf5(h%iWVsgV-k<5sR4Bt?SzA!_Si!Vs17{ z{6tvfF)5Sptk|88Zta~Yi^wNgFB3D>72<4rA$j}O^elvaJgTjo4ShF~YmiNpHeGbr zyKXGp)-!&Ibd!z^zbI+4QbF?)fGbwcwDyLFza9Z}=ghoEC1>_-5DRf*_-4`0`D_3% z-j$9^NUELnMfu|?&hgFGHu3n@;Oi!chfyGFC1tj zysM2L<;pVB&eZILeivP-DG6^E!_0P@Pv$*0)yMcNP8S ztipdgy#t~iDVyOeruzZb?;xzt0NZ53utk9^3ZvN}(iFQco`XI5+!2~Bt*g7s$UI9V zqTk}E=N|5KTZK~u!6+3ngR++0rc2UcL~b2^1ySOpH^5EkBa;19dk^IoLT_D(^eYV? zh)u!~KjQmm97L8GO!T6q$6zM-+4)P@I(QCal||#8B$YWzh+EnD6~{;lGD;KM(2Z~x zbfm^>#(c>3<`9QS(Mb$0_NoT37Om8`p*ft5u4+)-eY&scXqIdG8ph(=r%k3w~PVLOXd zvY%SJgzTUS)}20bSmIE#Ku2ArE#^+hFkz~5s)Jq}y~;DcyBxahE*PlD`+}A(u^rn<&8zczVDn%^A5dk-Vy_mr0qL*uM z+kH(G>dhnCDc>o`r?(AIs+^*rfe)ECTkV3CYD3Q#19fXQhe<>BD4P`WFJ{4fglrGp zMC#o(hLNzR_6BG%EOWFS0kBYlhLR^aX`ly0}L;y&ATq9Kgir+g(JSTR7eC^Kd70rtk@Qwh@u3M8?jc zvgkQ+ER2q@6iY?Es?2yUOPXy52HHmmw09OlCy8i1JSX$cFQ?Kz?WxLaD*;xXXdOZ= zBkjariS2=U=4{ztOD4WdLby%7@-N=%81G7r_onmAC}*~wh&dH`ElcXAaT1YCg!*3c zydPyIQxoLY1}B)t!AYV-sVm|=v@yqXQI~?W4Le?d1`+uZEGOQ|ee*VGf zrT|&74wW?}lFB{`V02N9RseY6=RHwR+vczuOFPU6KW$IutXl`cwNkIGa12qG zrJ%bP3TNk7J?}yS3x6XEWxoN1EKl;n-Jr)OR82@8A-lLcqJ0m!DhivFnJu)P!CIZozRj3Dupfu>UuxP6njtRWN0x(t)#GPjJ(W*QX;@KZebajIc;dm zCW~hL0jRsrD=aVq-P|3Oy{?-lW2lzd!ihrjVFr)oLbOS5oQOiE*S-!;?Lbx&bB@wB zIBCNkoH#5Y8I#5PlHx>EpLUEIfBnTV;pU3R%nfkZ z!YFhE-!>M@7lKEDX})s?nHWmd;*DDNM6GEm7PaY{ePtQ7vU*E6^Yo7t_xmKXg?pIw zLetbL($kGYR?TwDFJ{6?y@??DP->A;k*WI-u5h`r_Fj=a1?c8CaYv_fx+w3Y&sz)# z5l!Eerg8T>?FtY$ym)%@xf}a@V)bx@rCghzp-=;#(K|s@NOO*IZA)NzB23n8Oyp`N z6Y_)!pjq5GpOl;|9mspLVAjuk4Swf>dB>Z+oWGfksTiJHt6LL8{)`TN&}5mlo&S@f zn?k$j;4E88b8ms}U06xznINvR%znonws$*X0nXu~KR;D&0=; zq1MxLBj~1VFmZ3_rpJ&0B|edG0LL4z$TA%JtOE-~IHfCXompV+wy z8-&6rt-RaR;6BG2HZ5IoYkQ!W1K80!*5H1C5|T&@US7!VmLWU9nG%2IR0sf%g(q;p zir%R2#OCiM-FRbfu?u|_l)-Q7I{}F_K#B)nXF9wXSLm-9xO`&}clEL58GaMK6`1Uo zQKob~3zs=o{h-kD;27bhfCkdw{8=X?mD$rB(iIfJLV2z}Inma$btemM>{3VY_dH`c zRmH*W_;0{4Bi*0y!=kq3gCg}!KzsqQv(?<&2%Y|52_E_JZZE7axCF6;pWKz-h9;(1 zFEg|lBDp{TkLtU9pc8X{8!)$h;lT}wYiX`cFvH{sCC$IJ1nrkGsX1R-c54t zLc9jBHVaK(PZqQAK)*w|rQxaCi@4yDsR;BKp_0+QMY4^V@oQdty=y?g5jigp7$EqZ zjDUR~x@7qfAlguTFi<0JZx{E(?05$3ZrE!(`+7JwC(6-O)0zPfL-;9#k~GMZLtGy?nM#)>2+T`kNj ze-Cd%!Vd{3rx0cOIo+1L-plN7F!@)*0?vWum?{xsvwILKF<=UycOWzqNrt^1DAHo{ z&>l4+Ab^}}aY{#leq4;cq6#<-V$Ho7UKVZ81@Wh+CFOY)SxBEZUOMd5^n&4mJBI5y zhiL&%RP$EK=dU%dsx>v_%dKWSAnH{~OU>To6_twC8@+RTFwOV zjN#5sZh{G`WWFrn$+vV8xa_EdxGegTh$iG5fdf8|IkR2eF_u{^F!2%tv7EYty{ytY zfTzxF4)ngPoP_WTG|Fer08u&Q$%>o}_7yWw_VUke{^I-nDIPLL`#{~ep5)0hW*8ez z$=vvIc7ys0bTt^Z4cC$pSAr8jP+)*}S0n5;J4~41b{%cIM*fv_$1_a{7~CzEGF*%a zmo!~DyV(mH=a!>N6aTXY|l>8fd_G+w#(nF|q5jcLBA z13?#dl>PPCA}RNzqD6oVO(@OKym{I-Pa5JmLRwqW$FBiUBnL+P2)@~J(ec|s_sm!R2@$OKicGYN*2GqU(J&T z{Lqn)*=vxuAX1Gv0Dk!C`pCTtlDrGq_gKcHI?^jian>rS^UL?G0{-ilaNK#DTyw56 z{Mo5FbQ?Hew~5Kllovle5o!-n7?EA%~9 z%jQnBip8H@%a9KGo;gZW59-6s%P>_Y62@fk&z9tt_3vec<8wZNl}y-DPVJOG|Iin_ z626Fx(_8z21@R?Y6h3=m$wyZ(m0~u^gGm$C_>_E9bIWd}w}}Fi6`vO0&SEgSdVWB! z70oGSTwI5)%Dq)n3w0Upp_=|g;_;3OZw=}>WJUsdX*M=A4EsAwYD>0ZPrKc^Y`%(P zR4QJgyJNu4aNup&3279U6_ zdbsfLmw#jb+-(ai0SJf=$M4ESh--^XS307Zgwt`pJ8{}aNm%u@LRcdGx zw~H)F7#NIpX{7#kW5V(1H5 zz5AdL#5;!Xs~elu2h{fX{pR6_V=3+&^ruJ{iTx$`s^O_)RYD@?{ol+}(o43PDCFcy z>6@z&ig(9lnQ&Je#^YG*qG0nV5izc-nDi1Oya!vptC5L&xq!LbWas62!Jk9@Hgg$u zcf|NzytpAfC_?Eo)ZG&ywyD+)KyrtAk@F|5=o#Mda4t2W8yW1la)U@5zE9jn2t8L( zX81%5B2%>F4iIQQ*!=|^;t?PSN?@8gFwrSJ@S3$#y8xt&xUbuD-u=7}9#eLWR72-qTT@xu+BTcA6}iClYMq3D|3PS&w~_olnHK zbbUG}X3XIIUV2VpcbYSqR^lWK`E;G4pb|N_JYdhO-P9g;3Pq zx#XGZHE!5Xc?m~}&3$AbIXJZLI=xQV><&VT5CXbQ&*Kz10ue(bo$2A61QOcN*>`p;EOKRNXLPtn*{8w3F-Cleb(>;Dq;Q;C(4 zd?J7xq=(1C&}V+H(IjuWE!QWIPhSF^7YZk!fUfOIo+QzqwU^5k7P>3Y8U%-;?GA!O zHYcntF5ohIP^By2K2uO|W-gA~czK@O*61M(U{K*rXX`j+=FR!L5*bC z8%ZNoC}V;XL!Kpb>sP)JkSj_sf;rwMx2$<+g%bK77T7~8tSw-VD@GV=JA)2g5Hs@& zN(X^2sMAj;J;5fpbBvQ$s%Wr@mKo`t|+60qbQv%_fRc(1N8*2fDS zc~Y)?i3pyo`Y`?2GK=TmHMB1Sk?@)-KhzR}Oj=qWo(Ut-uUx}_lC%xNatZzBfmEBJ zSB2ILfPtS-VxP5RivoeD?|F1}MKFC}S2DXwe+>&i*)@^(pNc<0Ylm@t;ENoizkQkG z#jnpbKyf#qNVcsT*VPwT{GWW9AfDFmg(z^eN2;&JR3~wRYIg?8~`b z6w+Q}ETeZ#j>1Z?z5425VK$AnXI=J;)o?YW1AC@*n=7rc0xy8rmLo~Jcb!bgn3ceG zv1@S2g~rpP*}ia;hD~CRV%Kn2XA_Ux$o_4-22CZ*sM5r!eGy6Peeyw==5WHgAUBr! zfvRYibkq^Pj~pB0`BIi)Xx#xu3H)+%OM`sS+HY@3+2tFUh{#~*CgyA#2A6>lqfn z6S5O{6{Wk3D3`MS+HG^VfwulGBaN;h`#huNIg<4%zjQE;0edb^GBt_26eM9Eg~2<= z%x&8wNd;sz2J(b`T`Vn+b%GZu!pg_&@u44I_b|jc_M^Ast*GX% z~cER`C{E`DzN*%y4r>@ti4A$Le2~6EEK|BE&%nFopIQQ zN!-D9pX<=ija}?3M}Wur)SnR4!Q^=N{TZI>K-5OX+PuZ@ecEdP)O|3 z;Z49IgbEtgSJg(*(Aa^$Aoi=5ZV6^_E4HzP)mn?bbRzqSk-Q@}P! zU^@l7uS{R0FQ1#*uh%#!jP+VDBI7|deK+xz-o;cMwsFQa_N6oU`m|HL^uTLD=QXI? zqFiDND9*>fT!W9Zuh{5;R})jH-(6Au;dQ~kD`bIM)20??E{+DjC_(m7K9a=~L+3%m zmtNX7LSUw(wb78YdD4gQYKDwb0w6BK=Xyc%RRPAvWSvJs>w0h2R385!%w)PxhWr&M01bMie zx>a1ez2u_4;Q$qR#^a%(z`bD;W}PcbW;gZp$;XJ(jj16;20aY3xp5(V_)^EWM`}Gr zK#ADYB0DVWY&9JP_oH)FDL~K(Y0HNT%jo5+7MAC6`q*B*BqP)IfOA zSs1}p4ht#5?g87B?XYTl`HxLvWh($kg4e|Fz2Zvohr;hXR?n)(=s&V%ugp%$J_YTVFooJk<#&j9b704}aM+b!QM* zY2B{6NUDF@2GpzM?B-{6Ghg#rk|qw*Qr=FO%CA^HN`cxwni?*?^I8;o%^2I|#b!@H z!~kFZVrVLm*xR}zG$0!nJB)j{!+gufR3EieNl0$mvb9e%%PXc-huMH^XTw*p?1 zYyBDhW(uaF%N2hMyCTWakzvUi@hY_+R8p{u`b*vcrP^U z_*g|+yWK|d2olI`sQ^ThBwo*25*7;P@yH3tB(f9HU$-isz0RnuWHIEzUyNIb?n@Re zv$Du(b|ul3b3Fq0U>?6%DxBrqHZ@M!(Q9Sr<$XXSD&RZR=lmi8#WaVOpR03FJ!gJX7}xq)vi!L65L~h`COI7w7PQN!xMG^TmKZsOTAK%u z#7EYSymBa>Y&`4@Ffm&lxog|JGhG>BPx$u;Ig zhanra)@5TBV{@8(le)od=MZScTHK2=8cikHIuNW>^0PQLiQ-@U95r?P0sc?spnX8XB-Fwp8ZN9nk*gQNY==j2)0kCP> zDS3wH9LV%ani_3bU2|xy#zAU$rwL<`uAe~6y>{(&G8kQVUiZh>m`rur~bZ0XVL~QQ(q<_ClM)5o8+`+95hA?X0lOj&2f6?i%}xEm~y3R zZA1w3h^*;MJ*GFdRrP9o(a}EeSy$0MRB1H>ND#EI?o(ILX|D1yXsML7Jz;PiQelZ+ zp!i9t0BZQ}Y0c!zH|4A21GdDR7i)Cpg{XY}^=@lm1vWb9>y^p4F^Fj{5|XH~U(`1y zf0U&kUb4c0uQ(#`!MNRwE;%*DP}`saRhM}Q@8)WSInEKkDq_N)ih@A^4cDIuzpTR1 zg1^TRqQx;vVRq~}7XnA(a3&`_p-X}Rp+M!R82&a9yRuU2)qbcH!*(OuBG-ZxL$7^3 zk&b$I^~5I@OdQRRR`nvwa|Z8Ax*#R#RSH|9#$u7?>1oDhG*RHFDlwSr4bi&61QLwz zDLzl|vh{cbR+{+2Riced&uLkYy9`dK_ScE8u`N&ueqg2cUruA%=)P)#35CF58vwV> zIFPBlmMmvWShXzwjAC;X9Q9dnE`&F@@U8Utn=nx1ySEfLX(0((;LiiMhO*{o z332vyIVs;A+_1A?y(oW|?Fl2oUa(^_iON_+oYqiYgd}-iq2eyFl8e*2C7b|Q$7#)w zm1s2=sH^Fdv2u>d+BWU{?4KqFr-5CP>KbEH1xpYDVVij6M-c8AG=ym^@?d!I(P`9u z(W@77VDq{wy0<#R`)C@Tr;x*YPD61$^u=U&KnFrtLk+}c7XYQ}!}&%5t49-o8#I6j z8$BWc@|_PmISg)MZFq}`=(Tu&Y0*gn=!zUT%R6}HnzGC1I3zr#o#GHqMQG@>OzQj7okNAF z(psjhjkl6sE-6TI^GhnVg0K&Qnd~;28l$D{!$=pSZL9m)_hz5f__8{k;McQxsl7yL zoV4+ZL@DetHhsB+u&|Sr*#=j%+t!eitu!F$RMK>tLL_&GeKR_!oe^eQ=FnS3U9fs4 zI?FrCXlH>RT``+eW}G!(+Yec7JR&Y?WJi( zmoa%r*|6?kWI2MyMWFR&UR94W?=gsTJxJ}_*g_YkdUWL!owBrj-lX=Hx;)8+BIbFr zftcCqOWQ7{96mH7cGBrD==xgg7+$j^gyKT_a)O9QZ?{T>TX!jrkd>J#Cm|;2;tO2| z=43{SY5NJhTQKQ*&oeNy$u#WO!de&b$r+usOzH|f+vA&o_9PCcYXVad((7s>b=O!Z zxvTY)LL%1i&SDV@+C7(o`!I)3_ln}{m?q?=Y~@fKh>zj!lY5>N_O3$Ml2U5KPx+(7 zN0LYrf4JaN?NRvbXSVht{+PCc8`(XyfG??_f2D8e;jKH>`WI|T!;WbjqP9zrm*ZR7KW`bM%aMZ4>;lijsSslVlc+pT}&WfxFuQSMv0}uM1%mqJA$7GWa z6pIIode$f6LrBHlm1tMmunGE`=P4W`HIGYvT#t8kYINF0AA{{c=jGrCMA7YO`<&7m znPRW=3T+R(iyAEZD5LAgt+0a^)JQ95Y} zArV<65fxQBr;(Bl?f2HlYs0 ziGdJ%;O|#epl^W;RG_kRG@~>7OHhi=$l8MLJ1b@ZM>7{2pdviba?Qm47dPlXx4gn5 zAS((u#k2^#&-gl#^es}6f5-WyC+g41pS(8g(F7)M06uYiwe0*BfoQ)={+9!*<1+zM zpe4zFKtG#={Y zWh|VWfPQ@cp#n$BpCHi$Fq3A1NJ*f0`j5@bc=iX#zgcbujwXNJ%$8|Sv|Ql8_W^R* zf9Tq6-~sy2ga7Yw^MCDC&}KYbVj#*CIDmc}rk9j|j8g*IG1;2^%l?~tkPAUa! zoPSK-#rj{#|LUpVSk(V~Fn@15{M8crTjX;6d-DGbxPRIH@BK7?9A#=eKOijruWrUa zH|Ben#;-;|-(p?xH>CfwTj$T*@7>LQyk=br|G@pFquD<@LjKJ8-uCLNSK7B=k^Fbg zA3CS~4E^4B>8qpGw|FJ}1N48^U;fBn>u1XM)-XTrI(OM$QvTNt=KtpC^fUK+i;S=aQLge^)V~~G-zzMBoxuDS=O(|*`v;1gKX3c@GJ`*k za60qfF#ev4`Df+EpE=)Gb$=Bt{1(v`f5!Qj&icO6_{Yu)@%|;?4@$*8G>EADkeqE{m7WL`BO#91q`=2-V`_;N1uP(+}zs&l(<<*~)e?RN~b;0jj z5a;|l`5!F*{S5hjw(!SY+EDOI$ls&#chmVlGroU@`a19UEsRQj$M}a?NO>s;-~$;5 R2np~f1o-$>Q}y+){|A@R9n$~+ literal 0 HcmV?d00001 diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/gradle/wrapper/gradle-wrapper.properties b/examples/architecture-validator/hexagonal-spring-field-injection/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a9db115 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-field-injection/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,9 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/gradlew b/examples/architecture-validator/hexagonal-spring-field-injection/gradlew new file mode 100755 index 0000000..249efbb --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-field-injection/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/gradlew.bat b/examples/architecture-validator/hexagonal-spring-field-injection/gradlew.bat new file mode 100644 index 0000000..a51ec4f --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-field-injection/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/settings.gradle b/examples/architecture-validator/hexagonal-spring-field-injection/settings.gradle new file mode 100644 index 0000000..920a471 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-field-injection/settings.gradle @@ -0,0 +1,19 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' + id 'de.fayard.refreshVersions' version '0.60.6' +} + +refreshVersions { + rejectVersionIf { + candidate.stabilityLevel.isLessStableThan(current.stabilityLevel) + } +} + +rootProject.name = 'architecture-validator-hexagonal-spring-field-injection' diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/persistence/OrderRepositoryAdapter.java b/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/persistence/OrderRepositoryAdapter.java new file mode 100644 index 0000000..0e780b9 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/persistence/OrderRepositoryAdapter.java @@ -0,0 +1,18 @@ +package com.arc_e_tect.example.spring.fieldinjection.adapter.persistence; + +import com.arc_e_tect.example.spring.fieldinjection.application.domain.Order; +import com.arc_e_tect.example.spring.fieldinjection.application.port.outbound.OrderPort; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +@Repository +public class OrderRepositoryAdapter implements OrderPort { + + @Autowired + private PersistenceGateway gateway; + + @Override + public void save(Order order) { + gateway.persist(order); + } +} diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/persistence/PersistenceGateway.java b/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/persistence/PersistenceGateway.java new file mode 100644 index 0000000..0c30ed1 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/persistence/PersistenceGateway.java @@ -0,0 +1,10 @@ +package com.arc_e_tect.example.spring.fieldinjection.adapter.persistence; + +import com.arc_e_tect.example.spring.fieldinjection.application.domain.Order; + +public class PersistenceGateway { + + public void persist(Order order) { + // Example collaborator used to demonstrate field injection. + } +} diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/web/OrderController.java b/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/web/OrderController.java new file mode 100644 index 0000000..8913fdc --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/web/OrderController.java @@ -0,0 +1,18 @@ +package com.arc_e_tect.example.spring.fieldinjection.adapter.web; + +import com.arc_e_tect.example.spring.fieldinjection.application.port.inbound.OrderUseCase; +import org.springframework.stereotype.Controller; + +@Controller +public class OrderController { + + private final OrderUseCase orderUseCase; + + public OrderController(OrderUseCase orderUseCase) { + this.orderUseCase = orderUseCase; + } + + public void submit(String id) { + orderUseCase.createOrder(id); + } +} diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/domain/Order.java b/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/domain/Order.java new file mode 100644 index 0000000..a3cffdf --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/domain/Order.java @@ -0,0 +1,4 @@ +package com.arc_e_tect.example.spring.fieldinjection.application.domain; + +public record Order(String id) { +} diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/port/inbound/OrderUseCase.java b/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/port/inbound/OrderUseCase.java new file mode 100644 index 0000000..b87d38c --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/port/inbound/OrderUseCase.java @@ -0,0 +1,6 @@ +package com.arc_e_tect.example.spring.fieldinjection.application.port.inbound; + +public interface OrderUseCase { + + void createOrder(String id); +} diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/port/outbound/OrderPort.java b/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/port/outbound/OrderPort.java new file mode 100644 index 0000000..c6c7aa4 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/port/outbound/OrderPort.java @@ -0,0 +1,8 @@ +package com.arc_e_tect.example.spring.fieldinjection.application.port.outbound; + +import com.arc_e_tect.example.spring.fieldinjection.application.domain.Order; + +public interface OrderPort { + + void save(Order order); +} diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/service/OrderService.java b/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/service/OrderService.java new file mode 100644 index 0000000..7171a3f --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-field-injection/src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/service/OrderService.java @@ -0,0 +1,19 @@ +package com.arc_e_tect.example.spring.fieldinjection.application.service; + +import com.arc_e_tect.example.spring.fieldinjection.application.domain.Order; +import com.arc_e_tect.example.spring.fieldinjection.application.port.inbound.OrderUseCase; +import com.arc_e_tect.example.spring.fieldinjection.application.port.outbound.OrderPort; + +public class OrderService implements OrderUseCase { + + private final OrderPort orderPort; + + public OrderService(OrderPort orderPort) { + this.orderPort = orderPort; + } + + @Override + public void createOrder(String id) { + orderPort.save(new Order(id)); + } +} diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/versions.properties b/examples/architecture-validator/hexagonal-spring-field-injection/versions.properties new file mode 100644 index 0000000..a07c552 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-field-injection/versions.properties @@ -0,0 +1,10 @@ +#### Dependencies and Plugin versions with their available updates. +#### Generated by `./gradlew refreshVersions` version 0.60.6 +#### +#### Don't manually edit or split the comments that start with four hashtags (####), +#### they will be overwritten by refreshVersions. +#### +#### suppress inspection "SpellCheckingInspection" for whole file +#### suppress inspection "UnusedProperty" for whole file +#### +#### NOTE: Some versions are filtered by the rejectVersionIf predicate. See the settings.gradle.kts file. diff --git a/examples/architecture-validator/hexagonal-spring-misconfigured/README.adoc b/examples/architecture-validator/hexagonal-spring-misconfigured/README.adoc new file mode 100644 index 0000000..959e7c5 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-misconfigured/README.adoc @@ -0,0 +1,44 @@ += Hexagonal Spring Misconfigured Example +:toc: left + +This example intentionally leaves architectureValidator.basePackage blank. +It demonstrates fail-fast validation from RulePackConfigurationTest. + +== Files and Purpose + +[cols="1,2"] +|=== +|File |Purpose + +|build.gradle +|Configures architectureValidator with a blank basePackage to trigger fail-fast misconfiguration behavior. + +|src/main/java/com/arc_e_tect/example/spring/misconfigured/SampleDomain.java +|Minimal placeholder class so the project compiles. +|=== + +== Run + +[source,bash] +---- +./gradlew build +./gradlew testArchitecture +---- + +== Expected violations + +The architecture suite fails because architectureValidator.basePackage is blank. +In this fixture, all 23 architecture tests fail loudly instead of passing vacuously. +The actionable failure is RulePackConfigurationTest.requiredArchitecturePropertiesShouldBeConfigured. +That message explicitly states that architectureValidator.basePackage is not set and shows how to configure it. +The remaining failures are incidental noise caused by the invalid import scope and reinforce that the project is misconfigured. + +== Reports + +The Gradle HTML report is under build/reports/architecture-validator/html/. +The XML report is under build/reports/architecture-validator/xml/. + +== Fix + +Set architectureValidator.basePackage to the project root package. +Keep inPorts, outPorts, and domainModel configured so the rules validate real classes. diff --git a/examples/architecture-validator/hexagonal-spring-misconfigured/build.gradle b/examples/architecture-validator/hexagonal-spring-misconfigured/build.gradle new file mode 100644 index 0000000..5b7ab89 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-misconfigured/build.gradle @@ -0,0 +1,45 @@ +plugins { + id 'jacoco' + id 'java' + alias(libs.plugins.architecture.validator.iff) +} + +group = 'com.arc-e-tect.example.spring' +version = '0.0.1' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation libs.spring.context.iff + testArchitectureImplementation libs.architecture.validator.hexagonal.spring.rules.iff +} + +architectureValidator { + basePackage = '' + useBuiltInHexagonalRulePack = false + +} + +tasks.named('jacocoTestReport') { + reports { + xml.required = true + html.required = true + } +} + +tasks.named('testArchitecture', Test) { + ignoreFailures = false +} + +tasks.named('check') { + dependsOn tasks.named('testArchitecture') +} diff --git a/examples/architecture-validator/hexagonal-spring-misconfigured/gradle/libs.versions.toml b/examples/architecture-validator/hexagonal-spring-misconfigured/gradle/libs.versions.toml new file mode 100644 index 0000000..58fd4a4 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-misconfigured/gradle/libs.versions.toml @@ -0,0 +1,16 @@ +## Generated by $ ./gradlew refreshVersionsCatalog + +[plugins] + +architecture-validator-iff = { id = "com.arc-e-tect.architecture-validator", version.ref = "architecture-validator-iff" } + +[versions] + +architecture-validator-iff = "0.0.1" +architecture-validator-hexagonal-spring-rules-iff = "1.0.0-PRERELEASE" +spring-context-iff = "7.0.8" + +[libraries] + +architecture-validator-hexagonal-spring-rules-iff = { module = "com.arc-e-tect.architecture-validator:architecture-validator-hexagonal-spring-rules", version.ref = "architecture-validator-hexagonal-spring-rules-iff" } +spring-context-iff = { module = "org.springframework:spring-context", version.ref = "spring-context-iff" } diff --git a/examples/architecture-validator/hexagonal-spring-misconfigured/gradle/wrapper/gradle-wrapper.jar b/examples/architecture-validator/hexagonal-spring-misconfigured/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..b1b8ef56b44f16b14dc800fa8103a6d89abb526f GIT binary patch literal 48462 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 R2np~f1o-$>Q}y+){|A@R9n$~+ literal 0 HcmV?d00001 diff --git a/examples/architecture-validator/hexagonal-spring-misconfigured/gradle/wrapper/gradle-wrapper.properties b/examples/architecture-validator/hexagonal-spring-misconfigured/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a9db115 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-misconfigured/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,9 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/architecture-validator/hexagonal-spring-misconfigured/gradlew b/examples/architecture-validator/hexagonal-spring-misconfigured/gradlew new file mode 100755 index 0000000..249efbb --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-misconfigured/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/architecture-validator/hexagonal-spring-misconfigured/gradlew.bat b/examples/architecture-validator/hexagonal-spring-misconfigured/gradlew.bat new file mode 100644 index 0000000..a51ec4f --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-misconfigured/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/examples/architecture-validator/hexagonal-spring-misconfigured/settings.gradle b/examples/architecture-validator/hexagonal-spring-misconfigured/settings.gradle new file mode 100644 index 0000000..027e85b --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-misconfigured/settings.gradle @@ -0,0 +1,19 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' + id 'de.fayard.refreshVersions' version '0.60.6' +} + +refreshVersions { + rejectVersionIf { + candidate.stabilityLevel.isLessStableThan(current.stabilityLevel) + } +} + +rootProject.name = 'architecture-validator-hexagonal-spring-misconfigured' diff --git a/examples/architecture-validator/hexagonal-spring-misconfigured/src/main/java/com/arc_e_tect/example/spring/misconfigured/SampleDomain.java b/examples/architecture-validator/hexagonal-spring-misconfigured/src/main/java/com/arc_e_tect/example/spring/misconfigured/SampleDomain.java new file mode 100644 index 0000000..b33ff1f --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-misconfigured/src/main/java/com/arc_e_tect/example/spring/misconfigured/SampleDomain.java @@ -0,0 +1,4 @@ +package com.arc_e_tect.example.spring.misconfigured; + +public record SampleDomain(String id) { +} diff --git a/examples/architecture-validator/hexagonal-spring-misconfigured/versions.properties b/examples/architecture-validator/hexagonal-spring-misconfigured/versions.properties new file mode 100644 index 0000000..a07c552 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-misconfigured/versions.properties @@ -0,0 +1,10 @@ +#### Dependencies and Plugin versions with their available updates. +#### Generated by `./gradlew refreshVersions` version 0.60.6 +#### +#### Don't manually edit or split the comments that start with four hashtags (####), +#### they will be overwritten by refreshVersions. +#### +#### suppress inspection "SpellCheckingInspection" for whole file +#### suppress inspection "UnusedProperty" for whole file +#### +#### NOTE: Some versions are filtered by the rejectVersionIf predicate. See the settings.gradle.kts file. diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/README.adoc b/examples/architecture-validator/hexagonal-spring-naming-conventions/README.adoc new file mode 100644 index 0000000..4964e84 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-naming-conventions/README.adoc @@ -0,0 +1,46 @@ += Hexagonal Spring Naming Conventions Example +:toc: left + +This example demonstrates opt-in naming convention rules from NamingConventionTest. +It intentionally uses non-compliant suffixes for in-ports, out-ports, and repository adapters. + +== Files and Purpose + +[cols="1,2"] +|=== +|File |Purpose + +|src/main/java/com/arc_e_tect/example/spring/namingconventions/application/port/inbound/OrderCommands.java +|Inbound port that intentionally does not end with UseCase or Port. + +|src/main/java/com/arc_e_tect/example/spring/namingconventions/application/port/outbound/OrderStore.java +|Outbound port that intentionally does not end with Port. + +|src/main/java/com/arc_e_tect/example/spring/namingconventions/adapter/persistence/OrderPersistence.java +|Repository adapter that intentionally does not end with Repository or Adapter. +|=== + +== Run + +[source,bash] +---- +./gradlew build +./gradlew testArchitecture +---- + +== Expected violations + +NamingConventionTest.inputPortsShouldHaveConsistentSuffix fails for OrderCommands. +NamingConventionTest.outputPortsShouldHaveConsistentSuffix fails for OrderStore. +NamingConventionTest.adaptersShouldHaveConsistentSuffix fails for OrderPersistence. + +== Reports + +The Gradle HTML report is under build/reports/architecture-validator/html/. +The XML report is under build/reports/architecture-validator/xml/. + +== Fix + +Rename OrderCommands to OrderUseCase or OrderCommandsUseCase. +Rename OrderStore to OrderPort. +Rename OrderPersistence to OrderRepositoryAdapter or OrderRepository. diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/build.gradle b/examples/architecture-validator/hexagonal-spring-naming-conventions/build.gradle new file mode 100644 index 0000000..af54c0f --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-naming-conventions/build.gradle @@ -0,0 +1,48 @@ +plugins { + id 'jacoco' + id 'java' + alias(libs.plugins.architecture.validator.iff) +} + +group = 'com.arc-e-tect.example.spring' +version = '0.0.1' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation libs.spring.context.iff + testArchitectureImplementation libs.architecture.validator.hexagonal.spring.rules.iff +} + +architectureValidator { + basePackage = 'com.arc_e_tect.example.spring.namingconventions' + hexagonalArchitecture { + namingConventionsEnabled = true + } + useBuiltInHexagonalRulePack = false + +} + +tasks.named('jacocoTestReport') { + reports { + xml.required = true + html.required = true + } +} + +tasks.named('testArchitecture', Test) { + ignoreFailures = false +} + +tasks.named('check') { + dependsOn tasks.named('testArchitecture') +} diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/gradle/libs.versions.toml b/examples/architecture-validator/hexagonal-spring-naming-conventions/gradle/libs.versions.toml new file mode 100644 index 0000000..58fd4a4 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-naming-conventions/gradle/libs.versions.toml @@ -0,0 +1,16 @@ +## Generated by $ ./gradlew refreshVersionsCatalog + +[plugins] + +architecture-validator-iff = { id = "com.arc-e-tect.architecture-validator", version.ref = "architecture-validator-iff" } + +[versions] + +architecture-validator-iff = "0.0.1" +architecture-validator-hexagonal-spring-rules-iff = "1.0.0-PRERELEASE" +spring-context-iff = "7.0.8" + +[libraries] + +architecture-validator-hexagonal-spring-rules-iff = { module = "com.arc-e-tect.architecture-validator:architecture-validator-hexagonal-spring-rules", version.ref = "architecture-validator-hexagonal-spring-rules-iff" } +spring-context-iff = { module = "org.springframework:spring-context", version.ref = "spring-context-iff" } diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/gradle/wrapper/gradle-wrapper.jar b/examples/architecture-validator/hexagonal-spring-naming-conventions/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..b1b8ef56b44f16b14dc800fa8103a6d89abb526f GIT binary patch literal 48462 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 R2np~f1o-$>Q}y+){|A@R9n$~+ literal 0 HcmV?d00001 diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/gradle/wrapper/gradle-wrapper.properties b/examples/architecture-validator/hexagonal-spring-naming-conventions/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a9db115 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-naming-conventions/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,9 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/gradlew b/examples/architecture-validator/hexagonal-spring-naming-conventions/gradlew new file mode 100755 index 0000000..249efbb --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-naming-conventions/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/gradlew.bat b/examples/architecture-validator/hexagonal-spring-naming-conventions/gradlew.bat new file mode 100644 index 0000000..a51ec4f --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-naming-conventions/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/settings.gradle b/examples/architecture-validator/hexagonal-spring-naming-conventions/settings.gradle new file mode 100644 index 0000000..9f35ba5 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-naming-conventions/settings.gradle @@ -0,0 +1,19 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' + id 'de.fayard.refreshVersions' version '0.60.6' +} + +refreshVersions { + rejectVersionIf { + candidate.stabilityLevel.isLessStableThan(current.stabilityLevel) + } +} + +rootProject.name = 'architecture-validator-hexagonal-spring-naming-conventions' diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/adapter/persistence/OrderPersistence.java b/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/adapter/persistence/OrderPersistence.java new file mode 100644 index 0000000..0f6c383 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/adapter/persistence/OrderPersistence.java @@ -0,0 +1,14 @@ +package com.arc_e_tect.example.spring.namingconventions.adapter.persistence; + +import com.arc_e_tect.example.spring.namingconventions.application.domain.Order; +import com.arc_e_tect.example.spring.namingconventions.application.port.outbound.OrderStore; +import org.springframework.stereotype.Repository; + +@Repository +public class OrderPersistence implements OrderStore { + + @Override + public void save(Order order) { + // Example persistence operation. + } +} diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/adapter/web/OrderController.java b/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/adapter/web/OrderController.java new file mode 100644 index 0000000..855b040 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/adapter/web/OrderController.java @@ -0,0 +1,18 @@ +package com.arc_e_tect.example.spring.namingconventions.adapter.web; + +import com.arc_e_tect.example.spring.namingconventions.application.port.inbound.OrderCommands; +import org.springframework.stereotype.Controller; + +@Controller +public class OrderController { + + private final OrderCommands orderCommands; + + public OrderController(OrderCommands orderCommands) { + this.orderCommands = orderCommands; + } + + public void submit(String id) { + orderCommands.createOrder(id); + } +} diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/domain/Order.java b/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/domain/Order.java new file mode 100644 index 0000000..e23af1a --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/domain/Order.java @@ -0,0 +1,4 @@ +package com.arc_e_tect.example.spring.namingconventions.application.domain; + +public record Order(String id) { +} diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/port/inbound/OrderCommands.java b/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/port/inbound/OrderCommands.java new file mode 100644 index 0000000..f29afa2 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/port/inbound/OrderCommands.java @@ -0,0 +1,6 @@ +package com.arc_e_tect.example.spring.namingconventions.application.port.inbound; + +public interface OrderCommands { + + void createOrder(String id); +} diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/port/outbound/OrderStore.java b/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/port/outbound/OrderStore.java new file mode 100644 index 0000000..b789cc6 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/port/outbound/OrderStore.java @@ -0,0 +1,8 @@ +package com.arc_e_tect.example.spring.namingconventions.application.port.outbound; + +import com.arc_e_tect.example.spring.namingconventions.application.domain.Order; + +public interface OrderStore { + + void save(Order order); +} diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/service/OrderService.java b/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/service/OrderService.java new file mode 100644 index 0000000..3af7833 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-naming-conventions/src/main/java/com/arc_e_tect/example/spring/namingconventions/application/service/OrderService.java @@ -0,0 +1,19 @@ +package com.arc_e_tect.example.spring.namingconventions.application.service; + +import com.arc_e_tect.example.spring.namingconventions.application.domain.Order; +import com.arc_e_tect.example.spring.namingconventions.application.port.inbound.OrderCommands; +import com.arc_e_tect.example.spring.namingconventions.application.port.outbound.OrderStore; + +public class OrderService implements OrderCommands { + + private final OrderStore orderStore; + + public OrderService(OrderStore orderStore) { + this.orderStore = orderStore; + } + + @Override + public void createOrder(String id) { + orderStore.save(new Order(id)); + } +} diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/versions.properties b/examples/architecture-validator/hexagonal-spring-naming-conventions/versions.properties new file mode 100644 index 0000000..a07c552 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-naming-conventions/versions.properties @@ -0,0 +1,10 @@ +#### Dependencies and Plugin versions with their available updates. +#### Generated by `./gradlew refreshVersions` version 0.60.6 +#### +#### Don't manually edit or split the comments that start with four hashtags (####), +#### they will be overwritten by refreshVersions. +#### +#### suppress inspection "SpellCheckingInspection" for whole file +#### suppress inspection "UnusedProperty" for whole file +#### +#### NOTE: Some versions are filtered by the rejectVersionIf predicate. See the settings.gradle.kts file. diff --git a/examples/architecture-validator/hexagonal-spring-rules-disabled/README.adoc b/examples/architecture-validator/hexagonal-spring-rules-disabled/README.adoc new file mode 100644 index 0000000..5ffb300 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-rules-disabled/README.adoc @@ -0,0 +1,45 @@ += Hexagonal Spring Rules Disabled Example +:toc: left + +This example demonstrates per-rule opt-out using architectureValidator.rulesDisabled. +It intentionally introduces two violations and disables one rule id. + +== Files and Purpose + +[cols="1,2"] +|=== +|File |Purpose + +|src/main/java/com/arc_e_tect/example/spring/rulesdisabled/application/service/adapter/OrderService.java +|Annotated with @Service and directly depends on a @Repository, creating intentional violations. + +|src/main/java/com/arc_e_tect/example/spring/rulesdisabled/adapter/persistence/OrderRepository.java +|Spring repository adapter used directly by the service. + +|build.gradle +|Disables DomainIsolationTest.applicationServicesShouldNotCarrySpringStereotypes via rulesDisabled. +|=== + +== Run + +[source,bash] +---- +./gradlew build +./gradlew testArchitecture +---- + +== Expected violations + +DomainIsolationTest.applicationServicesShouldNotCarrySpringStereotypes is reported as SKIPPED because it is disabled. +SpringHexagonalArchitectureTest.servicesShouldNotAccessRepositoriesDirectly remains enabled and fails. +All other enabled rule classes pass for this fixture. + +== Reports + +The Gradle HTML report is under build/reports/architecture-validator/html/. +The XML report is under build/reports/architecture-validator/xml/. + +== Fix + +Remove direct service-to-repository coupling by introducing an outbound port. +Remove the rulesDisabled override after the project is aligned with the architecture rules. diff --git a/examples/architecture-validator/hexagonal-spring-rules-disabled/build.gradle b/examples/architecture-validator/hexagonal-spring-rules-disabled/build.gradle new file mode 100644 index 0000000..0253fad --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-rules-disabled/build.gradle @@ -0,0 +1,46 @@ +plugins { + id 'jacoco' + id 'java' + alias(libs.plugins.architecture.validator.iff) +} + +group = 'com.arc-e-tect.example.spring' +version = '0.0.1' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation libs.spring.context.iff + testArchitectureImplementation libs.architecture.validator.hexagonal.spring.rules.iff +} + +architectureValidator { + basePackage = 'com.arc_e_tect.example.spring.rulesdisabled' + rulesDisabled = ['DomainIsolationTest.applicationServicesShouldNotCarrySpringStereotypes'] + useBuiltInHexagonalRulePack = false + +} + +tasks.named('jacocoTestReport') { + reports { + xml.required = true + html.required = true + } +} + +tasks.named('testArchitecture', Test) { + ignoreFailures = false +} + +tasks.named('check') { + dependsOn tasks.named('testArchitecture') +} diff --git a/examples/architecture-validator/hexagonal-spring-rules-disabled/gradle/libs.versions.toml b/examples/architecture-validator/hexagonal-spring-rules-disabled/gradle/libs.versions.toml new file mode 100644 index 0000000..58fd4a4 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-rules-disabled/gradle/libs.versions.toml @@ -0,0 +1,16 @@ +## Generated by $ ./gradlew refreshVersionsCatalog + +[plugins] + +architecture-validator-iff = { id = "com.arc-e-tect.architecture-validator", version.ref = "architecture-validator-iff" } + +[versions] + +architecture-validator-iff = "0.0.1" +architecture-validator-hexagonal-spring-rules-iff = "1.0.0-PRERELEASE" +spring-context-iff = "7.0.8" + +[libraries] + +architecture-validator-hexagonal-spring-rules-iff = { module = "com.arc-e-tect.architecture-validator:architecture-validator-hexagonal-spring-rules", version.ref = "architecture-validator-hexagonal-spring-rules-iff" } +spring-context-iff = { module = "org.springframework:spring-context", version.ref = "spring-context-iff" } diff --git a/examples/architecture-validator/hexagonal-spring-rules-disabled/gradle/wrapper/gradle-wrapper.jar b/examples/architecture-validator/hexagonal-spring-rules-disabled/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..b1b8ef56b44f16b14dc800fa8103a6d89abb526f GIT binary patch literal 48462 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 R2np~f1o-$>Q}y+){|A@R9n$~+ literal 0 HcmV?d00001 diff --git a/examples/architecture-validator/hexagonal-spring-rules-disabled/gradle/wrapper/gradle-wrapper.properties b/examples/architecture-validator/hexagonal-spring-rules-disabled/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a9db115 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-rules-disabled/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,9 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/architecture-validator/hexagonal-spring-rules-disabled/gradlew b/examples/architecture-validator/hexagonal-spring-rules-disabled/gradlew new file mode 100755 index 0000000..249efbb --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-rules-disabled/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/architecture-validator/hexagonal-spring-rules-disabled/gradlew.bat b/examples/architecture-validator/hexagonal-spring-rules-disabled/gradlew.bat new file mode 100644 index 0000000..a51ec4f --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-rules-disabled/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/examples/architecture-validator/hexagonal-spring-rules-disabled/settings.gradle b/examples/architecture-validator/hexagonal-spring-rules-disabled/settings.gradle new file mode 100644 index 0000000..18a776b --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-rules-disabled/settings.gradle @@ -0,0 +1,19 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' + id 'de.fayard.refreshVersions' version '0.60.6' +} + +refreshVersions { + rejectVersionIf { + candidate.stabilityLevel.isLessStableThan(current.stabilityLevel) + } +} + +rootProject.name = 'architecture-validator-hexagonal-spring-rules-disabled' diff --git a/examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/adapter/persistence/OrderRepository.java b/examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/adapter/persistence/OrderRepository.java new file mode 100644 index 0000000..637e33a --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/adapter/persistence/OrderRepository.java @@ -0,0 +1,12 @@ +package com.arc_e_tect.example.spring.rulesdisabled.adapter.persistence; + +import com.arc_e_tect.example.spring.rulesdisabled.application.domain.Order; +import org.springframework.stereotype.Repository; + +@Repository +public class OrderRepository { + + public void save(Order order) { + // Example persistence operation. + } +} diff --git a/examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/adapter/web/OrderController.java b/examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/adapter/web/OrderController.java new file mode 100644 index 0000000..8e6e217 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/adapter/web/OrderController.java @@ -0,0 +1,18 @@ +package com.arc_e_tect.example.spring.rulesdisabled.adapter.web; + +import com.arc_e_tect.example.spring.rulesdisabled.application.port.inbound.OrderUseCase; +import org.springframework.stereotype.Controller; + +@Controller +public class OrderController { + + private final OrderUseCase orderUseCase; + + public OrderController(OrderUseCase orderUseCase) { + this.orderUseCase = orderUseCase; + } + + public void submit(String id) { + orderUseCase.createOrder(id); + } +} diff --git a/examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/application/domain/Order.java b/examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/application/domain/Order.java new file mode 100644 index 0000000..8ae3445 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/application/domain/Order.java @@ -0,0 +1,4 @@ +package com.arc_e_tect.example.spring.rulesdisabled.application.domain; + +public record Order(String id) { +} diff --git a/examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/application/port/inbound/OrderUseCase.java b/examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/application/port/inbound/OrderUseCase.java new file mode 100644 index 0000000..6815d93 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/application/port/inbound/OrderUseCase.java @@ -0,0 +1,6 @@ +package com.arc_e_tect.example.spring.rulesdisabled.application.port.inbound; + +public interface OrderUseCase { + + void createOrder(String id); +} diff --git a/examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/application/service/adapter/OrderService.java b/examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/application/service/adapter/OrderService.java new file mode 100644 index 0000000..5f01e14 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-rules-disabled/src/main/java/com/arc_e_tect/example/spring/rulesdisabled/application/service/adapter/OrderService.java @@ -0,0 +1,21 @@ +package com.arc_e_tect.example.spring.rulesdisabled.application.service.adapter; + +import com.arc_e_tect.example.spring.rulesdisabled.adapter.persistence.OrderRepository; +import com.arc_e_tect.example.spring.rulesdisabled.application.domain.Order; +import com.arc_e_tect.example.spring.rulesdisabled.application.port.inbound.OrderUseCase; +import org.springframework.stereotype.Service; + +@Service +public class OrderService implements OrderUseCase { + + private final OrderRepository repository; + + public OrderService(OrderRepository repository) { + this.repository = repository; + } + + @Override + public void createOrder(String id) { + repository.save(new Order(id)); + } +} diff --git a/examples/architecture-validator/hexagonal-spring-rules-disabled/versions.properties b/examples/architecture-validator/hexagonal-spring-rules-disabled/versions.properties new file mode 100644 index 0000000..a07c552 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-rules-disabled/versions.properties @@ -0,0 +1,10 @@ +#### Dependencies and Plugin versions with their available updates. +#### Generated by `./gradlew refreshVersions` version 0.60.6 +#### +#### Don't manually edit or split the comments that start with four hashtags (####), +#### they will be overwritten by refreshVersions. +#### +#### suppress inspection "SpellCheckingInspection" for whole file +#### suppress inspection "UnusedProperty" for whole file +#### +#### NOTE: Some versions are filtered by the rejectVersionIf predicate. See the settings.gradle.kts file. diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/README.adoc b/examples/architecture-validator/hexagonal-spring-type-leakage/README.adoc new file mode 100644 index 0000000..b565da0 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/README.adoc @@ -0,0 +1,49 @@ += Hexagonal Spring Type Leakage Example +:toc: left + +This example demonstrates a JPA entity leaking into an outbound port contract. +It is designed to trigger TypeLeakageTest. + +== Files and Purpose + +[cols="1,2"] +|=== +|File |Purpose + +|src/main/java/com/arc_e_tect/example/spring/typeleakage/application/port/outbound/OrderPort.java +|Outbound port intentionally returns OrderEntity to demonstrate entity leakage across the application boundary. + +|src/main/java/com/arc_e_tect/example/spring/typeleakage/persistence/OrderEntity.java +|JPA entity type that must not appear in port method signatures. + +|src/main/java/com/arc_e_tect/example/spring/typeleakage/adapter/persistence/OrderRepositoryAdapter.java +|Repository adapter implementation behind the outbound port. + +|build.gradle +|Disables PortContractTest.portsShouldOnlyDependOnJavaCoreAndDomainModel so this example isolates TypeLeakageTest behavior. +|=== + +== Run + +[source,bash] +---- +./gradlew build +./gradlew testArchitecture +---- + +== Expected violations + +TypeLeakageTest.portsShouldNotExposeJpaEntities fails because OrderPort exposes OrderEntity. +The failure message names OrderPort.findById and the leaked type com.arc_e_tect.example.spring.typeleakage.persistence.OrderEntity. +PortContractTest.portsShouldOnlyDependOnJavaCoreAndDomainModel is intentionally reported as SKIPPED via rulesDisabled to isolate this demonstration to TypeLeakageTest. + +== Reports + +The Gradle HTML report is under build/reports/architecture-validator/html/. +The XML report is under build/reports/architecture-validator/xml/. + +== Fix + +Change OrderPort to use domain types only. +Keep OrderEntity confined to persistence adapters. +Remove the temporary PortContractTest disable once the port signature no longer exposes OrderEntity. diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/build.gradle b/examples/architecture-validator/hexagonal-spring-type-leakage/build.gradle new file mode 100644 index 0000000..993fd88 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/build.gradle @@ -0,0 +1,47 @@ +plugins { + id 'jacoco' + id 'java' + alias(libs.plugins.architecture.validator.iff) +} + +group = 'com.arc-e-tect.example.spring' +version = '0.0.1' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +repositories { + mavenLocal() + mavenCentral() +} + +dependencies { + implementation libs.jakarta.persistence.api.iff + implementation libs.spring.context.iff + testArchitectureImplementation libs.architecture.validator.hexagonal.spring.rules.iff +} + +architectureValidator { + basePackage = 'com.arc_e_tect.example.spring.typeleakage' + useBuiltInHexagonalRulePack = false + rulesDisabled = ['PortContractTest.portsShouldOnlyDependOnJavaCoreAndDomainModel'] + +} + +tasks.named('jacocoTestReport') { + reports { + xml.required = true + html.required = true + } +} + +tasks.named('testArchitecture', Test) { + ignoreFailures = false +} + +tasks.named('check') { + dependsOn tasks.named('testArchitecture') +} diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/gradle/libs.versions.toml b/examples/architecture-validator/hexagonal-spring-type-leakage/gradle/libs.versions.toml new file mode 100644 index 0000000..60908ee --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/gradle/libs.versions.toml @@ -0,0 +1,18 @@ +## Generated by $ ./gradlew refreshVersionsCatalog + +[plugins] + +architecture-validator-iff = { id = "com.arc-e-tect.architecture-validator", version.ref = "architecture-validator-iff" } + +[versions] + +architecture-validator-iff = "0.0.1" +architecture-validator-hexagonal-spring-rules-iff = "1.0.0-PRERELEASE" +jakarta-persistence-api-iff = "3.2.0" +spring-context-iff = "7.0.8" + +[libraries] + +architecture-validator-hexagonal-spring-rules-iff = { module = "com.arc-e-tect.architecture-validator:architecture-validator-hexagonal-spring-rules", version.ref = "architecture-validator-hexagonal-spring-rules-iff" } +jakarta-persistence-api-iff = { module = "jakarta.persistence:jakarta.persistence-api", version.ref = "jakarta-persistence-api-iff" } +spring-context-iff = { module = "org.springframework:spring-context", version.ref = "spring-context-iff" } diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/gradle/wrapper/gradle-wrapper.jar b/examples/architecture-validator/hexagonal-spring-type-leakage/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..b1b8ef56b44f16b14dc800fa8103a6d89abb526f GIT binary patch literal 48462 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 R2np~f1o-$>Q}y+){|A@R9n$~+ literal 0 HcmV?d00001 diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/gradle/wrapper/gradle-wrapper.properties b/examples/architecture-validator/hexagonal-spring-type-leakage/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a9db115 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,9 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/gradlew b/examples/architecture-validator/hexagonal-spring-type-leakage/gradlew new file mode 100755 index 0000000..249efbb --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/gradlew.bat b/examples/architecture-validator/hexagonal-spring-type-leakage/gradlew.bat new file mode 100644 index 0000000..a51ec4f --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/settings.gradle b/examples/architecture-validator/hexagonal-spring-type-leakage/settings.gradle new file mode 100644 index 0000000..aa1e2e1 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/settings.gradle @@ -0,0 +1,19 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0' + id 'de.fayard.refreshVersions' version '0.60.6' +} + +refreshVersions { + rejectVersionIf { + candidate.stabilityLevel.isLessStableThan(current.stabilityLevel) + } +} + +rootProject.name = 'architecture-validator-hexagonal-spring-type-leakage' diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/adapter/persistence/OrderRepositoryAdapter.java b/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/adapter/persistence/OrderRepositoryAdapter.java new file mode 100644 index 0000000..0886265 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/adapter/persistence/OrderRepositoryAdapter.java @@ -0,0 +1,16 @@ +package com.arc_e_tect.example.spring.typeleakage.adapter.persistence; + +import com.arc_e_tect.example.spring.typeleakage.application.port.outbound.OrderPort; +import com.arc_e_tect.example.spring.typeleakage.persistence.OrderEntity; +import org.springframework.stereotype.Repository; + +@Repository +public class OrderRepositoryAdapter implements OrderPort { + + @Override + public OrderEntity findById(String id) { + OrderEntity entity = new OrderEntity(); + entity.setId(id); + return entity; + } +} diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/adapter/web/OrderController.java b/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/adapter/web/OrderController.java new file mode 100644 index 0000000..5139115 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/adapter/web/OrderController.java @@ -0,0 +1,18 @@ +package com.arc_e_tect.example.spring.typeleakage.adapter.web; + +import com.arc_e_tect.example.spring.typeleakage.application.port.inbound.OrderUseCase; +import org.springframework.stereotype.Controller; + +@Controller +public class OrderController { + + private final OrderUseCase orderUseCase; + + public OrderController(OrderUseCase orderUseCase) { + this.orderUseCase = orderUseCase; + } + + public void submit(String id) { + orderUseCase.createOrder(id); + } +} diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/domain/Order.java b/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/domain/Order.java new file mode 100644 index 0000000..2601b69 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/domain/Order.java @@ -0,0 +1,4 @@ +package com.arc_e_tect.example.spring.typeleakage.application.domain; + +public record Order(String id) { +} diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/port/inbound/OrderUseCase.java b/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/port/inbound/OrderUseCase.java new file mode 100644 index 0000000..a4c9a7a --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/port/inbound/OrderUseCase.java @@ -0,0 +1,6 @@ +package com.arc_e_tect.example.spring.typeleakage.application.port.inbound; + +public interface OrderUseCase { + + void createOrder(String id); +} diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/port/outbound/OrderPort.java b/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/port/outbound/OrderPort.java new file mode 100644 index 0000000..3d9d680 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/port/outbound/OrderPort.java @@ -0,0 +1,8 @@ +package com.arc_e_tect.example.spring.typeleakage.application.port.outbound; + +import com.arc_e_tect.example.spring.typeleakage.persistence.OrderEntity; + +public interface OrderPort { + + OrderEntity findById(String id); +} diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/service/OrderService.java b/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/service/OrderService.java new file mode 100644 index 0000000..d3ce72c --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/application/service/OrderService.java @@ -0,0 +1,18 @@ +package com.arc_e_tect.example.spring.typeleakage.application.service; + +import com.arc_e_tect.example.spring.typeleakage.application.port.inbound.OrderUseCase; +import com.arc_e_tect.example.spring.typeleakage.application.port.outbound.OrderPort; + +public class OrderService implements OrderUseCase { + + private final OrderPort orderPort; + + public OrderService(OrderPort orderPort) { + this.orderPort = orderPort; + } + + @Override + public void createOrder(String id) { + orderPort.findById(id); + } +} diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/persistence/OrderEntity.java b/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/persistence/OrderEntity.java new file mode 100644 index 0000000..2e070c2 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/src/main/java/com/arc_e_tect/example/spring/typeleakage/persistence/OrderEntity.java @@ -0,0 +1,17 @@ +package com.arc_e_tect.example.spring.typeleakage.persistence; + +import jakarta.persistence.Entity; + +@Entity +public class OrderEntity { + + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/versions.properties b/examples/architecture-validator/hexagonal-spring-type-leakage/versions.properties new file mode 100644 index 0000000..a07c552 --- /dev/null +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/versions.properties @@ -0,0 +1,10 @@ +#### Dependencies and Plugin versions with their available updates. +#### Generated by `./gradlew refreshVersions` version 0.60.6 +#### +#### Don't manually edit or split the comments that start with four hashtags (####), +#### they will be overwritten by refreshVersions. +#### +#### suppress inspection "SpellCheckingInspection" for whole file +#### suppress inspection "UnusedProperty" for whole file +#### +#### NOTE: Some versions are filtered by the rejectVersionIf predicate. See the settings.gradle.kts file. diff --git a/hexagonal-spring-rules/README.adoc b/hexagonal-spring-rules/README.adoc index 54e3806..553fc17 100644 --- a/hexagonal-spring-rules/README.adoc +++ b/hexagonal-spring-rules/README.adoc @@ -134,8 +134,17 @@ Example: `DomainIsolationTest.applicationServicesShouldNotCarrySpringStereotypes == Example Project -A self-contained example demonstrating a Spring Hexagonal architecture violation is at link:../examples/architecture-validator/single-module-spring/[`examples/architecture-validator/single-module-spring/`]. -See link:../examples/architecture-validator/single-module-spring/README.adoc[its README] for full details. +The following self-contained example projects demonstrate each rule-pack behavior in isolation-oriented fixtures. + +* link:../examples/architecture-validator/single-module-spring/[examples/architecture-validator/single-module-spring/] demonstrates the original SpringHexagonalArchitectureTest service-to-repository violation. +* link:../examples/architecture-validator/hexagonal-spring-misconfigured/[examples/architecture-validator/hexagonal-spring-misconfigured/] demonstrates fail-fast configuration errors from RulePackConfigurationTest. +* link:../examples/architecture-validator/hexagonal-spring-type-leakage/[examples/architecture-validator/hexagonal-spring-type-leakage/] demonstrates TypeLeakageTest detection of JPA entity leakage in a port contract. +* link:../examples/architecture-validator/hexagonal-spring-cycle-detection/[examples/architecture-validator/hexagonal-spring-cycle-detection/] demonstrates CycleFreedomTest detection of adapter-package cycles in nested base packages. +* link:../examples/architecture-validator/hexagonal-spring-rules-disabled/[examples/architecture-validator/hexagonal-spring-rules-disabled/] demonstrates selective per-rule opt-out via architectureValidator.rulesDisabled. +* link:../examples/architecture-validator/hexagonal-spring-field-injection/[examples/architecture-validator/hexagonal-spring-field-injection/] demonstrates DependencyInjectionStyleTest detection of @Autowired field injection. +* link:../examples/architecture-validator/hexagonal-spring-naming-conventions/[examples/architecture-validator/hexagonal-spring-naming-conventions/] demonstrates opt-in NamingConventionTest failures for inconsistent suffixes. + +Each example README contains runnable commands, expected violations, and remediation guidance. == Contributing From 54fbfa5236f4acbbdeb1f1f703ef61d46bea60f1 Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 21:16:50 +0400 Subject: [PATCH 15/20] fix(hexagonal-spring-rules): honor split inbound/outbound adapter properties What changed:\n- Extended RulePackConfiguration.adapters() to merge values from architectureValidator.adapters, architectureValidator.inboundAdapters, and architectureValidator.outboundAdapters.\n- De-duplicated merged adapter package patterns before returning them to rule classes.\n- Updated class-level documentation to describe the split adapter properties and their relationship to the legacy aggregate property.\n\nWhy:\n- The plugin now exposes inboundAdapters and outboundAdapters explicitly. Reading only the legacy adapters property caused rule coverage gaps when consumers configured the split properties without the aggregate fallback.\n\nHow:\n- Combined the three package arrays via stream flattening and distinct filtering to build a single adapter package set consumed by architecture rules.\n\nSide effects and constraints:\n- Existing consumers using only architectureValidator.adapters continue to work unchanged.\n- Consumers using split adapter properties now receive full rule coverage without extra compatibility wiring.\n\nOutcomes:\n- Adapter-scoped rules evaluate the same package set the plugin configuration intends, improving consistency across old and new configuration styles. --- .../spring/RulePackConfiguration.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java index 5c7c794..377da98 100644 --- a/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java +++ b/hexagonal-spring-rules/src/main/java/com/arc_e_tect/gradle/architecture/spring/RulePackConfiguration.java @@ -20,7 +20,9 @@ *

  • {@code architectureValidator.inPorts} - Comma-separated in-port packages
  • *
  • {@code architectureValidator.outPorts} - Comma-separated out-port packages
  • *
  • {@code architectureValidator.domainModel} - Comma-separated domain model packages
  • - *
  • {@code architectureValidator.adapters} - Comma-separated adapter packages
  • + *
  • {@code architectureValidator.adapters} - Comma-separated adapter packages (legacy aggregate)
  • + *
  • {@code architectureValidator.inboundAdapters} - Comma-separated inbound adapter packages, merged with {@code adapters}
  • + *
  • {@code architectureValidator.outboundAdapters} - Comma-separated outbound adapter packages, merged with {@code adapters}
  • *
  • {@code architectureValidator.applicationServices} - Comma-separated application service packages
  • *
  • {@code architectureValidator.rules.disabled} - Comma-separated rule identifiers to skip
  • *
  • {@code architectureValidator.namingConventions.enabled} - Enables optional naming convention rules
  • @@ -51,7 +53,17 @@ static String[] domainModel() { } static String[] adapters() { - return packages("architectureValidator.adapters"); + // The Architecture Validator plugin's inboundAdapters/outboundAdapters is the preferred + // split package layout; architectureValidator.adapters remains a legacy aggregate + // fallback. Merge all three so this rule pack matches whichever layout a consumer + // actually configured instead of only recognizing the legacy aggregate property. + return Stream.of( + packages("architectureValidator.adapters"), + packages("architectureValidator.inboundAdapters"), + packages("architectureValidator.outboundAdapters")) + .flatMap(Arrays::stream) + .distinct() + .toArray(String[]::new); } static String[] applicationServices() { From 3092d29b733d6ff037cf6bb9c0f07070bb144a31 Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 21:40:17 +0400 Subject: [PATCH 16/20] test(hexagonal-spring-rules): add regressions for service/adapter rule edge cases What changed:\n- Added regression fixture types under hexagonal-spring-rules testFixtures for a service that implements its own in-port.\n- Extended RulePackSelfTest to preserve and restore inboundAdapters/outboundAdapters system properties during tests.\n- Added regression test springHexagonalArchitectureShouldPassServicesRuleWhenServiceImplementsItsOwnInPort.\n- Added regression test dependencyDirectionShouldFailCoreRuleWhenOnlySplitAdapterPropertiesAreConfigured.\n\nWhy:\n- Recent rule behavior and configuration compatibility updates introduced subtle edge cases that can silently regress without targeted fixture coverage.\n- A valid @Service implementing an in-port should not be treated as an adapter access violation.\n- Consumers using only split adapter properties (inbound/outbound) must still trigger adapter dependency checks.\n\nHow:\n- Introduced a focused regression fixture package and wired new self-tests to assert pass/fail semantics for both scenarios.\n- Kept tests aligned with real system-property keys used by the plugin and rule-pack configuration.\n\nSide effects and constraints:\n- Changes are test-only; production rule logic is unchanged in this commit.\n\nOutcomes:\n- Rule-pack regression suite now guards both semantics and configuration-path correctness for the updated architecture rules. --- .../architecture/spring/RulePackSelfTest.java | 51 +++++++++++++++++++ .../port/in/CreateOrderUseCase.java | 6 +++ .../service/CreateOrderService.java | 26 ++++++++++ 3 files changed, 83 insertions(+) create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/in/CreateOrderUseCase.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/service/CreateOrderService.java diff --git a/hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java b/hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java index ba14d3d..5690ae0 100644 --- a/hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java +++ b/hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java @@ -26,9 +26,12 @@ class RulePackSelfTest { private static final String OUT_PORTS_KEY = "architectureValidator.outPorts"; private static final String DOMAIN_MODEL_KEY = "architectureValidator.domainModel"; private static final String ADAPTERS_KEY = "architectureValidator.adapters"; + private static final String INBOUND_ADAPTERS_KEY = "architectureValidator.inboundAdapters"; + private static final String OUTBOUND_ADAPTERS_KEY = "architectureValidator.outboundAdapters"; private static final String APPLICATION_SERVICES_KEY = "architectureValidator.applicationServices"; private static final String COMPLIANT_BASE = "com.arc_e_tect.fixtures.compliant"; + private static final String SERVICE_IMPLEMENTS_PORT_BASE = "com.arc_e_tect.fixtures.regression.serviceImplementsPort"; private static final String SPRING_CONTROLLERS_BASE = "com.arc_e_tect.fixtures.violating.spring.controllers"; private static final String SPRING_SERVICES_BASE = "com.arc_e_tect.fixtures.violating.spring.services"; @@ -58,6 +61,8 @@ void captureOriginalArchitectureProperties() { capture(OUT_PORTS_KEY); capture(DOMAIN_MODEL_KEY); capture(ADAPTERS_KEY); + capture(INBOUND_ADAPTERS_KEY); + capture(OUTBOUND_ADAPTERS_KEY); capture(APPLICATION_SERVICES_KEY); } @@ -68,6 +73,8 @@ void restoreOriginalArchitectureProperties() { restore(OUT_PORTS_KEY); restore(DOMAIN_MODEL_KEY); restore(ADAPTERS_KEY); + restore(INBOUND_ADAPTERS_KEY); + restore(OUTBOUND_ADAPTERS_KEY); restore(APPLICATION_SERVICES_KEY); } @@ -108,6 +115,26 @@ void rulePackShouldPassAllCoreRulesWhenFixturesAreCompliant() { ); } + @Test + void springHexagonalArchitectureShouldPassServicesRuleWhenServiceImplementsItsOwnInPort() { + configure( + SERVICE_IMPLEMENTS_PORT_BASE, + SERVICE_IMPLEMENTS_PORT_BASE + ".application.port.in..", + SERVICE_IMPLEMENTS_PORT_BASE + ".application.port.out..", + SERVICE_IMPLEMENTS_PORT_BASE + ".domain.model..", + SERVICE_IMPLEMENTS_PORT_BASE + ".adapters..", + SERVICE_IMPLEMENTS_PORT_BASE + ".application.service.." + ); + + SpringHexagonalArchitectureTest rules = new SpringHexagonalArchitectureTest(); + + // Regression test: a @Service implementing its own in-port (the standard Hexagonal + // pattern) must not be flagged as reaching into a repository/adapter merely because + // interface implementation is itself a dependency ArchUnit can see. Previously this + // rule used an allow-list that omitted in-ports, so this exact case failed. + assertDoesNotThrow(rules::servicesShouldNotAccessRepositoriesDirectly); + } + @Test void cycleFreedomTestShouldFailAdapterRuleWhenAdapterPackagesFormACycle() { configure( @@ -294,6 +321,30 @@ void dependencyDirectionShouldFailCoreRuleWhenCoreDependsOnAdapters() { assertDoesNotThrow(rules::onlyConfigurationMayDependOnServiceImplementations); } + @Test + void dependencyDirectionShouldFailCoreRuleWhenOnlySplitAdapterPropertiesAreConfigured() { + configure( + DEPENDENCY_CORE_BASE, + DEPENDENCY_CORE_BASE + ".application.port.in..", + DEPENDENCY_CORE_BASE + ".application.port.out..", + DEPENDENCY_CORE_BASE + ".domain.model..", + "", + DEPENDENCY_CORE_BASE + ".application.service.." + ); + // Deliberately leave the legacy ADAPTERS_KEY empty (set above) and only configure the + // split inboundAdapters property, mirroring a consumer who follows the Architecture + // Validator plugin's preferred split layout without also setting the legacy aggregate. + System.setProperty(INBOUND_ADAPTERS_KEY, DEPENDENCY_CORE_BASE + ".adapters.."); + System.setProperty(OUTBOUND_ADAPTERS_KEY, ""); + + DependencyDirectionTest rules = new DependencyDirectionTest(); + + // Regression test: RulePackConfiguration.adapters() must also honor inboundAdapters/ + // outboundAdapters, not only the legacy aggregate architectureValidator.adapters + // property; otherwise this violation would pass vacuously for split-layout consumers. + assertThrows(AssertionError.class, rules::coreApplicationLayerShouldNotDependOnAdapters); + } + @Test void dependencyDirectionShouldFailAdapterRuleWhenAdapterDependsOnServiceImplementation() { configure( diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/in/CreateOrderUseCase.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/in/CreateOrderUseCase.java new file mode 100644 index 0000000..04d9728 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/in/CreateOrderUseCase.java @@ -0,0 +1,6 @@ +package com.arc_e_tect.fixtures.regression.serviceImplementsPort.application.port.in; + +public interface CreateOrderUseCase { + + void create(); +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/service/CreateOrderService.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/service/CreateOrderService.java new file mode 100644 index 0000000..404427d --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/service/CreateOrderService.java @@ -0,0 +1,26 @@ +package com.arc_e_tect.fixtures.regression.serviceImplementsPort.application.service; + +import com.arc_e_tect.fixtures.regression.serviceImplementsPort.application.port.in.CreateOrderUseCase; +import com.arc_e_tect.fixtures.regression.serviceImplementsPort.application.port.out.OrderStorePort; +import org.springframework.stereotype.Service; + +/** + * A {@code @Service} that implements its own in-port, the standard Hexagonal wiring. + * Regression fixture for {@code servicesShouldNotAccessRepositoriesDirectly}: implementing + * an in-port is a real ArchUnit-visible dependency and must not be mistaken for reaching + * into an adapter/repository package. + */ +@Service +public class CreateOrderService implements CreateOrderUseCase { + + private final OrderStorePort orderStorePort; + + public CreateOrderService(OrderStorePort orderStorePort) { + this.orderStorePort = orderStorePort; + } + + @Override + public void create() { + orderStorePort.save(); + } +} From cedbc59633258da97c16ad8368b4d9b327770e9b Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 21:40:25 +0400 Subject: [PATCH 17/20] docs(architecture-validator): deepen rule-example README guidance What changed:\n- Expanded README content for all architecture-validator Spring example projects, including single-module-spring and the six hexagonal-spring-* examples.\n- Added explicit configuration sections showing architectureValidator settings used by each fixture.\n- Added concrete violation snippets and clarified which rule IDs should fail or be skipped in each scenario.\n- Improved fix guidance with precise rename/mapping/config steps tied to observed failures.\n\nWhy:\n- The examples are intended to be teaching fixtures; terse descriptions were not enough to explain why each failure appears or how to reproduce and remediate it.\n- Several examples depend on nuanced behavior (fail-fast config checks, selective rule disabling, opt-in naming checks) that must be documented explicitly to avoid confusion.\n\nHow:\n- Rewrote README sections to include files/purpose context, configuration blocks, expected outcome semantics, and remediation actions aligned with actual architecture-test output.\n\nSide effects and constraints:\n- Documentation-only changes; no fixture source code or build logic was modified in this commit.\n\nOutcomes:\n- Architecture example docs now function as step-by-step reference material for interpreting and fixing each rule-pack violation pattern. --- .../README.adoc | 66 +++++++++++---- .../README.adoc | 78 +++++++++++++++--- .../README.adoc | 66 +++++++++++---- .../README.adoc | 78 ++++++++++++++---- .../README.adoc | 69 ++++++++++++---- .../hexagonal-spring-type-leakage/README.adoc | 81 +++++++++++++++---- .../single-module-spring/README.adoc | 40 ++++++++- 7 files changed, 389 insertions(+), 89 deletions(-) diff --git a/examples/architecture-validator/hexagonal-spring-cycle-detection/README.adoc b/examples/architecture-validator/hexagonal-spring-cycle-detection/README.adoc index 8940833..451be94 100644 --- a/examples/architecture-validator/hexagonal-spring-cycle-detection/README.adoc +++ b/examples/architecture-validator/hexagonal-spring-cycle-detection/README.adoc @@ -1,8 +1,9 @@ = Hexagonal Spring Cycle Detection Example :toc: left -This example demonstrates package cycle detection in adapter packages. -It also documents the CycleFreedomTest slice-pattern bug fix that was required to make this check effective in nested base packages. +This example demonstrates package cycle detection in adapter packages via `CycleFreedomTest`. +It also documents the `CycleFreedomTest` slice-pattern bug fix that was required to make this check +effective in realistically nested base packages — see "Why this example exists" below. == Files and Purpose @@ -10,16 +11,40 @@ It also documents the CycleFreedomTest slice-pattern bug fix that was required t |=== |File |Purpose -|src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/persistence/OrderRepositoryAdapter.java -|Adapter package A that depends on adapter.notification.OrderNotifier. +|`src/main/java/.../cycledetection/adapter/persistence/OrderRepositoryAdapter.java` +|Adapter package A. Depends on `adapter.notification.OrderNotifier`. -|src/main/java/com/arc_e_tect/example/spring/cycledetection/adapter/notification/OrderNotifier.java -|Adapter package B that depends back on OrderRepositoryAdapter, creating a package cycle. +|`src/main/java/.../cycledetection/adapter/notification/OrderNotifier.java` +|Adapter package B. Depends back on `OrderRepositoryAdapter`, closing the cycle. -|src/main/java/com/arc_e_tect/example/spring/cycledetection/application/service/OrderService.java -|Plain application service that depends only on an outbound port. +|`src/main/java/.../cycledetection/application/service/OrderService.java` +|Plain application service that depends only on an outbound port — kept compliant so the only failure is the +cycle itself. |=== +== Configuration + +[source,groovy] +---- +architectureValidator { + basePackage = 'com.arc_e_tect.example.spring.cycledetection' + useBuiltInHexagonalRulePack = false +} +---- + +No special properties are needed — `CycleFreedomTest` runs against the plugin's default `adapters` pattern +(`..adapter..`/`..adapters..`). + +== Why this example exists + +`CycleFreedomTest` builds an ArchUnit slice pattern from the configured `adapters` package root. The +original implementation stripped a leading `..` from that pattern (e.g. `..adapters..` became `adapters`), +which anchors the resulting pattern to the *start* of a class's fully-qualified name. For any realistic, +multi-segment base package like `com.arc_e_tect.example.spring.cycledetection`, no class's FQN actually +starts with `adapters.` — so the check matched zero classes and passed vacuously, regardless of real cycles. +This example's base package is deliberately nested several segments deep so it would have silently passed +under the old, buggy pattern; it now correctly fails. + == Run [source,bash] @@ -30,15 +55,28 @@ It also documents the CycleFreedomTest slice-pattern bug fix that was required t == Expected violations -CycleFreedomTest.adapterPackagesShouldBeFreeOfCycles fails and reports the cycle between adapter.persistence and adapter.notification. -Other rule classes should pass. +The two adapter classes depend on each other, forming a two-package cycle: + +[source,java] +---- +// adapter.persistence.OrderRepositoryAdapter +public OrderRepositoryAdapter(OrderNotifier notifier) { ... } // depends on adapter.notification + +// adapter.notification.OrderNotifier +public OrderNotifier(OrderRepositoryAdapter repositoryAdapter) { ... } // depends back on adapter.persistence +---- + +`CycleFreedomTest.adapterPackagesShouldBeFreeOfCycles` fails and reports the cycle between +`adapter.persistence` and `adapter.notification`. Every other rule class passes for this fixture. == Reports -The Gradle HTML report is under build/reports/architecture-validator/html/. -The XML report is under build/reports/architecture-validator/xml/. +The Gradle HTML report is under `build/reports/architecture-validator/html/`. +The XML report is under `build/reports/architecture-validator/xml/`. == Fix -Break the dependency loop so adapter.notification no longer depends on adapter.persistence. -Keep adapter package dependencies acyclic to preserve maintainable boundaries. +Break the dependency loop so `adapter.notification` no longer depends on `adapter.persistence` — for +example, have `OrderRepositoryAdapter` publish through a port or event mechanism instead of calling into +`OrderNotifier` directly, or move the shared collaboration point into a package neither adapter owns. Keep +adapter package dependencies acyclic to preserve maintainable boundaries. diff --git a/examples/architecture-validator/hexagonal-spring-field-injection/README.adoc b/examples/architecture-validator/hexagonal-spring-field-injection/README.adoc index 516ccea..320c284 100644 --- a/examples/architecture-validator/hexagonal-spring-field-injection/README.adoc +++ b/examples/architecture-validator/hexagonal-spring-field-injection/README.adoc @@ -1,8 +1,8 @@ = Hexagonal Spring Field Injection Example :toc: left -This example demonstrates the no-field-injection rule from DependencyInjectionStyleTest. -It intentionally uses @Autowired on a field in an adapter class. +This example demonstrates the no-field-injection rule from `DependencyInjectionStyleTest`. +It intentionally uses `@Autowired` field injection on an adapter class, instead of constructor injection. == Files and Purpose @@ -10,16 +10,30 @@ It intentionally uses @Autowired on a field in an adapter class. |=== |File |Purpose -|src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/persistence/OrderRepositoryAdapter.java -|Repository adapter with an @Autowired field to trigger DependencyInjectionStyleTest.fieldsShouldNotBeAutowired. +|`src/main/java/.../fieldinjection/adapter/persistence/OrderRepositoryAdapter.java` +|Repository adapter with an `@Autowired` field, to trigger `DependencyInjectionStyleTest.fieldsShouldNotBeAutowired`. -|src/main/java/com/arc_e_tect/example/spring/fieldinjection/application/service/OrderService.java -|Plain application service that depends only on an outbound port. +|`src/main/java/.../fieldinjection/application/service/OrderService.java` +|Plain application service that depends only on an outbound port — kept compliant so the only failure is +the field injection. -|src/main/java/com/arc_e_tect/example/spring/fieldinjection/adapter/web/OrderController.java +|`src/main/java/.../fieldinjection/adapter/web/OrderController.java` |Inbound controller that depends only on the inbound port. |=== +== Configuration + +[source,groovy] +---- +architectureValidator { + basePackage = 'com.arc_e_tect.example.spring.fieldinjection' + useBuiltInHexagonalRulePack = false +} +---- + +`DependencyInjectionStyleTest` needs no extra configuration — it checks fields in whatever packages are +already configured as `domainModel`, `adapters`, and `applicationServices`. + == Run [source,bash] @@ -30,15 +44,53 @@ It intentionally uses @Autowired on a field in an adapter class. == Expected violations -DependencyInjectionStyleTest.fieldsShouldNotBeAutowired fails because OrderRepositoryAdapter uses field injection. -The failure names the specific class and field. +`OrderRepositoryAdapter` injects its collaborator via an `@Autowired` field instead of a constructor +parameter: + +[source,java] +---- +@Repository +public class OrderRepositoryAdapter implements OrderPort { + + @Autowired + private PersistenceGateway gateway; // <1> + + @Override + public void save(Order order) { + gateway.persist(order); + } +} +---- +<1> Field injection hides a required dependency and prevents constructing this class without a Spring +context — the rule this example is designed to trip. + +`DependencyInjectionStyleTest.fieldsShouldNotBeAutowired` fails, naming the specific class and field. == Reports -The Gradle HTML report is under build/reports/architecture-validator/html/. -The XML report is under build/reports/architecture-validator/xml/. +The Gradle HTML report is under `build/reports/architecture-validator/html/`. +The XML report is under `build/reports/architecture-validator/xml/`. == Fix -Replace the @Autowired field with constructor injection. -Keep dependencies explicit and testable without a Spring context. +Replace the `@Autowired` field with a constructor parameter: + +[source,java] +---- +@Repository +public class OrderRepositoryAdapter implements OrderPort { + + private final PersistenceGateway gateway; + + public OrderRepositoryAdapter(PersistenceGateway gateway) { + this.gateway = gateway; + } + + @Override + public void save(Order order) { + gateway.persist(order); + } +} +---- + +This keeps the dependency explicit and lets the class be constructed and tested without a Spring context. diff --git a/examples/architecture-validator/hexagonal-spring-misconfigured/README.adoc b/examples/architecture-validator/hexagonal-spring-misconfigured/README.adoc index 959e7c5..0993be2 100644 --- a/examples/architecture-validator/hexagonal-spring-misconfigured/README.adoc +++ b/examples/architecture-validator/hexagonal-spring-misconfigured/README.adoc @@ -1,8 +1,11 @@ = Hexagonal Spring Misconfigured Example :toc: left -This example intentionally leaves architectureValidator.basePackage blank. -It demonstrates fail-fast validation from RulePackConfigurationTest. +This example intentionally leaves `architectureValidator.basePackage` blank. +It demonstrates the fail-fast validation added in `RulePackConfigurationTest`: before this check existed, a +misconfigured consumer would see every architecture rule pass vacuously (`ClassFileImporter` imports zero +classes, so nothing is ever checked) — a silent false-green build. This example shows the loud failure that +replaces that silence. == Files and Purpose @@ -10,13 +13,26 @@ It demonstrates fail-fast validation from RulePackConfigurationTest. |=== |File |Purpose -|build.gradle -|Configures architectureValidator with a blank basePackage to trigger fail-fast misconfiguration behavior. +|`build.gradle` +|Configures `architectureValidator` with a blank `basePackage` to trigger fail-fast misconfiguration behavior. -|src/main/java/com/arc_e_tect/example/spring/misconfigured/SampleDomain.java -|Minimal placeholder class so the project compiles. +|`src/main/java/com/arc_e_tect/example/spring/misconfigured/SampleDomain.java` +|Minimal placeholder class so the project compiles. Its content is irrelevant — the point is that +`basePackage` never resolves to it. |=== +== Configuration + +[source,groovy] +---- +architectureValidator { + basePackage = '' + useBuiltInHexagonalRulePack = false // <1> +} +---- +<1> Disabled so the failure comes only from the external rule pack (`hexagonal-spring-rules`), not the +plugin's own generated built-in test — keeps the demonstration focused on `RulePackConfigurationTest`. + == Run [source,bash] @@ -27,18 +43,38 @@ It demonstrates fail-fast validation from RulePackConfigurationTest. == Expected violations -The architecture suite fails because architectureValidator.basePackage is blank. -In this fixture, all 23 architecture tests fail loudly instead of passing vacuously. -The actionable failure is RulePackConfigurationTest.requiredArchitecturePropertiesShouldBeConfigured. -That message explicitly states that architectureValidator.basePackage is not set and shows how to configure it. -The remaining failures are incidental noise caused by the invalid import scope and reinforce that the project is misconfigured. +With `basePackage` blank, `RulePackConfiguration.requireConfigured()` throws before `ClassFileImporter` ever +runs. Every rule class's `classes` field is initialized the same way +(`new ClassFileImporter()...importPackages(RulePackConfiguration.basePackage())`), and `basePackage()` now +calls `requireConfigured()` itself — so the same `AssertionError` propagates out of *every* rule class's +field initializer, not just `RulePackConfigurationTest`. All 23 architecture tests fail with the identical +root cause: + +[source] +---- +java.lang.AssertionError: architectureValidator.basePackage is not set. Configure it via the Architecture +Validator plugin's architectureValidator { basePackage = 'com.example.myapp' } extension. +---- + +The one that matters is `RulePackConfigurationTest.requiredArchitecturePropertiesShouldBeConfigured` — its +message is the actionable one, telling you exactly which property to set and how. The other 22 failures are +the same exception surfacing everywhere else; seeing all of them fail together (instead of quietly passing) +is the point of this example. == Reports -The Gradle HTML report is under build/reports/architecture-validator/html/. -The XML report is under build/reports/architecture-validator/xml/. +The Gradle HTML report is under `build/reports/architecture-validator/html/`. +The XML report is under `build/reports/architecture-validator/xml/`. == Fix -Set architectureValidator.basePackage to the project root package. -Keep inPorts, outPorts, and domainModel configured so the rules validate real classes. +Set `architectureValidator.basePackage` to the project's real root package. Keep `inPorts`, `outPorts`, and +`domainModel` configured (or leave them at the plugin's Hexagonal defaults) so the rules validate real +classes instead of an empty import scope: + +[source,groovy] +---- +architectureValidator { + basePackage = 'com.arc_e_tect.example.spring.misconfigured' +} +---- diff --git a/examples/architecture-validator/hexagonal-spring-naming-conventions/README.adoc b/examples/architecture-validator/hexagonal-spring-naming-conventions/README.adoc index 4964e84..0a318a2 100644 --- a/examples/architecture-validator/hexagonal-spring-naming-conventions/README.adoc +++ b/examples/architecture-validator/hexagonal-spring-naming-conventions/README.adoc @@ -1,8 +1,11 @@ = Hexagonal Spring Naming Conventions Example :toc: left -This example demonstrates opt-in naming convention rules from NamingConventionTest. -It intentionally uses non-compliant suffixes for in-ports, out-ports, and repository adapters. +This example demonstrates the opt-in naming convention rules from `NamingConventionTest`. +It intentionally uses non-compliant suffixes for an in-port, an out-port, and a repository adapter. + +`NamingConventionTest` is disabled by default — unlike the other example projects in this repository, simply +adding the rule pack dependency is not enough here. It must be explicitly turned on. == Files and Purpose @@ -10,16 +13,34 @@ It intentionally uses non-compliant suffixes for in-ports, out-ports, and reposi |=== |File |Purpose -|src/main/java/com/arc_e_tect/example/spring/namingconventions/application/port/inbound/OrderCommands.java -|Inbound port that intentionally does not end with UseCase or Port. +|`src/main/java/.../namingconventions/application/port/inbound/OrderCommands.java` +|Inbound port that intentionally does not end with `UseCase` or `Port`. + +|`src/main/java/.../namingconventions/application/port/outbound/OrderStore.java` +|Outbound port that intentionally does not end with `Port`. -|src/main/java/com/arc_e_tect/example/spring/namingconventions/application/port/outbound/OrderStore.java -|Outbound port that intentionally does not end with Port. +|`src/main/java/.../namingconventions/adapter/persistence/OrderPersistence.java` +|Repository adapter (`@Repository`) that intentionally does not end with `Repository` or `Adapter`. -|src/main/java/com/arc_e_tect/example/spring/namingconventions/adapter/persistence/OrderPersistence.java -|Repository adapter that intentionally does not end with Repository or Adapter. +|`build.gradle` +|Opts in to naming convention rules via `hexagonalArchitecture { namingConventionsEnabled = true }`. |=== +== Configuration + +[source,groovy] +---- +architectureValidator { + basePackage = 'com.arc_e_tect.example.spring.namingconventions' + hexagonalArchitecture { + namingConventionsEnabled = true // <1> + } + useBuiltInHexagonalRulePack = false +} +---- +<1> Required. Without this, all three `NamingConventionTest` rules are reported as *SKIPPED* rather than +failed — try commenting this line out and re-running `./gradlew testArchitecture` to see the difference. + == Run [source,bash] @@ -30,17 +51,42 @@ It intentionally uses non-compliant suffixes for in-ports, out-ports, and reposi == Expected violations -NamingConventionTest.inputPortsShouldHaveConsistentSuffix fails for OrderCommands. -NamingConventionTest.outputPortsShouldHaveConsistentSuffix fails for OrderStore. -NamingConventionTest.adaptersShouldHaveConsistentSuffix fails for OrderPersistence. +Three types deliberately break their expected suffix: + +[source,java] +---- +public interface OrderCommands { void createOrder(String id); } // should end with UseCase or Port +public interface OrderStore { void save(Order order); } // should end with Port + +@Repository +public class OrderPersistence implements OrderStore { ... } // should end with Repository or Adapter +---- + +All three fail, each naming the specific offending class: + +* `NamingConventionTest.inputPortsShouldHaveConsistentSuffix` fails for `OrderCommands`. +* `NamingConventionTest.outputPortsShouldHaveConsistentSuffix` fails for `OrderStore`. +* `NamingConventionTest.adaptersShouldHaveConsistentSuffix` fails for `OrderPersistence`. == Reports -The Gradle HTML report is under build/reports/architecture-validator/html/. -The XML report is under build/reports/architecture-validator/xml/. +The Gradle HTML report is under `build/reports/architecture-validator/html/`. +The XML report is under `build/reports/architecture-validator/xml/`. == Fix -Rename OrderCommands to OrderUseCase or OrderCommandsUseCase. -Rename OrderStore to OrderPort. -Rename OrderPersistence to OrderRepositoryAdapter or OrderRepository. +Rename each type to match its role's expected suffix: + +[cols="1,1"] +|=== +|From |To + +|`OrderCommands` +|`OrderUseCase` (or `OrderCommandsUseCase`) + +|`OrderStore` +|`OrderPort` + +|`OrderPersistence` +|`OrderRepositoryAdapter` (or `OrderRepository`) +|=== diff --git a/examples/architecture-validator/hexagonal-spring-rules-disabled/README.adoc b/examples/architecture-validator/hexagonal-spring-rules-disabled/README.adoc index 5ffb300..f9ad8b6 100644 --- a/examples/architecture-validator/hexagonal-spring-rules-disabled/README.adoc +++ b/examples/architecture-validator/hexagonal-spring-rules-disabled/README.adoc @@ -1,8 +1,9 @@ = Hexagonal Spring Rules Disabled Example :toc: left -This example demonstrates per-rule opt-out using architectureValidator.rulesDisabled. -It intentionally introduces two violations and disables one rule id. +This example demonstrates per-rule opt-out using `architectureValidator.rulesDisabled`. +It intentionally introduces two violations of two different rules and disables only one of them, so the +build still fails — proving the opt-out is selective, not a blanket "ignore everything" switch. == Files and Purpose @@ -10,16 +11,29 @@ It intentionally introduces two violations and disables one rule id. |=== |File |Purpose -|src/main/java/com/arc_e_tect/example/spring/rulesdisabled/application/service/adapter/OrderService.java -|Annotated with @Service and directly depends on a @Repository, creating intentional violations. +|`src/main/java/.../rulesdisabled/application/service/adapter/OrderService.java` +|Annotated `@Service` and depends directly on a `@Repository` — both intentional violations (see below). -|src/main/java/com/arc_e_tect/example/spring/rulesdisabled/adapter/persistence/OrderRepository.java -|Spring repository adapter used directly by the service. +|`src/main/java/.../rulesdisabled/adapter/persistence/OrderRepository.java` +|Spring repository adapter reached directly by the service instead of through an outbound port. -|build.gradle -|Disables DomainIsolationTest.applicationServicesShouldNotCarrySpringStereotypes via rulesDisabled. +|`build.gradle` +|Disables `DomainIsolationTest.applicationServicesShouldNotCarrySpringStereotypes` via `rulesDisabled`. |=== +== Configuration + +[source,groovy] +---- +architectureValidator { + basePackage = 'com.arc_e_tect.example.spring.rulesdisabled' + rulesDisabled = ['DomainIsolationTest.applicationServicesShouldNotCarrySpringStereotypes'] // <1> + useBuiltInHexagonalRulePack = false +} +---- +<1> Rule identifiers are `ClassSimpleName.methodName`, matching the `@Test` method names in +`hexagonal-spring-rules`'s rule classes exactly. + == Run [source,bash] @@ -30,16 +44,41 @@ It intentionally introduces two violations and disables one rule id. == Expected violations -DomainIsolationTest.applicationServicesShouldNotCarrySpringStereotypes is reported as SKIPPED because it is disabled. -SpringHexagonalArchitectureTest.servicesShouldNotAccessRepositoriesDirectly remains enabled and fails. -All other enabled rule classes pass for this fixture. +`OrderService` carries `@Service` *and* depends directly on `OrderRepository`: + +[source,java] +---- +@Service +public class OrderService implements OrderUseCase { + + private final OrderRepository repository; // <1> + + public OrderService(OrderRepository repository) { + this.repository = repository; + } + ... +} +---- +<1> Direct dependency on an adapter-layer `@Repository`, bypassing an outbound port. + +This is structurally two separate violations: + +* `DomainIsolationTest.applicationServicesShouldNotCarrySpringStereotypes` — flags the `@Service` + annotation itself. This is the one disabled via `rulesDisabled`, so it's reported as *SKIPPED*, not + passed and not absent. +* `SpringHexagonalArchitectureTest.servicesShouldNotAccessRepositoriesDirectly` — flags the direct + `OrderRepository` dependency. This one stays enabled and *fails* the build. + +All other enabled rule classes pass for this fixture — only the one deliberately-left-enabled violation +should be red. == Reports -The Gradle HTML report is under build/reports/architecture-validator/html/. -The XML report is under build/reports/architecture-validator/xml/. +The Gradle HTML report is under `build/reports/architecture-validator/html/`. +The XML report is under `build/reports/architecture-validator/xml/`. == Fix -Remove direct service-to-repository coupling by introducing an outbound port. -Remove the rulesDisabled override after the project is aligned with the architecture rules. +Remove direct service-to-repository coupling by introducing an outbound port, and remove the `rulesDisabled` +override once the project is aligned with the architecture rules — `rulesDisabled` is meant as a deliberate, +temporary or team-specific exception, not a permanent way to silence a rule you plan to leave violated. diff --git a/examples/architecture-validator/hexagonal-spring-type-leakage/README.adoc b/examples/architecture-validator/hexagonal-spring-type-leakage/README.adoc index b565da0..577f242 100644 --- a/examples/architecture-validator/hexagonal-spring-type-leakage/README.adoc +++ b/examples/architecture-validator/hexagonal-spring-type-leakage/README.adoc @@ -2,7 +2,7 @@ :toc: left This example demonstrates a JPA entity leaking into an outbound port contract. -It is designed to trigger TypeLeakageTest. +It is designed to trigger `TypeLeakageTest.portsShouldNotExposeJpaEntities`. == Files and Purpose @@ -10,19 +10,41 @@ It is designed to trigger TypeLeakageTest. |=== |File |Purpose -|src/main/java/com/arc_e_tect/example/spring/typeleakage/application/port/outbound/OrderPort.java -|Outbound port intentionally returns OrderEntity to demonstrate entity leakage across the application boundary. +|`src/main/java/.../typeleakage/application/port/outbound/OrderPort.java` +|Outbound port that intentionally returns `OrderEntity` to demonstrate entity leakage across the application boundary. -|src/main/java/com/arc_e_tect/example/spring/typeleakage/persistence/OrderEntity.java +|`src/main/java/.../typeleakage/persistence/OrderEntity.java` |JPA entity type that must not appear in port method signatures. -|src/main/java/com/arc_e_tect/example/spring/typeleakage/adapter/persistence/OrderRepositoryAdapter.java +|`src/main/java/.../typeleakage/adapter/persistence/OrderRepositoryAdapter.java` |Repository adapter implementation behind the outbound port. -|build.gradle -|Disables PortContractTest.portsShouldOnlyDependOnJavaCoreAndDomainModel so this example isolates TypeLeakageTest behavior. +|`build.gradle` +|Disables `PortContractTest.portsShouldOnlyDependOnJavaCoreAndDomainModel` so this example isolates +`TypeLeakageTest` behavior (that older, coarser rule would otherwise flag the same leak as generic +framework-type exposure and muddy the demonstration). |=== +== Configuration + +[source,groovy] +---- +dependencies { + implementation libs.jakarta.persistence.api.iff // <1> + testArchitectureImplementation libs.architecture.validator.hexagonal.spring.rules.iff +} + +architectureValidator { + basePackage = 'com.arc_e_tect.example.spring.typeleakage' + useBuiltInHexagonalRulePack = false + rulesDisabled = ['PortContractTest.portsShouldOnlyDependOnJavaCoreAndDomainModel'] // <2> +} +---- +<1> Needed only so `jakarta.persistence.Entity` is on the compile classpath for `OrderEntity`; a real +project would already depend on a JPA provider. +<2> The per-rule opt-out feature (see the `hexagonal-spring-rules-disabled` example for its own dedicated +demonstration) — used here purely to keep this example's failure output limited to `TypeLeakageTest`. + == Run [source,bash] @@ -33,17 +55,46 @@ It is designed to trigger TypeLeakageTest. == Expected violations -TypeLeakageTest.portsShouldNotExposeJpaEntities fails because OrderPort exposes OrderEntity. -The failure message names OrderPort.findById and the leaked type com.arc_e_tect.example.spring.typeleakage.persistence.OrderEntity. -PortContractTest.portsShouldOnlyDependOnJavaCoreAndDomainModel is intentionally reported as SKIPPED via rulesDisabled to isolate this demonstration to TypeLeakageTest. +The out-port method returns the JPA entity directly instead of a domain type: + +[source,java] +---- +public interface OrderPort { + + OrderEntity findById(String id); // <1> +} +---- +<1> `OrderEntity` (`persistence.OrderEntity`, annotated `@jakarta.persistence.Entity`) is a persistence +detail; exposing it here leaks that detail across the port boundary into anything calling `OrderPort`. + +`TypeLeakageTest.portsShouldNotExposeJpaEntities` fails, naming the exact method and leaked type: + +[source] +---- +OrderPort#findById exposes JPA entity com.arc_e_tect.example.spring.typeleakage.persistence.OrderEntity as return type +---- + +`PortContractTest.portsShouldOnlyDependOnJavaCoreAndDomainModel` is reported as SKIPPED (via `rulesDisabled`, +not because it wouldn't also catch this) — see the Configuration section above for why. == Reports -The Gradle HTML report is under build/reports/architecture-validator/html/. -The XML report is under build/reports/architecture-validator/xml/. +The Gradle HTML report is under `build/reports/architecture-validator/html/`. +The XML report is under `build/reports/architecture-validator/xml/`. == Fix -Change OrderPort to use domain types only. -Keep OrderEntity confined to persistence adapters. -Remove the temporary PortContractTest disable once the port signature no longer exposes OrderEntity. +Change `OrderPort` to return a domain type instead of `OrderEntity`: + +[source,java] +---- +public interface OrderPort { + + Order findById(String id); +} +---- + +Keep `OrderEntity` confined to `adapter.persistence`, mapping between it and `Order` inside +`OrderRepositoryAdapter`. Once the port signature no longer exposes `OrderEntity`, remove the temporary +`rulesDisabled` override in `build.gradle` — `PortContractTest.portsShouldOnlyDependOnJavaCoreAndDomainModel` +should pass cleanly on its own again. diff --git a/examples/architecture-validator/single-module-spring/README.adoc b/examples/architecture-validator/single-module-spring/README.adoc index 6b7bad7..24b7236 100644 --- a/examples/architecture-validator/single-module-spring/README.adoc +++ b/examples/architecture-validator/single-module-spring/README.adoc @@ -27,6 +27,25 @@ It demonstrates a direct dependency from an application service to a Spring repo |Domain model. |=== +== Configuration + +The only setup this rule pack needs is a `basePackage` and the `testArchitectureImplementation` dependency; +package boundaries (`inPorts`, `outPorts`, `domainModel`, `adapters`, `applicationServices`) fall back to the +plugin's Hexagonal defaults (`..application.port.inbound..`, `..application.port.outbound..`, +`..application.domain..`, `..adapter..`/`..adapters..`, `..application.service..`), which this project's +package layout already follows. + +[source,groovy] +---- +dependencies { + testArchitectureImplementation libs.architecture.validator.hexagonal.spring.rules.iff +} + +architectureValidator { + basePackage = 'com.arc_e_tect.example.spring' +} +---- + == Run [source,bash] @@ -44,7 +63,26 @@ The current sample is intentionally non-compliant, so both commands are expected == Expected violations -`OrderService` depends directly on `OrderRepository`, an adapter-layer Spring `@Repository`, instead of going through an outbound port. +`OrderService` depends directly on `OrderRepository`, an adapter-layer Spring `@Repository`, instead of going through an outbound port: + +[source,java] +---- +public class OrderService implements OrderUseCase { + + private final OrderRepository repository; // <1> + + public OrderService(OrderRepository repository) { + this.repository = repository; + } + + @Override + public void createOrder(String id) { + repository.save(new Order(id)); + } +} +---- +<1> `OrderRepository` lives in `adapter.persistence`, not behind an `application.port.outbound` interface — this is the violation. + That single design mistake trips several rules from different angles: * `DependencyDirectionTest.coreApplicationLayerShouldNotDependOnAdapters` — the application core must not depend on adapter-layer classes. From 4288c8fa779e821f5aea9623001bc51528d820d6 Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 21:40:30 +0400 Subject: [PATCH 18/20] docs(jacoco-marker): explain exclusion convention checks with examples What changed:\n- Reworked the jacoco-marker example README to explain the marker annotation semantics and the ArchUnit convention enforced by AbstractCoverageExclusionConventionsTest.\n- Added explicit compliant vs non-compliant code examples showing justification usage.\n- Added a concrete test subclass wiring snippet and exact expected failure output format.\n\nWhy:\n- Readers needed clearer guidance on when coverage exclusion is legitimate and how the convention test enforces accountability through required justifications.\n\nHow:\n- Expanded narrative context and embedded short focused snippets for usage patterns, wiring, and expected test results.\n\nSide effects and constraints:\n- Documentation-only change in the example project.\n\nOutcomes:\n- The README now serves as a practical quickstart for adopting marker-based coverage exclusions with enforceable governance. --- .../sedr-library/jacoco-marker/README.adoc | 60 +++++++++++++++++-- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/examples/sedr-library/jacoco-marker/README.adoc b/examples/sedr-library/jacoco-marker/README.adoc index 68f0394..8453495 100644 --- a/examples/sedr-library/jacoco-marker/README.adoc +++ b/examples/sedr-library/jacoco-marker/README.adoc @@ -4,7 +4,15 @@ :icons: font :source-highlighter: rouge -This self-contained example demonstrates compliant and non-compliant usage of `@ExcludeFromJacocoGeneratedCodeCoverage`. +`@ExcludeFromJacocoGeneratedCodeCoverage` (from `sedr-library`'s `com.arc_e_tect.sedr.utils.jacoco.marker` +package) marks a constructor, method, or type as intentionally excluded from JaCoCo coverage measurement — +for example, a framework entry point that only the container ever calls, which unit tests cannot reasonably +exercise. `AbstractCoverageExclusionConventionsTest` is an ArchUnit-based convention check that enforces the +one rule that makes the annotation trustworthy: every use must carry a non-blank `justification`, so a +reviewer can tell a deliberate exclusion from someone quietly hiding untested code. + +This self-contained example demonstrates both sides of that check: compliant usage (justification present, +passes) and non-compliant usage (justification missing, fails). // tag::root[] == Files and Purpose @@ -20,9 +28,45 @@ This self-contained example demonstrates compliant and non-compliant usage of `@ |Uses `@ExcludeFromJacocoGeneratedCodeCoverage` without a justification, so the ArchUnit test reports a violation. |`src/test/java/.../CoverageExclusionConventionsTest.java` -|Extends `AbstractCoverageExclusionConventionsTest` targeting the example package. +|Extends `AbstractCoverageExclusionConventionsTest` targeting the example package. This is the entire +integration point — see the snippet below. |=== +== The Two Cases + +[source,java] +---- +// CompliantService.java +@ExcludeFromJacocoGeneratedCodeCoverage( + justification = "Stub entry point executed only by the container, not unit-testable") +public void start() { + // framework entry point — excluded from coverage by design +} +---- + +[source,java] +---- +// NonCompliantService.java +@ExcludeFromJacocoGeneratedCodeCoverage // <1> +public void stop() { + // annotation has no justification — the ArchUnit test will fail here +} +---- +<1> No `justification` — this is what the convention check rejects. + +Wiring the check into a project is a three-line subclass naming the package to scan: + +[source,java] +---- +class CoverageExclusionConventionsTest extends AbstractCoverageExclusionConventionsTest { + + @Override + protected String getBasePackage() { + return "com.arc_e_tect.sedr.example.jacoco.marker"; + } +} +---- + == Running the Example This example requires `sedr-library` to be published to your local Maven repository first. @@ -36,9 +80,15 @@ cd sedr-library && ./gradlew clean build publishToMavenLocal cd ../examples/sedr-library/jacoco-marker && ./gradlew test ---- -The test run reports exactly one violation. -`Method NonCompliantService.stop() uses @ExcludeFromJacocoGeneratedCodeCoverage without a justification`. -`CompliantService` is not mentioned. +The test run reports exactly one violation: + +[source] +---- +Method com.arc_e_tect.sedr.example.jacoco.marker.NonCompliantService.stop() uses +@ExcludeFromJacocoGeneratedCodeCoverage without a justification +---- + +`CompliantService` is not mentioned — only the annotation usage missing a `justification` is flagged. // end::root[] == License From 66cfcdbf43b6a125bd08de6195933f4066a0716d Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 21:59:24 +0400 Subject: [PATCH 19/20] fix(gitignore): unignore application/port/out fixture packages The unanchored `out/` .gitignore rule (meant for IntelliJ's root build output) matched every nested `application/port/out/` package directory in hexagonal-spring-rules' test fixtures, silently excluding them from git even though they existed and compiled locally. CI checked out a tree missing these files entirely, failing with "package ... does not exist". Anchor the rule to the repo root and add the orphaned files. Co-Authored-By: Claude Sonnet 5 --- .gitignore | 2 +- .../compliant/application/port/out/OrderStorePort.java | 5 +++++ .../application/port/out/OrderStorePort.java | 6 ++++++ .../application/port/out/ForbiddenPort.java | 5 +++++ .../application/port/out/GoodOutputPort.java | 7 +++++++ .../application/port/out/NotInterfaceOutputPort.java | 10 ++++++++++ .../application/port/out/GoodOutputPort.java | 7 +++++++ 7 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/out/OrderStorePort.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/out/OrderStorePort.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/application/port/out/ForbiddenPort.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/out/GoodOutputPort.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/out/NotInterfaceOutputPort.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/out/GoodOutputPort.java diff --git a/.gitignore b/.gitignore index 8365f9e..b7826d4 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,7 @@ build/ # IntelliJ IDEA *.iml *.iws -out/ +/out/ # VS Code /worktrees/ diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/out/OrderStorePort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/out/OrderStorePort.java new file mode 100644 index 0000000..a9e6b07 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/out/OrderStorePort.java @@ -0,0 +1,5 @@ +package com.arc_e_tect.fixtures.compliant.application.port.out; + +public interface OrderStorePort { + void save(); +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/out/OrderStorePort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/out/OrderStorePort.java new file mode 100644 index 0000000..7b0bdd8 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/out/OrderStorePort.java @@ -0,0 +1,6 @@ +package com.arc_e_tect.fixtures.regression.serviceImplementsPort.application.port.out; + +public interface OrderStorePort { + + void save(); +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/application/port/out/ForbiddenPort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/application/port/out/ForbiddenPort.java new file mode 100644 index 0000000..5f090b7 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/application/port/out/ForbiddenPort.java @@ -0,0 +1,5 @@ +package com.arc_e_tect.fixtures.violating.domain.domainDependency.application.port.out; + +public interface ForbiddenPort { + void send(); +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/out/GoodOutputPort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/out/GoodOutputPort.java new file mode 100644 index 0000000..341f486 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/out/GoodOutputPort.java @@ -0,0 +1,7 @@ +package com.arc_e_tect.fixtures.violating.port.inputNotInterface.application.port.out; + +import com.arc_e_tect.fixtures.violating.port.inputNotInterface.domain.model.PortDomain; + +public interface GoodOutputPort { + PortDomain load(String id); +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/out/NotInterfaceOutputPort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/out/NotInterfaceOutputPort.java new file mode 100644 index 0000000..08d911f --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/out/NotInterfaceOutputPort.java @@ -0,0 +1,10 @@ +package com.arc_e_tect.fixtures.violating.port.outputNotInterface.application.port.out; + +import com.arc_e_tect.fixtures.violating.port.outputNotInterface.domain.model.PortDomain; + +public class NotInterfaceOutputPort { + + public PortDomain load(String id) { + return new PortDomain(id); + } +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/out/GoodOutputPort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/out/GoodOutputPort.java new file mode 100644 index 0000000..ecbff8c --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/out/GoodOutputPort.java @@ -0,0 +1,7 @@ +package com.arc_e_tect.fixtures.violating.port.signatureLeak.application.port.out; + +import com.arc_e_tect.fixtures.violating.port.signatureLeak.domain.model.PortDomain; + +public interface GoodOutputPort { + PortDomain load(String id); +} From d55e83e3cc1762ffcd0a78d6b0f12b5cf7937618 Mon Sep 17 00:00:00 2001 From: Iwan Eising Date: Sat, 18 Jul 2026 22:07:33 +0400 Subject: [PATCH 20/20] refactor(hexagonal-spring-rules): rename test fixture port.in/port.out to inbound/outbound Consistent with the rest of the codebase (plugin defaults, examples): a port package literally named "out" collides with the common .gitignore convention for IDE build-output directories, which is what silently excluded these fixtures from git in the first place. Renaming removes the dependency on the .gitignore anchor being exactly right, matching why inbound/outbound was already adopted everywhere else. Co-Authored-By: Claude Sonnet 5 --- .../architecture/spring/RulePackSelfTest.java | 72 +++++++++---------- .../OrderStoreRepositoryAdapter.java | 2 +- .../adapters/web/CreateOrderController.java | 2 +- .../port/in/CreateOrderUseCase.java | 5 -- .../port/inbound/CreateOrderUseCase.java | 5 ++ .../application/port/out/OrderStorePort.java | 5 -- .../port/outbound/OrderStorePort.java | 5 ++ .../service/CreateOrderService.java | 4 +- .../ApplicationConfiguration.java | 2 +- .../{in => inbound}/CreateOrderUseCase.java | 2 +- .../{out => outbound}/OrderStorePort.java | 2 +- .../service/CreateOrderService.java | 4 +- .../port/{out => outbound}/ForbiddenPort.java | 2 +- .../domain/model/BadDomain.java | 2 +- .../NotInterfaceInputPort.java | 2 +- .../{out => outbound}/GoodOutputPort.java | 2 +- .../port/{in => inbound}/GoodInputPort.java | 2 +- .../NotInterfaceOutputPort.java | 2 +- .../port/{in => inbound}/LeakyInputPort.java | 2 +- .../{out => outbound}/GoodOutputPort.java | 2 +- 20 files changed, 63 insertions(+), 63 deletions(-) delete mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/in/CreateOrderUseCase.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/inbound/CreateOrderUseCase.java delete mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/out/OrderStorePort.java create mode 100644 hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/outbound/OrderStorePort.java rename hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/{in => inbound}/CreateOrderUseCase.java (82%) rename hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/{out => outbound}/OrderStorePort.java (80%) rename hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/application/port/{out => outbound}/ForbiddenPort.java (80%) rename hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/{in => inbound}/NotInterfaceInputPort.java (91%) rename hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/{out => outbound}/GoodOutputPort.java (88%) rename hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/{in => inbound}/GoodInputPort.java (89%) rename hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/{out => outbound}/NotInterfaceOutputPort.java (90%) rename hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/{in => inbound}/LeakyInputPort.java (89%) rename hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/{out => outbound}/GoodOutputPort.java (90%) diff --git a/hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java b/hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java index 5690ae0..2a73e40 100644 --- a/hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java +++ b/hexagonal-spring-rules/src/test/java/com/arc_e_tect/gradle/architecture/spring/RulePackSelfTest.java @@ -82,8 +82,8 @@ void restoreOriginalArchitectureProperties() { void rulePackShouldPassAllCoreRulesWhenFixturesAreCompliant() { configure( COMPLIANT_BASE, - COMPLIANT_BASE + ".application.port.in..", - COMPLIANT_BASE + ".application.port.out..", + COMPLIANT_BASE + ".application.port.inbound..", + COMPLIANT_BASE + ".application.port.outbound..", COMPLIANT_BASE + ".domain.model..", COMPLIANT_BASE + ".adapters.web..," + COMPLIANT_BASE + ".adapters.persistence..", COMPLIANT_BASE + ".application.service.." @@ -119,8 +119,8 @@ void rulePackShouldPassAllCoreRulesWhenFixturesAreCompliant() { void springHexagonalArchitectureShouldPassServicesRuleWhenServiceImplementsItsOwnInPort() { configure( SERVICE_IMPLEMENTS_PORT_BASE, - SERVICE_IMPLEMENTS_PORT_BASE + ".application.port.in..", - SERVICE_IMPLEMENTS_PORT_BASE + ".application.port.out..", + SERVICE_IMPLEMENTS_PORT_BASE + ".application.port.inbound..", + SERVICE_IMPLEMENTS_PORT_BASE + ".application.port.outbound..", SERVICE_IMPLEMENTS_PORT_BASE + ".domain.model..", SERVICE_IMPLEMENTS_PORT_BASE + ".adapters..", SERVICE_IMPLEMENTS_PORT_BASE + ".application.service.." @@ -139,8 +139,8 @@ void springHexagonalArchitectureShouldPassServicesRuleWhenServiceImplementsItsOw void cycleFreedomTestShouldFailAdapterRuleWhenAdapterPackagesFormACycle() { configure( CYCLE_BASE, - CYCLE_BASE + ".application.port.in..", - CYCLE_BASE + ".application.port.out..", + CYCLE_BASE + ".application.port.inbound..", + CYCLE_BASE + ".application.port.outbound..", CYCLE_BASE + ".domain.model..", // Deliberately a floating "..X.." wildcard, not anchored to CYCLE_BASE like the // other fixture configuration in this file: this matches how the real Architecture @@ -165,8 +165,8 @@ void cycleFreedomTestShouldFailAdapterRuleWhenAdapterPackagesFormACycle() { void springHexagonalArchitectureShouldFailControllersRuleWhenControllerDependsOnService() { configure( SPRING_CONTROLLERS_BASE, - SPRING_CONTROLLERS_BASE + ".application.port.in..", - SPRING_CONTROLLERS_BASE + ".application.port.out..", + SPRING_CONTROLLERS_BASE + ".application.port.inbound..", + SPRING_CONTROLLERS_BASE + ".application.port.outbound..", SPRING_CONTROLLERS_BASE + ".domain.model..", SPRING_CONTROLLERS_BASE + ".adapters..", SPRING_CONTROLLERS_BASE + ".application.service.." @@ -184,8 +184,8 @@ void springHexagonalArchitectureShouldFailControllersRuleWhenControllerDependsOn void springHexagonalArchitectureShouldFailServicesRuleWhenServiceDependsOnRepository() { configure( SPRING_SERVICES_BASE, - SPRING_SERVICES_BASE + ".application.port.in..", - SPRING_SERVICES_BASE + ".application.port.out..", + SPRING_SERVICES_BASE + ".application.port.inbound..", + SPRING_SERVICES_BASE + ".application.port.outbound..", SPRING_SERVICES_BASE + ".domain.model..", SPRING_SERVICES_BASE + ".adapters..", SPRING_SERVICES_BASE + ".application.service.." @@ -203,8 +203,8 @@ void springHexagonalArchitectureShouldFailServicesRuleWhenServiceDependsOnReposi void springHexagonalArchitectureShouldFailRepositoryAccessRuleWhenRepositoryIsUsedOutsideAllowedLayers() { configure( SPRING_REPOSITORIES_BASE, - SPRING_REPOSITORIES_BASE + ".application.port.in..", - SPRING_REPOSITORIES_BASE + ".application.port.out..", + SPRING_REPOSITORIES_BASE + ".application.port.inbound..", + SPRING_REPOSITORIES_BASE + ".application.port.outbound..", SPRING_REPOSITORIES_BASE + ".domain.model..", SPRING_REPOSITORIES_BASE + ".adapters..", SPRING_REPOSITORIES_BASE + ".application.service.." @@ -222,8 +222,8 @@ void springHexagonalArchitectureShouldFailRepositoryAccessRuleWhenRepositoryIsUs void springHexagonalArchitectureShouldFailComponentLayerRuleWhenComponentIsOutsideHexagonalPackages() { configure( SPRING_COMPONENTS_BASE, - SPRING_COMPONENTS_BASE + ".application.port.in..", - SPRING_COMPONENTS_BASE + ".application.port.out..", + SPRING_COMPONENTS_BASE + ".application.port.inbound..", + SPRING_COMPONENTS_BASE + ".application.port.outbound..", SPRING_COMPONENTS_BASE + ".domain.model..", SPRING_COMPONENTS_BASE + ".adapters..", SPRING_COMPONENTS_BASE + ".application.service.." @@ -241,8 +241,8 @@ void springHexagonalArchitectureShouldFailComponentLayerRuleWhenComponentIsOutsi void domainIsolationShouldFailDomainDependencyRuleWhenDomainDependsOnPortContract() { configure( DOMAIN_DEPENDENCY_BASE, - DOMAIN_DEPENDENCY_BASE + ".application.port.in..", - DOMAIN_DEPENDENCY_BASE + ".application.port.out..", + DOMAIN_DEPENDENCY_BASE + ".application.port.inbound..", + DOMAIN_DEPENDENCY_BASE + ".application.port.outbound..", DOMAIN_DEPENDENCY_BASE + ".domain.model..", DOMAIN_DEPENDENCY_BASE + ".adapters..", DOMAIN_DEPENDENCY_BASE + ".application.service.." @@ -259,8 +259,8 @@ void domainIsolationShouldFailDomainDependencyRuleWhenDomainDependsOnPortContrac void domainIsolationShouldFailFrameworkDependencyRuleWhenDomainUsesSpringTypes() { configure( DOMAIN_FRAMEWORK_BASE, - DOMAIN_FRAMEWORK_BASE + ".application.port.in..", - DOMAIN_FRAMEWORK_BASE + ".application.port.out..", + DOMAIN_FRAMEWORK_BASE + ".application.port.inbound..", + DOMAIN_FRAMEWORK_BASE + ".application.port.outbound..", DOMAIN_FRAMEWORK_BASE + ".domain.model..", DOMAIN_FRAMEWORK_BASE + ".adapters..", DOMAIN_FRAMEWORK_BASE + ".application.service.." @@ -272,8 +272,8 @@ void domainIsolationShouldFailFrameworkDependencyRuleWhenDomainUsesSpringTypes() configure( DOMAIN_FRAMEWORK_BASE, - DOMAIN_FRAMEWORK_BASE + ".application.port.in..", - DOMAIN_FRAMEWORK_BASE + ".application.port.out..", + DOMAIN_FRAMEWORK_BASE + ".application.port.inbound..", + DOMAIN_FRAMEWORK_BASE + ".application.port.outbound..", DOMAIN_FRAMEWORK_BASE + ".domain.framework..", DOMAIN_FRAMEWORK_BASE + ".adapters..", DOMAIN_FRAMEWORK_BASE + ".application.service.." @@ -289,8 +289,8 @@ void domainIsolationShouldFailFrameworkDependencyRuleWhenDomainUsesSpringTypes() void domainIsolationShouldFailServiceStereotypeRuleWhenApplicationServiceIsAnnotatedWithService() { configure( DOMAIN_SERVICE_STEREOTYPE_BASE, - DOMAIN_SERVICE_STEREOTYPE_BASE + ".application.port.in..", - DOMAIN_SERVICE_STEREOTYPE_BASE + ".application.port.out..", + DOMAIN_SERVICE_STEREOTYPE_BASE + ".application.port.inbound..", + DOMAIN_SERVICE_STEREOTYPE_BASE + ".application.port.outbound..", DOMAIN_SERVICE_STEREOTYPE_BASE + ".domain.model..", DOMAIN_SERVICE_STEREOTYPE_BASE + ".adapters..", DOMAIN_SERVICE_STEREOTYPE_BASE + ".application.service.." @@ -307,8 +307,8 @@ void domainIsolationShouldFailServiceStereotypeRuleWhenApplicationServiceIsAnnot void dependencyDirectionShouldFailCoreRuleWhenCoreDependsOnAdapters() { configure( DEPENDENCY_CORE_BASE, - DEPENDENCY_CORE_BASE + ".application.port.in..", - DEPENDENCY_CORE_BASE + ".application.port.out..", + DEPENDENCY_CORE_BASE + ".application.port.inbound..", + DEPENDENCY_CORE_BASE + ".application.port.outbound..", DEPENDENCY_CORE_BASE + ".domain.model..", DEPENDENCY_CORE_BASE + ".adapters..", DEPENDENCY_CORE_BASE + ".application.service.." @@ -325,8 +325,8 @@ void dependencyDirectionShouldFailCoreRuleWhenCoreDependsOnAdapters() { void dependencyDirectionShouldFailCoreRuleWhenOnlySplitAdapterPropertiesAreConfigured() { configure( DEPENDENCY_CORE_BASE, - DEPENDENCY_CORE_BASE + ".application.port.in..", - DEPENDENCY_CORE_BASE + ".application.port.out..", + DEPENDENCY_CORE_BASE + ".application.port.inbound..", + DEPENDENCY_CORE_BASE + ".application.port.outbound..", DEPENDENCY_CORE_BASE + ".domain.model..", "", DEPENDENCY_CORE_BASE + ".application.service.." @@ -349,8 +349,8 @@ void dependencyDirectionShouldFailCoreRuleWhenOnlySplitAdapterPropertiesAreConfi void dependencyDirectionShouldFailAdapterRuleWhenAdapterDependsOnServiceImplementation() { configure( DEPENDENCY_ADAPTER_BASE, - DEPENDENCY_ADAPTER_BASE + ".application.port.in..", - DEPENDENCY_ADAPTER_BASE + ".application.port.out..", + DEPENDENCY_ADAPTER_BASE + ".application.port.inbound..", + DEPENDENCY_ADAPTER_BASE + ".application.port.outbound..", DEPENDENCY_ADAPTER_BASE + ".domain.model..", DEPENDENCY_ADAPTER_BASE + ".application.domain.service.adapter..", DEPENDENCY_ADAPTER_BASE + ".application.domain.service.impl.." @@ -367,8 +367,8 @@ void dependencyDirectionShouldFailAdapterRuleWhenAdapterDependsOnServiceImplemen void dependencyDirectionShouldFailConfigurationRuleWhenNonConfigurationDependsOnServiceImplementation() { configure( DEPENDENCY_NON_CONFIG_BASE, - DEPENDENCY_NON_CONFIG_BASE + ".application.port.in..", - DEPENDENCY_NON_CONFIG_BASE + ".application.port.out..", + DEPENDENCY_NON_CONFIG_BASE + ".application.port.inbound..", + DEPENDENCY_NON_CONFIG_BASE + ".application.port.outbound..", DEPENDENCY_NON_CONFIG_BASE + ".domain.model..", DEPENDENCY_NON_CONFIG_BASE + ".adapters..", DEPENDENCY_NON_CONFIG_BASE + ".application.service.." @@ -385,8 +385,8 @@ void dependencyDirectionShouldFailConfigurationRuleWhenNonConfigurationDependsOn void portContractShouldFailInputPortRuleWhenInputPortIsAConcreteClass() { configure( PORT_INPUT_BASE, - PORT_INPUT_BASE + ".application.port.in..", - PORT_INPUT_BASE + ".application.port.out..", + PORT_INPUT_BASE + ".application.port.inbound..", + PORT_INPUT_BASE + ".application.port.outbound..", PORT_INPUT_BASE + ".domain.model..", PORT_INPUT_BASE + ".adapters..", PORT_INPUT_BASE + ".application.service.." @@ -403,8 +403,8 @@ void portContractShouldFailInputPortRuleWhenInputPortIsAConcreteClass() { void portContractShouldFailOutputPortRuleWhenOutputPortIsAConcreteClass() { configure( PORT_OUTPUT_BASE, - PORT_OUTPUT_BASE + ".application.port.in..", - PORT_OUTPUT_BASE + ".application.port.out..", + PORT_OUTPUT_BASE + ".application.port.inbound..", + PORT_OUTPUT_BASE + ".application.port.outbound..", PORT_OUTPUT_BASE + ".domain.model..", PORT_OUTPUT_BASE + ".adapters..", PORT_OUTPUT_BASE + ".application.service.." @@ -421,8 +421,8 @@ void portContractShouldFailOutputPortRuleWhenOutputPortIsAConcreteClass() { void portContractShouldFailSignatureRuleWhenPortExposesFrameworkType() { configure( PORT_SIGNATURE_BASE, - PORT_SIGNATURE_BASE + ".application.port.in..", - PORT_SIGNATURE_BASE + ".application.port.out..", + PORT_SIGNATURE_BASE + ".application.port.inbound..", + PORT_SIGNATURE_BASE + ".application.port.outbound..", PORT_SIGNATURE_BASE + ".domain.model..", PORT_SIGNATURE_BASE + ".adapters..", PORT_SIGNATURE_BASE + ".application.service.." diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/persistence/OrderStoreRepositoryAdapter.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/persistence/OrderStoreRepositoryAdapter.java index 2872e7f..aaa932f 100644 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/persistence/OrderStoreRepositoryAdapter.java +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/persistence/OrderStoreRepositoryAdapter.java @@ -1,6 +1,6 @@ package com.arc_e_tect.fixtures.compliant.adapters.persistence; -import com.arc_e_tect.fixtures.compliant.application.port.out.OrderStorePort; +import com.arc_e_tect.fixtures.compliant.application.port.outbound.OrderStorePort; import org.springframework.stereotype.Repository; @Repository diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/web/CreateOrderController.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/web/CreateOrderController.java index ce65d62..38ad82b 100644 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/web/CreateOrderController.java +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/adapters/web/CreateOrderController.java @@ -1,6 +1,6 @@ package com.arc_e_tect.fixtures.compliant.adapters.web; -import com.arc_e_tect.fixtures.compliant.application.port.in.CreateOrderUseCase; +import com.arc_e_tect.fixtures.compliant.application.port.inbound.CreateOrderUseCase; import org.springframework.stereotype.Controller; @Controller diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/in/CreateOrderUseCase.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/in/CreateOrderUseCase.java deleted file mode 100644 index 6f2607f..0000000 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/in/CreateOrderUseCase.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.arc_e_tect.fixtures.compliant.application.port.in; - -public interface CreateOrderUseCase { - void create(); -} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/inbound/CreateOrderUseCase.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/inbound/CreateOrderUseCase.java new file mode 100644 index 0000000..b99d18f --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/inbound/CreateOrderUseCase.java @@ -0,0 +1,5 @@ +package com.arc_e_tect.fixtures.compliant.application.port.inbound; + +public interface CreateOrderUseCase { + void create(); +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/out/OrderStorePort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/out/OrderStorePort.java deleted file mode 100644 index a9e6b07..0000000 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/out/OrderStorePort.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.arc_e_tect.fixtures.compliant.application.port.out; - -public interface OrderStorePort { - void save(); -} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/outbound/OrderStorePort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/outbound/OrderStorePort.java new file mode 100644 index 0000000..32361b0 --- /dev/null +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/port/outbound/OrderStorePort.java @@ -0,0 +1,5 @@ +package com.arc_e_tect.fixtures.compliant.application.port.outbound; + +public interface OrderStorePort { + void save(); +} diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/service/CreateOrderService.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/service/CreateOrderService.java index 422f889..e1a57d8 100644 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/service/CreateOrderService.java +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/application/service/CreateOrderService.java @@ -1,7 +1,7 @@ package com.arc_e_tect.fixtures.compliant.application.service; -import com.arc_e_tect.fixtures.compliant.application.port.in.CreateOrderUseCase; -import com.arc_e_tect.fixtures.compliant.application.port.out.OrderStorePort; +import com.arc_e_tect.fixtures.compliant.application.port.inbound.CreateOrderUseCase; +import com.arc_e_tect.fixtures.compliant.application.port.outbound.OrderStorePort; public class CreateOrderService implements CreateOrderUseCase { diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/configuration/ApplicationConfiguration.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/configuration/ApplicationConfiguration.java index 3f3dd78..107a7d6 100644 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/configuration/ApplicationConfiguration.java +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/compliant/configuration/ApplicationConfiguration.java @@ -1,6 +1,6 @@ package com.arc_e_tect.fixtures.compliant.configuration; -import com.arc_e_tect.fixtures.compliant.application.port.out.OrderStorePort; +import com.arc_e_tect.fixtures.compliant.application.port.outbound.OrderStorePort; import com.arc_e_tect.fixtures.compliant.application.service.CreateOrderService; public class ApplicationConfiguration { diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/in/CreateOrderUseCase.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/inbound/CreateOrderUseCase.java similarity index 82% rename from hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/in/CreateOrderUseCase.java rename to hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/inbound/CreateOrderUseCase.java index 04d9728..d241cbd 100644 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/in/CreateOrderUseCase.java +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/inbound/CreateOrderUseCase.java @@ -1,4 +1,4 @@ -package com.arc_e_tect.fixtures.regression.serviceImplementsPort.application.port.in; +package com.arc_e_tect.fixtures.regression.serviceImplementsPort.application.port.inbound; public interface CreateOrderUseCase { diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/out/OrderStorePort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/outbound/OrderStorePort.java similarity index 80% rename from hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/out/OrderStorePort.java rename to hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/outbound/OrderStorePort.java index 7b0bdd8..3a0d0a0 100644 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/out/OrderStorePort.java +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/port/outbound/OrderStorePort.java @@ -1,4 +1,4 @@ -package com.arc_e_tect.fixtures.regression.serviceImplementsPort.application.port.out; +package com.arc_e_tect.fixtures.regression.serviceImplementsPort.application.port.outbound; public interface OrderStorePort { diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/service/CreateOrderService.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/service/CreateOrderService.java index 404427d..69b793b 100644 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/service/CreateOrderService.java +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/regression/serviceImplementsPort/application/service/CreateOrderService.java @@ -1,7 +1,7 @@ package com.arc_e_tect.fixtures.regression.serviceImplementsPort.application.service; -import com.arc_e_tect.fixtures.regression.serviceImplementsPort.application.port.in.CreateOrderUseCase; -import com.arc_e_tect.fixtures.regression.serviceImplementsPort.application.port.out.OrderStorePort; +import com.arc_e_tect.fixtures.regression.serviceImplementsPort.application.port.inbound.CreateOrderUseCase; +import com.arc_e_tect.fixtures.regression.serviceImplementsPort.application.port.outbound.OrderStorePort; import org.springframework.stereotype.Service; /** diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/application/port/out/ForbiddenPort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/application/port/outbound/ForbiddenPort.java similarity index 80% rename from hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/application/port/out/ForbiddenPort.java rename to hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/application/port/outbound/ForbiddenPort.java index 5f090b7..66bdee8 100644 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/application/port/out/ForbiddenPort.java +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/application/port/outbound/ForbiddenPort.java @@ -1,4 +1,4 @@ -package com.arc_e_tect.fixtures.violating.domain.domainDependency.application.port.out; +package com.arc_e_tect.fixtures.violating.domain.domainDependency.application.port.outbound; public interface ForbiddenPort { void send(); diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/domain/model/BadDomain.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/domain/model/BadDomain.java index c4e6986..b730082 100644 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/domain/model/BadDomain.java +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/domain/domainDependency/domain/model/BadDomain.java @@ -1,6 +1,6 @@ package com.arc_e_tect.fixtures.violating.domain.domainDependency.domain.model; -import com.arc_e_tect.fixtures.violating.domain.domainDependency.application.port.out.ForbiddenPort; +import com.arc_e_tect.fixtures.violating.domain.domainDependency.application.port.outbound.ForbiddenPort; public class BadDomain { diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/in/NotInterfaceInputPort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/inbound/NotInterfaceInputPort.java similarity index 91% rename from hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/in/NotInterfaceInputPort.java rename to hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/inbound/NotInterfaceInputPort.java index adeece5..137402d 100644 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/in/NotInterfaceInputPort.java +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/inbound/NotInterfaceInputPort.java @@ -1,4 +1,4 @@ -package com.arc_e_tect.fixtures.violating.port.inputNotInterface.application.port.in; +package com.arc_e_tect.fixtures.violating.port.inputNotInterface.application.port.inbound; import com.arc_e_tect.fixtures.violating.port.inputNotInterface.domain.model.PortDomain; diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/out/GoodOutputPort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/outbound/GoodOutputPort.java similarity index 88% rename from hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/out/GoodOutputPort.java rename to hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/outbound/GoodOutputPort.java index 341f486..f05af9e 100644 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/out/GoodOutputPort.java +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/inputNotInterface/application/port/outbound/GoodOutputPort.java @@ -1,4 +1,4 @@ -package com.arc_e_tect.fixtures.violating.port.inputNotInterface.application.port.out; +package com.arc_e_tect.fixtures.violating.port.inputNotInterface.application.port.outbound; import com.arc_e_tect.fixtures.violating.port.inputNotInterface.domain.model.PortDomain; diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/in/GoodInputPort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/inbound/GoodInputPort.java similarity index 89% rename from hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/in/GoodInputPort.java rename to hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/inbound/GoodInputPort.java index df03f0b..bed941d 100644 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/in/GoodInputPort.java +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/inbound/GoodInputPort.java @@ -1,4 +1,4 @@ -package com.arc_e_tect.fixtures.violating.port.outputNotInterface.application.port.in; +package com.arc_e_tect.fixtures.violating.port.outputNotInterface.application.port.inbound; import com.arc_e_tect.fixtures.violating.port.outputNotInterface.domain.model.PortDomain; diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/out/NotInterfaceOutputPort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/outbound/NotInterfaceOutputPort.java similarity index 90% rename from hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/out/NotInterfaceOutputPort.java rename to hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/outbound/NotInterfaceOutputPort.java index 08d911f..73250da 100644 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/out/NotInterfaceOutputPort.java +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/outputNotInterface/application/port/outbound/NotInterfaceOutputPort.java @@ -1,4 +1,4 @@ -package com.arc_e_tect.fixtures.violating.port.outputNotInterface.application.port.out; +package com.arc_e_tect.fixtures.violating.port.outputNotInterface.application.port.outbound; import com.arc_e_tect.fixtures.violating.port.outputNotInterface.domain.model.PortDomain; diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/in/LeakyInputPort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/inbound/LeakyInputPort.java similarity index 89% rename from hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/in/LeakyInputPort.java rename to hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/inbound/LeakyInputPort.java index e230132..cf425ba 100644 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/in/LeakyInputPort.java +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/inbound/LeakyInputPort.java @@ -1,4 +1,4 @@ -package com.arc_e_tect.fixtures.violating.port.signatureLeak.application.port.in; +package com.arc_e_tect.fixtures.violating.port.signatureLeak.application.port.inbound; import org.springframework.context.ApplicationContext; diff --git a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/out/GoodOutputPort.java b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/outbound/GoodOutputPort.java similarity index 90% rename from hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/out/GoodOutputPort.java rename to hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/outbound/GoodOutputPort.java index ecbff8c..4a213c8 100644 --- a/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/out/GoodOutputPort.java +++ b/hexagonal-spring-rules/src/testFixtures/java/com/arc_e_tect/fixtures/violating/port/signatureLeak/application/port/outbound/GoodOutputPort.java @@ -1,4 +1,4 @@ -package com.arc_e_tect.fixtures.violating.port.signatureLeak.application.port.out; +package com.arc_e_tect.fixtures.violating.port.signatureLeak.application.port.outbound; import com.arc_e_tect.fixtures.violating.port.signatureLeak.domain.model.PortDomain;