Camada de Aplicação, Domain Driven Design e Isolamento do Domínio

Para iniciar as publicações do Saga do Programador aqui no Medium, segue um repost de 2014, motivado por discussões recentes no trabalho. Boa leitura!

Após muito tempo sem escrever aqui, resolvi comentar sobre uma discussão que aconteceu na lista do .NetArchitects. A dúvida é sobre o que é a Camada de Aplicação em Domain Driven Design.

Não quero entrar em muitos detalhes sobre o que é Domain Driven Design, mas isolar seu domínio é algo fundamental. O capítulo 4 — Isolating the Domain, do livro azul, discorre sobre o tema e introduz a noção de Arquitetura em camadas (Layered Architecture). Divisão em camadas não é nenhuma novidade, mas Evans mostra como a arquitetura em camadas, pode ser utilizada como mecanismo de isolamento do domínio. No livro, as camadas são separadas assim:

Estas camadas são formas de organizar as dependências dentro da sua aplicação. User Interface, Domain e Infrastructure, são velhos conhecidos, e normalmente não se tem muita dúvida sobre quais são as responsabilidades de cada uma. Agora, o que é a Application Layer?

Camada de aplicação

Defines the jobs the software is supposed to do and directs the expressive domain objects to work out problems. The tasks this layer is responsible for are meaningful to the business or necessary for interaction with the application layers of other systems. This layer is kept in thin. It does not contain business rules or knowledge, but only coordinates tasks and delegates work to collaborations of domain objects in the next layer down. It does not have state reflecting the business situation, but it can have state that reflects the progress of task for the user program.
Domain Driven Design, página 70.

A definição acima, foi retirada do livro e descreve o que é a camada de aplicação dentro do DDD. O ponto em negrito é extremamente importante quando se tenta entender as responsabilidades dos objetos que ali residem. Resumindo: Na camada de aplicação não é implementada nenhuma regra de negócio, ela somente coordena a execução de uma tarefa e delega para os objetos de domínio na camada inferior.

Já vi vários programadores argumentando que esta camada é a mesma onde ficam os controladores do MVC. Ou seja, em uma aplicação Web, minha camada de aplicação está implementada junto com os Controllers. No próprio livro, Evans fala que em alguns projetos, a camada de aplicação pode não ser totalmente separada da User Interface. Eu particularmente não gosto de misturar as duas coisas, as responsabilidades são diferentes.

Uma das minhas influências na maneira de lidar com o problema da divisão de responsabilidades, é o livro do Craig Larman — Applying UML and Patterns, onde ele apresenta os Use Case Controllers. Basicamente a ideia é que toda ação executada pelo sistema se inicia a partir de estímulo externo, e este estímulo é normalmente descrito através de um caso de uso. Com esta ideia em mente, os casos de uso podem ser considerados pontos de entrada para execução das lógicas de domínio.

Quando modelamos o domínio de uma aplicação e atribuímos responsabilidades para os objetos, se olharmos apenas este domínio, estas responsabilidades sozinhas não dizem muita coisa, mas quando olhamos os objetos trocando mensagens dentro de um contexto, as coisas passam a fazer sentido. Este contexto pode ser definido pela camada de aplicação.

Modelando uma história de Usuário

Para ilustrar o que estamos discutindo, vou utilizar o mesmo exemplo do grupo e mostrar como as lógicas são divididas nestas camadas (aplicação e domínio).

Imagine um sistema que controle o abastecimento de veículos de uma empresa de ônibus. A empresa possui um mini-posto de combustível próprio e precisa saber o quanto de combustível tem disponível em cada bomba e quais são os veículos abastecidos.

Imagine a seguinte história de usuário:

Como gerente de frota, gostaria de poder registrar o abastecimento dos veículos da empresa, informando a quantidade em litros. Além disso, gostaria que fosse possível saber o quanto de combustível tem disponível na Bomba de Combustível utilizada para abastecimento.

Utilizando DDD, destaquei em negrito termos importantes da Ubiquitous Language. Como nosso principal trabalho é usar a linguagem para modelar o domíni0, podemos começar utilizando estes termos e definir um pequeno Domain Model.

Classes do domínio da aplicação. Note que somente olhando as classes, pode ser difícil entender como estes objetos vão colaborar entre si.

Este modelo de domínio provavelmente é incompleto, mas vai servir para o exemplo.

Para não alongar muito o post, vamos imaginar que depois de um discussão com os especialistas de domínio, conseguimos entender que existe uma relação entre bomba de combustível e veículo, conforme mostrado na figura acima. E esta relação é o abastecimento. Como estes conceitos são importantes para o domínio, vamos adicioná-los no nosso domain model. Observe o código da classe BombaDeCombustivel:

Pseudo código para a classe BombaDeCombustível

Repare como a lógica de abastecimento foi implementada. Veja que este objeto troca mensagens (invoca métodos) com o objeto Veículo. Somente objetos de domínio são manipulados aqui. As classes BombaDeCombustível e Veículo são objetos do nosso domain model.

Código da classe veículo:

Pseudo código da classe Veículo

A classe Veículo, não colabora com nenhum outro objeto de domínio, para a implementação desta história ou caso de uso, não há necessidade.

Se estes objetos estão no meu modelo de domínio, eu poderia então, no meu controller, no caso de aplicação web, instanciar estes objetos e executar a lógica de domínio.

Ao meu ver, isso não deveria ser feito desta maneira. Como eu disse antes, isso é uma questão de SoC (separação de conceitos). As responsabilidades são diferentes. Segue abaixo, de maneira simplificada, as responsabilidades de um controller:

1) receber uma request da interface e extrair os parâmetros

2) tratar erros que podem acontecer em uma requisição

3) redirecionar para outras páginas

4) montar a resposta de uma requisição

Um controller, não deveria saber detalhes de implementação de uma lógica de domínio. É neste momento que a camada de aplicação entra em cena. Ela é responsável por coordenar a execução destas lógicas e expor, para as camadas externas, o mínimo de detalhe possível sobre como isto é feito. Uma camada de aplicação, é como se fosse a “cola” do domínio da aplicação com a maneira que ela é exposta, neste caso, uma aplicação Web.

Então como seria a implementação de um objeto da camada de aplicação?

Implementando a Camada de aplicação

Como eu disse antes, a camada de aplicação nada mais é, que um conjunto de objetos que representam casos de usos / histórias de usuário que o seu domínio expõe. Neste caso, nosso caso de uso é AbastecerVeículo. Dê uma olhada na sua implementação:

Pseudo código para o caso de uso Abastecer Veículo

Repare como este objeto não conhece detalhes de implementação da lógica de domínio. Apenas delega para os objetos corretos a execução. Repositórios também são objetos de domínio, e o caso de uso faz uso deles para obter instancias de veículo e bomba de combustível. Eu gosto de nomear repositórios assim devido a este post, apesar de antigo, bastante interessante do Rodrigo Yoshima.

Desta maneira, é possível ter um isolamento bem interessante entre o domínio da aplicação e a maneira com a mesma é exposta. Poderíamos ter um controlador como este:

Uma possível implementação de um controlador em uma aplicação web

Não implementei o código todo aqui, é só um exemplo, mas dá para ver que as responsabilidades das camadas são diferentes. Neste código não me importei muito com quem vai suprir as dependências, mas como a maioria das vezes tento deixar meu domínio isolado, gosto de resolver as dependências sempre nas camadas mais externas. Por exemplo, neste caso, o controller poderia ter acesso a framework de IoCe injetar as dependências corretas no caso de uso.

Agora nosso modelo de domínio está isolado. No exemplo abaixo, o mesmo modelo de domínio está exposto para uma aplicação web e um Job fictício que tem sua execução agendada. As duas aplicações tem como ponto de entrada o caso de uso, que por sua vez, estimula os objetos de domínio corretos e coordena a execução da lógica.

Modelo de domínio sendo acessado por duas possíveis aplicações

No diagrama acima, não estou preocupado em como o domínio vai ser compartilhado entre estas aplicações, usei somente para ilustrar a possibilidade.

Com a separação de conceitos, é possível ter um design próximo a Clean Architecture que o Uncle Bob tem falado tanto. Neste exemplo, nosso domínio é isolado das aplicações. Os objetos de que estão nesta camada são agnóstico a maneira como eles são expostos. Ter um ponto de entrada único, a camada de aplicação, simplifica o uso do domínio pelas camadas mais externas.

Acho que é isso pessoal, até a próxima.


Originally published at sagadoprogramador.com.br on December 10, 2014.