01 - Entidade (Entity)
video: https://youtu.be/... no frontmatter da nota.Entidade (Entity)
Uma entidade é um objeto de domínio definido por sua identidade contínua, e não pelos valores de seus atributos. Dois objetos que representam a mesma conta bancária são a mesma conta, mesmo que o saldo, o limite e o endereço do titular tenham mudado ao longo dos anos. Essa é a diferença fundamental para o objeto de valor, que se define exatamente pelos atributos: dois valores de Dinheiro com o mesmo montante são intercambiáveis, mas duas contas com o mesmo saldo continuam sendo contas diferentes. Entidades vivem ao longo de um ciclo de vida, nascem, sofrem mudanças de estado e morrem, e o software precisa rastrear essa continuidade de forma confiável. Em produção, quase todo conceito que aparece em auditoria, em histórico ou em relacionamento com outros conceitos é uma entidade.
Identidade contínua ao longo do ciclo de vida
A identidade é o que permite dizer que o objeto de hoje é o mesmo de ontem, apesar das mudanças.
Uma assinatura de plano muda de preço, de cartão e até de titular, e continua sendo a mesma
assinatura para fins de cobrança, histórico e suporte. Essa continuidade precisa sobreviver a
processos, a reinícios e a gerações de versões do sistema, então a identidade não pode depender de
memória de processo nem de posição em uma lista. Perder ou duplicar identidade é dos defeitos mais
caros que existem: uma cobrança emitida duas vezes porque dois objetos representavam a mesma fatura
é um incidente clássico, com estorno, multa contratual e auditoria. Por isso a decisão de o que é
entidade e como sua identidade é gerada é decisão de negócio, e não detalhe de implementação.
Igualdade por identidade
Duas entidades são iguais quando compartilham a mesma identidade, ainda que os demais atributos
diferam. Em Java e C#, isso se materializa em sobrescrever equals e hashCode com base no campo de
identidade, e em TypeScript em um método ou função de comparação equivalente. O código a seguir mostra
a implementação típica em Java, com identidade imutável e geração explícita.
public final class Assinatura {
private final AssinaturaId id; // identidade imutável, nasce e nunca muda
private Plano plano; // atributos podem mudar sem alterar a identidade
private StatusDaAssinatura status;
public Assinatura(AssinaturaId id, Plano plano) {
this.id = id;
this.plano = plano;
this.status = StatusDaAssinatura.ATIVA;
}
public void suspender(MotivoDaSuspensao motivo) { // transição com nome de negócio
if (status != StatusDaAssinatura.ATIVA) throw new SuspensaoInvalida(id, status);
this.status = StatusDaAssinatura.SUSPENSA;
}
@Override
public boolean equals(Object outro) {
// igualdade por identidade: dois objetos com o mesmo id são a mesma assinatura
return outro instanceof Assinatura outra && outra.id.equals(this.id);
}
@Override
public int hashCode() {
return id.hashCode(); // coerente com equals para uso em coleções
}
}
O detalhe operacional mais ignorado é o uso em coleções. Se equals e hashCode não forem coerentes,
uma entidade aparece duplicada em um HashSet ou desaparece de um Map, e o defeito só aparece em
cenários específicos de produção. Regras práticas: o campo de identidade é imutável, equals compara
apenas identidade, e hashCode deriva dela.
Como gerar e representar a identidade
Existem três estratégias comuns, cada uma com consequências de produção. Identificadores naturais
usam um atributo que o negócio já garante único, como CPF de titular ou número de contrato emitido
por um sistema oficial; são legíveis, mas dependem de a unicidade ser realmente garantida pelo
processo externo. Identificadores gerados pelo banco, como sequências, são simples e compactos, mas
a identidade só existe depois do insert, o que complica testes e fluxos que precisam do id antes de
persistir. Identificadores gerados pela aplicação, tipicamente UUID, existem desde a criação do
objeto, funcionam offline e simplificam testes, ao custo de índices maiores em tabelas de alto
volume. Em qualquer estratégia, a recomendação de encapsulamento é a mesma: envolver o valor em um
tipo próprio, como AssinaturaId, para impedir que um identificador de um tipo seja confundido com
outro em um parâmetro trocado.
Ciclo de vida, transições e rastreabilidade
O ciclo de vida de uma entidade costuma ser expresso como estados e transições, e os métodos da
classe devem nomear essas transições com a linguagem do negócio: aprovar, suspender, cancelar,
renovar. Cada transição valida se o estado atual permite a mudança, atualiza o estado e, em
sistemas com auditoria ou event sourcing, registra o que aconteceu. Setter público destrói esse
desenho, porque permite pular do estado ATIVA para CANCELADA sem passar pela validação nem pelo
registro. Rastrear mudanças importa em produção por três motivos: auditoria regulatória, que exige
responder quem mudou o quê e quando; diagnóstico de incidentes, que depende de reconstruir a
sequência de estados; e suporte ao cliente, que precisa saber por que uma cobrança foi feita. Um
log de eventos por entidade, mesmo simples, atende os três.
Concorrência e versionamento
Entidades são exatamente os objetos sobre os quais duas pessoas podem agir ao mesmo tempo, então a
concorrência é parte do problema que elas resolvem. O mecanismo padrão em sistemas com banco
relacional é o versionamento otimista: a entidade carrega um número de versão, incrementado a cada
mudança persistida, e a atualização falha se a versão lida não corresponde mais à versão atual. O
custo é pequeno e o ganho é grande, pois o sistema nunca grava por cima de uma mudança que não viu.
Em entidades editadas simultaneamente por operadores humanos, como uma reserva alterada por dois
atendentes, a falha vira uma mensagem clara de reconciliação. Em entidades disputadas por processos
automáticos de alto volume, como uma conta que recebe débitos de várias esteiras, o desenho do
agregado deve evitar a disputa na mesma instância, dividindo a operação em entidades menores.
Entidade versus objeto de valor
| Critério | Entidade | Objeto de valor |
|---|---|---|
| Definição | Identidade contínua | Conjunto de atributos |
| Igualdade | Por identificador | Por valores dos atributos |
| Mutabilidade | Estado muda ao longo do ciclo de vida | Imutável, mudanças criam nova instância |
| Ciclo de vida | Nasce, muda e é encerrada | Sem ciclo de vida, é substituída |
| Exemplo em banking | Conta, empréstimo, contrato | Valor monetário, CPF, taxa de juros |
| Exemplo em e-commerce | Pedido, cliente, devolução | Endereço, item de catálogo, preço |
| Persistência típica | Linha com chave primária própria | Colunas embutidas na entidade dona |
A fronteira entre os dois é uma decisão de modelagem, e a mesma coisa pode mudar de classe entre
contextos. Endereço é objeto de valor no checkout e pode virar entidade no cadastro de imóveis de
uma imobiliária, onde cada endereço tem história própria. A pergunta orientadora é se o conceito
precisa ser rastreado individualmente ao longo do tempo; se a resposta é sim, é entidade.
Erros comuns e boas práticas
- Usar igualdade por atributos em entidades. Duas instâncias do mesmo contrato com valores iguais
continuam sendo dois contratos, e igualdade por atributos gera cobrança e faturamento duplicados. - Deixar a identidade mutável ou trocável. Se o id pode mudar, a continuidade que justifica a
entidade desaparece junto com a confiabilidade dos relacionamentos. - Expor setters para todo atributo. Transições de estado precisam de métodos com nome de negócio,
validação e registro, e setter destrói essa cadeia. - Gerar o identificador no banco quando o fluxo precisa dele antes de persistir. Prefira geração na
aplicação quando há fluxos assíncronos, testes de unidade ou publicação de eventos. - Usar o tipo primitivo cru como identidade em toda a base. Um parâmetro trocado entre
PedidoId e ClienteId compila sem erro e falha em produção. - Tratar toda coisa do domínio como entidade por reflexo. Conceitos sem rastreabilidade individual
são objetos de valor, e tratá-los como entidade multiplica tabelas e complexidade sem retorno.