No meu artigo anterior (Desmistificando o DDD) expliquei sobre as motivações envolvidas em aplicar o DDD em um projeto e os cenários favoráveis pra isso. Nesse artigo, quero resumir o que o Eric Evans incentiva como práticas de entendimento, destilação e implementação do domínio, usando um case pré-determinado como exemplo.
DDD é uma filosofia de desenvolvimento voltada para domínios de negócio complexos. Envolve um conjunto de práticas de entendimento do negócio, comunicação, colaboração, e desenvolvimento de uma solução emergente que reflita o domínio no próprio código.
Que práticas são essas? Como aplicá-las?
Model Driven Design
Um projeto de software normalmente surge a partir de uma necessidade de um indivíduo ou empresa. A prática recomendada pelo Eric Evans, o criador do DDD, de entendimento dessa necessidade para se atingir a solução é o Design Dirigido a Modelos, ou Model Driven Design, que é a modelagem do domínio a partir da constante interação entre o time técnico e os especialistas de negócio, por meio de uma linguagem ubíqua.
Significado do termo “ubíquo”:
- Que está ou pode estar em toda parte ao mesmo tempo; onipresente.
- Que se difundiu muito; universal.
Nessas interações, é comum identificar as palavras (substantivos) mais relevantes, e pensar em classes e suas relações. O Model Driven Design vai além disso, identifica quais são as ações (verbos) mais relevantes, mostrando como esses termos relevantes se comportam dentro do domínio. Não se trata apenas de que informações serão armazenadas e como elas se relacionam. É o que o sistema resolve, que ações ele executa. Por isso a importância de um time heterogêneo ser envolvido na análise, e não apenas um analista, ou DBA, ou desenvolvedor.
Para explicar um pouco mais sobre a aplicação de DDD, vamos utilizar um caso fictício: Imagine que nós fazemos parte de uma consultoria, e um de nossos clientes solicitou um software que gerencie a produção de equipamentos sob demanda a partir de um Pedido de Venda.
Abaixo seguem as especificações enviadas pelo cliente:
[box]
Fluxo de produção sob demanda
- O Pedido de Venda pode conter vários Produtos, compostos por vários Componentes;
- Quando um Pedido de Venda é emitido, é necessário verificar a disponibilidade dos Componentes do Produto. É feita a reserva dos Componentes disponíveis, e emissão de Pedidos de Compra para os indisponíveis;
- Há um sistema legado que gerencia a entrada e saída de Componentes no Estoque, que pode ser integrado para notificar a chegada dos Componentes solicitados;
- Quando todos os Componentes estiverem disponíveis, é emitida a Ordem de Serviço. Após a finalização da produção, outros processos podem ser iniciados, como emissão de Nota Fiscal.
[/box]
Num primeiro passo, podemos destacar os substantivos que parecem relevantes para o negócio:
[box]
Fluxo de produção sob demanda (identificação de substantivos)
- O Pedido de Venda pode conter vários Produtos, compostos por vários Componentes;
- Quando um Pedido de Venda é emitido, é necessário verificar a disponibilidade dos Componentes do Produto. É feita a reserva dos Componentes disponíveis, e emissão de Pedidos de Compra para os indisponíveis;
- Há um sistema legado que gerencia a entrada e saída de Componentes no Estoque, que pode ser integrado para notificar a chegada dos Componentes solicitados;
- Quando todos os Componentes estiverem disponíveis, é emitida a Ordem de Serviço. Após a finalização da produção, outros processos podem ser iniciados, como emissão de Nota Fiscal.
[/box]
Poderíamos facilmente obter um MER (Modelo Entidade Relacionamento) a partir daí. Mas sabemos que o sistema não é apenas um repositório de dados, então vamos buscar as ações relevantes:
[box]
Fluxo de produção sob demanda (identificação das ações)
- O Pedido de Venda pode conter vários Produtos, compostos por vários Componentes;
- Quando um Pedido de Venda é emitido, é necessário verificar a disponibilidade dos Componentes do Produto. É feita a reserva dos Componentes disponíveis, e emissão de Pedidos de Compra para os indisponíveis;
- Há um sistema legado que gerencia a entrada e saída de Componentes no Estoque, que pode ser integrado para notificar a chegada dos Componentes solicitados;
- Quando todos os Componentes estiverem disponíveis, é emitida a Ordem de Serviço. Após a finalização da produção, outros processos podem ser iniciados, como emissão de Nota Fiscal.
[/box]
Destilando o domínio
Com as interações é possível identificar o cerne do negócio, ou seja, o core business. Dessa forma, podemos iniciar o processo de destilação de domínio, isso é, identificar qual, ou quais, são os domínios principais, os auxiliares e os genéricos, e entender como eles se relacionam:
- Domínio principal é o que traz valor para o negócio. É onde a principal lógica fica;
- Domínio auxiliar é o que suporta o domínio principal. Pode trazer informações ou atividades necessárias, porém que não representam o principal do negócio;
- Domínio genérico é um domínio que não traz uma regra específica do negócio em questão. Pode ser um CMS, uma área de email marketing, um serviço de SMS. Para ajudar a visualizar, pode-se perguntar: “uma solução externa resolveria esse problema?” Se sim, provavelmente se trata de um domínio genérico.
Destilar o domínio não só ajuda a categorizar os contextos em principal, auxiliar e genérico, mas mostra qual a relação entre eles.
- Núcleo Compartilhado: é um projeto que é consumido por mais de um subdomínio. Ele promove a eficiência, por evitar que determinado trecho seja replicado em dois domínios, por exemplo. Porém, cuidado: às vezes a informação que você precisa em um domínio é diferente nos outros. Às vezes, uma mesma entidade pode ter comportamentos diferentes em cada domínio, e compartilhá-la pode causar processamento desnecessário, ou mesmo inadequado. Quando for pertinente utilizar o Núcleo Compartilhado, é importante se preocupar com integração contínua, já que mais de um subdomínio estará consumindo o mesmo projeto. Alterar o Núcleo para atender um requisito de um subdomínio pode afetar o funcionamento dos outros, e assim por diante;
- Cliente-Fornecedor: Normalmente é o cenário de duas equipes distintas, da mesma empresa ou não, desenvolvendo sistemas que se comunicam. É comum haver um grau de hierarquia, ou seja, uma equipe tem uma certa liberdade em alterar a estrutura da comunicação (cliente) e a outra precisa constantemente se adaptar a essas alterações (fornecedor). Pode ser que em algum momento, essa relação se transforme em Conformista, como mostrado abaixo;
- Conformista: É o caso de um sistema em que não há adaptação por um dos lados. Pode ser um projeto legado, ou projeto externo que deixou de ter suporte, por exemplo. Para que a comunicação ocorra, é necessário que o projeto local (ou em andamento) traduza as entradas e saídas para esse sistema, enquanto fizer sentido utilizá-lo;
- Linguagem Publicada: Normalmente, é um domínio genérico, com uma especificação sobre como a comunicação deve ocorrer. Pode ser uma API, Serviço, ou Driver;
- Camada anticorrupção: É uma camada que fornece a comunicação entre sistemas, “traduzindo” as entradas e saídas. Pode ser um jeito eficaz de conviver com projetos legados – com ela, o domínio principal não será poluído com débitos técnicos associados ao projeto legado.
Voltemos ao nosso case. Iniciamos com uma visão superficial do domínio:
Com base na especificação enviada, identificamos os subdomínios envolvidos:
Depois de algumas interações e diálogos que foram bem além da especificação inicial, identificamos que o cerne do negócio desta empresa é a inteligência de produção e o controle de estoque. São esses dois subdomínios que contém as peculiaridades que garantirão a eficiência e a qualidade nos serviços que a empresa oferece. A área de cadastros é fundamental, porque fornecerá as informações que serão utilizadas nos outros domínios, porém não tem muitas regras. É um domínio auxiliar. No caso de Vendas, Emissor NF-e e Compras, não há nenhuma regra em particular além do básico — emitir uma lista de itens para algum destinatário. São domínios genéricos.
A identificação dos subdomínios e seus papéis pode ser um fator decisivo para o sucesso do projeto. A energia do time deve ser direcionada para o domínio principal, que será implementado com a linguagem ubíqua, envolvendo decisões arquiteturais e de design que ajudem a entender o domínio pelo código. Um domínio auxiliar não precisa do mesmo esforço. Ele pode ser apenas… Bom o suficiente. Estratégias de arquitetura mais simples podem muito bem resolver o problema, mesmo com um time de desenvolvedores menos experientes. E um domínio genérico pode ser adquirido, seja por uma solução open source, ou por um produto pago, ou mesmo ter o desenvolvimento terceirizado.
Após diversas interações, levantamento do cenário atual, quais projetos já existem, como eles se integram e o que realmente falta, identificamos que:
- O sistema de controle de estoque é um sistema legado, que funciona. Há algumas preocupações com qualidade e desempenho, mas não afeta o negócio;
- O fluxo de produção é um processo rigoroso, hoje feito manualmente por meio de formulários e auditoria;
- O sistema de compras é uma solução fechada de mercado, que atende o esperado;
- O sistema de vendas é uma solução terceirizada, que já possui a funcionalidade de Emissão de NF-e integrada.
Nesse caso, ao classificarmos os domínios em conjunto com os especialistas de negócio, o PO (Product Owner) decidiu que a área de Cadastros e Produção será desenvolvida internamente. O projeto de Controle de Estoque será mantido. O sistema de compras continuará a ser utilizado, e o sistema de vendas deverá ser adaptado para se integrar com o domínio de Produção.
Para nós, qual foi o impacto disso?
Nós alocamos os desenvolvedores com competências técnicas mais avançadas para atuar no domínio Produção, e outros desenvolvedores mais iniciantes para atuar no domínio Cadastros. Ambos os sistemas compartilharão o mesmo controle de Autenticação e Permissões.
Desenvolvemos uma camada anticorrupção para a comunicação com o projeto legado, assim, os sistemas poderão se comunicar sem “poluir” o domínio principal.
A arquitetura desses três sistemas desenvolvidos internamente (Cadastros, Produção e Controle de Estoque) pode ser vista abaixo:
- Para o sistema de Cadastros, basta um código limpo e funcional. Não é um problema ter uma abordagem transacional e estruturada. Investir em uma arquitetura sofisticada para resolver um problema tão simples pode significar perda de tempo e dinheiro.
- A arquitetura do sistema de Controle de Estoque é bem confusa. É um projeto que passou por mudanças de equipe e gestão, possuía alguma padronização nas camadas e abstrações, mas por uma série de fatores, acabou virando um emaranhado confuso. Observação: um emaranhado confuso que funciona. Se estivesse impactando o negócio negativamente, talvez fosse interessante contratar mais uma equipe para refatorá-lo. Mas nesse momento, é um projeto que atende os requisitos. Refatorar pelo simples prazer de deixar o código mais “bonito”, sem resolver um problema específico de negócio, pode significar perda de tempo e dinheiro;
- Para o sistema de Produção: aí sim, pode-se começar com uma arquitetura um pouco mais robusta, em que se possa identificar a lógica de domínio isoladamente de implementações de infra estrutura, se possível. Não estou dizendo que o time passará um mês implementando Patterns e serviços que talvez nunca sejam usados. O objetivo do sistema é atender o domínio. Investir em abstrações e Patterns desnecessários pode, mais uma vez, significar perda de tempo e dinheiro.
Continuando com a nossa solução:
- Solicitamos uma interface de comunicação para a equipe responsável pelo sistema de Vendas, e realizamos a integração;
- Desenvolvemos uma interface de tradução conformista para comunicação com o sistema de Compras;
O resultado pode ser visto abaixo, no Mapa de Contexto:
Se existe um documento em que o Evans recomenda manter, é o mapa de contexto. Ele deve estar acessível para todos os times envolvidos internamente. Assim, cada desenvolvedor poderá medir os impactos e riscos de suas alterações.
Observe que a Entidade Produto é replicada em três domínios. Isso porque, neste cenário, cada domínio utiliza um certo comportamento da Entidade, que não faz sentido ser generalizado.
Building Blocks
Ok, temos informação, identificamos o domínio principal em que aplicaremos o DDD a nível de código, e vamos iniciar o desenvolvimento. O Evans destacou uma série de boas práticas de desenvolvimento já conhecidas que podem ser utilizadas para refletir o domínio, a linguagem ubíqua, no próprio código. Ele as chamou de Building Blocks.
Vou explicar superficialmente os termos menos intuitivos:
- A entidade é um objeto que possui uma identidade, comportamento, e um ciclo de vida definido;
- Um agregado é um conjunto de objetos relacionados, tendo uma entidade como raiz;
- O objeto de valor é um objeto imutável, normalmente com estrutura mais simples;
- Um agregado muitas vezes possui regras específicas para criação (objeto novo) ou recuperação (objeto existente). Nesse caso, pode-se criar fábricas para facilitar a “montagem” de objetos e garantir a integridade;
- Evento de domínio é alguma ação com relevância para o negócio. Pode ser um evento que precise ser registrado, ou que dispare outros processos internos (dentro do próprio sistema), ou externos (outros sistemas), por exemplo.
Nada disso é exatamente novidade. São conceitos de Orientação a Objetos, SOLID, e Design Patterns. Mas esses conceitos sozinhos, embora aplicados corretamente, não necessariamente revelam o domínio. O Evans mostrou, exemplo após exemplo, que esses recursos, combinados com a forma de pensar DDD, podem atingir esse objetivo.
Entre os exemplos voltados para o nosso case, podemos citar:
- Entidades: Produto, Componente;
- Agregados: Pedido de Compra, Pedido de Venda;
- Objetos de valor: Item de Pedido de Compra, Item de Pedido de Venda;
- Serviços: Emissão de Pedido de Venda, Emissão de Pedido de Compra;
- Eventos de domínio: Chegada de componentes no estoque.
Solução emergente e refatoração
No início, a chegada de componentes no estoque era tratada diretamente com um job que lia uma saída do sistema de Controle de Estoque e chamava um serviço no sistema de Produção. Com o tempo, o sistema de Produção cresceu, e começou a se integrar com outras áreas da empresa. A solução foi refatorada para uma arquitetura de microsserviços, e utilizou o conceito de mensageria para a comunicação de eventos externos.
Separei alguns princípios utilizados na implementação do sistema Produção:
- Começar simples: Você não precisa aplicar todo o conhecimento técnico que você adquiriu na vida de uma só vez. Se ater ao necessário pode ser desafiador para os entusiastas. Mesmo assim, vale o esforço, para evitar causar uma complexidade técnica acidental. Um sistema raramente vai começar com uma arquitetura de microsserviços, por exemplo;
- Não se apegar a Patterns e recursos enquanto eles não forem necessários: É importante tomar cuidado com linguagens e recursos de “estimação” — acredito que a cada início de projeto, é importante identificar em conjunto com o time que recursos atenderão melhor o domínio, e não mostrar um apego exagerado a certos recursos / frameworks antes de entender se eles realmente representam um ganho para o projeto;
- Aproveitar as oportunidades de melhoria: Pode ser que, após diversas interações, o time tenha amadurecido o suficiente para ver que a implementação está limitando o crescimento do projeto. Bom, talvez tenha chegado a hora de refatorar parte, ou até mesmo todo o código. Deixar de aproveitar essas oportunidades pode poluir o domínio, estimular “remendos” e tornar a manutenção mais difícil e custosa. O receio de refatorar quando aparece a necessidade pode trazer impactos a longo prazo bem negativos. O esforço inicial em aplicar o DDD perde o resultado, e o projeto se torna mais um peso, mais um candidato a ser trocado futuramente com um custo muito mais alto do que se tivesse sido adaptado conforme a necessidade.
Como pode-se ver, o DDD vai muito além da codificação, Pode representar o direcionamento estratégico do projeto. Permite uma base mais concreta para a tomada de decisões, como alocar pessoas de acordo com suas skills técnicas e entendimento de negócios nos domínios mais adequados, identificar o que pode ser paralelizado, terceirizado ou adquirido no mercado, entender o que pode ser mantido e o que deve ser refatorado.
É saber onde investir numa arquitetura mais robusta e onde manter o mais simples. É saber conviver com um projeto legado ou um sistema de terceiros adequadamente. É saber direcionar o melhor do seu time. E sim, também é sobre código.
Uma das minhas frases preferidas do Evans é quando ele rebate o conceito que o código é para os usuários. Para ele, “código também é para os desenvolvedores”. O código é a documentação viva do projeto, e sua qualidade, simplicidade e clareza vão auxiliar no seu ciclo de vida, para que as próximas equipes tenham condição de entendê-lo, mantê-lo, e quando ou se necessário, refatorá-lo.
Graziella Bonizi