Architecture
StackRivet is a modular monolith: one deployable Spring Boot application, split into modules with clear, enforced boundaries. It’s not a microservice mesh, and it’s not a loose admin template.
That lets a small team (or an AI coding tool) ship quickly without giving up the boundaries that keep a real enterprise project maintainable.
flowchart LR
Web["Vue 3 Admin"]
CLI["CLI"]
API["REST / OpenAPI"]
subgraph Backend["Backend modules"]
Sec["Auth and RBAC"]
Sys["System"]
Asset["Asset Service"]
Gen["Code Generator"]
Task["Async Tasks"]
end
DB[("MySQL / PostgreSQL")]
Store["S3 / OSS / Local"]
Redis[("Redis")]
Web --> API
CLI --> API
API --> Sec
API --> Sys
API --> Asset
API --> Gen
API --> Task
Sec --> DB
Sys --> DB
Gen --> DB
Task --> DB
Asset --> Store
API -.-> Redis
Why a modular monolith
Section titled “Why a modular monolith”| Goal | How the architecture serves it |
|---|---|
| Start a project in ~10 minutes | One process, one docker compose, one mvn spring-boot:run |
| Generate a working module in ~30 minutes | The code generator writes backend, frontend, menu, permissions, OpenAPI and tests together |
| Keep AI inside the lines | Module boundaries, layering and test rules are written down and machine-readable |
| Stay upgradeable after customization | Module manifests, Flyway migrations and template versions track what changed |
| Scale without a rewrite | Clear module seams mean you can extract a service when scale or ownership requires it |
You get the speed of a single codebase, with clear seams to split out a service once scale or team ownership justifies it.
Modules
Section titled “Modules”The backend is 16 Maven modules. The full list and responsibilities are in Project Structure; what matters architecturally is the dependency direction between them.
Dependency rules
Section titled “Dependency rules”These rules are not suggestions — they are enforced by ArchUnit tests in stackrivet-app, so a violation fails the build:
stackrivet-commondepends on no business module. It only holds shared primitives:R<T>,PageR<T>,TraceIdHolder,BusinessException, base entities.stackrivet-securitydoes not depend onstackrivet-systemor itssr_sys_*tables. The user/role data it needs for authentication comes through theSystemUserDirectorySPI defined incommon. Security stays decoupled from the system module’s storage.stackrivet-assetnever references business tables. It relates to your data only throughbizType,refIdand a permission SPI — so the file layer doesn’t get tangled into every feature’s schema.stackrivet-generatordoes not write core source directly. All output goes through templates and the module manifest, which is what keeps generated code reviewable and upgradeable.- Demo/example modules are never a dependency of a core module.
stackrivet-democonsumes public APIs; it cannot pollute the core in reverse.
The result is an acyclic dependency graph with security, system, audit, asset and the generator as independent, swappable modules.
Layering
Section titled “Layering”Every business module follows the same four layers, and requests flow in one direction only:
Controller → Application / Service → Domain → Infrastructure / Mapper| Layer | Responsibility | Must not |
|---|---|---|
| Controller | Param validation, permission annotations, OpenAPI annotations, response wrapping | Contain business rules; call a Mapper directly |
| Application / Service | Use-case orchestration, transactions, permission context, audit events | Return database entities to the client |
| Domain | Domain rules, state transitions, value objects | Depend on web or persistence frameworks |
| Infrastructure / Mapper | SQL, persistence, external adapters | Bypass the Service to reach the Controller |
| DTO / VO | Request and response models | Reuse Entities across the boundary |
The entity never leaves the persistence boundary — DTOs and VOs cross it instead. The code generator is held to the same rules: it will not emit a “Controller calls Mapper directly” shortcut.
Security by default
Section titled “Security by default”The architecture is default-deny:
- Unauthenticated requests get 401; unauthorized requests get 403.
- Every protected endpoint declares its permission; generated modules ship with permission annotations, not without.
- Five permission layers cover the surface: menu (navigation), button (UI actions), API (server entry), data (query scope) and asset (private file access).
See Security & RBAC for the full model.
Data and migrations
Section titled “Data and migrations”- Both MySQL 8.4 LTS and PostgreSQL 18.4 are supported in 1.0.
- All schema changes go through Flyway — no manual production changes that get documented afterwards.
- Tables use the
sr_prefix so they don’t collide when StackRivet lives inside a customer’s database. - Core tables carry
id,created_at,updated_at; auditable tables addcreated_by/updated_by; multi-tenant-ready tables carrytenant_id; soft-deletable tables carrydeleted/deleted_at.
Observability
Section titled “Observability”Every request carries a traceId, surfaced in the unified exception response so an error in the UI can be traced to a server log. Actuator, Micrometer and an optional OpenTelemetry profile are built in, and stackrivet doctor diagnoses common environment problems (JDK, Node, Docker, database, object storage, port conflicts).
Deployment shapes
Section titled “Deployment shapes”The same application runs from a laptop to an HA cluster:
| Shape | Topology |
|---|---|
| Local dev | Vite dev server + Spring Boot + Docker MySQL/PostgreSQL + MinIO |
| Small-team production | Nginx → static admin + Spring Boot app → managed database + S3/OSS |
| Enterprise | Load balancer → multiple app nodes → HA database + enterprise object storage + OIDC/SAML/LDAP |
Because the app aims to be stateless (uploads go straight to object storage, heavy work runs as async tasks, lists are paginated), scaling out is adding nodes behind a load balancer — not re-architecting.