Infrastructure
The backend depends on several platform services, but it still runs as one Spring Boot application. This page covers the backend-facing runtime contracts that shape code behavior. It intentionally stays out of cluster operations and day-2 runbooks.
This page is a reference document.
Source Paths
| Path | Role |
|---|---|
app/src/main/resources/application.yaml | Main runtime config: data sources, Flyway, Redis, RabbitMQ, MinIO, worker URLs, CORS, rate limits, OpenAPI, actuator |
app/src/main/resources/application-dev.yml | Dev-only overrides such as cookie SameSite |
app/src/main/java/com/lumie/app/config/RoutingDataSourceConfig.java | Primary and readonly Hikari pools plus Flyway migrator data source |
app/src/main/java/com/lumie/app/config/RuntimeDbRoleGuard.java | Startup guard against RLS-bypassing DB roles |
app/src/main/java/com/lumie/app/config/ShedLockConfig.java | Distributed scheduler locking |
app/src/main/java/com/lumie/app/config/CorsConfig.java | CORS contract |
app/src/main/java/com/lumie/app/config/ratelimit/RateLimitFilter.java | In-process rate limiting |
modules/exam/src/main/java/com/lumie/exam/adapter/out/config/* | RabbitMQ, RestClient, and MinIO infrastructure owned by the exam module |
modules/auth/src/main/java/com/lumie/auth/adapter/out/persistence/RedisTokenRepository.java | Auth's Redis persistence contract |
modules/file/src/main/java/com/lumie/file/adapter/out/storage/MinioStorageAdapter.java | File-service object storage contract |
modules/billing/src/main/java/com/lumie/billing/adapter/out/external/* | Billing's external provider adapters |
Runtime Topology
Dependency Contracts
| Dependency | Config surface | Main backend owners | Contract notes |
|---|---|---|---|
| PostgreSQL | app.datasource.*, spring.flyway.* | all modules | Shared-schema RLS, Modulith outbox, ShedLock, primary plus readonly pools |
| RabbitMQ | spring.rabbitmq.* | mainly exam | Queue-backed grading and report workflows |
| Redis | spring.data.redis.* | mainly auth | Refresh tokens, blacklist, and session index storage |
| MinIO | minio.* | exam, file, tenant | OMR objects, presigned uploads, file metadata, tenant logos |
grading-svc | lumie.services.grading.url | exam | Direct HTTP grading plus queue callback ecosystem |
report-svc | lumie.services.report.url | exam | Direct HTTP report generation plus queue callback ecosystem |
chatbot-svc | lumie.services.chatbot.url | ai | HTTP streaming proxy out, HMAC callback surface back in |
| Toss Payments | billing config plus provider secrets | billing | Implemented HTTP integration |
| Popbill tax invoice path | billing config plus provider secrets | billing | Current code is still a stubbed adapter, not a full live integration |
Database And Migration Infrastructure
Read/write split
RoutingDataSourceConfig declares:
app.datasource.primary.*app.datasource.readonly.*- a
RoutingDataSourcewrapped inLazyConnectionDataSourceProxy
Routing rule:
@Transactional(readOnly = true)-> readonly pool- write transaction or non-transactional access -> primary pool
Flyway runs with separate credentials
Flyway does not use the runtime application pool credentials. The backend
creates a dedicated @FlywayDataSource from the primary URL plus
spring.flyway.user/password, so DDL can run with table-owning privileges
while runtime traffic stays on the restricted lumie_app role.
Runtime DB role guard
RuntimeDbRoleGuard fails startup if the active runtime role is:
SUPERUSERBYPASSRLS
That is a backend safety invariant, not just an operations preference.
Async And Scheduler Infrastructure
- Spring Modulith stores outbox rows in
public.event_publication spring.modulith.events.completion-mode=deletespring.modulith.events.republish-outstanding-events-on-restart=true- ShedLock uses
public.shedlock @EnableAsyncis on the application, and Spring-managed async execution gets the tenant-aware task decorator
Important boundary detail:
- Spring-managed async work gets context propagation automatically
- custom executors, such as the AI module's dedicated chat executor, still have to re-establish context manually
Object Storage And External HTTP
MinIO
examuses two MinIO clients: an internal endpoint client and a presign client usingminio.external-endpointfileuses MinIO for uploads, downloads, and presigned URLstenantuses MinIO for logo objectsFileServiceAdapterdeletes MinIO objects only after database commit
Worker and provider HTTP
examusesRestClientwith external-call logging and explicit timeoutsOmrServiceClientoverrides the read timeout to90saipinschatbot-svctraffic to HTTP/1.1 for uvicorn compatibility- billing provider calls are intentionally kept outside long transactions
HTTP Surface And Backend Guards
From application.yaml and app config:
- OpenAPI:
/v3/api-docs - Swagger UI:
/swagger-ui.html - CORS origins: production, dev, and local
http://localhost:3000 - multipart limit:
5MBper file,25MBper request - graceful shutdown timeout:
30s - actuator exposure:
health,info,prometheus,metrics - per-IP in-process rate limiting on selected
POSTroutes
RequestContextFilter also provides:
- generated or forwarded
X-Request-Id - MDC population for request ID, tenant, user, and optional trace ID
Current Drift And Caveats
- The billing module contains a real Toss integration, but
PopbillTaxInvoiceClientis still a stub that returns a synthetic success response and logs a warning. - RabbitMQ topology is not declared in backend Java config; backend code assumes the queues and policies exist and only wires the application-layer beans.
Verification Commands
cd /Users/bluemayne/Projects/Lumie/lumie-backend
./gradlew :app:test
./gradlew :modules:exam:test
./gradlew :modules:auth:test
./gradlew integrationTest
Most relevant tests:
app/src/test/java/com/lumie/app/config/RoutingDataSourceIntegrationTest.javaapp/src/test/java/com/lumie/app/config/RuntimeDbRoleGuardTest.javaapp/src/test/java/com/lumie/app/migration/MigrationsRlsIntegrationTest.java