Projetando para a exclusão
Todo sistema sabe criar um registro. Poucos sabem excluir direito. Projete a exclusão primeiro e o resto fica mais simples.
Ninguém projeta a exclusão. Os times passam semanas no fluxo de criação, na validação, no caminho feliz, e depois alguém adiciona numa tarde um endpoint DELETE que roda uma instrução e devolve 204. Seis meses depois esse endpoint é a origem de uma fatura órfã, uma chave estrangeira quebrada, uma tabela de analytics que ainda conta o cliente e um pedido de privacidade que ninguém consegue atender.
Passei a acreditar que a exclusão é a operação mais difícil na maioria dos sistemas, e que projetá-la primeiro é o jeito mais rápido de descobrir o que seu modelo de dados realmente é.
Exclusão é uma pergunta sobre propriedade
Excluir um registro obriga a responder uma pergunta que o caminho de criação deixa evitar: o que mais essa coisa possui? Um cliente tem pedidos. Pedidos têm pagamentos. Pagamentos têm estornos. Estornos têm lançamentos no razão. Excluir o cliente exclui o razão? Obviamente não, contadores existem. Então o que acontece?
Só existem algumas respostas honestas. Cascata, em que os filhos vão com o pai, certa para coisas sem significado próprio, como itens de um pedido. Restrição, em que a exclusão é recusada enquanto existirem filhos, certa para qualquer coisa com peso legal ou financeiro. Desvinculação, em que os filhos sobrevivem com a referência limpa ou apontando para uma lápide, certa para comentários de uma conta excluída. E anonimização, em que o registro fica mas os dados pessoais são apagados, que é o que a maioria das leis de privacidade pede.
Todo relacionamento do modelo precisa de uma dessas quatro respostas, por escrito. Se você não consegue responder para um relacionamento, ainda não o entende, e é esse tipo de lacuna que produz os bugs que encontro em auditorias.
Soft delete não é uma decisão, é um adiamento
A saída comum é a coluna deleted_at. Nada é realmente excluído; uma flag é marcada e toda consulta ganha um filtro. Eu uso, mas sou honesto sobre o que é: um jeito de adiar a pergunta, não de respondê-la.
Soft delete tem custos reais. Toda consulta precisa lembrar do filtro, e um filtro esquecido vaza dados excluídos num relatório ou numa tela. Restrições de unicidade quebram, porque o e-mail excluído ainda ocupa sua linha. Chaves estrangeiras continuam apontando para linhas 'excluídas', e os filhos ficam num estado que ninguém projetou. E os dados continuam lá: o pedido de privacidade não foi atendido, o backup ainda os tem e o disco ainda paga por eles.
Quando uso soft delete, combino com três coisas: uma view no banco ou um escopo padrão que aplica o filtro para que não seja esquecido, índices únicos que incluem a flag de exclusão, e uma exclusão definitiva agendada após a retenção que executa as regras reais de propriedade. O soft delete vira um período de carência, não um estado final.
O caminho de exclusão é um fluxo
Em qualquer sistema além do trivial, exclusão não é uma instrução. É um fluxo com etapas que falham de forma independente: revogar sessões, cancelar assinaturas no provedor de pagamento, remover do índice de busca, limpar o cache da CDN, notificar consumidores a jusante, apagar dos backups após a retenção, escrever um registro de auditoria dizendo o que foi excluído, por quem e por quê.
Então a exclusão deveria ser modelada como qualquer outra operação longa: um registro de solicitação com status, etapas idempotentes, retries e um jeito de ver onde ela está. Um 204 que retorna antes de o índice de busca ser atualizado mente para o usuário, que vai buscar e encontrar o que acabou de excluir.
E o registro de auditoria não é opcional. A única coisa que um registro excluído precisa deixar para trás é a evidência de que existiu e foi excluído de propósito. Sem isso, o primeiro chamado dizendo 'meus dados sumiram' fica sem resposta.
O que eu verifico
Quando audito exclusão, escolho uma entidade central, peço ao time para excluir uma instância de teste em staging, e olho toda tabela, índice, cache e sistema externo que a referenciava. Sempre sobra alguma coisa. Geralmente o índice de busca ou um fluxo de eventos de analytics; muitas vezes um arquivo no object storage; às vezes um webhook que um parceiro já recebeu e não pode desreceber.
Depois pergunto sobre o pedido de privacidade: uma pessoa real pede para ser esquecida, o que acontece? Se a resposta envolve um script SQL manual e uma pessoa citada numa wiki, o sistema não suporta exclusão. Suporta alguém fazendo isso heroicamente na mão.
Projete a exclusão primeiro. Ela conta o que possui o quê, quais relacionamentos têm peso, quais sistemas externos guardam cópias, e quanto do seu modelo de dados é uma história que você não terminou de contar. Tudo o que você aprende melhora o caminho de criação também.