From a583ea9dbe7ac0e6e69cc7f4261a724469bdf0b4 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Fri, 20 Feb 2026 08:41:18 +0100 Subject: [PATCH 01/17] Add ArchUnit dependency --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 34bfbeba..c170aee8 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,12 @@ ${mockito.version} test + + com.tngtech.archunit + archunit-junit5 + 1.4.1 + test + org.yaml snakeyaml From 59b13976a470c33365c0be682a736c291e4527f7 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Fri, 20 Feb 2026 09:36:41 +0100 Subject: [PATCH 02/17] Create ArchitectureTest --- src/test/java/org/juv25d/ArchitectureTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/test/java/org/juv25d/ArchitectureTest.java diff --git a/src/test/java/org/juv25d/ArchitectureTest.java b/src/test/java/org/juv25d/ArchitectureTest.java new file mode 100644 index 00000000..c5981708 --- /dev/null +++ b/src/test/java/org/juv25d/ArchitectureTest.java @@ -0,0 +1,13 @@ +package org.juv25d; + +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import org.junit.jupiter.api.Test; + +@AnalyzeClasses(packages = "org.juv25d") +public class ArchitectureTest { + + @ArchTest + void ArchTest() + {} +} From 442c6a5a97c76489b88332338f18bf7e333bdd02 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Fri, 20 Feb 2026 15:02:58 +0100 Subject: [PATCH 03/17] Create initial ArchRule --- .../java/org/juv25d/ArchitectureTest.java | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/juv25d/ArchitectureTest.java b/src/test/java/org/juv25d/ArchitectureTest.java index c5981708..091c1c1a 100644 --- a/src/test/java/org/juv25d/ArchitectureTest.java +++ b/src/test/java/org/juv25d/ArchitectureTest.java @@ -2,12 +2,37 @@ import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; +import com.tngtech.archunit.library.Architectures; import org.junit.jupiter.api.Test; +import static com.tngtech.archunit.library.Architectures.layeredArchitecture; + @AnalyzeClasses(packages = "org.juv25d") public class ArchitectureTest { + @ArchTest - void ArchTest() - {} + static final ArchRule rule = + ArchRuleDefinition.classes() + .that().haveSimpleName("ConnectionHandler") + .should().onlyBeAccessed().byClassesThat() + .haveSimpleName("Server") + .orShould().haveSimpleName("ConnectionHandler") + .orShould().haveSimpleName("DefaultConnectionHandlerFactory") + .orShould().haveSimpleName("ConnectionHandlerFactory"); } + +// @ArchTest +// public static final ArchRule lifecycleArchitecture = layeredArchitecture() +// .consideringAllDependencies() +// +// .layer("Server").definedBy("org.juv25d.Server..") +// .layer("ConnectionHandler").definedBy("org.juv25d.ConnectionHandler..") +// +// .whereLayer("ConnectionHandler").mayOnlyBeAccessedByLayers("Server"); +// +// +// +//} From 583087c7f575967c9d59212711bf4b43735c86c4 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Sun, 22 Feb 2026 13:30:05 +0100 Subject: [PATCH 04/17] Update connectionHandlerAccessRule test --- .../java/org/juv25d/ArchitectureTest.java | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/test/java/org/juv25d/ArchitectureTest.java b/src/test/java/org/juv25d/ArchitectureTest.java index 091c1c1a..b261b262 100644 --- a/src/test/java/org/juv25d/ArchitectureTest.java +++ b/src/test/java/org/juv25d/ArchitectureTest.java @@ -4,35 +4,26 @@ import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; -import com.tngtech.archunit.library.Architectures; -import org.junit.jupiter.api.Test; -import static com.tngtech.archunit.library.Architectures.layeredArchitecture; + +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName; + @AnalyzeClasses(packages = "org.juv25d") public class ArchitectureTest { @ArchTest - static final ArchRule rule = + static final ArchRule connectionHandlerAccessRule = ArchRuleDefinition.classes() .that().haveSimpleName("ConnectionHandler") - .should().onlyBeAccessed().byClassesThat() - .haveSimpleName("Server") - .orShould().haveSimpleName("ConnectionHandler") - .orShould().haveSimpleName("DefaultConnectionHandlerFactory") - .orShould().haveSimpleName("ConnectionHandlerFactory"); + .should().onlyBeAccessed().byClassesThat( + simpleName("Server") + .or(simpleName("ConnectionHandler")) + .or(simpleName("DefaultConnectionHandlerFactory")) + .or(simpleName("ConnectionHandlerFactory"))) + .as("ConnectionHandler access rule") + .because("connectionHandler should only be accessed by server") + ; } -// @ArchTest -// public static final ArchRule lifecycleArchitecture = layeredArchitecture() -// .consideringAllDependencies() -// -// .layer("Server").definedBy("org.juv25d.Server..") -// .layer("ConnectionHandler").definedBy("org.juv25d.ConnectionHandler..") -// -// .whereLayer("ConnectionHandler").mayOnlyBeAccessedByLayers("Server"); -// -// -// -//} From d55c02117f3843c58bcadaf20f2a79838bd1dc82 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Sun, 22 Feb 2026 14:05:02 +0100 Subject: [PATCH 05/17] Add class description --- .../java/org/juv25d/ArchitectureTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/test/java/org/juv25d/ArchitectureTest.java b/src/test/java/org/juv25d/ArchitectureTest.java index b261b262..7c4202fd 100644 --- a/src/test/java/org/juv25d/ArchitectureTest.java +++ b/src/test/java/org/juv25d/ArchitectureTest.java @@ -8,6 +8,32 @@ import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName; +/** + * These tests ensure that classes only depend on other classes according to the intended architecture. + * They enforce strict boundaries between components and prevent unintended coupling or dependency violations. + * + * The request lifecycle is designed to follow this strict flow: + * + * Client + * ↓ + * ServerSocket + * ↓ + * ConnectionHandler (Virtual Thread) + * ↓ + * Pipeline + * ↓ + * FilterChain + * ↓ + * Router + * ↓ + * Plugin + * ↓ + * HttpResponseWriter + * ↓ + * Client + * + * Each component has a clearly defined responsibility and must not violate the intended direction of dependencies. + */ @AnalyzeClasses(packages = "org.juv25d") public class ArchitectureTest { From 97ae30f0f10093e4795c65bf0987458df465dc63 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Sun, 22 Feb 2026 20:40:41 +0100 Subject: [PATCH 06/17] Create pipelineAccessRule --- .../java/org/juv25d/ArchitectureTest.java | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/juv25d/ArchitectureTest.java b/src/test/java/org/juv25d/ArchitectureTest.java index 7c4202fd..9f52458d 100644 --- a/src/test/java/org/juv25d/ArchitectureTest.java +++ b/src/test/java/org/juv25d/ArchitectureTest.java @@ -1,5 +1,6 @@ package org.juv25d; +import com.tngtech.archunit.core.importer.ImportOption; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchRule; @@ -35,7 +36,10 @@ * Each component has a clearly defined responsibility and must not violate the intended direction of dependencies. */ -@AnalyzeClasses(packages = "org.juv25d") +@AnalyzeClasses( +packages = "org.juv25d", +importOptions = ImportOption.Predefined.DoNotIncludeTests.class) + public class ArchitectureTest { @@ -44,12 +48,24 @@ public class ArchitectureTest { ArchRuleDefinition.classes() .that().haveSimpleName("ConnectionHandler") .should().onlyBeAccessed().byClassesThat( - simpleName("Server") - .or(simpleName("ConnectionHandler")) - .or(simpleName("DefaultConnectionHandlerFactory")) - .or(simpleName("ConnectionHandlerFactory"))) + simpleName("Server") + .or(simpleName("ConnectionHandler")) + .or(simpleName("DefaultConnectionHandlerFactory")) + .or(simpleName("ConnectionHandlerFactory"))) .as("ConnectionHandler access rule") - .because("connectionHandler should only be accessed by server") - ; + .because("connectionHandler should only be accessed by server"); + + @ArchTest + static final ArchRule pipelineAccessRule = + ArchRuleDefinition.classes() + .that().haveSimpleName("Pipeline") + .should().onlyBeAccessed().byClassesThat( + simpleName("ConnectionHandler") + .or(simpleName("ConnectionHandlerFactory")) + .or(simpleName("Pipeline")) + .or(simpleName("Server"))) //TODO right now server creates pipeline. Shold this be handled by connectionHandler instead to keep the strict flow? + .as("Pipeline access rule") + .because("Pipeline should only be accessed by ConnectionHandler"); } + From 612a2dd17c3791326d4a0f216d67fc0b9e22ad09 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Mon, 23 Feb 2026 10:00:39 +0100 Subject: [PATCH 07/17] Create filterChainRule --- src/test/java/org/juv25d/ArchitectureTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/java/org/juv25d/ArchitectureTest.java b/src/test/java/org/juv25d/ArchitectureTest.java index 9f52458d..402b13ac 100644 --- a/src/test/java/org/juv25d/ArchitectureTest.java +++ b/src/test/java/org/juv25d/ArchitectureTest.java @@ -66,6 +66,19 @@ public class ArchitectureTest { .or(simpleName("Server"))) //TODO right now server creates pipeline. Shold this be handled by connectionHandler instead to keep the strict flow? .as("Pipeline access rule") .because("Pipeline should only be accessed by ConnectionHandler"); + + @ArchTest + static final ArchRule filterChainRule = + ArchRuleDefinition.classes() + .that().haveSimpleName("filterChain") + .should().onlyBeAccessed().byClassesThat( + simpleName("Pipeline") + .or(simpleName("FilterChain")) + .or(simpleName("FilterChainImpl")) + .or(simpleName("ConnectionHandler"))) // TODO This needs to be accessed because connectionhandler creates doFilter() + .as("FilterChain access rule") + .because("FilterChain should only be accessed by Pipeline"); + } From f2cdee4cd0b8caadd579e2ccedc9a2b425ecd1b8 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Mon, 23 Feb 2026 10:09:01 +0100 Subject: [PATCH 08/17] Create routerRule --- src/test/java/org/juv25d/ArchitectureTest.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/juv25d/ArchitectureTest.java b/src/test/java/org/juv25d/ArchitectureTest.java index 402b13ac..5de7f23f 100644 --- a/src/test/java/org/juv25d/ArchitectureTest.java +++ b/src/test/java/org/juv25d/ArchitectureTest.java @@ -70,7 +70,7 @@ public class ArchitectureTest { @ArchTest static final ArchRule filterChainRule = ArchRuleDefinition.classes() - .that().haveSimpleName("filterChain") + .that().haveSimpleName("FilterChain") .should().onlyBeAccessed().byClassesThat( simpleName("Pipeline") .or(simpleName("FilterChain")) @@ -79,6 +79,19 @@ public class ArchitectureTest { .as("FilterChain access rule") .because("FilterChain should only be accessed by Pipeline"); + @ArchTest + static final ArchRule routerRule = + ArchRuleDefinition.classes() + .that().haveSimpleName("Router") + .should().onlyBeAccessed().byClassesThat( + simpleName("FilterChain") + .or(simpleName("FilterChainImpl")) + .or(simpleName("Router")) + .or(simpleName("Pipeline")) //TODO Pipeline injects router + .or(simpleName("App"))) //TODO App Creates router + .as("Router access rule") + .because("Router should only be accessed by FilterChain"); + } From 9844753a8dab172656d9b60716e0e4c6a369a9b2 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Mon, 23 Feb 2026 10:45:21 +0100 Subject: [PATCH 09/17] Update filterChainRule and pipelineAccessRule --- src/test/java/org/juv25d/ArchitectureTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/java/org/juv25d/ArchitectureTest.java b/src/test/java/org/juv25d/ArchitectureTest.java index 5de7f23f..e450d353 100644 --- a/src/test/java/org/juv25d/ArchitectureTest.java +++ b/src/test/java/org/juv25d/ArchitectureTest.java @@ -7,6 +7,7 @@ import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName; /** @@ -63,6 +64,7 @@ public class ArchitectureTest { simpleName("ConnectionHandler") .or(simpleName("ConnectionHandlerFactory")) .or(simpleName("Pipeline")) + .or(simpleName("App")) // App handles bootstrapping and wiring of the Pipeline during startup. This should stay. .or(simpleName("Server"))) //TODO right now server creates pipeline. Shold this be handled by connectionHandler instead to keep the strict flow? .as("Pipeline access rule") .because("Pipeline should only be accessed by ConnectionHandler"); @@ -75,6 +77,7 @@ public class ArchitectureTest { simpleName("Pipeline") .or(simpleName("FilterChain")) .or(simpleName("FilterChainImpl")) + .or(resideInAPackage("..filter..")) .or(simpleName("ConnectionHandler"))) // TODO This needs to be accessed because connectionhandler creates doFilter() .as("FilterChain access rule") .because("FilterChain should only be accessed by Pipeline"); From 8e1e74f64335232c7f5fc032dc9082a7f088660a Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Mon, 23 Feb 2026 11:22:04 +0100 Subject: [PATCH 10/17] Create pluginRule and update docs --- .../java/org/juv25d/ArchitectureTest.java | 65 +++++++++++++++---- 1 file changed, 52 insertions(+), 13 deletions(-) diff --git a/src/test/java/org/juv25d/ArchitectureTest.java b/src/test/java/org/juv25d/ArchitectureTest.java index e450d353..6c171657 100644 --- a/src/test/java/org/juv25d/ArchitectureTest.java +++ b/src/test/java/org/juv25d/ArchitectureTest.java @@ -11,10 +11,12 @@ import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName; /** - * These tests ensure that classes only depend on other classes according to the intended architecture. - * They enforce strict boundaries between components and prevent unintended coupling or dependency violations. + * This system follows a specific lifecycle where an HTTP request is processed through specialized layers. + * To maintain a clean architecture, the core principle is that lower layers must never depend on higher layers. + * While the flow is not strictly linear—for instance, filters must call the chain to proceed—these rules ensure that + * dependencies only move in authorized directions and that components do not "skip" steps unnecessarily * - * The request lifecycle is designed to follow this strict flow: + * The request lifecycle is designed to follow this strict flow: (Runtime Flow) * * Client * ↓ @@ -34,7 +36,9 @@ * ↓ * Client * - * Each component has a clearly defined responsibility and must not violate the intended direction of dependencies. + * Note: This describes the runtime execution flow, not direct code dependencies. + * Dependencies are allowed for bootstrapping and controlled object creation, + * but must never violate the downward lifecycle direction. */ @AnalyzeClasses( @@ -44,6 +48,10 @@ public class ArchitectureTest { + /** + * This rule ensures that only the Server and its associated factories can initiate a ConnectionHandler. + * This prevents other parts of the application from accidentally manipulating direct client connections. + */ @ArchTest static final ArchRule connectionHandlerAccessRule = ArchRuleDefinition.classes() @@ -56,6 +64,11 @@ public class ArchitectureTest { .as("ConnectionHandler access rule") .because("connectionHandler should only be accessed by server"); + + /** + * Only the network layer (ConnectionHandler) or the application's startup class (App) may interact with the Pipeline. + * This guarantees that the execution chain remains intact and is not modified during an active request. + */ @ArchTest static final ArchRule pipelineAccessRule = ArchRuleDefinition.classes() @@ -66,9 +79,14 @@ public class ArchitectureTest { .or(simpleName("Pipeline")) .or(simpleName("App")) // App handles bootstrapping and wiring of the Pipeline during startup. This should stay. .or(simpleName("Server"))) //TODO right now server creates pipeline. Shold this be handled by connectionHandler instead to keep the strict flow? - .as("Pipeline access rule") - .because("Pipeline should only be accessed by ConnectionHandler"); + .as("Pipeline access rule") + .because("Pipeline should only be accessed by ConnectionHandler"); + + /** + * The FilterChain is created by the Pipeline and triggered by the ConnectionHandler. + * This rule also allows individual filters to access the chain. + */ @ArchTest static final ArchRule filterChainRule = ArchRuleDefinition.classes() @@ -78,10 +96,15 @@ public class ArchitectureTest { .or(simpleName("FilterChain")) .or(simpleName("FilterChainImpl")) .or(resideInAPackage("..filter..")) - .or(simpleName("ConnectionHandler"))) // TODO This needs to be accessed because connectionhandler creates doFilter() - .as("FilterChain access rule") - .because("FilterChain should only be accessed by Pipeline"); + .or(simpleName("ConnectionHandler"))) //This needs to be accessed because connectionhandler creates doFilter() + .as("FilterChain access rule") + .because("FilterChain should only be accessed by Pipeline"); + + /** + * The Router should only be accessed by the FilterChain to determine which plugin to execute, + * or by App and Pipeline during the system's bootstrapping phase. + */ @ArchTest static final ArchRule routerRule = ArchRuleDefinition.classes() @@ -90,11 +113,27 @@ public class ArchitectureTest { simpleName("FilterChain") .or(simpleName("FilterChainImpl")) .or(simpleName("Router")) - .or(simpleName("Pipeline")) //TODO Pipeline injects router - .or(simpleName("App"))) //TODO App Creates router - .as("Router access rule") - .because("Router should only be accessed by FilterChain"); + .or(simpleName("Pipeline")) //Pipeline injects router + .or(simpleName("App"))) //App Creates router + .as("Router access rule") + .because("Router should only be accessed by FilterChain"); + + /** + * Plugins must only be instantiated by App at startup and subsequently called by the router or + * the execution chain (FilterChainImpl). + */ + @ArchTest + static final ArchRule pluginRule = + ArchRuleDefinition.classes() + .that().resideInAPackage("..plugin..") + .should().onlyBeAccessed().byClassesThat( + resideInAPackage("..router..") + .or(resideInAPackage("..plugin..")) + .or(simpleName("App")) //App creates plugin + .or(simpleName("FilterChainImpl"))) //FilterChainImpl calls the plugin after the router has decided which one to run. + .as("Plugin access rule") + .because("Plugins should only be managed by the Router or during startup"); } From e13dcf7bf93d06df86b6d6af04819545efd0fb66 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Mon, 23 Feb 2026 11:27:54 +0100 Subject: [PATCH 11/17] Update README.md Architecture Overview --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 86871819..b5abc7e3 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ Pipeline ↓ FilterChain ↓ +Router + ↓ Plugin ↓ HttpResponseWriter From 965fb6c6c6848e8dc2ca9da99eafeefc1ab81231 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Mon, 23 Feb 2026 16:52:08 +0100 Subject: [PATCH 12/17] Update pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c170aee8..a86081ee 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ com.tngtech.archunit - archunit-junit5 + archunit 1.4.1 test From e1c15f3352357707e2b5fc66291d59d184b95d16 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Mon, 23 Feb 2026 17:14:07 +0100 Subject: [PATCH 13/17] refactor: migrate ArchUnit tests from @ArchTest to explicit ClassFileImporter setup --- .../java/org/juv25d/ArchitectureTest.java | 61 ++++++++++++------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/src/test/java/org/juv25d/ArchitectureTest.java b/src/test/java/org/juv25d/ArchitectureTest.java index 6c171657..a67983a7 100644 --- a/src/test/java/org/juv25d/ArchitectureTest.java +++ b/src/test/java/org/juv25d/ArchitectureTest.java @@ -1,10 +1,12 @@ package org.juv25d; +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.core.importer.ImportOption; -import com.tngtech.archunit.junit.AnalyzeClasses; -import com.tngtech.archunit.junit.ArchTest; -import com.tngtech.archunit.lang.ArchRule; + import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage; @@ -41,19 +43,23 @@ * but must never violate the downward lifecycle direction. */ -@AnalyzeClasses( -packages = "org.juv25d", -importOptions = ImportOption.Predefined.DoNotIncludeTests.class) - public class ArchitectureTest { + private static JavaClasses importedClasses; + + @BeforeAll + static void setup() { + importedClasses = new ClassFileImporter() + .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) + .importPackages("org.juv25d"); + } /** * This rule ensures that only the Server and its associated factories can initiate a ConnectionHandler. * This prevents other parts of the application from accidentally manipulating direct client connections. */ - @ArchTest - static final ArchRule connectionHandlerAccessRule = + @Test + void connectionHandlerAccessRule () { ArchRuleDefinition.classes() .that().haveSimpleName("ConnectionHandler") .should().onlyBeAccessed().byClassesThat( @@ -62,15 +68,16 @@ public class ArchitectureTest { .or(simpleName("DefaultConnectionHandlerFactory")) .or(simpleName("ConnectionHandlerFactory"))) .as("ConnectionHandler access rule") - .because("connectionHandler should only be accessed by server"); - + .because("ConnectionHandler should only be accessed by server, connectionhandler or its factories") + .check(importedClasses); + } /** * Only the network layer (ConnectionHandler) or the application's startup class (App) may interact with the Pipeline. * This guarantees that the execution chain remains intact and is not modified during an active request. */ - @ArchTest - static final ArchRule pipelineAccessRule = + @Test + void pipelineAccessRule () { ArchRuleDefinition.classes() .that().haveSimpleName("Pipeline") .should().onlyBeAccessed().byClassesThat( @@ -80,15 +87,17 @@ public class ArchitectureTest { .or(simpleName("App")) // App handles bootstrapping and wiring of the Pipeline during startup. This should stay. .or(simpleName("Server"))) //TODO right now server creates pipeline. Shold this be handled by connectionHandler instead to keep the strict flow? .as("Pipeline access rule") - .because("Pipeline should only be accessed by ConnectionHandler"); + .because("Pipeline should only be accessed by ConnectionHandler") + .check(importedClasses); + } /** * The FilterChain is created by the Pipeline and triggered by the ConnectionHandler. * This rule also allows individual filters to access the chain. */ - @ArchTest - static final ArchRule filterChainRule = + @Test + void filterChainRule () { ArchRuleDefinition.classes() .that().haveSimpleName("FilterChain") .should().onlyBeAccessed().byClassesThat( @@ -98,15 +107,17 @@ public class ArchitectureTest { .or(resideInAPackage("..filter..")) .or(simpleName("ConnectionHandler"))) //This needs to be accessed because connectionhandler creates doFilter() .as("FilterChain access rule") - .because("FilterChain should only be accessed by Pipeline"); + .because("FilterChain should only be accessed by Pipeline") + .check(importedClasses); + } /** * The Router should only be accessed by the FilterChain to determine which plugin to execute, * or by App and Pipeline during the system's bootstrapping phase. */ - @ArchTest - static final ArchRule routerRule = + @Test + void routerRule () { ArchRuleDefinition.classes() .that().haveSimpleName("Router") .should().onlyBeAccessed().byClassesThat( @@ -116,15 +127,17 @@ public class ArchitectureTest { .or(simpleName("Pipeline")) //Pipeline injects router .or(simpleName("App"))) //App Creates router .as("Router access rule") - .because("Router should only be accessed by FilterChain"); + .because("Router should only be accessed by FilterChain") + .check(importedClasses); + } /** * Plugins must only be instantiated by App at startup and subsequently called by the router or * the execution chain (FilterChainImpl). */ - @ArchTest - static final ArchRule pluginRule = + @Test + void pluginRule () { ArchRuleDefinition.classes() .that().resideInAPackage("..plugin..") .should().onlyBeAccessed().byClassesThat( @@ -133,7 +146,9 @@ public class ArchitectureTest { .or(simpleName("App")) //App creates plugin .or(simpleName("FilterChainImpl"))) //FilterChainImpl calls the plugin after the router has decided which one to run. .as("Plugin access rule") - .because("Plugins should only be managed by the Router or during startup"); + .because("Plugins should only be managed by the Router or during startup") + .check(importedClasses); + } } From 3fa978b274dd116b79504642a1a6c11b7939f90d Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Mon, 23 Feb 2026 18:03:03 +0100 Subject: [PATCH 14/17] Update pipelineAccessRule to include DefaultConnectionHandlerFactory --- src/test/java/org/juv25d/ArchitectureTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/juv25d/ArchitectureTest.java b/src/test/java/org/juv25d/ArchitectureTest.java index a67983a7..f22b8447 100644 --- a/src/test/java/org/juv25d/ArchitectureTest.java +++ b/src/test/java/org/juv25d/ArchitectureTest.java @@ -83,6 +83,7 @@ void pipelineAccessRule () { .should().onlyBeAccessed().byClassesThat( simpleName("ConnectionHandler") .or(simpleName("ConnectionHandlerFactory")) + .or(simpleName("DefaultConnectionHandlerFactory")) .or(simpleName("Pipeline")) .or(simpleName("App")) // App handles bootstrapping and wiring of the Pipeline during startup. This should stay. .or(simpleName("Server"))) //TODO right now server creates pipeline. Shold this be handled by connectionHandler instead to keep the strict flow? From 5a1ff77a7b6ebc44f01d8f5e6bdb304f85fc1ef2 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Mon, 23 Feb 2026 18:18:55 +0100 Subject: [PATCH 15/17] Update pipelineAccessRule to include new Bootstrap class --- src/test/java/org/juv25d/ArchitectureTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/juv25d/ArchitectureTest.java b/src/test/java/org/juv25d/ArchitectureTest.java index f22b8447..736b9d30 100644 --- a/src/test/java/org/juv25d/ArchitectureTest.java +++ b/src/test/java/org/juv25d/ArchitectureTest.java @@ -86,9 +86,10 @@ void pipelineAccessRule () { .or(simpleName("DefaultConnectionHandlerFactory")) .or(simpleName("Pipeline")) .or(simpleName("App")) // App handles bootstrapping and wiring of the Pipeline during startup. This should stay. - .or(simpleName("Server"))) //TODO right now server creates pipeline. Shold this be handled by connectionHandler instead to keep the strict flow? + .or(simpleName("Server")) //TODO right now server creates pipeline. Shold this be handled by connectionHandler instead to keep the strict flow? + .or(simpleName("Bootstrap"))) .as("Pipeline access rule") - .because("Pipeline should only be accessed by ConnectionHandler") + .because("Pipeline should only be accessed by ConnectionHandler, App, Server, Bootstrap during setup") .check(importedClasses); } From c7a8e2cd2dc97b0103e9dce983a7ab86c5fa4c18 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Tue, 24 Feb 2026 11:25:21 +0100 Subject: [PATCH 16/17] Update pipelineAccessRule. Remove TODO decouple Server from Pipeline --- src/test/java/org/juv25d/ArchitectureTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/org/juv25d/ArchitectureTest.java b/src/test/java/org/juv25d/ArchitectureTest.java index 736b9d30..91f12c38 100644 --- a/src/test/java/org/juv25d/ArchitectureTest.java +++ b/src/test/java/org/juv25d/ArchitectureTest.java @@ -86,10 +86,9 @@ void pipelineAccessRule () { .or(simpleName("DefaultConnectionHandlerFactory")) .or(simpleName("Pipeline")) .or(simpleName("App")) // App handles bootstrapping and wiring of the Pipeline during startup. This should stay. - .or(simpleName("Server")) //TODO right now server creates pipeline. Shold this be handled by connectionHandler instead to keep the strict flow? .or(simpleName("Bootstrap"))) .as("Pipeline access rule") - .because("Pipeline should only be accessed by ConnectionHandler, App, Server, Bootstrap during setup") + .because("Pipeline should only be accessed by ConnectionHandler, App, Bootstrap during setup") .check(importedClasses); } From 2a7620a101178383cea9cdad68ee096ed670d096 Mon Sep 17 00:00:00 2001 From: Fiona Friberg Date: Wed, 25 Feb 2026 11:27:13 +0100 Subject: [PATCH 17/17] Create httpResponseWriterAccessRule --- .../java/org/juv25d/ArchitectureTest.java | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/juv25d/ArchitectureTest.java b/src/test/java/org/juv25d/ArchitectureTest.java index 91f12c38..6923bece 100644 --- a/src/test/java/org/juv25d/ArchitectureTest.java +++ b/src/test/java/org/juv25d/ArchitectureTest.java @@ -106,9 +106,9 @@ void filterChainRule () { .or(simpleName("FilterChain")) .or(simpleName("FilterChainImpl")) .or(resideInAPackage("..filter..")) - .or(simpleName("ConnectionHandler"))) //This needs to be accessed because connectionhandler creates doFilter() + .or(simpleName("ConnectionHandler"))) //This needs to be accessed because ConnectionHandler creates doFilter() .as("FilterChain access rule") - .because("FilterChain should only be accessed by Pipeline") + .because("FilterChain should only be accessed by Pipeline, ConnectionHandler") .check(importedClasses); } @@ -128,7 +128,7 @@ void routerRule () { .or(simpleName("Pipeline")) //Pipeline injects router .or(simpleName("App"))) //App Creates router .as("Router access rule") - .because("Router should only be accessed by FilterChain") + .because("Router should only be accessed by FilterChain, Pipeline, App") .check(importedClasses); } @@ -147,7 +147,24 @@ void pluginRule () { .or(simpleName("App")) //App creates plugin .or(simpleName("FilterChainImpl"))) //FilterChainImpl calls the plugin after the router has decided which one to run. .as("Plugin access rule") - .because("Plugins should only be managed by the Router or during startup") + .because("Plugins should only be managed by the Router, App, FilterChainImpl") + .check(importedClasses); + } + + /** + * The HttpResponseWriter is the final step in the request lifecycle, responsible for delivering the response to the client. + * This rule ensures that only the ConnectionHandler accesses it, guaranteeing controlled delivery and architectural integrity. + */ + @Test + void httpResponseWriterAccessRule() { + ArchRuleDefinition.classes() + .that().haveSimpleName("HttpResponseWriter") + .should().onlyBeAccessed().byClassesThat( + simpleName("ConnectionHandler") + .or(simpleName("HttpResponseWriter"))) + .as("HttpResponseWriter access rule") + .because("HttpResponseWriter is the final step in the lifecycle and should" + + "only be used by ConnectionHandler to ensure controlled delivery") .check(importedClasses); } }