Pular para o conteúdo

Tenancy em produção

O inventário antigo (docs/architecture/legacy-systems-inventory.md na versão 2025) afirmava “1 VPS por cliente”. Errado. O modelo real, confirmado pelo dossiê de arqueologia, é layered multi-tenant:

flowchart TB
    subgraph FRANQUIA1[VPS Franquia 1]
        DB1[(PostgreSQL único)]
        APP1[sowil-api + Grifos]
        DB1 --> C1A[Cliente A]
        DB1 --> C1B[Cliente B]
        DB1 --> C1C[Cliente C]
    end

    subgraph FRANQUIA2[VPS Franquia 2]
        DB2[(PostgreSQL único)]
        APP2[sowil-api + Grifos]
        DB2 --> C2A[Cliente D]
        DB2 --> C2B[Cliente E]
    end

    subgraph FRANQUIA3[VPS Franquia 3]
        DB3[(PostgreSQL único)]
        APP3[sowil-api + Grifos]
        DB3 --> C3A[Cliente F]
        DB3 --> C3B[Cliente G]
    end

    classDef vps fill:#2b5383,stroke:#0e2a3a,color:#fff
    classDef db fill:#00a9c9,stroke:#0e2a3a,color:#fff
    classDef client fill:#76c0d5,stroke:#0e2a3a,color:#0e2a3a
    class FRANQUIA1,FRANQUIA2,FRANQUIA3 vps
    class DB1,DB2,DB3 db
    class C1A,C1B,C1C,C2A,C2B,C3A,C3B client
CamadaMecanismoGarantia
Entre franquiasVPS separadasForte (rede, disco, processo)
Entre clientes dentro da franquiaFiltro de aplicação WHERE ex_empresa IN (...)Fraca — sem RLS de banco
flowchart TB
    subgraph V2POD[Pod multi-tenant default]
        DBV2[(PostgreSQL TimescaleDB)]
        ACTASTENANT[acts_as_tenant<br/>por Organization]
        PUNDIT[Pundit policies<br/>por Organization]
        DBV2 --> ACTASTENANT
        ACTASTENANT --> PUNDIT
        PUNDIT --> O1[Org 1<br/>franquia A]
        PUNDIT --> O2[Org 2<br/>franquia B]
        PUNDIT --> O3[Org 3<br/>franquia C]
    end

    subgraph ENTERPRISE[Pod isolado por tenant — opcional enterprise]
        DBE[(PostgreSQL dedicado)]
        ACTASTENANT2[acts_as_tenant]
        DBE --> ACTASTENANT2
        ACTASTENANT2 --> OE[Org Enterprise]
    end

    classDef pod fill:#00a9c9,stroke:#2b5383,color:#fff
    classDef db fill:#2b5383,stroke:#0e2a3a,color:#fff
    classDef org fill:#76c0d5,stroke:#0e2a3a,color:#0e2a3a
    class V2POD,ENTERPRISE pod
    class DBV2,DBE db
    class O1,O2,O3,OE org

A v2 usa acts_as_tenant + Pundit para garantia em duas camadas:

  1. Banco: toda query passa pelo escopo de current_organization automaticamente.
  2. Aplicação: políticas Pundit explícitas por recurso.

Enterprise tier opcional: pod dedicado por tenant (DB + app), para central que exige isolamento físico.

flowchart LR
    A[VPS franquia 1<br/>atual] --> B[ETL]
    B --> C[Solutio v2 pod<br/>Organization 1]
    D[VPS franquia 2<br/>atual] --> B
    B --> E[Solutio v2 pod<br/>Organization 2]
    classDef legacy fill:#cccccc,stroke:#666,color:#333,stroke-dasharray:5
    classDef v2 fill:#00a9c9,stroke:#2b5383,color:#fff
    classDef etl fill:#f4a261,stroke:#9a5d2c,color:#fff
    class A,D legacy
    class C,E v2
    class B etl

ETL franquia-a-franquia, mapeando ex_empresa (campo do sistema atual) → organization_id v2. Plano detalhado vive em docs/migration/.