Construí Multi-Tenancy no Dia 2. No Dia 67, Reconstruí Tudo
Achei que adicionar org_id a cada tabela significava multi-tenancy à prova de balas. Então minha auditoria de segurança revelou que agências estavam escrevendo em tabelas de PME — não por um bug, mas por design.
27 de outubro de 2025, 23h47. Estou rodando o que achei que seria uma auditoria de segurança de rotina no STRAŦUM. Tudo estava funcionando bem por semanas — PMEs com seus dados, agências com os seus, multi-tenancy sólido.
Café frio. O script de auditoria está processando logs. Então vejo: Agências estavam escrevendo em tabelas de PME.
Não por um bug. Não por uma brecha de segurança. Pela própria arquitetura.
Dois meses atrás, tomei a decisão de construir arquitetura multi-tenant desde o Dia 2. Decisão ousada para um fundador solo com um agente de IA funcionando. Adicionei org_id a cada tabela, escrevi políticas RLS, construí roteamento separado para PMEs e Agências. Estava funcionando — PMEs com suas campanhas, agências com seus clientes, dados fluindo para os lugares certos.
Ou assim eu pensava.
Fiquei parado por uns 20 minutos só olhando para o schema. Como perdi isso? Tinha passado semanas construindo arquitetura multi-tenant, escrevendo 83 políticas RLS, testando com contas de PME e Agência. Tudo *funcionava*. Mas "funcionando" e "correto" não são a mesma coisa.
Esse é o tipo de bug que faz você questionar se deveria estar construindo software. Porque não é um erro de digitação. Não é um caso de borda perdido. É ingenuidade arquitetural.
Cometi o erro clássico: assumi que filtrar por org_id era suficiente para isolamento multi-tenant. Não era.
Esta é a história de descobrir que o verdadeiro isolamento multi-tenant exige mais do que apenas adicionar org_id a cada tabela — e as 33 migrações ao longo de 48 horas que finalmente resolveram tudo.
---
> **Nota**: Os exemplos de SQL neste post usam nomes genéricos de schema e tabela (tenant_b, workspace_entities, entity_data) por segurança. Os conceitos continuam os mesmos independentemente das suas convenções de nomenclatura específicas.
---
O Problema: Nem Todos os Tenants São Iguais
Aqui está o que construí inicialmente:
```sql
-- Tabela de diretrizes de marca (compartilhada por PMEs e Agências)
CREATE TABLE brand_guidelines (
id UUID PRIMARY KEY,
org_id UUID REFERENCES organizations(id),
name TEXT,
guidelines JSONB,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Política RLS (parece segura)
CREATE POLICY brand_guidelines_org_isolation ON brand_guidelines
FOR ALL TO authenticated
USING (org_id = get_user_org_id());
```
Isso funciona perfeitamente para PMEs. Cada organização tem suas próprias diretrizes de marca. A Row-Level Security garante que não possam ver os dados umas das outras. (Pelo menos, eu achava que garantia. Mais sobre isso em breve.)
Mas Agências são diferentes.
Agências não têm apenas um conjunto de diretrizes de marca. Têm uma por cliente:
- Diretrizes de marca do Cliente A (paleta de cores vibrante, tipografia arrojada, mensagem focada em inovação)
- Diretrizes de marca do Cliente B (paleta de cores suave, design minimalista, posicionamento focado em qualidade)
Mesma agência, clientes diferentes, marcas completamente diferentes.
A solução ingênua (o que construí primeiro):
```sql
-- Adiciona entity_id à tabela compartilhada
ALTER TABLE brand_guidelines ADD COLUMN entity_id UUID;
-- Atualiza política RLS
CREATE POLICY brand_guidelines_isolation ON brand_guidelines
FOR ALL TO authenticated
USING (
organization_id = get_user_org_id() AND
(entity_id IS NULL OR entity_id = get_user_entity_id())
);
```
Problema: Isso criou uma tabela com dois modelos de dados diferentes:
```
Linha PME: organization_id='org-123', entity_id=NULL, guidelines=\{...\}
Linha Agência: organization_id='org-456', entity_id='entity-a', guidelines=\{...\}
Linha Agência: organization_id='org-456', entity_id='entity-b', guidelines=\{...\}
```
As queries viraram uma bagunça com tratamento complexo de NULL, e cada funcionalidade precisava de lógica "if PME, else Agência" no código de aplicação.
E sim, escrevi tudo isso antes de perceber que eram sintomas de um problema mais profundo. Semanas de trabalho, todas apontando para a mesma conclusão: tinha me arquitetado em um canto.
Cada funcionalidade precisava de lógica personalizada: "Se PME, faça isso. Se Agência, faça aquilo."
Pior, a arquitetura fazia suposições incorretas:
- Agências escrevendo entity_id=NULL poluiriam dados de PME
- PMEs não podiam ter sub-entidades mesmo que quisessem sub-contas
- O schema virou "queijo suíço" com colunas nullable
Isso não era arquitetura multi-tenant. Era uma única tabela tentando servir a dois modelos de dados diferentes.
---
A Revelação: Tenants Diferentes Precisam de Schemas Diferentes
No final de outubro, percebi a verdade: PMEs e Agências não compartilham o mesmo modelo de dados.
Modelo de dados de PME:
```
organization → campaigns → agent_outputs
```
Modelo de dados de Agência:
```
organization → workspace_entities → campaigns → agent_outputs
↓
entity_data (ex: diretrizes de marca, personas)
```
As agências têm uma camada inteira (entidades de workspace) que as PMEs não têm. Também têm inteligência específica de entidade que não deveria existir no mundo de PMEs.
A solução: Schemas de banco de dados separados.
```sql
-- Tabelas de PME (schema público)
public.brand_guidelines
public.campaigns
public.outputs
-- Tabelas de Agência (schema tenant_b)
tenant_b.workspace_entities
tenant_b.entity_data -- Inclui diretrizes de marca, personas, etc.
tenant_b.campaigns
tenant_b.outputs
```
Agora PMEs e Agências têm tabelas completamente diferentes. Sem schema compartilhado. Sem poluição de entity_id nullable. Sem lógica "if PME, else Agência".
---
Por Que Isso Importa: O Caso de Negócios para Roteamento de Schema
Antes de mergulhar na implementação técnica, vamos falar sobre por que essa decisão arquitetural importa além de "é código mais limpo."
Preparação para o Futuro (Talvez)
O roteamento de schema não é só sobre resolver o problema de hoje. É sobre manter portas abertas para oportunidades que nem consigo prever ainda.
Ainda estou em alpha privado com 15 usuários. Não tenho clientes enterprise. Não conversei com um advogado de GDPR. Mas aqui está o que o roteamento de schema *poderia* habilitar se o STRAŦUM crescer:
Expansão Internacional:
- Se expandirmos para a UE: Schemas separados poderiam habilitar residência de dados (dados de clientes da UE no schema eu_agency em servidores da UE)
- O direito à exclusão fica mais simples: consulta um schema, não filtra por tabelas mistas
- Trilhas de auditoria: "Mostre-me todos os dados do Cliente X" = uma consulta de schema
Conversas de Conformidade:
- Quando alguém eventualmente perguntar "Como você garante o isolamento de dados?"
- Com filtro org_id: "Usamos políticas de Row-Level Security" (vago, difícil de verificar)
- Com roteamento de schema: "Os dados de cada cliente vivem em um schema de banco de dados separado" (concreto, auditável)
- Não sei se isso importa ainda. Mas *poderia* importar se tivermos essas conversas.
A Verdade Honesta:
Não estou construindo para conformidade HIPAA ou SOC 2 agora. Estou construindo para PMEs e pequenas agências que precisam de melhor estratégia de marketing.
Mas o roteamento de schema significa que se alguém um dia perguntar "Vocês conseguem lidar com clientes de saúde?" ou "Vocês suportam residência de dados?", a resposta é "sim, deixa eu te mostrar a arquitetura" em vez de "deixa eu reconstruir tudo primeiro."
As Desvantagens (Sendo Honesto)
Roteamento de schema não é tudo positivo. Aqui está o que realmente custa:
Complexidade de Desenvolvimento:
- Cada operação de ESCRITA precisa de uma função roteadora
- Cada operação de LEITURA precisa de uma view de segurança
- Testes exigem ambos os caminhos de PME e Agência
- Com Claude Code: 2 dias de trabalho intenso (27-29 de outubro de 2025) nas noites
- Sem ferramentas de IA: Teriam levado semanas
Risco de Migração:
- 33 migrações sequenciais = 33 oportunidades de erros de digitação
- Um ALTER TABLE errado = corrupção de dados em produção
- Tive que rodar cada migração 3X no staging antes de tocar em produção
- A paranoia era real
Overhead de Performance das Queries:
- Views com UNION ALL = leituras ligeiramente mais lentas
- Funções roteadoras = chamada de função extra em escritas
- RLS + views = planos de query mais complexos
- (Na prática: ainda não percebi lentidões, mas também só tenho 15 usuários alpha)
Complexidade Operacional:
- Migrações de schema agora afetam 2+ schemas (público + agência)
- Backups de banco de dados precisam de restore com consciência de schema
- Queries de monitoramento precisam verificar múltiplos schemas
- Isso vai me machucar eventualmente, só não sei quando
Por Que Fiz a Troca Mesmo Assim
O valor de opção pode ser enorme. Ou pode não importar nada.
O roteamento de schema mantém portas abertas que nem tenho certeza se quero atravessar:
- Parcerias white-label: Poderia dar a um parceiro seu próprio schema, rebrandear a UI
- Oportunidades de revendedor: Agências poderiam revender com isolamento de dados comprovável
- Diferentes faixas de preço: Clientes "premium" poderiam ter schemas dedicados
- Expansão geográfica: Schema EU, schema US, schema APAC - mesma base de código
Aqui está a coisa: estou em alpha privado. Não sei se algum desses vai importar. Talvez eu nunca receba uma solicitação de white-label. Talvez a expansão geográfica esteja anos distante. Talvez o negócio pivote e nada disso seja relevante.
Mas aqui está o que sei: com roteamento de schema, essas opções existem. Com filtro org_id, a maioria exigiria uma reescrita completa.
Essa é a aposta que fiz: Gastar 2 dias extras agora (com Claude Code) para manter opções abertas depois.
A aposta está certa? Pergunte-me daqui a um ano.
---
A Arquitetura: Roteamento de Schema
Padrão 1: Tabelas Específicas por Schema
Algumas tabelas só existem para um tipo de tenant:
```sql
-- Schema de tenant especializado
CREATE SCHEMA tenant_b;
-- Entidades de workspace (específicas para esse tipo de tenant)
CREATE TABLE tenant_b.workspace_entities (
id UUID PRIMARY KEY,
organization_id UUID,
name TEXT,
metadata JSONB
);
-- Dados específicos de entidade
CREATE TABLE tenant_b.entity_data (
id UUID PRIMARY KEY,
organization_id UUID,
entity_id UUID REFERENCES tenant_b.workspace_entities(id),
data_type TEXT,
content JSONB
);
```
PMEs nunca tocam essas tabelas. Elas não existem no schema public.
Padrão 2: Funções Roteadoras de Banco de Dados
Como você escreve no schema correto? **Funções roteadoras**.
Aqui está o conceito (simplificado):
```sql
CREATE FUNCTION save_resource_routed(params)
RETURNS JSONB
LANGUAGE plpgsql
SECURITY DEFINER -- Executa com privilégios elevados
AS $$
BEGIN
-- Passo 1: Detecta o tipo de organização
SELECT type INTO org_type FROM organizations WHERE id = p_org_id;
-- Passo 2: Roteia para o schema correto baseado no tipo
IF org_type = 'TENANT_B' THEN
INSERT INTO tenant_b.entity_data (...) VALUES (...);
ELSE
INSERT INTO public.brand_guidelines (...) VALUES (...);
END IF;
RETURN result;
END;
$$;
```
Como funciona:
1. Detecta o tipo de organização: Consulta a tabela de organizações para determinar o tipo de tenant
2. Roteia para o schema correto: Escreve no schema apropriado baseado no tipo
3. Retorna resultado: Inclui qual schema foi usado para debugging
Código de aplicação (igual para todos os tipos de tenant):
```typescript
// Só chame a função roteadora - sem lógica específica de tenant
const result = await supabase.rpc('save_resource_routed', \{
p_org_id: orgId,
p_entity_id: entityId, // null para tenants simples
p_data: { ... \}
});
```
Sem if/else no código de aplicação. O banco de dados faz o roteamento.
Escrever minha primeira função roteadora levou 4 horas. Depurar por que não estava funcionando? Mais 6 horas. O problema? Esqueci de conceder permissões EXECUTE. Energia clássica de fundador solo: brilhantismo arquitetural, descuidos de permissão. :P
Padrão 3: Views com Security-Invoker para Leituras
Escritas usam funções roteadoras. Leituras usam **views**.
```sql
-- View unificada combinando ambos os schemas
CREATE VIEW resources_unified
WITH (security_invoker = on) -- Respeita políticas RLS
AS
SELECT id, organization_id, NULL AS entity_id, data, 'public' AS source
FROM public.brand_guidelines
UNION ALL
SELECT id, organization_id, entity_id, content AS data, 'tenant_b' AS source
FROM tenant_b.entity_data
WHERE data_type = 'brand_guidelines';
```
Código de aplicação (leituras unificadas):
```typescript
// Lê recursos (funciona para todos os tipos de tenant)
const \{ data \} = await supabase
.from('resources_unified')
.select('*')
.eq('organization_id', orgId);
// As políticas RLS filtram corretamente independente do schema de origem
```
Detalhe-chave: WITH (security_invoker = on) garante que as políticas RLS sejam aplicadas. Sem isso, as views bypassam o RLS (desastre de segurança).
---
A Migração: 33 Migrações em 48 Horas
Adicionar roteamento de schema não foi uma única migração. Foi uma jornada.
Sabe o que é divertido? Escrever 33 migrações de banco de dados seguidas sabendo que se qualquer UMA tiver um erro de digitação, você corromperá dados em produção. Na verdade, "divertido" não é a palavra. "Aterrorizante" é mais preciso. Rodei cada migração no staging três vezes antes de tocar em produção.
27-29 de outubro de 2025: 33 migrações sequenciais para roteamento de schema completo.
As fases de migração:
1. Criar schema especializado — Configurar schema tenant_b com permissões adequadas
2. Criar tabelas específicas do schema — Espelhar tabelas necessárias no novo schema
3. Construir funções roteadoras — Uma para cada tipo de recurso que precisa de roteamento
4. Criar views de segurança — Views unificadas com UNION ALL para leituras
5. Atualizar políticas RLS — Garantir que ambos os schemas tenham isolamento adequado
6. Migração de dados — Mover dados existentes para os schemas corretos
7. Atualizações de aplicação — Trocar de queries diretas para funções/views roteadoras
Esforço total: 33 migrações, 2 dias com Claude Code, 100% valeu a pena.
---
Os Resultados: Verdadeiro Isolamento Multi-Tenant
Antes (Tabelas Compartilhadas com org_id)
Modelo de dados:
```sql
public.brand_guidelines (organization_id, entity_id, guidelines)
```
Problemas:
- ❌ entity_id nullable para um tipo de tenant (confusão no modelo de dados)
- ❌ Queries complexas com tratamento de NULL
- ❌ Lógica de aplicação: if (tenantTypeA) \{ ... \} else \{ ... \}
- ❌ Risco de contaminação cruzada
Depois (Roteamento de Schema)
Modelo de dados:
```sql
public.brand_guidelines (organization_id, guidelines) -- Tipo de Tenant A
tenant_b.entity_data (organization_id, entity_id, data) -- Tipo de Tenant B
```
Benefícios:
- ✅ Modelos de dados limpos (sem chaves estrangeiras nullable)
- ✅ Queries simples sem tratamento complexo de NULL
- ✅ Sem if/else de aplicação (o banco de dados lida com o roteamento)
- ✅ Impossível contaminação cruzada de schemas (fisicamente separado)
Melhorias de Segurança
Antes: Risco de contaminação cruzada com colunas nullable e tabelas compartilhadas
Depois: Funções roteadoras direcionam automaticamente escritas para o schema correto baseado no tipo de organização. A separação física de schema torna a contaminação cruzada impossível.
Nível de isolamento: Separação aplicada pelo banco de dados. Não verificações em nível de aplicação.
---
Quando Usar Roteamento de Schema vs. Filtro org_id
Nem todo app multi-tenant precisa de roteamento de schema. Use esta árvore de decisão:
Use Filtro org_id (Mais Simples) Se:
✅ Todos os tenants têm o mesmo modelo de dados (ex: app de tarefas)
✅ Sem tenancy hierárquica (sem sub-entidades dentro de organizações)
✅ Queries simples (WHERE org_id = X funciona em todo lugar)
✅ B2C ou B2B pequeno (sem vendas enterprise, sem requisitos de conformidade)
✅ Velocidade de MVP importa (chegue ao mercado em semanas, não meses)
Raciocínio de negócios: Você está validando product-market fit, não construindo para requisitos de conformidade Fortune 500. Lance rápido, refatore depois se tiver tração enterprise.
Exemplo: Ferramenta de gerenciamento de projetos onde cada organização gerencia seus próprios projetos.
```sql
CREATE TABLE projects (
id UUID PRIMARY KEY,
org_id UUID, -- Filtro simples
name TEXT
);
```
Use Roteamento de Schema (Mais Complexo) Se:
✅ Tipos de tenant diferentes precisam de modelos de dados diferentes (Tipo A vs. Tipo B vs. Tipo C)
✅ Tenancy hierárquica (organizações → workspace_entities → sub-entidades)
✅ Vendas enterprise no roadmap (Fortune 500, saúde, finanças, governo)
✅ Conformidade regulatória exigida (GDPR, HIPAA, SOC 2, FedRAMP)
✅ White-label ou potencial de revendedor (parceiros precisam de isolamento completo de dados)
✅ Expansão internacional planejada (requisitos de residência de dados)
Raciocínio de negócios: Se você está mirando clientes enterprise, "isolamento de dados" se torna uma caixa de seleção em questionários de segurança. Roteamento de schema permite responder com confiança. Filtro em nível de linha deixa você titubeando.
Exemplo: Plataforma onde alguns tenants têm estruturas de workspace hierárquicas que diferem fundamentalmente dos clientes diretos.
Custo/Benefício (Minha Experiência):
- Custos do roteamento de schema: 2 dias com Claude Code (teriam sido semanas sem assistência de IA)
- Potencial de ganho: Arquitetura mais limpa, prontidão para conformidade, opções de parceria
- Ganho real: Desconhecido — ainda estou em alpha privado
- Break-even: Se o roteamento de schema abrir apenas uma porta que de outra forma não conseguiria atravessar, se paga
A questão real: Você está otimizando para velocidade de chegada ao mercado ou opcionalidade? Ambos são válidos. Escolhi opcionalidade.
---
Estratégias Alternativas de Isolamento
Roteamento de schema não é a única abordagem. Aqui está o espectro:
Nível 1: Bancos de Dados Separados (Isolamento Mais Alto)
```
database_tenant_1
database_tenant_2
database_tenant_3
```
Prós:
- ✅ Isolamento físico completo
- ✅ Backups por tenant
- ✅ Escalonamento independente
- ✅ Conformidade regulatória (residência de dados)
Contras:
- ❌ Alta complexidade operacional (gerenciar N bancos de dados)
- ❌ Caro (instância de banco de dados por tenant)
- ❌ Queries cross-tenant impossíveis
- ❌ Migrações de schema em todos os bancos de dados
Caso de uso: SaaS enterprise com requisitos regulatórios, clientes de alto valor ($10k+/mês).
Nível 2: Schemas Separados (Isolamento Forte)
```
database
├── schema_tenant_1
├── schema_tenant_2
└── public (tabelas compartilhadas)
```
Prós:
- ✅ Isolamento lógico forte
- ✅ Infraestrutura compartilhada (um banco de dados)
- ✅ Permissões em nível de schema
- ✅ Modelos de dados diferentes por tipo de tenant
Contras:
- ❌ Mais complexo que nível de linha
- ❌ Requer funções roteadoras
- ❌ Complexidade de migração (N schemas)
Caso de uso: SaaS B2B com diferentes faixas de clientes (abordagem do STRAŦUM).
Nível 3: Filtro em Nível de Linha com RLS (Isolamento Moderado)
```
database
└── public
└── table (com coluna org_id)
```
Prós:
- ✅ Simples de implementar
- ✅ Migrações fáceis (um schema)
- ✅ Analytics cross-tenant possível
- ✅ RLS do Postgres aplica isolamento
Contras:
- ❌ Todos os tenants compartilham o mesmo modelo de dados
- ❌ Overhead de performance do RLS
- ❌ Risco de misconfigurações de RLS
Caso de uso: SaaS B2B com modelos de dados uniformes (gerenciamento de projetos, CRM).
---
Checklist de Implementação: Roteamento de Schema
Se você está implementando roteamento de schema, use este checklist:
Fase 1: Design de Schema
- [ ] Criar discriminador de tipo de tenant (organizations.type)
- [ ] Projetar tabelas específicas por schema (o que vai onde?)
- [ ] Criar schema especializado: CREATE SCHEMA tenant_b;
- [ ] Espelhar tabelas necessárias no schema especializado
- [ ] Documentar quais tabelas vivem em qual schema
Fase 2: Funções Roteadoras
- [ ] Escrever função roteadora para cada tipo de recurso
- [ ] Usar SECURITY DEFINER para permissões elevadas
- [ ] Definir search_path = public, tenant_b para acesso multi-schema
- [ ] Lidar com detecção de tipo de tenant: SELECT type FROM organizations
- [ ] Retornar informações de schema para debugging
- [ ] Conceder EXECUTE ao papel autenticado
Fase 3: Views de Segurança
- [ ] Criar views unificadas para leituras (UNION ALL entre schemas)
- [ ] Usar WITH (security_invoker = on) para aplicação de RLS
- [ ] Adicionar coluna source_schema para debugging
- [ ] Testar que as políticas RLS funcionam nas views
- [ ] Conceder SELECT ao papel autenticado
Fase 4: Integração de Aplicação
- [ ] Atualizar escritas para usar funções roteadoras: supabase.rpc('save_resource_routed', ...)
- [ ] Atualizar leituras para usar views: supabase.from('resource_unified').select()
- [ ] Remover lógica if/else em nível de aplicação
- [ ] Testar fluxo do tipo de tenant A (escreve em public)
- [ ] Testar fluxo do tipo de tenant B (escreve em tenant_b)
- [ ] Verificar isolamento de dados (Entidade A ≠ Entidade B)
Fase 5: Migração & Testes
- [ ] Escrever scripts de migração (30+ para cobertura completa)
- [ ] Testar no staging com dados reais
- [ ] Executar auditoria de segurança (vazamento cross-schema?)
- [ ] Teste de carga (performance de RLS + views)
- [ ] Monitorar em produção (queries lentas?)
---
Lições Aprendidas
1. org_id é Necessário, Não Suficiente
Adicionar org_id a cada tabela te dá filtro em nível de linha. Mas se tipos de tenant diferentes precisam de modelos de dados diferentes, você precisa de roteamento de schema.
Aprendizado: "Multi-tenant" não é binário. Há níveis de isolamento. Escolhi um nível mais alto do que estritamente precisava para meus atuais 15 usuários. O tempo dirá se isso foi inteligente ou apenas trabalho extra.
2. Lógica de Aplicação → Lógica de Banco de Dados
Todo if (tenantType === 'TYPE_B') na sua aplicação é um code smell. Mova a lógica com consciência de tenant para o banco de dados com funções roteadoras.
Aprendizado: Funções de banco de dados são mais difíceis de escrever mas potencialmente mais fáceis de auditar. Se eu algum dia tiver clientes enterprise perguntando "prove seu isolamento de dados", posso apontar para stored procedures. Mas agora isso é hipotético.
3. Views + RLS = Leituras Unificadas
Ler de múltiplos schemas é complexo. Views + security_invoker = on te dão leituras unificadas com isolamento adequado.
Aprendizado: Views criam camadas de abstração que podem facilitar conformidade algum dia. Ou podem apenas adicionar complexidade que não precisava. Vamos ver.
4. Funções Security Definer São Poderosas
SECURITY DEFINER permite que funções rodem com privilégios elevados enquanto ainda respeitam políticas RLS. Essencial para funções roteadoras.
5. Migrações Valem a Pena (Provavelmente)
33 migrações para roteamento de schema pareceram muito. Mas o resultado? Arquitetura limpa, isolamento verdadeiro e zero bugs cross-tenant.
Aprendizado: Dívida técnica tem um preço. Escolhi pagá-la cedo quando tenho 15 usuários alpha em vez de esperar até ter clientes pagantes. Foi a decisão certa? Saberei se um dia tiver 100 clientes para me preocupar.
6. Falhas Arquiteturais Doem Mais Que Bugs de Código
Encontrar um null pointer exception às 23h47? Irritante. Descobrir que toda a sua arquitetura multi-tenant é fundamentalmente quebrada? Esse é o tipo de descoberta que te faz ficar acordado à noite.
Mas aqui está o que aprendi: erros arquiteturais são corrigíveis. São caros, sim. Demorados, absolutamente. Mas fui de "um tipo de tenant pode acidentalmente contaminar os dados de outro" para "isolamento aplicado pelo banco de dados que é impossível de bypassar."
Corrija cedo, corrija direito, e você dormirá melhor.
7. Arquitetura Pode Ser Estratégia (Ou Talvez Só Over-Engineering)
A decisão de roteamento de schema não foi apenas sobre "código limpo." Foi sobre manter opções futuras abertas — parcerias white-label, vendas enterprise, expansão internacional.
Mas aqui está a verdade honesta: estou em alpha privado com 15 usuários. Não tenho clientes enterprise batendo à porta. Não recebi uma única pergunta sobre GDPR. As parcerias que imagino podem nunca se materializar.
Achei que estava tomando uma decisão técnica. Talvez estivesse tomando uma decisão de estratégia de negócios. Ou talvez estivesse apenas over-engineering porque acho arquitetura de banco de dados interessante. :)
Você já descobriu uma falha arquitetural que não era um bug mas um erro de design? Como lidou com a reconstrução — foi incremental ou arrancou tudo de uma vez como eu fiz?
Abraços,
Chandler
A série de arquitetura do STRAŦUM: Esta é a parte 2 da jornada de multi-tenancy. Começou com construir multi-tenancy no Dia 2. Depois da reconstrução do schema, descobri 31 telas em branco por contexto de navegação perdido e que meu banco de dados estava correto mas 296x muito lento.
---
Construindo SaaS multi-tenant com necessidades complexas de isolamento? O STRAŦUM usa roteamento de schema para servir diferentes tipos de tenant com verdadeiro isolamento de dados. Solicite acesso alpha em https://stratum.chandlernguyen.com/request-invitation
---
*Ainda aprendendo que "multi-tenant" tem muitos níveis de isolamento. Ainda depurando políticas RLS à meia-noite. Ainda questionando minhas decisões de arquitetura do Dia 2 (mas menos agora). Mais aventuras de banco de dados em https://www.chandlernguyen.com/ .
---





