07Detalhamento · domain
Um modelo de domínio para agenda, vendas e entrega
Três produtos que pareciam não ter nada a ver, todos dividindo um mesmo conjunto de entidades-verdade. A refatoração que fez uma mudança em um parar de quebrar os outros dois.
O problema
Agenda, vendas e entrega pareciam três produtos sem relação, então cada um fez a própria cópia das mesmas ideias: um cliente, um profissional, uma oferta, um pedido, cada um um pouco diferente. O preço apareceu depois: mexer em como vendas tratava um cliente quebrava como a agenda achava a mesma pessoa, porque "a mesma pessoa" eram três registros que só pareciam iguais.
As restrições
- 01Um conjunto de entidades-verdade. Cliente, oferta, profissional: definidos uma vez, referenciados em todo lugar, nunca remodelados por feature.
- 02Referenciar por id, não por cópia. As camadas apontam pra verdade em vez de carregar cada uma a sua duplicata.
- 03Migrar sem downtime. É sistema em produção com cliente pagando, então o refactor tinha que andar debaixo do tráfego.
- 04O que é compartilhado vira biblioteca com nome. Reúso que ninguém nomeia volta a ser três cópias em pouco tempo.
A arquitetura
entidades-verdade
cliente, profissional, oferta, pedido — definidos uma vez
camadas referenciam por id
agenda, vendas e entrega apontam para os mesmos ids
biblioteca compartilhada
os blocos sobre a verdade, com nome e reusados de propósito
A regra que resolveu cabe numa linha: tudo por id. Camada nenhuma guarda a própria cópia de um cliente ou de uma oferta; ela guarda o id e pergunta pra verdade. Depois que isso passou a valer, uma feature de um produto perdeu a capacidade de corromper os outros dois, porque só existia um de cada coisa pra corromper.
A parte difícil
A parte difícil não foi desenhar o modelo unificado. Foi acreditar no código em vez do diagrama. No papel, os três produtos já compartilhavam um cliente. No dado, não: a mesma pessoa existia em registros separados, com ids separados, e metade dos bugs era duas camadas discordando sobre qual deles era o verdadeiro.
O refactor tinha que juntar essas duplicatas sem perder histórico, com tudo funcionando o tempo todo. Então andou por id: introduz a entidade-verdade, aponta uma camada pra ela, confere contra o banco, e só então vai pra próxima. Nada de reescrever de uma vez. Foi essa disciplina sem graça, uma referência por vez, que deixou uma mudança estrutural entrar em produção sem janela de manutenção.
Trade-offs
- Referência por id significa mais join e mais indireção do que uma linha gorda desnormalizada. Eu pago o join com prazer pra nunca ter duas cópias discordando.
- Biblioteca compartilhada acopla os três produtos: mexer na verdade toca todos eles. O acoplamento é o objetivo — é ele que faz os três concordarem — mas exige a suíte de provas pra mexer com segurança.
- Unificar desde o começo teria saído mais barato do que unificar com três produtos já de pé. Só que eu não tinha o formato dos três até eles existirem, então parte da duplicação foi o preço de descobrir o que unificar.
O que eu faria diferente
Eu escreveria o invariante como teste, não só como princípio. "Tudo por id" morava na minha cabeça e no code review. Uma regra de lint, ou uma restrição de schema que falha quando uma camada guarda a própria cópia de uma entidade-verdade, seguraria o modelo no lugar no dia em que não for eu revisando o PR.