Rodrigo Allemand

DDD encapsula a minha Regra de Negócio

por Rodrigo Allemand em Oct.16, 2009, como DDD

Resolvi responder ao comentário do Diogo (no post Blindando o Domínio) em um post separado por achar que essa é a duvida mais frequente que eu vejo, seja em conversas informais, fóruns, palestras, etc.

Onde colocar as minhas regras de negócio?

A resposta é…. Depende! rs

Se você escolheu utilizar DDD no seu projeto é porque você conhece e já isolou o problema a ser resolvido que, por ser complexo, será baseado em um modelo, recebendo o foco e a prioridade necessárias para a solução deste domínio. Quando você isola este domínio, obviamente você deve encapsular tambem todas as regras de negócio dentro desse domínio.

Para exemplificar melhor vamos fazer a seguinte funcionalidade de exemplo no nosso dóminio.

Todos os pedidos com valor acima 1.000,00 e que são pagos com dinheiro em espécie são elegíveis a um desconto de 10% por produto.

Claro que esta informação tem que ser passada para o usuário final do sistema, mas é responsabilidade do sistema checar se as informações passadas estão corretas. Você pode até colocar a regra de validação tambem na camada de apresentação ou dentro do seu banco de dados em forma de triggers (argh, rs!), mas de qualquer maneira o domínio tem que ter esta validação, já que um dos princípios do DDD é justamente poder trocar a camada de apresentação sem que o domínio seja alterado ou compartilhar o mesmo domínio entre interfaces/sistemas diferentes.

Portanto, segue a primeira afirmação:

Por mais que a sua camada de apresentação faça as regras necessárias para uma determinada funcionalidade, o seu domínio tambem deve, obrigatoriamente, fazer com que estas regras estejam presente internamente, tornando-o coeso da camada que ele comunica.

Vamos melhorar este exemplo mostrando como ficariam as entidades envolvidas nesta funcionalidade.

public class Pedido {
	private List<ItemPedido> itens;
	private BigDecimal precoTotal;
	//Acessores omitidos
}
public class ItemPedido {
	private Produto produto;
	private BigDecimal precoVenda;
	private BigDecimal desconto;
	//Acessores omitidos
}

OBS.: Muitos podem perguntar porque a informação referente ao preço final é um atributo da classe e não um comportamento da entidade (um método que poderia somar todos os preços finais decrementando os descontos dados por cada produto, multiplicado pela quantidade de cada produto). Vamos pensar que o preço do Produto pode – e deve – variar e o sistema precisa manter a informação do preço de venda naquele momento, ok?

Eu encaro que esta regra de negócio deve estar em uma checagem no momento da inclusão, como por exemplo uma Specification destinada somente a esta regra. Lembro que, a Specification tem, neste caso, o intúito de validar uma informação e que ela não precisa ser necessariamente apenas uma única specification. Eu posso – e é até melhor – dividir a informação em várias Specifications, uma para cada regra de negócio que a minha funcionalidade exige.

Vamos pensar então que a minha classe ValidarDescontoDadoSpecification tem o seguinte código:

public class ValidarDescontoDadoSpecification
	implements Specification<ItemPedido> {

	public static final BigDecimal DESCONTO_MAXIMO_PGTO_DINHEIRO = 10;

	public boolean isSatisfiedBy(ItemPedido item){
		if(itemPedido.getFormaPagamento.equal(FormaPagamento.DINHEIRO){
			BigDecimal descontoDado =
				Calculadora.percentualEntre(
					item.getPrecoVenda(),
					item.getDesconto());
			if(descontoDado.compareTo(DESCONTO_MAXIMO_PGTO_DINHEIRO) > 1){
				//É necessário comunicar ao domínio o porque da falha
				//Neste caso, leia mais sobre Notification
				return false;
			}
		}
		return true;
	}
}

Pensando que você sempre salva o pedido, poderiamos ter uma outra Specification para isso, conforme abaixo:

public class PersistirPedidoSpecification
	implements Specification<Pedido> {

	public boolean isSatisfiedBy(Pedido pedido){
		/*
		  As boas maneiras de aplicativos internet diz que
		  vc deve validar tudo de uma unica vez, rs
		*/
		boolean resultado = true;

		//Validações referente ao Pedido

		ValidarDescontoDadoSpecification spec =
			new ValidarDescontoDadoSpecification();
		for(ItemPedido item : pedido.getItens()){
			if(!spec.isSatisfiedBy(item)){
				/*
				  Lembre que a informação da falha deve ser feita
				  pela outra Specification, não por esta!
				*/
				resultado = false;
			}
		}
		return resultado;
	}
}

Assim, no final de tudo, nós poderiamos ter no repositório Pedidos, o seguinte código:

public class Pedidos{

	public static void persistir(Pedido pedido){
		PersistirPedidoSpecification spec
			=  new PersistirPedidoSpecification();
		if(spec.isSatisfiedBy(pedido){
			dao.salvar(pedido);
		} else {
			//Comunica a falha encontrada às outras camadas
		}
	}
}

Espero ter ajudado!!!

Abraço a todos!!!

Comente e Recomende!!

:, ,
13 comentários para este post:
  1. Diego

    Rodrigo,

    Muito obrigado pelo exemplo ele me mostrou q uma das formas como eu havia pensado é válida, pq eu pensei q a validação dentro da implementação do repositório era algo errado, mas agora vejo q é uma possibilidade.

    Uma outra coisinha, essas validações podem ser feitas em um Service qdo envolve + de uma agregação?

    []’s

  2. Rodrigo Allemand

    @Diego

    Validar as regras dentro do domínio não é apenas uma possibilidade, é o correto e eu até julgo obrigatório. Pense em exportar esse domínio para um outro sistema. Ele não deve ser forte o suficiente pra resolver todo o problema do domínio? Isto requer as validações e regras de negocio dentro dele!

    Sobre serviços e agregações, segue a mesma linha. Ele tem que ser forte! No exemplo eu tentei ser simplista para ser mais didático, em um mundo real de um sistema de vendas, provavelmente, existiria um serviço agregando todas as funcionalidades de venda, como validar itens, descontos e preços, movimentar estoques, avisar usuários (como um backoffice ou uma área de compras ou uma fábrica de produtos, por exemplo), etc. Encare, neste caso de Services e Aggregations, como um repositório que responde a mais de um repositório!

    Abraços!

  3. Diego

    Foi me foi mais util que toda a literatura que tive acesso, pois eu precisava de alguém que já tivesse feito algo para garantir q oq eu pensei estava na linha certa.

    []’s

  4. Rodrigo Allemand

    Obrigado!!!
    Esse é o intuito do blog… trocar experiências!!!
    Continue acompanhando que teremos muita coisa boa por vir…
    Estou escrevendo para um curso de Scrum e XP alem de estar desenvolvendo uma aplicação de exemplo guiando as boas práticas de DDD + Struts 2 + Hibernate e irei postar bastante coisas aqui…

    Recomende para seus amigos!

    Abraços!

  5. Diego

    Vou aguardar pelos proximos post, pois temos pouco material em portugues e sua iniciativa é mto bacana.

  6. Diego

    Me diz uma outra coisa.
    No ebook DomainDrivenDesignQuickly OnLine na explicação de service ele menciona o seguinte exemplo:
    Imagine um sistema de transferencia de fundos entre contas de um mesmo banco.
    A funcao de transferir o dinheiro deve estar na conta que esta mandando ou na que esta recebendo o dinheiro?
    Ele sugere um service, até ai ok. Mais ai me veio uma questão a mente, um service somente é acessado através de uma entity? Ou vc pode acessa-lo diretamente, por exemplo, na camada application passando as duas entities conta para o serviço? Vc teria algum implementação de exemplo?

    []’s

  7. Rodrigo Allemand

    Existem várias maneiras de acessar este serviço, não existe uma regra… Eu, por exemplo, gosto sempre de ter uma fachada [Facade] pra blindar o domínio e, nestes caso, eu chamo direto na fachada, passando pelo Registro [Registry, Martin Fowler].
    Existem pessoas que fazem o chamado Dominio Exposto [Pojo In Action], fazendo com que qualquer “coisa” possa chamar qualquer öutra coisa”, entende?
    Eu costumo falar que um serviço é necessário quando para se fazer uma ação vc precise invocar comportamentos de mais de uma entidade.
    No exemplo dado no DDDQuickly, eu particularmanete colocaria dentro da entidade Conta.transferirPara(outraConta)…
    Vou pegar uns exemplos de Serviço pra colocar no proximo post… pode deixar!

    Abraço!!

  8. Ari Garcia

    Rodrigo parabens pelos posts sobre DDD. Incentivo você a colocar tudo isto que escreveu em um artigo ou mesmo num livro, claro que para isto precisa rever alguns conceitos e exemplos que ficram tão simplorios que podem ser problemas expressá-los da mesma forma em algo mais bem elaborado.
    Bom, vou acompanhar os próximos posts e se me permitir fazer referencias ao seu blog na nossa página.

    Abração!

  9. Rodrigo Allemand

    Olá Ari!

    Obrigado pela visita e sinta-se a vontade com os textos e links!
    Estou terminando um otimo material de exemplo utilizando DDD e irei colocar no site assim que possivel!

    Abraços!

  10. Marco Antonio

    Oi Rodrigo,
    Também gostaria de te parabenizar pela iniciativa. Já inseri seu blog aqui nos meus favoritos.
    Sobre o post, eu tenho uma dúvida: O comportamento e os dados não deveriam ficar na mesma classe? Por exemplo, ValidarDescontoDado não deveria ser um método dentro da classe ItensPedido ? Ou isso não é regra ?

  11. Rodrigo Allemand

    Ola Marcos!

    Desculpe a demora, mas alguem tem que descansar as vezes, rs!
    Pois bem, não é uma regra, é um consenso que quanto mais comportamento dentro de uma entidade, melhor.
    Se você tem uma entidade onde o entendimento seja total pra colocar esse comportamento, coloque!

    Até!

  12. Bruno F.

    Desculpe, havia postado no lugar errado:

    Rodrigo, muito interessante a postagem, mas me preocupo também com a redundância das verificações, apesar de achar seguro e coeso.
    A preocupação é principalmente nos casos onde as transações devem ser verificadas em banco (triggers e procedures). Neste caso não seria interessante apenas as verificações do banco?
    Sei que muita gente até “arrepia” os cabelos ao falar em trigger para regra de negócio, mas existem casos que elas são essenciais, de fato.

  13. Rodrigo Allemand

    Olá Bruno!

    Primeiramente, obrigado pela visita!

    Conforme dito por muitos, em desenvolvimento não existe uma bala de prata, uma regra ‘fixa’ para sair do outro lado com um ótimo produto. Os padrões são soluções catalogadas para um determinado problema. Porém os problemas não são os mesmos sempre, eles mudam de caso em caso, mesmo que sutilmente. EU PESSOALMENTE, não gosto de soluções por triggers e procedure por achar que banco de dados apenas armazena dados. Eu penso que se um banco de dados tem regra de negocio é porque a aplicação não é ‘forte’ o suficiente pra gerir tais regras. E mais, se vc pensar que a sua aplicação – o seu domínio – é ‘usável’ em qualquer contexto, o seu banco e a sua interface são ‘trocaveis’, ou seja, vc tem que garantir que todas as regras de negocio – dentre elas a validação – sejam feitas por este domínio. A redundancia da verificação sempre vai – e ao meu ver DEVE – existir, seja ela no banco (trigger e constraints), na interface (Javascript) ou dentro do domínio. Sei que esse não é o melhor dos mundos mas existem aplicações que são consideradas ótimas mesmo tendo toda verificação no banco por intermédio de triggers e procedures.

    Enfim, em cada lugar que eu vou eu vejo um tipo de ‘verificação’ diferente e todas bastante funcionais. Cabe a equipe de desenvolvimento se adequar às regras da casa, seja ela por trigger, por javascript, por Hibernate Validation, por Specification ou por todas elas ao mesmo tempo!

Comente!

Procurando por algo?

Use o campo abaixo para procurar por todo o site:

Ainda não achou? Deixe um comentário ou me mande um email que eu cuido disso!

Minhas indicações!

Alguns blogs que eu recomendo...