-
Notifications
You must be signed in to change notification settings - Fork 0
Test/architecture test #121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
a583ea9
Add ArchUnit dependency
FionaSprinkles 59b1397
Create ArchitectureTest
FionaSprinkles 442c6a5
Create initial ArchRule
FionaSprinkles 583087c
Update connectionHandlerAccessRule test
FionaSprinkles d55c021
Add class description
FionaSprinkles 97ae30f
Create pipelineAccessRule
FionaSprinkles 612a2dd
Create filterChainRule
FionaSprinkles f2cdee4
Create routerRule
FionaSprinkles 9844753
Update filterChainRule and pipelineAccessRule
FionaSprinkles 8e1e74f
Create pluginRule and update docs
FionaSprinkles e13dcf7
Update README.md Architecture Overview
FionaSprinkles 965fb6c
Update pom.xml
FionaSprinkles e1c15f3
refactor: migrate ArchUnit tests from @ArchTest to explicit ClassFile…
FionaSprinkles 3fa978b
Update pipelineAccessRule to include DefaultConnectionHandlerFactory
FionaSprinkles 5a1ff77
Update pipelineAccessRule to include new Bootstrap class
FionaSprinkles c7a8e2c
Update pipelineAccessRule. Remove TODO decouple Server from Pipeline
FionaSprinkles 2a7620a
Create httpResponseWriterAccessRule
FionaSprinkles File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,6 +47,8 @@ Pipeline | |
| ↓ | ||
| FilterChain | ||
| ↓ | ||
| Router | ||
| ↓ | ||
| Plugin | ||
| ↓ | ||
| HttpResponseWriter | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| 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.lang.syntax.ArchRuleDefinition; | ||
| import org.junit.jupiter.api.BeforeAll; | ||
| import org.junit.jupiter.api.Test; | ||
|
FionaSprinkles marked this conversation as resolved.
|
||
|
|
||
|
|
||
| import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage; | ||
| import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName; | ||
|
|
||
| /** | ||
| * 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: (Runtime Flow) | ||
| * | ||
| * Client | ||
| * ↓ | ||
| * ServerSocket | ||
| * ↓ | ||
| * ConnectionHandler (Virtual Thread) | ||
| * ↓ | ||
| * Pipeline | ||
| * ↓ | ||
| * FilterChain | ||
| * ↓ | ||
| * Router | ||
| * ↓ | ||
| * Plugin | ||
| * ↓ | ||
| * HttpResponseWriter | ||
| * ↓ | ||
| * Client | ||
| * | ||
| * 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. | ||
| */ | ||
|
|
||
| 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. | ||
| */ | ||
| @Test | ||
| void connectionHandlerAccessRule () { | ||
| ArchRuleDefinition.classes() | ||
| .that().haveSimpleName("ConnectionHandler") | ||
| .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, 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. | ||
| */ | ||
| @Test | ||
| void pipelineAccessRule () { | ||
| ArchRuleDefinition.classes() | ||
| .that().haveSimpleName("Pipeline") | ||
| .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("Bootstrap"))) | ||
| .as("Pipeline access rule") | ||
| .because("Pipeline should only be accessed by ConnectionHandler, App, Bootstrap during setup") | ||
| .check(importedClasses); | ||
| } | ||
|
FionaSprinkles marked this conversation as resolved.
|
||
|
|
||
|
|
||
| /** | ||
| * The FilterChain is created by the Pipeline and triggered by the ConnectionHandler. | ||
| * This rule also allows individual filters to access the chain. | ||
| */ | ||
| @Test | ||
| void filterChainRule () { | ||
| ArchRuleDefinition.classes() | ||
| .that().haveSimpleName("FilterChain") | ||
| .should().onlyBeAccessed().byClassesThat( | ||
| simpleName("Pipeline") | ||
| .or(simpleName("FilterChain")) | ||
| .or(simpleName("FilterChainImpl")) | ||
| .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, ConnectionHandler") | ||
| .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. | ||
| */ | ||
| @Test | ||
| void routerRule () { | ||
| ArchRuleDefinition.classes() | ||
| .that().haveSimpleName("Router") | ||
| .should().onlyBeAccessed().byClassesThat( | ||
| simpleName("FilterChain") | ||
| .or(simpleName("FilterChainImpl")) | ||
| .or(simpleName("Router")) | ||
| .or(simpleName("Pipeline")) //Pipeline injects router | ||
| .or(simpleName("App"))) //App Creates router | ||
| .as("Router access rule") | ||
| .because("Router should only be accessed by FilterChain, Pipeline, App") | ||
| .check(importedClasses); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Plugins must only be instantiated by App at startup and subsequently called by the router or | ||
| * the execution chain (FilterChainImpl). | ||
| */ | ||
| @Test | ||
| void 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, 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); | ||
|
FionaSprinkles marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
FionaSprinkles marked this conversation as resolved.
|
||
|
|
||
|
|
||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.