Entidades e Value Objects no Node.js são dois blocos fundamentais para representar regras de negócio com clareza. Entidades possuem identidade e histórico. Value Objects são definidos pelos seus valores e normalmente são imutáveis.
A diferença parece simples, mas muda decisões sobre igualdade, persistência, validação, atualização e testes. Quando tudo vira um objeto mutável ou uma linha do banco, regras importantes se espalham pela aplicação.
O que é uma entidade?
Uma entidade continua sendo a mesma mesmo quando seus atributos mudam. Um pedido com ID p-123 é o mesmo pedido antes e depois do pagamento.
export class Pedido {
constructor({ id, clienteId, status = 'rascunho', itens = [] }) {
if (!id) throw new Error('ID obrigatório');
this.id = id;
this.clienteId = clienteId;
this.status = status;
this.itens = itens;
}
confirmar() {
if (this.itens.length === 0) {
throw new Error('Pedido vazio não pode ser confirmado');
}
if (this.status !== 'rascunho') {
throw new Error('Transição inválida');
}
this.status = 'confirmado';
}
}A classe não é apenas um recipiente. Ela impede estados inválidos.
Identidade
A identidade pode ser UUID, ULID, número sequencial ou chave composta. O domínio não precisa conhecer detalhes do banco, mas precisa comparar entidades corretamente.
igualA(outro) {
return outro instanceof Pedido && outro.id === this.id;
}Evite usar todos os campos para comparar entidades. Dois snapshots diferentes podem representar o mesmo objeto de negócio.
O que é um Value Object?
Um Value Object não possui identidade independente. Dois endereços com os mesmos dados podem ser considerados iguais. Dinheiro, e-mail, CPF, período e coordenadas são exemplos comuns.
export class Email {
constructor(valor) {
const normalizado = String(valor).trim().toLowerCase();
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(normalizado)) {
throw new Error('E-mail inválido');
}
this.valor = normalizado;
Object.freeze(this);
}
equals(outro) {
return outro instanceof Email && outro.valor === this.valor;
}
toString() {
return this.valor;
}
}Imutabilidade
Value Objects devem preferir imutabilidade. Uma mudança retorna uma nova instância.
export class Dinheiro {
constructor(centavos, moeda = 'BRL') {
if (!Number.isInteger(centavos)) throw new Error('Use centavos inteiros');
this.centavos = centavos;
this.moeda = moeda;
Object.freeze(this);
}
somar(outro) {
if (outro.moeda !== this.moeda) throw new Error('Moedas incompatíveis');
return new Dinheiro(this.centavos + outro.centavos, this.moeda);
}
}Isso evita alterações invisíveis em referências compartilhadas.
Validação no construtor
Um objeto criado deve nascer válido. Se um período exige início anterior ao fim, valide na construção.
export class Periodo {
constructor(inicio, fim) {
if (!(inicio instanceof Date) || !(fim instanceof Date)) {
throw new Error('Datas inválidas');
}
if (inicio >= fim) throw new Error('Período inválido');
this.inicio = new Date(inicio);
this.fim = new Date(fim);
Object.freeze(this);
}
}Entidade anêmica
Uma entidade anêmica contém apenas getters e setters, enquanto regras ficam em services genéricos. Isso aumenta a chance de estados inválidos.
// Evite
pedido.status = 'entregue';
// Prefira
pedido.marcarComoEntregue({ entregueEm: clock.now() });O método pode validar que o pedido estava em transporte.
Persistência
O banco armazena estado; o domínio define comportamento. Um mapper converte linhas em objetos.
export function mapearPedido(row) {
return new Pedido({
id: row.id,
clienteId: row.cliente_id,
status: row.status,
itens: JSON.parse(row.itens_json)
});
}Não deixe detalhes como cliente_id contaminar o modelo.
Serialização
Entidades e Value Objects não precisam ser enviados diretamente como JSON. Crie DTOs na borda.
export function pedidoDto(pedido) {
return {
id: pedido.id,
status: pedido.status,
total: pedido.total.centavos
};
}IDs como Value Objects
Encapsular IDs reduz confusão entre identificadores de tipos diferentes.
export class PedidoId {
constructor(valor) {
if (!valor) throw new Error('PedidoId obrigatório');
this.valor = valor;
Object.freeze(this);
}
}Em TypeScript, branded types também ajudam.
Datas e tempo
Evite chamar new Date() dentro de toda regra. Injete um relógio na camada de aplicação e entregue a data ao método.
pedido.cancelar({ motivo, canceladoEm: clock.now() });Isso torna testes determinísticos.
Value Objects compostos
Um endereço pode agrupar rua, cidade, CEP e país, validando combinação e normalização. Não exponha setters individuais se o conjunto precisa permanecer consistente.
Testes
const emailA = new Email('USER@EXAMPLE.COM');
const emailB = new Email('user@example.com');
assert.equal(emailA.equals(emailB), true);
const pedido = new Pedido({ id: 'p1', clienteId: 'c1', itens: [] });
assert.throws(() => pedido.confirmar());Quando usar classes?
Classes funcionam bem quando há identidade, invariantes e comportamento. Funções e objetos congelados também podem modelar Value Objects. O importante é garantir validade e encapsulamento, não seguir uma sintaxe específica.
Relação com aggregates
Entidades internas normalmente são alteradas por uma raiz de aggregate. O limite define quais objetos precisam ser consistentes na mesma transação.
Erros comuns
- usar ID em Value Object sem necessidade;
- criar entidade para cada tabela;
- permitir setters para qualquer campo;
- usar número decimal para dinheiro;
- misturar DTO e domínio;
- serializar classes diretamente;
- permitir objetos parcialmente válidos;
- comparar entidades por todos os atributos.
Checklist
- entidades têm identidade estável;
- métodos expressam ações do negócio;
- Value Objects são válidos ao nascer;
- valores são imutáveis quando possível;
- mappers isolam persistência;
- DTOs isolam transporte;
- testes cobrem invariantes.
Conclusão
Entidades e Value Objects no Node.js tornam regras explícitas e evitam que dados inválidos circulem pela aplicação. Entidades preservam identidade e comportamento; Value Objects encapsulam conceitos que dependem do valor.
Use esses padrões onde eles simplificam decisões reais. Para uma visão mais ampla, leia Domain-Driven Design no Node.js, além de Clean Architecture no Node.js.
Consulte o material de Martin Fowler sobre Value Objects e a referência de Domain-Driven Design.



