Domain Service no Domain Driven Design

Domain Service no Domain Driven Design: eles sempre são Stateless. Veja Exemplos relacionando agregados dicas sobre as boas práticas no uso.

Vamos falar um pouquinho sobre o Domain Service no Domain Driven Design. O DDD se ancora em estruturas táticas como os agregados, entidades e value objects. Mas ele também possui outras estruturas como os serviços: aquelas que demonstram ações particulares do domínio e não das agregações dele. Assim, isso pode suar um pouco confuso, em especial por que o termo serviço é amplamente utilizado na TI com sentidos bem distintos. Bom, esse artigo traz uma visão particular sobre essa temática.

Além disso, temos aqui no blog uma série de artigos sobre Domain Driven Design. Assim, caso queira se aprofundar ou apenas ter uma outra visão sobre esse tema tão rico, fique a vontade para conhecer através dos links a seguir:

O que são serviços?

Não é incomum ouvir a palavra serviço na TI mas com uma quantidade enorme de diferentes usos. Desse modo quero aqui falar sobre eles para esclarecer o que é e o que não é serviço do ponto de vista do DDD. Para usuários de windows a ideia de Windows Service não é incomum, por exemplo. Trata-se de uma aplicação que roda sem parar ao mesmo tempo que é, até certa medida, gerenciado pelo sistema operacional. Há também os componentes COM+ que funcionam de maneira muito simular e também são tratados como serviço.

Além disso, as aplicações frequentemente fazem chamadas para suas funções internas em posições de memória através da instrução CALL (asm x86). Com isso o ponteiro de execução é direcionado para uma posição de memória que executa até encontrar uma instrução RET (asm x86). Mas há um formato de comunicação binário onde parte do código que é executado está em outra estrutura, através das Remote Procedure Calls (RPC), que funcionam como um serviço esperando requisições.

Um pouco mais popular do ponto de vista de mercado são os web services que desembocam para conceitos como API, SOAP, SOA, REST, etc. Tudo isso tem a ideia de algo que espera requisições e respode adequadamente a elas.

Por fim há um outro conceito mais ligado a gestão: são os serviços ITIL. Esse conceito abarca uma funcionalidade ou mesmo um produto que a TI tem a responsabilidade de prover e manter disponível para os usuários. Por exemplo, um sistema de controle de ponto é um serviço ITIL; ou mesmo um telefone disponível na sala de reunião.

Serviços de domínio

O domínio é composto por agregados que, por sua vez tem entidades e value objects. Mas ele também tem serviços prontos para serem utilizados pelos consumidores do domínio. Eles podem utilizar agregados ou não para executar funções que fazem sentido para o domínio.

Um diagrama simples com um domínio que tem 2 domain services e 3 agregados de exemplo.

Vendo sobre outro prisma, considere que o domínio é vivo e quem tem capacitades que podem ser observáveis por agregados e por serviços.

Formas de uso

Em algumas situações os serviços são obrigatórios. Um exemplo é o de reajuste em massa de preço de todos os Produtos, por exemplo. Não faz sentido um método desse numa entidade de Produto e muito menos uma entidade chamada Todos os Produtos.

Vale destacar que uma alteração em massa não requer que se obtenha do banco todas as entidades para atualizar em memória e atualizar no banco uma a uma. Fazer isso é um erro de interpretação disfarçado de puritanismo. Não há qualquer problema de fazer uma query sql de update ou mesmo chamar um procedure.

Outra situação em que os serviços são úteis é quando há a necessidade de trabalhar com mais do que uma entidade ao mesmo tempo. Por exemplo, digamos que a atualização do preço utilize a quantidade de estoque como referência. Se o estoque for muito alto e há interesse em trazer novos itens para ele, o preço deve ser mais barato.

Stateless

Algo super importante que todos devemos ter em mente é que os serviços são stateless. Diferentemente das entidades eles nunca devem manter estado. Inclusive é muito comum ver implementações de serviços em classes estáticas ou utilizando singleton pattern.

Domínios inchados

Além de tudo isso, um problema bastante comum é ver implementações em que as entidades vão murchando, ficando cada vez mais anêmicas a medida que os Domain Services vão ficando cada vez maiores. Isso é um erro! É fundamental entender quem tem a responsabilidade natural por uma determinada ação: o domínio diretamente ou uma entidade. Aloque corretamente essa responsabilidade.

Exemplo de uso do Domain Service no Domain Driven Design

Veja a seguir um pequeno exemplo de uma classe ProductPriceAdjustmentService, responsável pelo ajuste de preços. Ela possui o método AdjustAllPrices que está correto e, para fins didaticos há um método AdjustPrice que não está correto.

Note que no código recomendamos que o método AdjustPrice deveria estar dentro da própria entidade e não do serviço. Ou seja, nesse caso em particular a alteração no banco seria chamada diretamente pela entidade e não pela camada de serviço. Nós defendemos essa abordagem por entender que não é responsabilidade da camada de serviços alterar a entidade em específico. Entretanto uma coisa é o que entendemos ser o conceito defendido pelo DDD outra coisa é o que convém para a implamentação real de um projeto. Deve-se avaliar caso a caso.

using System;
using Anselme.Domain.Models;
using Anselme.Infrastructure.Repositories; 

namespace Anselme.Domain.Services
{
    public class ProductPriceAdjustmentService
    {
        private readonly IProductRepository _productRepository;

        public ProductPriceAdjustmentService(IProductRepository productRepository)
        {
            _productRepository = productRepository ?? throw new ArgumentNullException(nameof(productRepository));
        }

        // Método natural dessa classe que representa o ServiceDomain
        public void AdjustAllPrices(decimal percentual)
        {
            if (percentual <= 0)
                throw new ArgumentOutOfRangeException(nameof(percentual), "O percentual deve ser maior que zero");

            var produtos = _productRepository.GetAll(); 
            foreach (var produto in produtos)
            {
                AdjustPrice(produto, percentual);             }

            _productRepository.SaveChanges();         }

        // Método que não deve fazer parte do Domain Service
        // Esse método deveria fazer parte da Entidade Produto
        private void AdjustPrice(Produto produto, decimal percentual)
        {
            decimal novoPreco = produto.Preco * (1 + percentual / 100);
            produto.Preco = Math.Round(novoPreco, 2); 

            _productRepository.Update(produto); 
        }
    }
}

Boas práticas: Nomeando Serviços

Por fim uma pequena curiosidade é como nomear uma classe de serviço. É muito comum vê-la com o nome do agregado+service, algo como ProdutoService. Mas alguns defendem que isso não é correto e que devemos observar qual é o comportamento do domínio em questão para nomear a Service.

O nome deve representar a ação envolvida respeitando a linguagem ubíqua. No exemplo que indicamos anteriormente a classe se chama ProductPriceAdjustmentService e não apenas ProductService. Em alguns cenários é possível que a linguagem ubíqua direcione para outra forma de escrita.

Conclusão de Domain Service no Domain Driven Design

O padrão tático Domain Service é amplamente utilizado no mercado mas é bem comum ver que ele não segue as recomendações do DDD de maneira não intencional. Assim, o artigo explica como o conceito Domain Service é aplicado no mercado e aplicado no DDD em si. Após isso o vemos em detalhes e por fim vemos uma pequena implementação real de seu uso.


Thiago Anselme
Thiago Anselme - Gerente de TI - Arquiteto de Soluções

Ele atua/atuou como Dev Full Stack C# .NET / Angular / Kubernetes e afins. Ele possui certificações Microsoft MCTS (6x), MCPD em Web, ITIL v3 e CKAD (Kubernetes) . Thiago é apaixonado por tecnologia, entusiasta de TI desde a infância bem como amante de aprendizado contínuo.

Deixe um comentário