Padrões de Design de Banco de Dados para Aplicações SaaS
De multi-tenancy a faturamento por assinatura e log de auditoria, aplicações SaaS têm necessidades previsíveis de banco de dados. Aqui estão os padrões que funcionam, com exemplos de schema para cada um.
Toda aplicação SaaS tem o mesmo conjunto recorrente de problemas de design de banco de dados. Como você isola dados entre clientes? Como você modela assinaturas e faturamento? Como você rastreia quem mudou o quê? Esses padrões aparecem com tanta frequência que existem soluções bem estabelecidas — mas os detalhes importam, e escolher a abordagem errada cedo é caro de desfazer.
Este guia cobre os padrões de design de banco de dados mais importantes para aplicações SaaS. Para cada padrão, você aprenderá o que ele resolve, como implementá-lo e que trade-offs esperar. ER Flow é a ferramenta usada ao longo do guia para visualizar e projetar esses schemas.
Multi-Tenancy: A Fundação do SaaS
Multi-tenancy é a capacidade de servir múltiplos clientes (tenants) a partir de uma única instância de aplicação. Como você lida com isso no nível do banco de dados determina suas garantias de isolamento de dados, complexidade operacional e teto de escalabilidade.
Tabelas compartilhadas com `tenant_id` é a abordagem mais comum. Toda tabela com escopo de tenant recebe uma coluna tenant_id (uma chave estrangeira para uma tabela tenants), e toda consulta filtra por tenant_id. O schema é simples: users(id, tenant_id, email, ...), orders(id, tenant_id, user_id, ...). O risco são bugs no nível da aplicação que acidentalmente expõem dados de um tenant para outro. Mitigue isso com segurança em nível de linha no PostgreSQL ou impondo filtros de tenant_id em uma classe de consulta base. Schema por tenant dá a cada tenant seu próprio namespace dentro da mesma instância de banco de dados. A aplicação seleciona o schema correto no momento da conexão. Isso fornece isolamento mais forte, mas adiciona sobrecarga operacional quando você tem centenas de tenants.
Banco de dados por tenant é o padrão ouro para isolamento, usado por produtos SaaS corporativos que devem satisfazer requisitos rígidos de conformidade. Cada tenant recebe uma instância de banco de dados completamente separada. O gerenciamento de migrations se torna um problema de sistemas distribuídos e os custos escalam linearmente com a contagem de tenants. Reserve isso para situações onde o isolamento é um requisito difícil, não apenas uma preferência.
Schema de Assinatura e Faturamento
O padrão de faturamento por assinatura envolve quatro tabelas principais. Planos definem o que é oferecido: id, name, price_monthly_cents, price_yearly_cents, features (JSON), trial_days, is_active. Assinaturas rastreiam o que um tenant está inscrito: id, tenant_id, plan_id, status (um enum de trialing, active, past_due, canceled, paused), trial_ends_at, current_period_start, current_period_end, canceled_at e payment_gateway_subscription_id.
Faturas registram o que foi cobrado: id, tenant_id, subscription_id, amount_cents, currency, status (draft, open, paid, void, uncollectible), due_date, paid_at e payment_gateway_invoice_id. Pagamentos registram tentativas individuais de pagamento: id, invoice_id, amount_cents, status, payment_method, gateway_payment_id, failed_reason, created_at. Armazene valores monetários como inteiros na menor unidade da moeda (centavos para BRL) para evitar completamente problemas de precisão de ponto flutuante.
Autenticação de Usuários e Funções
Um schema de autenticação flexível precisa lidar com login social, redefinição de senha, verificação de e-mail e acesso baseado em funções. Tabelas principais: users(id, email, email_verified_at, password_hash, remember_token, created_at, updated_at). Para auth social, adicione oauth_providers(id, user_id, provider, provider_user_id, access_token, refresh_token, expires_at). Para acesso baseado em funções, adicione roles(id, tenant_id, name, permissions JSON) e user_roles(user_id, role_id) como tabela de junção.
Para permissões granulares em produtos SaaS complexos, considere uma tabela de permissões separada em vez de embutir permissões em JSON: permissions(id, name, description) e role_permissions(role_id, permission_id). Isso permite adicionar novas permissões sem migrations de schema. Indexe user_roles.user_id e role_permissions.role_id pesadamente — essas colunas são lidas em cada verificação de autorização ao longo da aplicação.
Log de Auditoria
Toda aplicação SaaS em produção precisa saber quem mudou o quê e quando. O padrão padrão usa um log de auditoria polimórfico: audit_logs(id, tenant_id, user_id, auditable_type, auditable_id, event, old_values JSON, new_values JSON, ip_address, user_agent, created_at). As colunas auditable_type e auditable_id criam uma referência polimórfica — uma tabela de log de auditoria cobre mudanças em qualquer entidade do seu schema.
Indexe em (tenant_id, auditable_type, auditable_id) para consultar o histórico de auditoria de um registro específico, e em (tenant_id, user_id) para consultar todas as mudanças feitas por um usuário específico. Armazene old_values e new_values como JSON com apenas os campos alterados, não o registro completo — isso mantém o tamanho da tabela gerenciável mesmo para entidades de alta frequência. O ER Flow permite modelar esse relacionamento polimórfico visualmente, facilitando a comunicação do design para colegas não familiarizados com padrões polimórficos.
Soft Deletes
Em SaaS, fazer hard delete de registros geralmente é uma má ideia. Um cliente exclui acidentalmente algo e espera que você restaure. Você precisa manter registros de faturamento para conformidade. Você quer analisar churn examinando contas excluídas. Soft deletes resolvem isso com uma única coluna deleted_at TIMESTAMP NULL. Um valor NULL significa que o registro está ativo; um timestamp significa que foi excluído.
O trade-off é que cada consulta deve incluir WHERE deleted_at IS NULL — ou você confia em um escopo de consulta no seu ORM. Certifique-se de incluir deleted_at como parte de índices compostos em colunas frequentemente consultadas, e coloque-o após as colunas de filtro de alta cardinalidade. Se você precisar realmente purgar registros para conformidade com LGPD, pode fazer hard delete de linhas onde deleted_at cai fora da sua janela de retenção.
Schema de Feature Flags
Feature flags permitem controlar quais recursos estão habilitados por tenant ou por usuário sem deploys de código. Um schema prático: features(id, key, description, is_enabled_globally, created_at) como o registro de todos os recursos. tenant_features(tenant_id, feature_key, is_enabled, overrides JSON) para sobrescritas por tenant. user_features(user_id, feature_key, is_enabled) para sobrescritas por usuário. Ao verificar se um recurso está habilitado, a precedência é: sobrescrita no nível do usuário → sobrescrita no nível do tenant → padrão global.
Faça cache agressivo de consultas de feature flags — você os verifica em cada requisição. Use Redis com um TTL curto (60 segundos) e invalide o cache quando uma configuração de recurso muda. Esse padrão é mais flexível do que embutir feature flags na tabela de planos porque recursos e planos podem evoluir independentemente. No ER Flow, projetar isso como três tabelas separadas com relacionamentos claros evita o erro comum de acoplar a disponibilidade de recursos diretamente ao nível de assinatura.
Event Sourcing para Domínios com Auditoria Intensa
Para domínios onde o histórico completo de mudanças é crítico — transações financeiras, registros de conformidade, máquinas de estado de pedidos — considere event sourcing no nível do banco de dados. Em vez de atualizar registros no lugar, você acrescenta eventos: domain_events(id, tenant_id, aggregate_type, aggregate_id, event_type, payload JSON, metadata JSON, occurred_at, sequence_number). O estado atual de qualquer entidade é derivado reproduzindo seus eventos.
Isso é significativamente mais complexo do que CRUD padrão, mas fornece uma trilha de auditoria imutável, a capacidade de reproduzir eventos para depuração e uma fundação natural para arquitetura orientada a eventos. Use-o seletivamente para as partes do seu schema onde o histórico importa mais. O ER Flow permite modelar tanto seu event store quanto seu modelo de leitura (as views desnormalizadas construídas a partir de eventos) no mesmo diagrama, tornando o relacionamento entre os dois explícito e revisável por todo o time.
Projetar schemas SaaS bem requer pensar vários passos à frente. Os padrões descritos aqui foram testados em batalha em centenas de aplicações SaaS em produção. Comece com a abordagem de tabelas compartilhadas de multi-tenancy, adicione segurança em nível de linha à medida que cresce, e adicione event sourcing apenas onde resolve genuinamente um problema que você tem hoje.