Data Model Overview
Purpose
Lumie's primary data model lives in PostgreSQL and is owned by lumie-backend.
The backend is a modular monolith, so database ownership is organized by module
boundaries rather than by separate service databases. Runtime tenant isolation is
implemented with a shared public schema plus PostgreSQL Row-Level Security
(RLS), not schema-per-tenant databases.
This page is an overview document. Use it to choose the right deeper reference
before changing a table, migration, repository, or cross-module data contract.
Source Paths
| Path | Role |
|---|---|
lumie-backend/app/src/main/resources/db/migration/public/ | Flyway migrations for the backend database |
V1__create_platform_tables.sql | Initial platform tables such as tenants and tenant_settings |
V18__rls_baseline.sql | Shared public-schema RLS baseline for tenant-scoped domain tables |
V27__langgraph_schema.sql | Dedicated langgraph schema for chatbot checkpoint persistence |
V28__billing_platform_tables.sql | Platform-scoped Lumie-to-tenant subscription billing tables |
V29__tuition_tenant_tables.sql | Tenant-scoped academy-to-guardian tuition billing tables |
lumie-backend/modules/**/domain/entity/ | JPA entities that map backend modules to tables |
lumie-infra/applications/lumie/**/common-values.yaml | Runtime database, pooler, and service configuration |
Schema Families
| Family | Scope | RLS | Examples |
|---|---|---|---|
| Platform core | Cross-tenant Lumie control plane | No | tenants, tenant_settings, owner_directory, plans |
| Tenant domain | Academy-owned operational data | Yes | users, students, staff, classes, lectures, exams, attendance_* |
| Platform billing | Lumie charges tenants | No | subscriptions, billing_keys, invoices, payment_transactions |
| Tenant tuition | Academies charge guardians or students | Yes | guardians, tuition_invoices, tuition_payments, cash_receipts |
| Worker state | Service-owned technical state | Separate schema | langgraph checkpointer tables |
| Operational support | Cross-cutting backend support | Depends on table | event_publication, shedlock, idempotency_keys |
Runtime Isolation Model
Tenant-scoped tables carry a non-null tenant_id column and use the canonical
RLS predicate:
tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::bigint
The runtime database role is intentionally not allowed to bypass RLS. Backend code must establish tenant context before any tenant-scoped repository or JDBC query touches data.
Ownership Boundaries
- Backend modules own their aggregate tables through JPA entities and Flyway migrations.
- Cross-module relationships are not always database-level foreign keys. Some references are deliberately soft references when a hard FK would couple two module boundaries.
- Platform billing tables are cross-tenant and protected by application-level authorization rather than RLS.
- Worker services do not own tenant data tables. They call backend internal APIs
or use dedicated technical schemas such as
langgraph.
Change Rules
- Add every schema change through a forward-only Flyway migration.
- Tenant-scoped tables must include
tenant_id, RLS enablement,FORCE ROW LEVEL SECURITY, and atenant_isolationpolicy. - Add indexes for tenant-scoped lookup patterns using
tenant_idas part of the access path. - Avoid cross-module FKs unless the owning modules intentionally share a hard persistence boundary.
- Keep generated docs aligned with migrations, entity mappings, and repository queries.