Saltar a contenido

ADR 007: Persistencia multi-tenant (PostgreSQL por plugin)

Estado

Aceptado — 2026-06 · Diseño de ingeniería (sin implementación aún)

Relacionado con ADR 001 (stack HIVE) y ADR 008 (primer plugin con persistencia real).

Contexto

HIVE declara PostgreSQL 16 con una base de datos física por tenant (cortex_{tenant}), alineado con aislamiento de datos (Habeas Data / Ley 1581). Hoy:

  • CORTEX_DATABASE_URL_TEMPLATE y tenant_database_url() existen en framework/cortex_framework/tenant/database.py.
  • Los plugins de dominio usan fixtures en memoria; no hay capa ORM ni migraciones activas.
  • alembic_stub.py documenta la intención futura sin ejecutar migraciones.

Se requiere un modelo claro: cada plugin es dueño de su esquema de datos; el framework solo resuelve la URL por tenant y orquesta el aprovisionamiento.

Decisión

Aislamiento por tenant

  • Una base de datos física por tenant: cortex_{tenant} (nombre sanitizado desde X-Tenant-Id).
  • Sin schema compartido entre tenants en la misma instancia PG (salvo BD de sistema/admin para provisioning).

Responsabilidades

Capa Responsabilidad
framework tenant_database_url(), factory de sesión async, hook de provisioning (crear BD + invocar migraciones de plugins habilitados)
plugin Modelos SQLAlchemy 2 async, alembic/, seeds de dev opcionales
core Sin dependencia de ORM

Convención de tablas

  • Tablas en schema public con prefijo por plugin: booking_resources, booking_slots, booking_bookings.
  • Cada plugin mantiene su propio alembic.ini y cadena de revisiones independiente.
  • Sin ORM compartido en framework; utilidad mínima: get_async_session(tenant) -> AsyncSession.

Provisioning de tenant

  1. Alta de tenant (API admin o script ops).
  2. CREATE DATABASE cortex_{tenant}.
  3. Para cada plugin en CORTEX_ENABLED_PLUGINS: alembic upgrade head contra la URL del tenant.
  4. Registro de versión de migración por plugin (tabla cortex_migrations en framework o metadata Alembic por plugin).

Runtime

  1. TenantMiddleware fija el tenant en contexto.
  2. Handler del plugin obtiene sesión vía factory con tenant_database_url().
  3. Transacciones por request; sin estado de sesión global.

Leyenda: Provisioning de BD por tenant y sesión en runtime (módulo Dependiente tenant). Estado: Diseño.

flowchart TB
  subgraph provision [Provisioning tenant]
    Admin[Alta tenant]
    CreateDB["CREATE DATABASE cortex_tenant"]
    MigPlugin[Alembic por plugin habilitado]
  end
  subgraph runtime [Request]
    MW[TenantMiddleware]
    URL[tenant_database_url]
    Session[AsyncSession plugin]
  end
  Admin --> CreateDB --> MigPlugin
  MW --> URL --> Session

Leyenda: Flujo alta tenant → migraciones por plugin. Estado: Diseño.

flowchart LR
  subgraph framework [cortex_framework]
    TURL[tenant_database_url]
    Factory[get_async_session]
    Prov[TenantProvisioner]
  end
  subgraph plugins [cortex_plugin_*]
    Models[models.py]
    Alembic[alembic/]
  end
  subgraph pg [PostgreSQL 16]
    DB1[(cortex_tenant_a)]
    DB2[(cortex_tenant_b)]
  end
  Prov --> Alembic
  TURL --> Factory
  Factory --> Models
  Models --> DB1
  Models --> DB2

Leyenda: Framework resuelve URL; cada plugin Independiente posee modelos y Alembic. Estado: Diseño.

Migración desde fixtures

  • Desarrollo: seed opcional desde JSON actual (fixtures/) tras migraciones.
  • Producción: sin _STORE en memoria; datos solo en PG.

Variables de entorno

Variable Rol
CORTEX_DATABASE_URL_TEMPLATE postgresql+asyncpg://user:pass@host:5432/cortex_{tenant}
CORTEX_DEFAULT_TENANT Tenant para /ready y dev local

Consecuencias

  • Positivas: aislamiento legal por tenant, plugins desacoplados en esquema, Alembic independiente por dominio.
  • Negativas: N bases de datos que administrar; provisioning debe ser idempotente y observable.
  • El plugin booking será el primero en adoptar este ADR (ver ADR 008).

Referencias

  • framework/cortex_framework/tenant/database.py
  • framework/cortex_framework/tenant/alembic_stub.py
  • ADR 009 (/ready comprueba PG)