O DDD (Domain-Driven Design)
Quando estudei MVC pela primeira vez no livro falava muito sobre DDD (Domain-Driven Design). Pelo que eu entendi na época, a ideia do DDD era simplesmente programar o software com foco no desenvolvimento da camada de domínio.
Depois disso, lendo sobre o processo de análise e projeto em orientação a objetos tive a impressão de que sempre que programamos um software orientado a objetos damos essa ênfase na camada de domínio, começando por ela e usando os requisitos pra montar ela corretamente.
Isso faz parecer que o DDD prega basicamente a mesma coisa que o processo de análise e projeto em orientação a objetos. Dessa forma, o que realmente é o DDD e quando ele se aplica de verdade? Quais as vantagens de se usar DDD em um projeto?
O DDD (Domain-Driven Design) é uma técnica de desenvolvimento divulgada por Eric Evans desde 2003. “Divulgada” porque nem tudo foi criação dele. Ele percebeu que um conjunto de boas práticas de desenvolvimento, trabalho em equipe e estratégias de negócio podem contribuir para que um projeto complexo seja bem sucedido.
O livro de Evans é um grande catálogo de Padrões, baseados em experiências do autor ao longo de mais de 20 anos desenvolvendo software utilizando técnicas de Orientação a Objetos. O que seria um Padrão?
Um padrão é uma regra de três partes que expressa a relação entre um contexto (1), um problema (2) e uma solução (3).
DDD pode ser visto por alguns como a volta da orientação a objetos. É verdade que o livro é um chamado às boas práticas de programação que já existem desde a época remota do SmallTalk. Quando se fala em Orientação a Objetos pensa-se logo em classes, heranças, polimorfismo, encapsulamento. Mas a essência da Orientação a Objetos também tem coisas como:
- Alinhamento do código com o negócio: o contato dos desenvolvedores com os especialistas do domínio é algo essencial quando se faz DDD (o pessoal de métodos ágeis já sabe disso faz tempo);
- Favorecer reutilização: os blocos de construção, que veremos adiante, facilitam aproveitar um mesmo conceito de domínio ou um mesmo código em vários lugares;
- Mínimo de acoplamento: Com um modelo bem feito, organizado, as várias partes de um sistema interagem sem que haja muita dependência entre módulos ou classes de objetos de conceitos distintos;
- Independência da Tecnologia: DDD não foca em tecnologia, mas sim em entender as regras de negócio e como elas devem estar refletidas no código e no modelo de domínio. Não que a tecnologia usada não seja importante, mas essa não é uma preocupação de DDD.
Eric Evans dividiu o livro em quatro partes, que apresentaremos a seguir.
1) Colocando o modelo de domínio para funcionar:
Para ter um software que atenda perfeitamente a um determinado domínio, é necessário que se estabeleça, em primeiro lugar, uma linguagem comum, com termos bem definidos, que fazem parte do domínio do negócio e que são usados por todas as pessoas que fazem parte do processo de desenvolvimento de software. Nessa linguagem estão termos que fazem parte das conversas diárias entre especialistas de negócio e times de desenvolvimento. Todos devem usar os mesmos termos tanto na linguagem falada quanto no código.
2) Blocos de construção do Model Driven Design (MDD):
Uma vez que decidimos criar um modelo usando MDD, precisamos, inicialmente, isolar o modelo de domínio das demais partes que compõem o sistema. Essa separação pode ser feita utilizando-se uma arquitetura em camadas, que dividirá uma aplicação em quatro partes:
Interface de Usuário – parte responsável pela exibição de informações do sistema ao usuário e também por interpretar comandos do usuário;Aplicação – essa camada não possui lógica de negócio. Ela é apenas uma camada fina, responsável por conectar a Interface de Usuário às camadas inferiores;
Domínio – representa os conceitos, regras e lógicas de negócio. Todo o foco de DDD está nessa camada. Mas o que é domínio? Domínio é o coração do negócio em que você está trabalhando. É baseado em um conjunto de ideias, conhecimento e processos de negócio. É a razão do negócio existir. Sem o domínio todo o sistema, todos os processos auxiliares, não servirão para nada. Se uma empresa existe, é porque ela tem um core business e, normalmente, esse core business é composto pelo domínio principal. Resumindo, sempre que se falar em domínio, estaremos falando da razão daquele software existir. Sem aquele ponto principal, não há razão para o desenvolvimento do software.
Infra-estrutura – fornece recursos técnicos que darão suporte às camadas superiores. São normalmente as partes de um sistema responsáveis por persistência de dados, conexões com bancos de dados, envio de mensagens por redes, gravação e leitura de discos, etc.
Camada de Domínio - Para modelar a camada de domínio, utilizamos alguns Padrões propostos em DDD. Esses padrões são chamados de blocos de construção e serão utilizados para representar nosso modelo abstrato. Esses blocos podem ser:
Entidades – classes de objetos que necessitam de uma identidade. Normalmente são elementos do domínio que possuem ciclo de vida dentro de nossa aplicação: um Cliente, por exemplo, se cadastra no sistema, faz compras, se torna inativo, é excluído, etc.;Objetos de Valores – objetos que só carregam valores, mas que não possuem distinção de identidade. Bons exemplos de objetos de valores seriam: strings, números ou cores. As instâncias de Objetos de Valores são imutáveis, isto é, uma vez criados, seus atributos internos não poderão mais ser modificados. Em Java, temos, por exemplo, a classe BigDecimal, muito utilizada para fazer cálculos com valores grandes.
Agregados – compostos de Entidades ou Objetos de Valores que são encapsulados numa única classe. O Agregado serve para manter a integridade do modelo. Elegemos uma classe para servir de raiz do Agregado. Quando algum cliente quiser manipular dados de uma das classes que compõem o Agregado, essa manipulação só poderá ser feita através da raiz;
Fábricas – classes responsáveis pelo processo de criação dos Agregados ou dos Objetos de Valores. Algumas vezes, Agregados são relativamente complexos e não queremos manter a lógica de criação desses Agregados nas classes que o compõem. Extraímos então as regras de criação para uma classe externa:
Fábrica de Serviços – classes que contém lógica de negócio, mas que não pertence a nenhuma Entidade ou Objetos de Valores. É importante ressaltar que Serviços não guardam estado, ou seja, toda chamada a um mesmo serviço, dada uma mesma pré-condição, deve retornar sempre o mesmo resultado;
Fábrica de Repositórios – classes responsáveis por administrar o ciclo de vida dos outros objetos, normalmente Entidades, Objetos de Valor e Agregados. Os repositórios são classes que centralizam operações de criação, alteração e remoção de objetos. Em linguagens como Java e .NET, repositórios são comumente implementados usando-se frameworks como Hibernate ou Nhibernate. Já em RubyOnRails, o ActiveRecord faz o papel de repositório;
Fábrica de Módulos – abstrações que têm por objetivos agrupar classes por um determinado conceito do domínio. A maioria das linguagens de programação oferecem suporte a módulos (pacotes em Java, namespaces em .NET ou módulos em Ruby). Um anti-padrão comum é a criação de módulos que agrupam as classes segundo conceitos de infra-estrutura. Um exemplo seria, ao se trabalhar em Java, criar um pacote que conterá todas as Actions do sistema. Ao usar DDD devemos agrupar classes se esse agrupamento faz sentido do ponto de vista do domínio, ou seja, do negócio. Se tivermos, por exemplo, várias classes que compõem diversas informações de um objeto num sistema, podemos criar um módulo chamado nome-objeto e colocar as classes do objeto num mesmo pacote.
Fábricas – classes responsáveis pelo processo de criação dos Agregados ou dos Objetos de Valores. Algumas vezes, Agregados são relativamente complexos e não queremos manter a lógica de criação desses Agregados nas classes que o compõem. Extraímos então as regras de criação para uma classe externa:
Fábrica de Serviços – classes que contém lógica de negócio, mas que não pertence a nenhuma Entidade ou Objetos de Valores. É importante ressaltar que Serviços não guardam estado, ou seja, toda chamada a um mesmo serviço, dada uma mesma pré-condição, deve retornar sempre o mesmo resultado;
Fábrica de Repositórios – classes responsáveis por administrar o ciclo de vida dos outros objetos, normalmente Entidades, Objetos de Valor e Agregados. Os repositórios são classes que centralizam operações de criação, alteração e remoção de objetos. Em linguagens como Java e .NET, repositórios são comumente implementados usando-se frameworks como Hibernate ou Nhibernate. Já em RubyOnRails, o ActiveRecord faz o papel de repositório;
Fábrica de Módulos – abstrações que têm por objetivos agrupar classes por um determinado conceito do domínio. A maioria das linguagens de programação oferecem suporte a módulos (pacotes em Java, namespaces em .NET ou módulos em Ruby). Um anti-padrão comum é a criação de módulos que agrupam as classes segundo conceitos de infra-estrutura. Um exemplo seria, ao se trabalhar em Java, criar um pacote que conterá todas as Actions do sistema. Ao usar DDD devemos agrupar classes se esse agrupamento faz sentido do ponto de vista do domínio, ou seja, do negócio. Se tivermos, por exemplo, várias classes que compõem diversas informações de um objeto num sistema, podemos criar um módulo chamado nome-objeto e colocar as classes do objeto num mesmo pacote.
3) Refatorando para compreender profundamente o modelo:
Depois de elaborar um modelo de dados que reflete o seu domínio, usando os blocos de construção do MDD, o processo de aperfeiçoamento do modelo continua. O modelo deve ser refatorado e melhorado na medida em que se obtém maior compreensão de conceitos importantes do domínio. Muitas vezes alguns conceitos do domínio estão implícitos no código. O trabalho do desenvolvedor é tentar identificar esses conceitos implícitos e torná-los explícitos. Alguns padrões podem ajudar a compreender mais profundamente o modelo:
Funções sem Efeitos-Colaterais – tentar deixar o código com o maior número possível de métodos que não alterem o estado dos objetos, concentrando esse tipo de operação (alteração de estado) em Comandos;
Asserções – para os Comandos que alteram estados, criar testes de unidade que rodem automaticamente, ou colocar asserções no código que validem, após a chamada dos comandos, as alterações de estado esperadas.
4) Projeto estratégico:
As técnicas de criação e refinamento do modelo (como o uso da Linguagem Simples e MDD) são importantes para começarmos a entender como desenvolver um sistema dirigido pelas regras de negócio do domínio, ou seja, como aplicar DDD.
Mas a parte mais importante e difícil de Domain Driven Design é quando começamos a lidar com sistemas complexos. Como deve ser a interação entre sistemas que interagem entre si? Como dividir nosso trabalho, de maneira que foquemos nossos esforços naquilo que tem maior valor para o negócio? Como fazer a comunicação entre as equipes que desenvolvem esses sistemas?
Temos alguns padrões que nos ajudam a dividir nosso software em várias partes, que chamamos de contextos. Cada Contexto Delimitado deve estar bem claro para todos que estão envolvidos no processo de desenvolvimento. A fronteira entre contextos deve ser clara para todos, ou seja, todo mundo deve saber a qual contexto um determinado pedaço de código pertence.
Os padrões que nos ajudarão a estabelecer qual é a relação existente entre os vários times são:
Mapa de Contextos – uma forma pragmática de documentar claramente os vários Contextos Delimitados que fazem parte de um sistema complexo. No mapa de contextos devemos ressaltar os vários componentes do software e como as equipes que cuidam desse sistema interagem. Além disso, criaremos um mapa de tradução que servirá para facilitar a comunicação. Muitas vezes, existem termos usados para um mesmo conceito em vários contextos.Todas essas coisas são bem exemplificadas e mostradas na forma de vários padrões em DDD. Mas o livro também mostra muitos padrões que não dizem respeito a código ou modelagem. Aparecem coisas que estão mais ligadas a processos (como Integração Contínua) ou a formas de relacionamento entre times que fazem parte do desenvolvimento de um sistema complexo.
Conhecido pelo seu livro “Domain-Driven Design – Atacando as Complexidades no Coração do Software”, Evans apresenta uma mudança de paradigma tão grande sobre a forma de entender e solucionar problemas complexos, que inspirou diversos outros ótimos livros sobre o assunto.
O modelo padrão de análise e desenvolvimento de software no qual os analistas de negócio, em conjunto com os especialistas de negócio e/ou usuários-chave, farão um levantamento funcional, e elaborarão um extenso documento de especificação funcional, em boa parte dos dos casos esse modelo não irá funcionar. Entre as razões possíveis, podemos citar:
- A visão do analista de negócios pode não ser técnica o suficiente para entender os riscos, prazos e impactos da solução;
- O entendimento de um negócio é algo crescente. Dificilmente a primeira versão de uma especificação funcional é a que soluciona adequadamente o problema;
- O arquiteto, ou responsável técnico, vai desenhar a solução de acordo com a especificação, que pode mudar ao longo do projeto. Ou seja, a arquitetura proposta no início pode não ser a mais adequada no final;
- O arquiteto ou responsável técnico, se não tiver um contato próximo ao time de desenvolvimento, pode tomar decisões arquiteturais ou de implementação não compatíveis com o time, seja por competências técnicas ou mesmo por estilo de desenvolvimento;
- Se o arquiteto tem uma visão limitada sobre o problema a solucionar, os desenvolvedores têm em dobro: além da especificação funcional que tende a ficar defasada, estão trabalhando numa arquitetura imposta que não necessariamente soluciona o problema de negócio. Pode resolver problemas que não existem, como a troca de um banco de dados que pode nunca acontecer, e não ser flexível o bastante para o crescimento da aplicação, por exemplo;
- Os desenvolvedores vão dar o seu melhor para atender o prazo e os requisitos dentro da arquitetura imposta. Porém quando o projeto for disponibilizado aos usuários, talvez meses depois da análise de negócios, os usuários tenham entendido que não era bem isso que eles queriam.
As mudanças serão necessárias para atender as necessidades atuais dos usuários, e não as de meses atrás. Mas os desenvolvedores podem encontrar dificuldades em implementar as mudanças dentro de um padrão imposto. Isso gera complexidade técnica acidental. O código muitas vezes fica mais complexo do que deveria porque precisa seguir certos padrões definidos no início. À medida que o sistema vai crescendo, a complexidade técnica acidental fica maior do que a própria complexidade do negócio.
O domínio do desenvolvimento do código reflete no domínio da mesma linguagem dos especialistas de negócio e usuários. Termos estritamente técnicos são substituídos por termos que o usuário compreenda. Talvez alguns diagramas possam servir de apoio, mas ao invés de documentos extensos e detalhados, temos um código rico, claro e coeso.
A equipe de desenvolvimento de software como um todo se torna responsável pela arquitetura do projeto. O papel do arquiteto de software pode até continuar a existir, mas numa posição muito mais colaborativa e próxima do dia a dia de desenvolvimento.
A implementação da solução é emergente. Começa simples, e cresce junto com o entendimento do domínio, e não em etapas separadas. Pode ser que em algum momento, perceba-se que a arquitetura não atenda mais. Um conceito chave que impacta na clareza do domínio (código) pode ser percebido, gerando a necessidade de refatoração parcial. E tudo bem se isto acontecer.
Não se investe tempo em padrões e abstrações que não são necessárias. A energia da equipe, do técnico ou do usuário, não é direcionada para um único objetivo: solucionar o problema de domínio, de uma forma em que o código reflita o domínio do negócio.
Em resumo, mesmo o DDD não sendo um padrão de arquitetura, ele afeta como as decisões arquiteturais são tomadas, e o próprio papel do arquiteto na equipe de desenvolvimento do software.
O DDD é voltado para domínios complexos, e depende da quebra de barreiras de comunicação entre especialistas de negócio e especialistas técnicos, além do engajamento do time técnico em programar de forma que o código reflita o domínio. Se uma dessas variáveis for negativa, provavelmente o DDD não serve para o cenário que você está lidando.
Já está claro que o DDD não é uma arquitetura, nem uma bala de prata que resolverá qualquer problema que vier. Mas caso seu time identifique que o domínio em questão é complexo, e esteja disposto programá-lo de forma que a representação do domínio seja clara até para o especialistas de negócio, e perceba que o cliente ou empresa está interessado em colaborar constantemente com o entendimento do domínio e validação da solução, talvez o DDD seja uma boa abordagem de desenvolvimento.
Para aplicar o DDD como modelo de aplicação, deverá ser levado em consideração:
A cultura da empresa: uma empresa que está acostumada com ITIL ou semelhante, com papéis bem definidos e times segregados, terá dificuldades em romper essas barreiras para um modelo colaborativo. Pode ser que, dentro do possível, as coisas estejam indo bem dessa forma, então não há a necessidade de mudar. Mas caso o modelo atual de trabalho esteja impactando as entregas, gerando desgastes, documentação defasada e código de baixa qualidade, entre outros efeitos, talvez os gestores sintam a necessidade de mudança. Se sim, pode ser válido conversar sobre DDD, o que não garante que os gestores se sintam confortáveis com as mudanças propostas. Em outras palavras, as pessoas envolvidas precisam querer e acreditar nessa mudança, a ponto de se comunicarem e colaborarem diretamente com o desenvolvimento do software, e não apenas com a especificação e prazos;O que não é DDD? O DDD não é uma tecnologia ou uma metodologia. Pode ser utilizado independente da linguagem. Não importa se é C# ou Java, se é MVC ou Windows Forms. Não é arquitetura em camadas e não impõe processos rígidos para a equipe de desenvolvimento de software.