Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Entidades e Value Objects no Node.js

Atualizado em: 14 de outubro de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

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.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita