3 esquemas PostgreSQL para estruturar o Supabase corretamente
Pontos chave: Separar uma base de dados Supabase PostgreSQL em três esquemas distintos (privat,collaborative,gamification) desde o início evita o caos à medida que o projeto cresce. Este artigo aborda o porquê, o como, as políticas RLS, as convenções de nomenclatura e os erros a não repetir.
Quando tu inicias um projeto, a tentação é enorme: tu crias as tuas tabelas no esquema public do PostgreSQL e avanças. Uma tabela aqui, outra ali. Em três semanas, tu te encontras com 40 tabelas misturadas sem nenhuma lógica, nomes inconsistentes e um medo terrível de tocar em qualquer coisa.
Decidi muito cedo no desenvolvimento do TAMSIV estruturar a base de dados com três esquemas separados. Seis meses e mais de 650 commits depois, é provavelmente a melhor decisão arquitetural que tomei. Aqui está o porquê e como fazer isso concretamente com Supabase.
Por que não colocar tudo no esquema público?
O esquema public do PostgreSQL é o padrão. Supabase o utiliza para suas próprias tabelas internas. Quando tu adicionas as tuas tabelas lá, tu misturas os teus dados de negócio com a infraestrutura. É como guardar as tuas roupas na cozinha — funciona, mas é um caos.
Os problemas concretos que identifiquei:
- Legibilidade: Com mais de 40 tabelas em um único esquema, encontrar uma tabela torna-se um jogo de adivinhação.
- Segurança: As políticas RLS (Row Level Security) tornam-se impossíveis de raciocinar quando tabelas de domínios diferentes coexistem.
- Evolução: Adicionar um novo domínio funcional (gamificação, analytics) sem tocar no existente é impossível se tudo estiver no mesmo lugar.
- Colaboração: Quando outro desenvolvedor (ou tu mesmo daqui a 6 meses) descobre o projeto, ele não sabe por onde começar.
Como estruturar os esquemas de uma app Supabase?
Aqui estão os três esquemas do TAMSIV e o que eles contêm:
Esquema privat — os dados pessoais
Tudo o que pertence a um utilizador e só a ele:
privat.tasks— Tarefas pessoaisprivat.memos— Memos de voz e textoprivat.calendar_events— Eventos de calendárioprivat.user_profiles— Perfis de utilizadorprivat.task_attachments/privat.memo_attachments— Anexos
Por que privat e não private? Porque private é uma palavra reservada em SQL. Aprendi isso da pior maneira depois de uma migração falha que quebrou todo o esquema. A mensagem de erro nem era clara — levou 45 minutos para entender que o nome do esquema era o problema.
Esquema collaborative — os grupos
Tudo o que se refere ao trabalho em equipa:
collaborative.groups— Grupos hierárquicos (até 6 níveis)collaborative.group_members— Membros e papéiscollaborative.group_tasks/collaborative.group_memos— Conteúdo partilhadocollaborative.checklists— Listas de verificação com validação
Este esquema é o mais complexo em termos de políticas RLS, pois as regras de acesso dependem do papel do utilizador no grupo, da hierarquia dos grupos e das permissões herdadas. Detalhei a complexidade dos grupos hierárquicos no artigo dedicado.
Esquema gamification — o engajamento
Tudo o que torna a aplicação viciante (no bom sentido):
gamification.user_stats— Pontos, nível, sequência atualgamification.user_badges— Emblemas desbloqueáveisgamification.points_history— Histórico detalhado de pontosgamification.daily_challenges— Desafios diáriosgamification.feed_activity— Feed de atividade social
Este esquema foi adicionado três meses após o início do projeto. Graças à separação, criei um novo esquema sem tocar nos outros dois. Zero risco de quebrar o existente. A arquitetura de gamificação é detalhada no artigo sobre o esquema de gamificação.
Quais as vantagens concretas na prática?
Além da teoria, aqui está o que a separação em esquemas me trouxe no dia a dia:
A legibilidade imediata
Quando faço SELECT * FROM privat.tasks, sei instantaneamente que é um dado pessoal. Quando vejo collaborative.group_members, sei que está ligado aos grupos. Não é necessária documentação adicional — o esquema É a documentação.
Políticas RLS mais simples de raciocinar
As regras do esquema privat são claras: tu só vês os teus dados. Ponto. A política RLS cabe numa linha:
CREATE POLICY "users_own_data" ON privat.tasks
USING (user_id = auth.uid());
As do esquema collaborative são mais complexas: tu vês os dados dos grupos dos quais és membro, de acordo com o teu papel, com herança de permissões parentais. Mas como estão isoladas no seu esquema, a complexidade não se espalha para o resto.
A evolução sem risco
Adicionar a gamificação não exigiu nenhuma modificação nos esquemas existentes. Adicionar o calendário com participantes e filtros também não. Cada novo domínio funcional é autónomo.
Como gerir as políticas RLS com múltiplos esquemas?
Supabase usa Row Level Security (RLS) para proteger o acesso aos dados. O princípio é simples: cada tabela tem regras que determinam quem pode ler, escrever, modificar, apagar.
Na prática, é um labirinto. No total, TAMSIV tem mais de 30 políticas RLS, cada uma testada individualmente. Eis como as organizo:
- Esquema
privat: Políticas simples, baseadas emauth.uid() = user_id. - Esquema
collaborative: Políticas complexas que verificam a pertença ao grupo E o papel. Utilização de subconsultas emcollaborative.group_members. - Esquema
gamification: Políticas mistas — leitura pública para o leaderboard, escrita apenas através de funções RPC do servidor.
A armadilha mais comum: uma política RLS demasiado permissiva que permite a fuga de dados entre utilizadores. Falo sobre isso no artigo sobre auditoria de segurança e rate limiting.
Quais convenções de nomenclatura adotar?
Convenções estritas desde o primeiro dia evitam horas de confusão mais tarde. Aqui estão as do TAMSIV:
- Tabelas:
snake_caseno plural (tasks,group_members,daily_challenges) - Colunas:
snake_case(user_id,created_at,is_completed) - Parâmetros RPC: Prefixo
p_(p_start_date,p_group_id,p_user_id) - Funções RPC: verbo_objeto (
get_consolidated_feed,add_gamification_points)
O prefixo p_ para os parâmetros RPC é crítico. Sem ele, no dia em que o teu parâmetro user_id entrar em conflito com a coluna user_id numa consulta, o PostgreSQL não levantará um erro — ele priorizará a coluna. O resultado: uma consulta que retorna dados inesperados, um bug silencioso e perigoso.
Como migrar para múltiplos esquemas se o projeto já existe?
Se tu já tens tudo em public e queres migrar, aqui está a estratégia:
- Auditoria: Lista todas as tuas tabelas e classifica-as por domínio funcional.
- Cria os esquemas:
CREATE SCHEMA privat; CREATE SCHEMA collaborative; - Migra tabela por tabela:
ALTER TABLE public.tasks SET SCHEMA privat; - Atualiza as políticas RLS: Elas estão ligadas à tabela, então seguem o movimento.
- Atualiza o código frontend: Todas as consultas Supabase devem agora especificar o esquema.
Atenção: as chaves estrangeiras, os gatilhos e as funções RPC devem ser atualizados manualmente. É um trabalho árduo, mas o ganho em manutenibilidade é enorme. Se tu usas Supabase, lê também o meu artigo sobre a redução do egress para otimizar os custos.
Quais erros evitar ao estruturar?
Em 6 meses de desenvolvimento solo, identifiquei várias armadilhas:
- Não usar palavras reservadas SQL como nomes de esquema.
private,public,usersão todos reservados. Usa alternativas (privat,app_public,accounts). - Não esquecer as permissões
GRANT. Criar um esquema não é suficiente — é preciso dar explicitamente os direitos de acesso aos papéis Supabase (anon,authenticated,service_role). - Nunca confiar nos arquivos de migração locais. A base de dados real é a única fonte de verdade. Os arquivos de migração podem divergir após correções manuais.
- Testar cada política RLS individualmente. Mais de 30 políticas significam mais de 30 cenários de teste mínimos. É metódico, entediante e absolutamente indispensável.
Qual o impacto no desempenho?
Boas notícias: os esquemas PostgreSQL não têm impacto no desempenho das consultas. Um SELECT FROM privat.tasks é exatamente tão rápido quanto um SELECT FROM public.tasks. Os esquemas são uma organização lógica, não física.
Por outro lado, a separação facilita a otimização. Tu podes adicionar índices específicos por domínio, configurar parâmetros de vacuum diferentes por esquema e monitorizar o desempenho por domínio funcional.
Para o frontend, o refactoring clean architecture também desempenhou um papel crucial na manutenibilidade do código que interage com a base.
FAQ
Quantos esquemas devem ser criados para uma aplicação clássica?
Dois a quatro são suficientes para a maioria dos projetos. Um para os dados do utilizador, um para as funcionalidades partilhadas/colaborativas, eventualmente um para a análise ou gamificação. O importante é separar por domínio funcional, não por tipo de dado.
O Supabase suporta bem múltiplos esquemas?
Sim, mas é preciso configurar as permissões manualmente. Por padrão, apenas o esquema public é acessível via API REST. Para expor outro esquema, é preciso adicioná-lo nas configurações do projeto Supabase e configurar os GRANT apropriados nos papéis anon e authenticated.
São necessárias chaves estrangeiras entre esquemas?
Sim, e isso é perfeitamente suportado pelo PostgreSQL. Uma tabela em collaborative pode referenciar uma tabela em privat. As restrições de chave estrangeira funcionam entre esquemas sem qualquer limitação.
Como gerir as funções RPC que afetam vários esquemas?
As funções RPC (como get_consolidated_feed) podem unir tabelas de esquemas diferentes sem problemas. A boa prática é criar a função no esquema do domínio principal que ela serve, e usar nomes totalmente qualificados (privat.tasks) nas consultas.
É possível reverter depois de migrar para vários esquemas?
Tecnicamente sim, com ALTER TABLE SET SCHEMA public. Mas na prática, uma vez que o código frontend referencia os esquemas, a reversão é custosa. É por isso que é melhor tomar essa decisão cedo no projeto.