Add archunit module: ArchUnit for Spring Boot - enforcing layered architecture as a unit test
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# ArchUnit for Spring Boot — Enforcing Layered Architecture as a Unit Test
|
||||
|
||||
Companion module for [ArchUnit for Spring Boot: Enforcing Layered Architecture as a Unit
|
||||
Test](https://ankurm.com/archunit-spring-boot-layered-architecture-unit-test/) on
|
||||
[ankurm.com](https://ankurm.com). Every code sample and every console transcript quoted in
|
||||
that post comes from the files in this directory — nothing was hand-typed into the article.
|
||||
|
||||
## Versions this was built and run against
|
||||
|
||||
| Component | Version | Notes |
|
||||
|---|---|---|
|
||||
| ArchUnit | **1.5.1** | current GA per `maven-metadata.xml` on Maven Central at the time of writing |
|
||||
| JUnit Jupiter / Platform | **6.1.3** | via `archunit-junit5`, which brings its own JUnit 5 extension |
|
||||
| Spring Framework | 7.0.9 | `spring-context` + `spring-webmvc` only — no `spring-boot-starter-web`, no embedded server, no `ApplicationContext` anywhere in this module |
|
||||
| JDK | **25 (Temurin, LTS)** | build and run; `maven.compiler.release` is set to 17 |
|
||||
| Maven | 3.9.11 | |
|
||||
| Maven Surefire Plugin | 3.5.2 | |
|
||||
|
||||
This module deliberately does not depend on any `spring-boot-starter-*` artifact. ArchUnit
|
||||
reads compiled `.class` files directly — it has never needed a running Spring context, and
|
||||
the whole point of the first section of the post is that none of these tests say
|
||||
`@SpringBootTest`.
|
||||
|
||||
## Quickstart
|
||||
|
||||
```bash
|
||||
mvn test # all four rules, no application ever starts, ~4s total
|
||||
mvn test -Dtest=LayeredArchitectureTest # just the three structural rules
|
||||
mvn test -Dtest=FreezingArchRuleTest # just the frozen-violation rule
|
||||
```
|
||||
|
||||
Gradle users: there is no `build.gradle.kts` in this module (unlike this repo's other
|
||||
modules) because the whole demonstration is Maven-plugin-agnostic — `archunit-junit5`
|
||||
plugs into the JUnit 5 engine the same way under either build tool, and the commands above
|
||||
are the part that matters.
|
||||
|
||||
## Source files
|
||||
|
||||
| File | Demonstrates |
|
||||
|---|---|
|
||||
| [`OrderRepository.java`](src/main/java/com/ankurm/tutorials/junit/archunit/repository/OrderRepository.java) / [`InMemoryOrderRepository.java`](src/main/java/com/ankurm/tutorials/junit/archunit/repository/InMemoryOrderRepository.java) | the repository layer — a `@Repository` with no framework beyond the stereotype annotation itself |
|
||||
| [`OrderStatusService.java`](src/main/java/com/ankurm/tutorials/junit/archunit/service/OrderStatusService.java) | the service layer — constructor injection only, which is what makes `noFieldInjection` meaningful rather than cosmetic |
|
||||
| [`OrderStatusController.java`](src/main/java/com/ankurm/tutorials/junit/archunit/controller/OrderStatusController.java) | the web layer — depends on the service layer only |
|
||||
| [`LegacyOrderExporter.java`](src/main/java/com/ankurm/tutorials/junit/archunit/service/LegacyOrderExporter.java) | a deliberately-kept pre-existing `throws Exception` violation — the thing `FreezingArchRuleTest` freezes rather than fixes |
|
||||
| [`LayeredArchitectureTest.java`](src/test/java/com/ankurm/tutorials/junit/archunit/LayeredArchitectureTest.java) | `layeredArchitecture()`, `noFields().should().beAnnotatedWith(Autowired.class)`, and `slices().should().beFreeOfCycles()` — three independent structural rules, all checked without starting anything |
|
||||
| [`FreezingArchRuleTest.java`](src/test/java/com/ankurm/tutorials/junit/archunit/FreezingArchRuleTest.java) | `FreezingArchRule.freeze(...)` wrapping a rule that would otherwise fail immediately, recording today's known violations as an accepted baseline |
|
||||
| [`archunit.properties`](src/test/resources/archunit.properties) | the three properties that control where the frozen-violation store lives and whether it may be created or updated |
|
||||
| [`archunit_store/`](archunit_store) | the frozen-violation store itself, committed to version control — this directory **is** the baseline the rule enforces against |
|
||||
|
||||
## Captured output
|
||||
|
||||
| File | What it shows |
|
||||
|---|---|
|
||||
| [`00-baseline-passing-run.txt`](docs/output/00-baseline-passing-run.txt) | the three structural rules, all green, ~1.3s, no `ApplicationContext` started |
|
||||
| [`01-layering-violation-failure.txt`](docs/output/01-layering-violation-failure.txt) | a real `layeredArchitecture()` failure, captured by temporarily adding a controller that bypassed the service layer, then deleting it |
|
||||
| [`02-freeze-run1-baseline-captured.txt`](docs/output/02-freeze-run1-baseline-captured.txt) | the first-ever run of `FreezingArchRuleTest`: no store existed yet, so it was created with today's one known violation already accepted |
|
||||
| [`03-freeze-run2-new-violation-caught.txt`](docs/output/03-freeze-run2-new-violation-caught.txt) | a second, genuinely new `throws Exception` added after the baseline was recorded — the failure names only the new offender, never the frozen one |
|
||||
| [`04-full-suite-final.txt`](docs/output/04-full-suite-final.txt) | all four rules, exactly as committed, green in under four seconds |
|
||||
|
||||
## A diagnostic note, not a defect
|
||||
|
||||
`LegacyOrderExporter.export()` still declares `throws Exception` in the committed code, and
|
||||
`noNewGenericExceptionsDeclared` in `FreezingArchRuleTest` still technically covers it. The
|
||||
build is green anyway, on purpose: `archunit_store/` records that specific violation as
|
||||
already known at the time the rule was turned on, and `freeze()` only fails a build on a
|
||||
violation that is *new* since that recording. This is the realistic case — a team adopting
|
||||
ArchUnit on a codebase that is not already clean — and the point the post's freezing section
|
||||
makes is that you do not have to choose between "fix two hundred existing violations today"
|
||||
and "don't bother enforcing the rule at all."
|
||||
|
||||
## License
|
||||
|
||||
MIT, matching the rest of this repository.
|
||||
Reference in New Issue
Block a user