Backend — Architecture¶
The Genesis backend is a Spring Boot 3 modular monolith on Java 21, built with Maven. It exposes a REST API consumed by the Next.js frontend and persists everything in PostgreSQL.
The backend's layered shape at a glance:

Module structure¶
The build is a Maven multi-module project. genesis-api is the entry point —
it holds the @SpringBootApplication class, wires every other module
together, and contains no business logic of its own.
| Module | Role |
|---|---|
genesis-api |
Application entry point; configuration and wiring only |
genesis-common |
Shared DTOs, the TextProcessor interface, value objects |
genesis-user |
Authentication (JWT + refresh tokens) and user management |
genesis-workspace |
Workspace and document lifecycle, member roles |
genesis-coref |
Coreference mentions and clusters |
genesis-ner |
Named-entity annotation |
genesis-pos |
Part-of-speech tagging, custom tag sets |
genesis-wsd |
Word-sense disambiguation, sense inventories |
genesis-editor |
Per-user editor session persistence |
genesis-notification |
Event-driven in-app notifications |
genesis-recommend |
Annotation recommendations |
genesis-import-export |
TXT / CoNLL-2012 import and export |
genesis-logging |
Structured logging support |
genesis-infra |
Database config, pluggable file storage (Cloudinary or local disk), cross-cutting infrastructure |
Layering inside each module¶
Every module follows the same internal layout:
controller/ thin REST layer — delegates immediately to the service
service/ transactional business logic
repository/ Spring Data JPA interfaces (infra layer only)
entity/ JPA entities
dto/ request/response objects — entities never cross module boundaries
event/ Spring ApplicationEvent subclasses for cross-module signals
health/ per-module HealthIndicator implementations
Cross-module communication¶
Modules never call each other's services directly. Instead they publish Spring application events and interested modules listen. Example — the document upload flow:
DocumentController
→ DocumentService (saves document, publishes DocumentUploadedEvent)
→ ImportService (listens; runs prepareForAnnotation)
→ TextProcessor (tokenises into sentences/tokens)
→ publishes DocumentTokenizedEvent
→ notification module creates in-app notifications
This keeps module boundaries enforceable (verified with ArchUnit tests) and makes new annotation types addable without touching existing modules.
The full module dependency and event graph:

Authentication¶
- JWT access tokens (default expiry 15 minutes) signed with HS256; the secret must be at least 256 bits and the app refuses to boot without it.
- Refresh tokens (default 7 days) with rotation on use.
- Tokens are issued by the user module; a Spring Security filter validates them on every request.
API conventions¶
- Every endpoint returns a uniform envelope:
{ "success": boolean, "data": T, "message": string }. - Database identifiers and column/table names are
snake_case. - Pagination on large collections (documents, tokens) uses cursor/keyset pagination rather than offsets.
/actuator/healthis the liveness/readiness endpoint; in theprodprofile onlyhealth,info,metrics, andprometheusare exposed.
Persistence¶
- PostgreSQL with Flyway migrations (versioned
V*scripts) in theprodprofile; thedevprofile may useddl-auto=updatefor speed. - Connection pooling via HikariCP with conservative pool sizes suitable for a single-VM deployment.
- Uploaded source files go to the configured storage backend — Cloudinary or
the local filesystem (
STORAGE_PROVIDER); the database stores metadata, tokens, sentences, and annotations. After tokenization the source is read only for re-tokenizing, soSTORAGE_RETAIN_SOURCE=falsecan reclaim it.
Configuration¶
All configuration is environment-driven (12-factor). The important variables:
| Variable | Purpose |
|---|---|
DB_URL, DB_USERNAME, DB_PASSWORD |
PostgreSQL connection |
JWT_SECRET |
Token signing key (≥ 32 chars, required) |
JWT_ACCESS_TOKEN_EXPIRY, JWT_REFRESH_TOKEN_EXPIRY |
Optional, Spring Duration strings |
STORAGE_PROVIDER |
cloudinary (default) or local |
STORAGE_RETAIN_SOURCE |
Keep the raw upload after tokenization (default true) |
STORAGE_LOCAL_BASE_PATH |
Upload directory for the local provider (default ./data/uploads) |
CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET |
File storage — required only when STORAGE_PROVIDER=cloudinary |
CORS_ALLOWED_ORIGINS |
Allowed frontend origins — required in prod |
SPRING_PROFILES_ACTIVE |
dev or prod |
PORT |
HTTP port (default 8080) |
Build & runtime¶
mvn clean packageproduces a single executable jar ingenesis-api/target/.- The repo ships a multi-stage
Dockerfile: a Maven/JDK 21 build stage with per-module dependency caching, then a slim JRE 21 Alpine runtime stage. - In the
prodprofile the app writes a rolling log file to/app/logs/(rotated daily, 30 days retained) in addition to stdout.