Architecture
ArchForge is four sibling Git repositories. This page explains how the pieces fit together, how a request travels through the backend, and which CI gates keep implementations honest against contracts.
System context
Two Spring Boot apps are built from one backend codebase, each with its own sa-token realm and response style. The backend owns the contract: its generated OpenAPI file and enum registry (spec/) feed both frontends via generated TypeScript.
Key consequences of this layout:
- No cross-calls between the two apps.
server-webnever importsserver-admincode; an ArchUnit rule fails the build if that ever happens. - Frontends never hand-write API types.
pnpm gen:apiregenerates them fromopenapi.yaml, and CI fails when committed artifacts drift. - Deleted paths stay deleted.
/system/menuand/system/roleare gone from the contract and cannot reappear in a controller.
Request lifecycle (admin)
A typical authenticated admin request — token issuance, the refresh queue, and permission checks:
On the C-end the same job is done by StpWebUtil + WebAuthInterceptor; errors come back as RFC 9457 ProblemDetail with an ArchForge error code property instead of the admin envelope.
Backend module layering
One Gradle root, layered modules. Arrows point in the allowed dependency direction:
The layering is not a drawing — it is enforced by ArchitectureTest:
- controllers must not touch repositories directly,
- domain modules must not depend on server packages,
- starters must stay business-free,
- every rule fails when it matches zero classes (
failOnEmptyShould=true), so a typo'd package filter cannot silently disable architecture checks.
Contract-first workflow
The backend is the source of the contract: spec/openapi.yaml is generated from the running code and committed with the change that caused it. Every arrow below ends in a CI gate:
To add or change an endpoint: implement it behind controllers/services, run ./gradlew generateOpenApi and commit the regenerated spec/openapi.yaml; CI fails if the file is stale and oasdiff proves nothing broke for existing consumers. The clients then regenerate their types (pnpm gen:api).
Deployment topology
Development and production both start from docker/ compose files:
Details per environment live in the deployment guides (Docker, observability, production).
Quality gates summary
| Concern | Enforcement |
|---|---|
| Module boundaries | Spring Modulith verification + ArchUnit |
| Coverage | aggregated JaCoCo; changed-line coverage ≥ 60% on PRs |
| Test tiers | JUnit @Tag: P0 / P1 / contract / slow (container tests) |
| Contract drift | oasdiff + sdk-sync + enum-sync + error-code registry |
| Style | Spotless, JSpecify @NullMarked on every package |