Por que meu domínio usaria um Serviço?

Por que meu domínio usaria um Serviço?

O que acontece quando você está desenvolvendo um modelo de dominio, e você se depara com alguma lógica que não se encaixa dentro de uma Entidade ou Agregação? Neste momento temos um ponto do domínio que será um forte candidato a tornar-se em um Serviço de Dominio.

Normalmente, o termo Serviço (Service) gera muita confusão no momento da implementação, mas existem duas características que podem ajudar a melhor definir o que é um Serviço de Domínio:

  • O Serviço de Domínio, representa os conceitos e comportamentos de um domínio. Por exemplo: A composição do número de um Pedido.
  • Um Serviço de Domínio, NÃO tem estado.

Conceitualmente, Serviços de Domínio representam sempre conceitos do seu modelo de domínio, que tem comportamentos que intencioam resolver cenários do Problema do Domínio (Domain Problem) - Mas NUNCA se esqueça que, mesmo entendendo os conceitos e quando aplicar, você sempre precisará conversar com os especialistas do seu negócio para buscar entender: Quando e onde aplicá-lo.

Quando usar um Serviço de Domínio?

Essa é uma pergunta que levanta muitas discussões entre os desenvolvedores de software e os grupos de discussão sobre o assundo DDD. Você já se encontrou na sitação em que, dentro do seu domínio existia a necessidade de comunicação com outros recursos que não faziam parte do seu domínio, propriamente dito, mas que pertencia ao contexto da linguagem ubíqua do seu domínio? [Acredito que sim :) !!!]

Torna-se viável a implementação de um Serviço de Domínio (Service Domain), quando na modelagem de um dado domínio "A" e, obviamente, conversando com os especialistas de negócio, é identificado um comportamento no seu domínio "A" que torne necessário a iteração com agentes externos (Repositório, Requisição HTTP, etc) no domínio "A", e que o resultado desse serviço de domínio forneça o resultado esperado e requisitado, pela linguagem ubíqua, para o domínio "A".

Encapsulamento políticas de processos/negócio

O cuidado primário que se deve ter quando estamos descobrindo Serviços de Domínio, é a execução de alguns comportamentos que, normalmente, envolvem entidade e objetos de valor. Isso pode ser demonstrado, na primeira versão do código de exemplo, na listagem abaixo:

PROBLEMA:

public class CarrinhoDeCompras  
{
    private IList<ItemDoCarrinho> _itens;

    public Identidade Id { get; protected set; }
    public string CodigoPromocial { get; private set; }
    public IReadOnlyList<ItemDoCarrinho> Itens { get { return _itens.ToList(); }; }
    public decimal Total { get { return _itens.Sum( x => x.Preco * x.Quantidade); } private set; }

    public CarrinhoDeCompras()
    {
        _itens = new List<ItemDoCarrinho>();
    }

    public void AdicionarItem(ItemDoCarrinho produto)
    {
        _itens.Add(produto);
    }

    public void RemoverItem(ItemDoCarrinho produto)
    {
        _itens.Remove(produto);
    }

    public CarrinhoDeCompras FinalizarCarrinho(string codigoPromocional)
    {
        // Note que a regra de aplicação do desconto promocional está dentro do domínio do pedido.
        // Ainda é necessário consultar, em algum repositório de dados, qual o valor correspondente ao código promocinal.

        CodigoPromocial = codigoPromocional;
        decimal percentualAplicado = 0;

        if (CodigoPromocial.Equals("PROMOCODE-15"))
            percentualAplicado = 0.85;
        else if (CodigoPromocial.Equals("PROMOCODE-20"))
            percentualAplicado = 0.80;
        else if (CodigoPromocial.Equals("PROMOCODE-25"))
            percentualAplicado = 0.75;

        Total = Total * percentualAplicado;
    }
}

public class ItemDoCarrinho  
{
    public string Sku { get; }
    public decimal Preco { get; }    
    public int Quantidade { get;  }

    public ItemDoCarrinho(string sku, decimal preco, int quantidade)
    {
        //...

        Sku = sku;        
        Preco = preco;
        Quantidade = quantidade;
    }
}

Code-Smells

Note que no código acima, temos problemas de implementação na lógica de negócio, com a utilização de Magic-Strings - strings (PROMOCODE) fixas no código, sendo comparadas como se NUNCA fossem sofrer nenhum tipo de alteração. Doce ilusão! Hard-Code (fixos dentro do código do domínio). Evite HARD-CODE !

SOLUÇÃO:

public class CarrinhoDeCompras : ICarrinhoDeCompras  
{
    private IList<ItemDoCarrinho> _itens;

    public Identidade Id { get; protected set; }
    public string CodigoPromocial { get; private set; }
    private IPoliticaDeDescontoPromocional PoliticaDeDescontoPromocional { get; }
    public IReadOnlyList<ItemDoCarrinho> Itens { get { return _itens.ToList(); }; }
    public decimal TotalParcial { get { return _itens.Sum( x => x.Preco * x.Quantidade); } }
    public decimal Total { get; private set; }

    public CarrinhoDeCompras(IPoliticaDeDescontoPromocional politicaDeDesconto)
    {
        PoliticaDeDescontoPromocional = politicaDeDesconto;
        _itens = new List<ItemDoCarrinho>();
    }

    public void AdicionarItem(ItemDoCarrinho produto)
    {
        _itens.Add(produto);
    }

    public void RemoverItem(ItemDoCarrinho produto)
    {
        _itens.Remove(produto);
    }

    public CarrinhoDeCompras FinalizarCarrinho(string codigoPromocional)
    {
        CodigoPromocial = codigoPromocional;

        //Note que aqui, a implementação consome um serviço de domínio que tem todo o trabalho consultar um repositório e calcular o desconto
        Total = PoliticaDeDescontoPromocional.CalcularTotalComDesconto(this);

        return this;
    }
}

public class ItemDoCarrinho  
{
    public string Sku { get; }
    public decimal Preco { get; }    
    public int Quantidade { get;  }

    public ItemDoCarrinho(string sku, decimal preco, int quantidade)
    {
        Sku = sku;        
        Preco = preco;
        Quantidade = quantidade;
    }
}

public interface ICarrinhoDeCompras  
{
    Identidade Id { get; protected set; }
    string CodigoPromocial { get; private set; }
    IReadOnlyList<ItemDoCarrinho> Itens { get; }
    decimal Total { get; private set; }
}

public interface IPoliticaDeDescontoPromocional  
{
    decimal CalcularTotalComDesconto(ICarrinhoDeCompras carinhoDeCompras);
}

public class PoliticaDeDescontoPromocional: IPoliticaDeDescontoPromocional  
{
    private IPromocaoRepositorio PromocaoRepo;

    public PoliticaDeDescontoPromocional(IPromocaoRepositorio promocaoRepositorio)
    {
        PromocaoRepo = promocaoRepositorio;
    }

    public decimal CalcularTotalComDesconto(ICarrinhoDeCompras carinhoDeCompras)
    {
        decimal? percentualDeDesconto = PromocaoRepo.ObterPercentualDeDesconto(carinhoDeCompras.CodigoPromocional);

        if (percentualDeDesconto == null)
            return carinhoDeCompras.TotalParcial;

        return carinhoDeCompras.TotalParcial * percentualDeDesconto;
    }
}

Agora, note que foi implementada algumas alterações que deram mais sentido a UL (Ubiquitous Language/Linguagem Ubíqua) do domínio e, acima de tudo, colocou suas responsabilidades em seus devidos lugares. Com isso, o serviço de domínio assumiu sua responsabilidade de consultar em um respositório o valor cadastrado para cada cupom que seja informado na finalização do carrinho. E o domínio do Carrinho tem a responsabilidade de gerenciar, apenas, o contexto do carrinho - Ou seja, a implementação do cálculo do desconto promocional, fica por conta do serviço de domínio PoliticaDeDescontoPromocional, utilizado por meio de contrato.

Minha intenção aqui, NÃO é apresentar mecanismos de validação de erros e nem o mecanismos de notificação de erros, dentro desse domínio. APENAS como podemos resolver um problema específico com foco em SERVIÇO DE DOMÍNIO.

Representação de Contratos

Existe um grande caso de uso que torna factível o uso de Serviço de Domínio que é o Contrato. Nesse ponto, o conceito por si só é importante, mas a implementação não faz sentido estar dentro do domínio, devido a dependência de infraestrutura, o que não NUNCA existir dentro de um domínio: Dependência da camada de Infraestrutura.

Vamos analisar a seguinte implementação:

public class PesquisadorSintesePessoaJuridica : IPesquisadorSintesePessoaJuridica  
{
    public SintecePessoaJuridica PesquisarSintese(CNPJ cnpj)
    {
        var cliente = new RestClient("http://www.serasa-experian.com");
        var consulta = new RestRequest("consultar-sintese/{cnpj}", Method.POST);
        RestResponse<SinteceDaPessoaJuridica> resultadoDaConsulta = cliente.Execute<Person>(consulta);

        if (resultadoDaConsulta.Data.SituacaoCNPJ == -1)
            throw new InvalidOperationException("Empresa está em situação de inatividade!");

        return resultadoDaConsulta.Data;
    }    
}

public interface IPesquisadorSintesePessoaJuridica  
{
    SintecePessoaJuridica PesquisarSintese(CNPJ cnpj);
}

public class SintecePessoaJuridica  
{
    public string RazaoSocial { get; set; }
    public DateTime Fundacao { get; set; }
    public int SituacaoCNPJ { get; set; }
}

Perceba que o código acima é a implementação de um serviço de domínio, e que a lógica do domínio que precisa realizar esta validação, por meio de uma consulta em um parceiro externo, está realizando uma integração que está vinculada, diretamente, com a infraestrutura de consulta HTTP. Esse tipo de implementação, NÃO pode existir dentro do domínio, mas o contrato ou interface deste serviço de domínio, pode Sim ser utilizada no modelo de domínio, porque faz parte da Linguagem Ubíqua.

A utilização de Serviço de Domínio, na forma de contratos, pode ser aplicado a uma série de cenários. Abaixo eu cito alguns:

  • Identificação de Entidade;
  • Validação/Verificação da consistência de uma Entidade;
  • Localização de informações em agentes (parceiros de integração) externos;
  • Notificação em tempo-real.
Premissas de um Serviço de Domínio

Existem três caracterísiticas fundamentais, as quais você precisa ter em mente, para não cometer nenhum tipo de equívoco ao implementar essa abordagem, são eles:

  • Um Serviço de Domínio, representa um conceito e um comportamento do domínio;
  • Um Serviço de Domínio, NÃO tem IDENTIDADE;
  • Um Serviço de Domínio, NÃO tem ESTADO;
  • Um Serviço de Domínio, ORQUESTRA multiplas entidades ou objetos de valor, como parte de operações sem ESTADO.

Comments