Domain Services no Node.js representam regras de negócio que não pertencem naturalmente a uma única entidade ou Value Object. Eles trabalham com conceitos do domínio sem coordenar detalhes técnicos como HTTP, filas ou conexão com banco.
O padrão é útil quando uma decisão envolve vários objetos, políticas ou cálculos. Porém, usar um serviço para toda regra cria um modelo anêmico. Primeiro tente colocar o comportamento na entidade; extraia um Domain Service apenas quando a responsabilidade realmente atravessar objetos.
Domain Service ou Application Service?
Um Domain Service executa regra do negócio. Um Application Service coordena o caso de uso, carrega dados, abre transação, chama o domínio e persiste resultados.
export function calcularLimiteDeCompra({ cliente, carrinho, politica }) {
const risco = politica.classificar(cliente);
return risco === 'alto'
? Math.min(carrinho.total(), cliente.limiteDisponivel)
: carrinho.total();
}A função não conhece repository, Express ou PostgreSQL.
Exemplo de Application Service
export function criarFinalizarCompra({ clientes, pedidos, politica, unitOfWork }) {
return async function finalizar({ clienteId, pedidoId }) {
return unitOfWork.run(async trx => {
const cliente = await clientes.obter(clienteId, trx);
const pedido = await pedidos.obter(pedidoId, trx);
const limite = calcularLimiteDeCompra({ cliente, carrinho: pedido, politica });
pedido.finalizarComLimite(limite);
await pedidos.salvar(pedido, trx);
return pedido;
});
};
}A aplicação coordena; o domínio decide.
Quando criar um Domain Service?
- a regra envolve duas ou mais entidades independentes;
- o conceito possui nome reconhecido pelo negócio;
- colocar a regra em uma entidade causaria dependência artificial;
- a operação é sem estado ou recebe dependências de domínio explícitas;
- o resultado representa uma decisão ou cálculo do negócio.
Políticas
Políticas são serviços de domínio que encapsulam uma decisão variável.
export class PoliticaDeDesconto {
calcular({ cliente, pedido }) {
if (cliente.vip && pedido.total.centavos >= 10000) {
return 0.15;
}
if (pedido.itens.length >= 5) return 0.05;
return 0;
}
}O pedido pode receber a porcentagem e aplicar a regra de arredondamento internamente.
Strategy
Diferentes políticas podem implementar o mesmo contrato.
export function selecionarFrete({ modalidade, normal, expresso }) {
if (modalidade === 'expresso') return expresso;
return normal;
}A composição ocorre fora do domínio, normalmente no composition root.
Pureza
Serviços puros são fáceis de testar: recebem valores e retornam resultado sem I/O. Quando a regra depende de cotação externa, use uma porta.
export function criarConversorDeMoeda(cotacoes) {
return {
async converter(valor, destino) {
const taxa = await cotacoes.obter(valor.moeda, destino);
return valor.converter(taxa, destino);
}
};
}cotacoes é uma abstração de domínio; o adapter HTTP fica na infraestrutura.
Nomes orientados ao negócio
Evite classes como UtilsService, CommonService e DataService. Prefira PoliticaDeCredito, CalculadoraDeFrete e AlocadorDeEstoque.
Estado
Domain Services normalmente não mantêm estado mutável entre requisições. Dados necessários devem ser argumentos ou dependências imutáveis. Isso reduz problemas de concorrência no Node.js.
Erros do domínio
export class CreditoInsuficienteError extends Error {
constructor() {
super('Crédito insuficiente para a operação');
this.code = 'CREDITO_INSUFICIENTE';
}
}Adapters traduzem o erro para HTTP, mensagem de fila ou saída de CLI.
Eventos
Uma decisão de domínio pode resultar em eventos, mas prefira que o aggregate registre o fato após aplicar a alteração. O serviço pode retornar uma decisão estruturada.
const decisão = politica.analisar({ cliente, pedido });
pedido.aplicarDecisao(decisão);Testes unitários
const politica = new PoliticaDeDesconto();
const desconto = politica.calcular({
cliente: { vip: true },
pedido: { total: { centavos: 15000 }, itens: [1] }
});
assert.equal(desconto, 0.15);Teste limites, combinações, arredondamento e erros.
Injeção de dependência
Receba políticas e portas pelo construtor ou fábrica. Consulte Dependency Injection no Node.js.
Domain Service e aggregate
O serviço não deve contornar a raiz para alterar entidades internas. Ele calcula ou decide; a raiz mantém invariantes. Leia Aggregates no Node.js.
Domain Service e Value Object
Se a operação depende apenas dos valores de um conceito, talvez pertença ao próprio Value Object. Uma soma monetária, por exemplo, deve estar em Dinheiro, não em MoneyService.
Erros comuns
- mover todo comportamento para services;
- misturar SQL e regra de domínio;
- usar nomes genéricos;
- manter estado global;
- retornar DTO de HTTP;
- depender diretamente de SDK externo;
- duplicar regra já protegida pela entidade;
- criar uma interface sem variação real.
Checklist
- a operação tem nome no negócio;
- não pertence claramente a uma entidade;
- não conhece transporte ou persistência;
- recebe dependências por portas;
- preserva aggregates;
- é testável isoladamente;
- não vira depósito de funções.
Conclusão
Domain Services no Node.js são úteis para regras que atravessam objetos do domínio sem pertencer a uma entidade específica. Eles devem permanecer pequenos, expressivos e independentes da infraestrutura.
Use entidades e Value Objects como primeira opção. Quando a regra realmente representar uma política ou cálculo externo ao objeto, modele um serviço com nome do negócio e dependências explícitas.
Leia também Domain-Driven Design no Node.js, Entidades e Value Objects no Node.js e a referência de DDD.



