Skip to main content

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

PathRole
lumie-backend/app/src/main/resources/db/migration/public/Flyway migrations for the backend database
V1__create_platform_tables.sqlInitial platform tables such as tenants and tenant_settings
V18__rls_baseline.sqlShared public-schema RLS baseline for tenant-scoped domain tables
V27__langgraph_schema.sqlDedicated langgraph schema for chatbot checkpoint persistence
V28__billing_platform_tables.sqlPlatform-scoped Lumie-to-tenant subscription billing tables
V29__tuition_tenant_tables.sqlTenant-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.yamlRuntime database, pooler, and service configuration

Schema Families

FamilyScopeRLSExamples
Platform coreCross-tenant Lumie control planeNotenants, tenant_settings, owner_directory, plans
Tenant domainAcademy-owned operational dataYesusers, students, staff, classes, lectures, exams, attendance_*
Platform billingLumie charges tenantsNosubscriptions, billing_keys, invoices, payment_transactions
Tenant tuitionAcademies charge guardians or studentsYesguardians, tuition_invoices, tuition_payments, cash_receipts
Worker stateService-owned technical stateSeparate schemalanggraph checkpointer tables
Operational supportCross-cutting backend supportDepends on tableevent_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

  1. Add every schema change through a forward-only Flyway migration.
  2. Tenant-scoped tables must include tenant_id, RLS enablement, FORCE ROW LEVEL SECURITY, and a tenant_isolation policy.
  3. Add indexes for tenant-scoped lookup patterns using tenant_id as part of the access path.
  4. Avoid cross-module FKs unless the owning modules intentionally share a hard persistence boundary.
  5. Keep generated docs aligned with migrations, entity mappings, and repository queries.