Em janeiro de 2020 a PHPSP me chamou pra palestrar sobre DDD em um meetup, mas por ser um tema que eu já palestrei algumas vezes, fiquei pensando em uma abordagem diferente para trazer.
No artigo Desmistificando o DDD eu falo sobre os princípios do Domain Driven Design, no artigo Destilando um domínio com DDD eu falo sobre destilação de domínios e building blocks usando um case fictício para fins didáticos, e no artigo DDD e Microsserviços – Do negócio à arquitetura eu falo sobre como esses princípios podem ser aplicados em decisões arquiteturais, como microsserviços. O que percebi que faltava era mostrar um cenário real, um caso de sucesso, para que as pessoas tivessem um exemplo concreto de como o DDD pode trazer resultados positivos para o produto e para o time, quando bem aplicado.
Consultei alguns colegas. Falei com o Leonardo Prange, engenheiro de software na Qualyteam, que palestrou sobre o tema na trilha de Arquitetura .NET no TDC São Paulo em 2019, e é claro, com o Elemar Jr., que tem ampla experiência no assunto. Comecei conversando com o Leonardo, e fiquei muito feliz e surpresa quando ele mencionou que, em certo momento do projeto, a Qualyteam contratou a consultoria do Elemar pra orientá-los. Percebi que eu teria as duas visões do mesmo case – a do time e a da consultoria, e que seria mais interessante do que eu imaginava. Espero que esse artigo agregue tanto para quem ler quanto agregou para mim escrevê-lo! Vamos lá.
O cenário
Em 2018, a Qualyteam, uma empresa que fornece plataformas de gestão de qualidade, entendeu que suas soluções não estavam preparadas para o mercado internacional. O sistema monolítico era de difícil expansão, e o time estava considerando iniciar um processo de reconstrução do software legado para torná-lo mais generalista, mas optou por construir uma solução mais específica e enxuta, visando um modelo de negócio escalável de self-service.
A solução nasceu com uma estrutura monolítica multi camadas separadas por módulos de negócio, porém o time frequentemente se deparava com complexidade técnica acidental. Eles acreditaram que, se utilizassem uma arquitetura de microsserviços com princípios do DDD, teriam mais clareza no domínio e mais velocidade ao implantar novas funcionalidades. Tomaram a decisão de contratar a consultoria do Elemar Jr, arquiteto conhecido pelo seu conhecimento em sistemas distribuídos e soluções de alta complexidade, para orientá-los nesse processo de transição.
A consultoria
A consultoria durou 2 dias. Na visão do Elemar, o conceito da linguagem ubíqua não estava sendo trabalhado. Ele defendeu a prototipação como um meio dos especialistas de domínio (usuários chave e analistas de negócio) e dos técnicos (desenvolvedores e arquitetos) se comunicarem com a mesma linguagem. É o que o usuário consegue visualizar melhor – as telas e as navegações do produto que ele irá receber, ao invés de classes e diagramas que, para ele, podem ser abstratos. Mesmo durante esse processo de prototipação, é importante orientar os usuários a pensarem nas ações ao invés dos dados em si. Sem esse critério, é comum que os protótipos e a modelagem comecem a partir dos cadastros, sendo que na realidade eles são um suporte para as operações que devem ser realizadas no sistema. Também defendeu que o time abrisse mão de conceitos que não eram necessários. Toda decisão técnica em que o time não fosse capaz de justificar deveria ser revista. Lembrando que dizer que algo “é uma tendência de mercado” NÃO é uma justificativa. 🙂
O resultado
Com o tempo o time da Qualyteam tomou um rumo técnico e estratégico que culminou em sistemas mais coesos, testáveis, e uma melhor percepção do time sobre o desenvolvimento. Vamos destacar as decisões mais relevantes:
Era necessário reescrever o legado?
O time entendeu que não. Era um sistema que pagava as contas, e atendia bem um nicho de mercado. Decidiram dedicar seus esforços no domínio que ainda não era atendido, sem adicionar uma preocupação em torná-lo generalista. Atendendo um domínio específico é mais simples identificar a linguagem ubíqua e evitar a complexidade acidental.
Utilizar microsserviços ia melhorar algo?
Não. O time percebeu que estava considerando adotar essa arquitetura por ser uma tendência de mercado, e não porque resolveria algum problema. Pelo contrário, os problemas que eles enfrentavam com o monolito seriam multiplicados pelo número de serviços distribuídos. Decidiram construir sistemas monolíticos que atendessem um domínio e seus subdomínios.
Começar pela modelagem de dados foi uma boa escolha?
Ao começar pela modelagem, o time não sentiu tanto um problema clássico ao aplicar os conceitos do DDD utilizando banco de dados relacionais – precisar “sujar” o domínio com objetos de mapeamento de dados, por exemplo classes para representar o relacionamento many-to-many e outros recursos de infra.
O banco e as entidades estavam compatíveis, e a maior parte da lógica ficava em classes de serviços. Isso para um CRUD pode ser adequado, mas pouco revela sobre o domínio quando se trata de sistemas mais complexos. As entidades anêmicas não mostram o comportamento dos objetos, e não há um destaque para o que o sistema faz, e sim para que dados ele abriga.
Além disso, algumas desvantagens técnicas acabam acontecendo – a utilização de ORM’s tipicamente gera agregados “gordos”, com múltiplos níveis de navegação, e eventualmente dados que não são necessários acabam sendo trazidos para o domínio. Múltiplas dependências precisavam ser injetadas nos serviços, o que gerava um esforço maior para codificar testes, já que era necessário mockar as dependências de infra. Tudo isso contribuía para a complexidade técnica acidental.
Quais ações foram tomadas para revelar mais sobre o domínio?
As entidades foram remodeladas para trazer apenas as informações diretamente necessárias. A lógica de negócio foi aos poucos sendo transferida dos serviços para as entidades correspondentes. Quando uma operação envolvia mais de uma entidade, e ficava confuso sobre qual entidade escolher para codificar a operação, eram criados serviços de domínio. O time optou por utilizar CQRS e Rabbit MQ, o que permitiu desacoplar os serviços, diminuindo o número de dependências injetadas e tornando-os mais enxutos, facilitando a codificação de testes.
Que recursos foram retirados por não serem necessários?
Com a simplificação da modelagem, o time percebeu que não havia a necessidade de controlar as operações com o pattern Unit of Work, já que, nesse domínio, apenas um agregado era alterado por vez. O padrão de serviços foi substituído aos poucos por comandos e queries do padrão CQRS.
Como ficou a arquitetura?
A arquitetura de cada domínio ficou dividida em quatro camadas. Pela simplicidade dos domínios, cada um contemplava entre uma a quatro agregações. A linguagem utilizada foi .NET Core.
De uma forma geral, qual foi o resultado dessas decisões?
O time entendeu que, ao utilizar a linguagem ubíqua e seguir essa diretiva na escolha da arquitetura, modelagem de domínio e seleção de tecnologias e patterns, são criados sistemas mais simples e fáceis de manter e expandir. As entregas têm sido mais velozes, com maior qualidade e menor complexidade acidental. A percepção do time foi positiva, gerando engajamento técnico com a solução, e hoje alguns dos desenvolvedores participam de eventos compartilhando suas lições aprendidas com o Domain Driven Design.
Considerações finais
Esse cenário foi bem efetivo para ilustrar alguns pontos chave ao aplicar o DDD:
- Linguagem ubíqua é mais do que usar os mesmos termos que o usuário-chave utiliza. É aproximar essas duas realidades, a técnica e a de negócios, procurando vencer as barreiras da comunicação com os recursos mais adequados, produzindo software que efetivamente reflita as necessidades do negócio. Um dos recursos destacados aqui é a prototipação guiada, uma forma do usuário explicar na prática o que ele precisa com algo que ele tenha condições de visualizar e validar, mais do que um diagrama ou uma documentação escrita, com a orientação do time técnico para evitar se preocupar mais com os cadastros e objetos auxiliares do que com as ações e eventos da solução.
- Focar em um único domínio torna o processo de destilação, análise, arquitetura e codificação mais simples. Generalize apenas se houver a necessidade. Generalizar precocemente muda o foco da solução e costuma causar complexidade técnica acidental.
- Ao modelar um software que não seja apenas um conjunto de cadastros, priorize a linguagem ubíqua ao invés dos dados. Depois, modele os dados de uma forma que atendam o domínio, destacando as ações e eventos, trazendo somente os dados diretamente necessários.
- Utilize apenas os building blocks e recursos que irão ser úteis no seu contexto. Nesse cenário, Unit of Work não foi necessário, mas Domain Services sim. Em outro cenário poderia ser diferente. Você não precisa aplicar todos os conceitos do DDD para dizer que está utilizando o DDD. Muito pelo contrário – você escolhe os recursos que vão facilitar o desenvolvimento, deixar o domínio mais claro e simplificar o código, sempre que possível.
- Da mesma forma, a arquitetura deve ser tão simples quando a natureza do seu domínio permitir. Nesse cenário, quatro camadas foram o suficiente para organizar o código de maneira coesa. Aquela expressão “não construa um canhão pra matar uma mosca” se adequa bastante.
Espero que esse conteúdo seja de algum valor pra vocês. Agradecimentos especiais ao Leonardo Prange, por não só ter compartilhado esse case mas contribuído com exemplos de código, ao Elemar Jr pela participação, e à PHPSP, que me convidou para o meetup que originou esse artigo. Para o público de PHP, um dos participantes do meetup me recomendou o livro Domain Driven Desing in PHP, eu já havia recomendado o livro do Scott Millett & Nick Tune com exemplos em C#, outro muito conhecido é o “Implementing Domain-Driven Design” do Vaughn Vernon com exemplos em Java e agora mais esse com exemplos em PHP, mostrando que esses princípios podem ser aplicados em qualquer linguagem e tecnologia!
Graziella Bonizi