Modelagem de dados: a decisão mais cara de reverter
Framework, linguagem e interface se trocam. O modelo de dados, depois de em produção com dados reais, resiste — e cada feature nasce brigando com ele. Por que essa é a decisão que mais merece cuidado no início.
Quase toda decisão técnica é reversível a um custo tolerável. Trocar de framework dá trabalho; trocar de linguagem, mais ainda; refazer uma interface é rotina. O modelo de dados é a exceção: uma vez em produção, com dados reais de gente real, ele resiste à mudança como nenhuma outra parte do sistema.
O custo não está no diagrama — está no que já foi gravado. Migrar uma tabela viva significa script de migração reversível, integridade a preservar, janela de indisponibilidade a negociar e código acoplado a ajustar em cascata. Errar cedo é barato; errar depois de mil registros é caro; errar depois de um milhão vira projeto.
Modele o domínio, não a tela
O erro mais comum é moldar o schema em torno da interface atual — uma tabela por tela, um campo por input. Interface muda a cada trimestre; o domínio, não. Modelar o domínio é capturar as entidades reais do negócio e as regras que valem sempre (as invariantes), independentemente de como a tela mostra isso hoje.
- Pergunte o que é verdade sempre, não o que a tela precisa agora.
- Nomeie as entidades como o negócio as nomeia, não como o CRUD as expõe.
- Deixe o banco garantir o que é invariante — chave, unicidade, integridade referencial — em vez de confiar só no código da aplicação.
Limites de serviço seguem os donos do dado
Quando o sistema cresce e a pergunta vira “onde dividir”, a resposta costuma estar no dado, não no código. Um serviço deve ser dono do que escreve. Dois serviços disputando a mesma tabela não são dois serviços — são um só, com a fronteira desenhada no lugar errado, e cada deploy de um quebra o outro.
Um exemplo concreto: em vez de deixar a aplicação garantir que todo pedido tem um cliente válido e um total não-negativo, a própria modelagem fecha essa porta.
CREATE TABLE pedido (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
cliente_id uuid NOT NULL REFERENCES cliente(id),
total_cents integer NOT NULL CHECK (total_cents >= 0),
criado_em timestamptz NOT NULL DEFAULT now()
);A constraint não é burocracia: é a invariante escrita onde ninguém consegue contornar. Código de aplicação esquece de validar; o banco, não. Um pedido sem cliente ou com total negativo simplesmente não existe — e isso vale para todos os caminhos de escrita, inclusive o script de migração apressado de uma sexta à noite.
Nenhum modelo nasce perfeito, e não é disso que se trata. Trata-se de gastar o cuidado onde ele rende mais: uma boa modelagem faz feature nova custar pouco; uma ruim faz cada feature brigar com o passado. É a fundação — e fundação se acerta antes de subir a parede.