Architecture
A modular monolith: one deployable Spring Boot application, with module boundaries enforced at compile time and in tests by Maven, Spring Modulith and ArchUnit.
1. Shape
Strata is deliberately a single process, single deployment unit. Horizontal concerns (authentication, rate limiting, idempotency, auditing, transactions) are Servlet filters, AOP aspects or Spring beans, never remote calls. The only optional distributed dependencies are PostgreSQL and Redis (login locks, token denylist, distributed locks).
Choosing a modular monolith over microservices keeps the operational surface small while the module boundaries stay
explicit: they are verified on every mvn verify, so a later extraction along a module seam remains possible.
2. Modules
| Module | What it contains |
|---|---|
strata-common-* |
technical kernel: web/result, security, data/JPA, Redis, metrics, SPI contracts |
strata-system-* |
system domain: RBAC, operations (logs, tasks, backups), configuration (dicts, params), communication |
strata-ai-* |
optional AI domain: models/providers, chat, knowledge bases, tooling |
strata-plugin-* |
optional capabilities behind SPI: storage, payments, LLM gateways, Excel, field encryption, workflows |
strata-generator |
code generator (development tool, disabled by default) |
strata-spring-boot-starter |
auto-configuration, framework schema migration, Swagger, actuator wiring |
Every domain is split into two Maven modules:
| Module | Content | May be depended on by |
|---|---|---|
*-api |
entities, DTOs, service ports, domain enums/exceptions | other domains' *-biz |
*-biz |
controllers, app services, repositories, mappers | the assembly module only |
Dependency direction: assembly → domain *-biz → own *-api → common; a domain may depend on another domain's
api module only, never on its -biz. Plugins depend on strata-plugin-api + strata-common-core, never on
business implementations.
3. Layering inside a domain
| Layer | Package | Responsibility |
|---|---|---|
| Controller | ...biz.web |
binding, validation, @PreAuthorize, calls the app service, returns Result |
| AppService | ...biz.appservice / ...biz.service |
use-case orchestration, transaction boundary |
| Repository | ...biz.repository |
Spring Data JPA interfaces |
| Domain / DTO | ...api.entity, ...api.dto |
entities and request/response objects |
Rules: controllers never inject repositories, DTO↔entity mapping goes through MapStruct, an HTTP entry point lives in
...biz.web only, and a type is defined exactly once.
4. HTTP surface
/api/v1/<domain>/... authenticated staff API (JWT + menu/authority strings)
- Success bodies use
Result<T>; errors use RFC 7807application/problem+jsonwithtype/title/status/detail/code/timestamp/traceId. - Business errors are registered in
ErrorCodeand raised as framework exceptions; controllers never build JSON by hand and never usePATCH. - Authorities follow
module:entity:action(create|update|delete|list), referenced through thePermissionsconstants. - Public callbacks/webhooks declare their paths through
PermitPathContributor; the security assembly aggregates them instead of hard-coding URLs. - URI versioning (
/api/vN/...) supports deprecated-version headers driven by configuration.
5. Request pipeline
trace id → rate limit → Spring Security (CORS, headers, JWT, @PreAuthorize) → idempotency
→ sanitisation → DispatcherServlet → controller → @Transactional app service → repository
→ Result / ProblemDetails → audit aspect
6. Extension points
- Plugins: self-contained modules implementing a SPI (
StorageProvider,PaymentGateway,LlmGateway, field encryption, workflow engine) and their own auto-configuration, enabled by property. - Framework SPIs with safe defaults:
VerificationSender(email/SMS codes),BackupExecutor(database backups) andAuditChangeApplier(audit replay). The default beans log and do nothing; declare your own bean to replace them. - Starter auto-configuration: adding the starter wires the system domain, its entities/repositories, scheduling, security components, the framework schema (Flyway) and Swagger. Optional AI/generator modules wire themselves when their jars are present.
7. Explicit non-goals
No microservices, no service discovery/gateway/Feign/Dubbo, no OSGi-style runtime hot-plugging, no second ORM or second UI framework, no backward-compatibility shims for internal APIs.