Backend architecture
Java 25, Spring Boot 4, Spring Modulith, hexagonal modules, persistence, and executable boundaries.
SmartGare is one deployable modular monolith. Business capabilities are
independent Maven modules assembled by product/apps/server.
Module shape
product/modules/<feature>/
├── core/
│ ├── model/ pure domain rules and value objects
│ ├── usecase/ commands and decisions
│ └── port/ needs stated in domain vocabulary
├── inbound/ web, messaging, scheduling, MCP
├── outbound/ persistence and external systems
├── api/ published cross-module contract
└── config/ compositionDependencies point inward. Core code imports no adapter or framework type. A
business module reaches another only through its named api interface.
Enforced invariants
- Spring Modulith detects modules explicitly and rejects cycles.
- ArchUnit keeps domain models framework-free and adapters pointing inward.
- Only use cases publish integration events.
- HTTP and MCP operations must declare authorization.
- Every scheduled method holds a named distributed lock.
- Modules use typed text identifiers and never handle
UUIDdirectly. - Persistence readers project to response objects; writers rehydrate domain models.
Web contract
Controllers receive RequestContext as their first argument. Use cases validate
input for every adapter, not only HTTP. One error model maps validation,
not-found, conflict, forbidden, business, and technical failures.
OpenAPI adds shared error responses centrally and hides resolver-supplied arguments. Controller integration tests assert the generated document.
Persistence
Each module owns Flyway migrations under db/migration/<domain> and one
PostgreSQL schema. Conditional SQL is a concurrency guard: a losing writer gets
409, never a silent overwrite.
Reads do not round-trip through the domain model. They project the requested view directly.
Adding a module
Declare the boundary
Add the Maven module, application dependency, application-module package, and architecture-test package entry.
Own persistence
Create the domain schema and Flyway location. Never share a migration file or read another module's tables.
Publish the contract
Expose only named api packages and integration events required by other
modules.
Ship proof and documentation
Add domain tests, adapter integration tests, controller tests, architecture
gates, and the module's shipped.md/backlog.md changes.