loading...
Cover image for Entendendo Domain-Driven Design

Entendendo Domain-Driven Design

guisfits profile image Guilherme Camargo Updated on ・11 min read

Um dos principais conceitos que aprendi e mudou a forma como escrevo software foi Domain-Driven Design. Ele me trouxe outra visão de como abordar problemas, como criar soluções mais alinhadas com a área de negócio e com um melhor design. Neste artigo irei revisá-lo e compartilhar o meu conhecimento sobre os seus conceitos.

Overview

O que é, onde aplicar e benefícios

Domain-Driven Design (DDD) é uma metodologia de desenvolvimento de software. Originou-se em 2003 por Eric Evans em seu conhecido livro Domain-Driven Design: Tackling Complexity in the Heart of Software.

Ele é, acima de tudo, uma metodologia que habilita o desenvolvimento de software de alta complexidade de negócio de maneira efetiva. Através da sua filosofia, práticas e patterns, DDD nos permite gerar valor, reduzir custos e facilitar a evolução de aplicações que poderiam facilmente terminar de maneira trágica. Ele nos apresenta maneiras de modelar essas aplicações de tal forma que fique muito próxima à área de problema que estamos resolvendo, isso habilita a equipe técnica a desenvolver tais sistemas de maneira consistente e saudável.

O que não é, onde evitar e malefícios

Devemos ter ciência que quando falamos exclusivamente de arquitetura de software ou design patterns (Repositories, Entities, Services, Factories, etc) não estamos falando sobre DDD necessariamente. A parte prática com Building Blocks e Design de Arquitetura são importantes também, sem dúvida, porém elas não se sustentam sozinhas e não são o ponto central do DDD, estão lá simplesmente para nos auxiliar. Podemos ter um sistema que implementa todos os design patterns sugeridos, mas não atende o negócio. Por outro lado podemos ter um design mais simplista, mas que esteja totalmente alinhado ao domínio. DDD não exige que nenhum design pattern seja implementado, a única coisa exigida é que nosso domínio esteja isolado do restante da aplicação para que possamos focar nas regras de negócio e não misturar esses conceitos com problemas técnicos, além disso, DDD não está restrito somente ao paradigma de programação orientada a objetos. Concluímos então que não existe receita de bolo ou template padrão para implementá-lo.

Não devemos considerar DDD em aplicações onde não há complexidade de negócio. Tais aplicações não iriam se beneficiar do que nos é apresentado. DDD somente traria complexidade desnecessária e dificuldade em sua execução. Também devemos evitar seu uso onde não temos pessoas qualificadas para aplicar o conhecimento e as práticas requeridas, nem onde não temos contato direto com pessoas de negócio.

Problem Space

Quando falamos de Problem Space estamos nos referindo a área de negócio na qual o cliente está inserido e o que nosso software pretende resolver. Aqui aprendemos a gerenciar o problema e a destila-lo a fim de reduzir a sua complexidade.

Problem Space

Domain

O domínio é realidade do negócio, se refere a área de conhecimento e atividade na qual a organização e as pessoas estão inseridas. Envolve todas as regras, comportamentos, processos e entendimentos que lá habitam. Domínio é onde as pessoas de negócio vivem e na qual nós, desenvolvedores, não temos interferência.

Domain

Subdomains

Subdomains

Subdomain se dá no processo de decompor o nosso domínio em três categorias:

Core

É o que gera maior valor, o motivo da organização existir, o diferencial entre os seus concorrentes, é onde os 20% geram 80% dos resultados. O Core Domain é onde se deve gastar maior parte do tempo e recursos. Onde os desenvolvedores mais experientes devem trabalhar e onde devemos criar o design mais robusto.

Em um projeto de e-commerce, por exemplo, o Core Domain pode ser Sales. Isso não quer dizer que todos os e-commerces possuem o mesmo Core. Se um e-commerce se propõe a fazer entregas em até 24 horas, o Core Domain será Shipping, se outro e-commerce se destaca por ser o mais barato entre a concorrência, seu Core Domain será Pricing, então tenha em mente que os subdomains serão diferentes de empresa para empresa. Nosso papel aqui é mapear a realidade do negócio existente, não tentar encaixar modelos preconcebidos nele.

Supporting

Ele é o que habilita o Core Domain exercer seu papel, sem ele a organização não iria funcionar e ele precisa de um certo nível de customização, porém não possui vantagem competitiva. Ele é importante, mas não devemos gastar mais esforços do que o necessário. Aqui feito é bem feito!

Olhando nosso exemplo, os items em amarelo são áreas que sem elas o sistema não iria funcionar. Não conseguimos vender um produto sem um meio de pagamento, sem estoque no armazém, sem uma boa logística, etc. Todas essas áreas servem de auxílio para que o Core funcione adequadamente.

Generic

Não são de maior preocupação e não possui relação exclusiva com nosso Core Domain. Caso seja criada uma solução interna devemos fazer com que ela simplesmente funcione, nada mais, provavelmente delegar para desenvolvedores menos experientes. Outra alternativa seria contratar uma solução de terceiros.

As nossas caixinhas em vermelho representam o que é genérico para nós. Como o nosso foco é a venda temos que deixar algumas coisas de lado. Aceitamos que existem soluções de recomendações muito melhores do que podemos criar, podemos contratar uma dessas soluções através de SaaS. Também não é nosso foco customizar a experiência de cada cliente, só precisamos de alguns dados básicos - uma API com alguns métodos CRUD resolverá o nosso problema.

Solution Space

Solution Space se refere ao como iremos resolver o Problem Space. Envolve práticas e patterns a fim de criar uma solução efetiva.

Solution Space

Domain Models

O que é exatamente um "Modelo"? Segundo o Dicionário Criativo temos a seguinte definição:

Representação abstrata de um fenômeno, capaz de captar as características formais ou funcionais do objeto de estudo.

Podemos entender então que um modelo nada mais é do que uma visão/imagem que temos de um conceito. Trazendo para nosso cenário seria criar abstrações que representam nosso domínio em um determinado contexto. Ao fazer isso poderemos discutir com mais propriedade os termos e obter das pessoas de negócio conceitos subentendidos. Ele é fruto da colaboração. Sua maior utilidade se dá no entendimento compartilhado das regras e processos. Em resumo, Domain Model é a implementação de uma solução para o Domain.

Domain Model

Analysis Model

Durante as reuniões irão surgir ideias que podemos traduzir para diagramas (UML), onde desenvolvedores e pessoas de negócio possam entender e colaborar sobre os processos discutidos, estes são conhecidos como analysis model. Eles não são focados no aspecto técnico e não devem ter detalhes de implementação.

Code Model

Code Model é a implementação dos Analysis Model. Eles são criados utilizando os conceitos aprendidos no modelo anterior. Todo o Code Model deve servir apenas e exclusivamente ao nosso domínio, não devemos utilizar aqui nomes de patterns como Repository, Factory, Service, Manager, etc - isso somente iria gerar ruído na comunicação. Ao invés disso devemos utilizar a Ubiquitous Language na implementação.

Model-Driven Design

É o processo de sincronização entre o analysis model com code model. Em um cenário ideal ambos são traduzíveis de 1:1, para isso precisamos de uma implementação livre de detalhes técnicos e um analysis model que não seja tão abstrato. Durante esse processo precisamos analisar se os jargões foram modelados de forma explícita para garantir que os mesmo termos usados nas discussões estejam na implementação. Durante a implementação do code model a equipe técnica poderá identificar falhas de conceitos e regras, isso permite trazer novas reflexões durante reuniões e refinar o processo de maneira iterativa, o que levará na atualização do analysis model inicial. Desta forma isso gera um processo de melhoria contínua na qual analysis model e code model se retroalimentam.

Strategic Patterns

Os Strategic Patterns nos ajudam a destilar o Problem Space e arquitetar a aplicação guiada pelo domínio.

Ubiquitous Language

Entende-se como onipresente aquilo que está em todos os lugares. Para conseguirmos ter uma comunicação efetiva com pessoas de domínio é essencial falarmos a mesma língua. Ubiquitous Language é criada entre a colaboração da equipe técnica e pessoas de negócio, ela deve ser falada e escrita durante conversas, desenhos e implementações. Ela é dividida entre os Bounded Context. Esta linguagem é um artefato vivo, terá evoluções e refinamentos. É importante deixá-la de forma explícita para que todos entendam os seus termos, talvez manter um glossário ou outra documentação em que todos tenham acesso.

Inglês ou Português?

É prática comum, até recomendável, que nosso código seja escrito em inglês, porém isso entra em conflito com a Ubiquitous Language. Quando estamos trabalhando com domínio complexo devemos deixar nosso código com o idioma falado para não perder a relação com as pessoas de negócio.

Bounded Context

Após identificar nossos subdomains e ter uma linguagem compartilhada com pessoas de negócio iremos perceber que os termos utilizados tendem a ter mais de um significado para pessoas/processos/áreas diferentes. Um Product para o contexto Sales significa uma coisa e para o contexto Shipping outra. Ao invés de tentar criar um modelo único, aka God Object, a ideia do Bounded Context é criar divisões entre essa linguagem, onde cada um tem o seu modelo que representa uma abstração do problema, isso impede que os conceitos se misturem e que nosso software acabe se tornando um BBoM.

Contexts

Um Subdomain é algo abstrato/conceitual no qual podemos apenas discutir. Um Bounded Context, por outro lado, é um artefato que traduz esses conceitos para o software. Em um mundo perfeito todos nossos Subdomains seriam traduzíveis para um Bounded Context correspondente, infelizmente a realidade pode não ser tão generosa em alguns casos. Podemos ter N Bounded Contexts para satisfazer um Subdomain, o que ira guiar a divisão neste caso é a Ubiqutious Language. Devemos ficar atentos somente quando um Bounded Context tende a atender mais de um Subdomain, isso fere o Single Responsibility Principle, pois um B.C. terá mais de um motivo para ser alterado.

Seguindo os Subdomains propostos, vamos pensar na organização do projeto. Dentro de Customer podemos ter um Bounded Context chamado User que irá tratar do Nome, Endereços, Formas de Pagamento, Avatar, etc; e outro Bounded Context de Account contendo E-mail, Password, Roles, Claims, etc. E em Sales temos Orders, Pricing e ShoppingCart, para os demais acredito que um Bounded Context para cada irá servir.

App
  ├── Billing
  ├── Catalog
  ├── Customers
  │ ├── Account
  │ └── User
  ├── Sales
  │ ├── Orders
  │ ├── Pricing
  │ └── ShoppingCart
  ├── Shipping
  └── Warehouse

Tome cuidado para não dividir os Bounded Contexts pelos dados e reuso, infelizmente nos como desenvolvedores temos esse hábito. É um erro pensar em Bounded Context desta forma, isso levará a inconsistências no modelo e ao longo prazo pode acabar comprometendo a evolução do sistema. Bounded Context é meio difícil de entender no começo, estude bem antes de implementar!

Bounded Context geralmente é recomendado como a primeira linha de divisão em aplicações microservices.

Context Maps

Dado o cenário onde você já identificou e separou os contextos da sua aplicação, agora é hora de entender como eles se comunicam. Context Maps nos ajudam a entender melhor a barreira entre os Bounded Context e visualizar como eles interagem entre si. Há alguns patterns para a comunicação entre os contextos de maneira eficiente, porém irie comentar apenas dos dois principais:

SharedKernel

Haverá cenários em que teremos, dentro de um Subdomain, modelos e regras que se aplicam em mais de um Bounded Context, a fim de evitar duplicidade e não criamos acoplamento podemos ter uma camada que contém o que é comum entre os Bounded Contexts envolvidos.

SharedKernel

Podemos exemplificar criando uma camada Core em Sales, ela pode ter o modelo base de Product.

App/Sales
├── Core
├── Orders
├── Pricing
└── ShoppingCart

Anticorruption Layer

Quando estamos criando um Bounded Context seguindo a Ubiquios Language não queremos contaminá-lo com models externas, ou ainda pior, criar acoplamento com partes legadas do sistema. Neste cenário podemos criar uma camada intermediária entre o Bounded Context e suas dependências. Através de uma interface definida nele, podemos implementar nesta nova camada e traduzir o retorno de chamadas de outros Bounded Contexts para o nosso. Desta forma conseguimos utilizar recursos externos de forma transparente e sem acoplamento.

AntiCorruption-Layer

Em Billing temos uma camada chamada Integration que será responsável por obter dados de Customer, que está dentro do Bounded Context User. Ela também será responsável pela comunicação com a API de pagamentos externa.

App/Billing
├── src
│ ├── Domain
│ └── Integration
└── tests

Highlights

Colaboração

É fundamental para o entendimento do domínio que desenvolvedores e pessoas de negócio trabalhem juntas. Somente com a colaboração e entrevistas constantes podemos extrair os conceitos mais complexos e entender fluxos de negócio de maneira esmiuçada.

Foque no Core-Business

Nem toda parte da nossa aplicação tem a mesma relevância. DDD nos permite enxergar com mais clareza o problema que estamos resolvendo e entendendo melhor o que gera valor, devemos então direcionar nossos esforços para os processos que realmente impactam o negócio, aumentando assim a chance de sucesso do nosso software.

Evolução constante

Não importa o quão experiente você seja, não há como mapear e ter um design perfeito de início. O processo para destilar o domínio e criar modelos que o representam é iterativo. Além do que conceitos podem e vão mudar, novas necessidades irão surgir e precisamos ficar atentos para alinhar nosso design com o negócio. Podemos ter um sistema 1:1 no começo, porém se não darmos continuidade nas práticas e processos do DDD em alguns meses estaremos defasados. DDD necessita de manutenção constante!

Conclusão

Neste artigo tentei ilustrar com exemplos didáticos o que seria a abordagem do DDD em um software que pretende resolver um domínio complexo. Tenha em mente que devido a sua natureza, nenhum artigo, livro ou curso irá te passar a experiência real do DDD, pois isso envolve trabalhar com pessoas de negócio, distilar e refinar o domínio, colaboração, entre outros. Aprender sobre DDD não é como aprender uma linguagem ou framework novo, envolve muita soft-skill e recursos do dia-a-dia. Os problemas de negócio aqui apresentados são pensados para ilustrar possíveis situações que podem acontecer, porém, somente em um domínio real você irá notar as nuances do DDD e o absorver com mais clareza. Recomendo continuar estudando e, caso tenha a oportunidade, começar a pratica-lo com outros desenvolvedores mais experientes.

Irei falar mais sobre DDD nos próximos artigos onde abordarei os Tactical Patterns e Architecture Patterns.

Referências

Discussion

pic
Editor guide