Blog
Architecture
6 de outubro de 20257 min

3 esquemas PostgreSQL para um aplicativo limpo — estruturando o Supabase desde o início

Quando você inicia um projeto, a tentação é grande de colocar tudo no esquema public do PostgreSQL. Uma tabela aqui, outra ali, e em três semanas você se encontra com 40 tabelas misturadas sem nenhuma lógica.

Decidi muito cedo estruturar o TAMSIV com três esquemas separados.

Os três esquemas

  • privat — Todos os dados pessoais: tarefas, memorandos, eventos de calendário, perfis de usuário, anexos
  • collaborative — Tudo relacionado a grupos: membros, funções, tarefas compartilhadas, listas de verificação de grupo
  • gamification — Estatísticas, distintivos, sequências, desafios diários, histórico de pontos

Por que privat e não private? Porque private é uma palavra reservada em SQL. Aprendi isso da pior forma depois de uma migração falha.

Por que separar?

1. Legibilidade. Quando faço SELECT * FROM privat.tasks, sei imediatamente que é um dado pessoal.

2. Segurança. As políticas RLS são mais fáceis de raciocinar quando as tabelas são agrupadas por domínio. As regras do esquema privat são simples: você só vê seus dados. As do esquema collaborative são mais complexas: você vê os dados dos grupos dos quais é membro, de acordo com sua função.

3. Evolução. Quando adicionei a gamificação três meses após o início do projeto, criei um novo esquema sem tocar nos outros dois. Risco zero de quebrar o existente.

O pesadelo das políticas RLS

O Supabase usa RLS para proteger o acesso aos dados. O princípio é simples: cada tabela tem regras que determinam quem pode ler, escrever, modificar, excluir. Na prática, é um labirinto.

No total, o projeto tem mais de 30 políticas RLS. Cada uma testada individualmente.

As convenções de nomenclatura

Adotei convenções rigorosas: tabelas em snake_case no plural, colunas em snake_case, parâmetros RPC com prefixo p_. Este prefixo p_ evita colisões com nomes de colunas nas consultas. No dia em que seu parâmetro user_id entra em conflito com a coluna user_id, você entende o porquê.

Essa estrutura me economizou um tempo considerável no futuro. Cada nova funcionalidade encontra seu lugar naturalmente.