No desenvolvimento de software criar sistemas que atendam às necessidades práticas ao mesmo tempo que permaneçem flexíveis diante das constantes mudanças, é uma tarefa desafiadora. O artigo Domain Driven Design: Contornos Conceituais analisa profundamente o Domain Driven Design (DDD) e a linguagem ubíqua, indo além da mera escrita de código, entendendo como o domínio opera. A ideia é explorar de que maneira o software absorve novos conceitos para que a evolução faça sentido para o cliente e para o desenvolvedor.
Esse artigo faz parte de uma série sobre design flexível com base no livro classico do Eric Evans, Domain Driven Design: atacando a complexidade no coração do software, tais como:
- Domain Driven Design: Afirmações
- Operações isentas de efeitos colaterais
- Interfaces reveladoras de intenções
Sumário
Agrupando conceitos em maiores
Muitas vezes quando programamos ou quando organizamos itens em um documento excel ou ainda recursos na nuvem, sentimos a necessidade de agrupá-los. Agrupamos quando percebemos que há um conceito comum, mesmo que não receba um nome formal, mas que agrega esse conjunto.
Eventualmente outros grupos surgem e outros e outros. Percebemos na prática que há super-conceitos que agrupam níveis maiores e, em alguns casos, conceitos ruins exigem que alguns grupos sejam rearranjados.
Os grupos podem ser feitos através de classes, módulos, polimorfismos, encapsulamentos, entre outros. Mas veja que agrupar pode trazer alguns benefícios interessantes como:
- Clareza: Evidenciar que muitas coisas podem ser vistas como algo comum, facilitando o entendimento;
- Reaproveitamento: Aumentar o reaproveitamento, uma vez que lógicas comuns podem ser mais facilmente aplicadas a grupos;
- Facilidade: Facilitar o uso, uma vez que apenas um ponto corresponde a várias coisas;
- Percepção do conceito: É mais fácil enxergar o conceito por trás das coisas;
- Codifica-se o necessário: Reduz-se a chance de duplicação de código desnecessário.
Já algumas outras vantagens são parciais e podem não ser aplicáveis em todos os casos. Entretanto, ao faze-la é fundamental levar esses pontos em consideração.
- Legibilidade: Menos verbosidade, ou seja, pouco código pode representar muitas coisas. Entretendo pode ser mais complexo de entender dependendo da implementação;
- Simplicidade: As coisas podem ficar mais simples, mas depende de como for implementado;
- Complexidade: Há um controle da complexidade em detrimento da carga cognitiva.
O que são ‘Conceitos’ para o DDD
Isso parece ser muito subjetivo até por que nem sempre é nomeável. Um conceito é uma ideia abstrata ou generalização que representa uma classe de objetos, eventos, padrões ou fenômenos com características comuns. Os conceitos são construções mentais que nos ajudam a organizar e compreender o mundo, facilitando a comunicação e a categorização de informações.
Quando desenvolvemos sistemas orientados ao domínio ficamos a mercê de descobertas a medida que a construção do software avança. Novos conceitos são trazidos e progressivamente incorporados.
Como exemplo: Hospitais podem lidar com prontuários, anamneses, leitos, plantonistas, remédios ou nível de emergência. Esses exemplos que comentei podem ser relacionados entre si, vistos no seu interior detalhadamente ou agrupados. Se houver a necessidade de associar um leito a um plantonista, posso estar dizendo que aquele médico deve trabalhar naquele horário com os pacientes daqueles quartos. Há um conceito do domínio por trás desse exemplo, embora não tenha nenhum nome específico o representando.
Toda vez que fazemos agrupamentos, relações, encapsulamentos, polimorfismos, classes, módulos, etc. há conceitos que permeiam essas decisões. Nem sempre claros ou coerentes, mas estão lá.
Desagrupando em conceitos menores
Agora, pelo lado contrário o desagrupamento trás códigos com mais conteúdo, com menos generalização e características como:
- Especificidade: Geração de mais código específico e com leitura mais objetiva;
- Menos complexidade: Redução da complexidade em detrimento de um código mais verboso;
- Perda de ponto único: Impossibilidade de alteração em massa por um ponto único;
- Conceitos ocultos: Os conceitos envolvidos podem não estar evidentes. Na prática é possível analisar e extraí-los.;
- Duplicação: Há certo nível de duplicação de código;
- Legibilidade: É mais fácil de produzir e, em alguns casos de ler o código;
- Simplicidade: o código fica mais simples (ainda que maior);
- Reutilização perdida: Perde-se capacidade de reutilização;
- Menos simplismo: Evita o risco da simplificação exagerada (do agrupamento).
Relação entre conceito e intenção
Particularmente entendo que os conceitos são contrapontos às intenções. Enquanto um conceito é uma abstração que envolve diferentes pontos de um sistema ou processo, uma intenção é uma ação específica, pontual e concreta. As intenções estão submetidas aos conceitos e não o contrário. Evans comenta sobre interfaces que revelam intenções como uma fortaleza de um bom design, e posteriormente fala sobre os conceitos.
Mas há algumas relações interessantes nesse sentido. No artigo A essência da Orientação a objetos comento sobre coesão e coerência. Uma estrutura coesa é aquela que cumpre o seu propósito particular, único e desconectado. Já uma estrutura coerente é aquela que faz sentido estar onde está, se relacionando com quem deveria no momento certo. Portanto coesão é micro e coerência é macro.
Além desses, há outro binômio que vale destaque, a alta coesão e o baixo acoplamento. Quanto mais coeso, mais sentido a coisa faz em si mesma. Testes de unidade muitas vezes são alinhados com estruturas assim. Quando falamos de acoplamento estamos falando, de outra maneira, de coerência.
Quero assim dizer que a relação entre as estruturas deve estar bem feita e flexível, garantindo assim coerência e, logo, baixo acoplamento. Para consolidar o argumento: “conceitos referem-se a coerência e os argumentos que os justificam. Por outro lado, intenções referem-se a coesão e a unidade bem feita.”
Qual é a granularidade ideal
Pois bem, estamos falando que estruturas podem ser agrupadas ou desagrupadas, e que conceitos bem definidos criam estruturas coerentes enquando intenções bem definidas criam estruturas coesas, portanto pouco acopladas. O problema é que não estou sendo pragmático (como Uncle Bob), ou seja, não falo o que é certo ou errado como numa tábua de Moisés. Não costumo gostar de saber soluções pragmáticas antes de saber o que as justifica.
Portanto, para entendermos minha visão sobre a granularidade ideal das estruturas, vou trazer alguns outros temas a tona para subsidiar meus argumentos.
Requisitos
Na engenharia de software Summervile em seu livro clássico Engenharia de Software de 1982 traça um conceito sobre requisitos:
“Os requisitos de software representam uma descrição detalhada e documentada das funções e restrições que um sistema deve atender. Eles são uma expressão dos serviços e restrições operacionais que são colocados no software.”
Summerville, Ian
Alguns podem entender que requisitos são, também, as especificações, o que torna único e inequívoco o estruto em questão. Acho inclusive que essa visão de especificação é a vigente hoje em dia (2024). Entretanto, ela apresenta problemas graves.
Quando um analista de negócios (ou PO, BO, etc.) elicita e documenta requisitos para que os desenvolvedores (ou arquitetos, etc.) muitas vezes considera-se que se trata de uma verdade imutável. O problema disso é que na prática o que temos no máximo são hipóteses. Não sabemos se o que o usuário falou está preciso, se trará o valor que o negócio espera no tempo adequado. Não há certezas em nenhum desses momentos. Por conta desse aspecto entendo que modelos ágeis compreendem melhor a natureza do software.
A evolução dos requisitos
Em consequência, a mentalidade por trás de um cenário onde se obtém todos os requisitos de um software antes do seu desenvolvimento é irresponsável e, muitas vezes, inviável. Os requisitos são, na prática, hipóteses de geração de valor. Que deve ser testado com o menor esforço e o mais rápido possível, para que manobras possam ser feitas em caso de falhas, e que haja o seu consumo em caso de sucesso. Entretanto, apenas o software em produção pode dar a real percepção sobre sua geração de valor. O uso de ferramentas como ORK ou mesmo monitoramento pode auxiliar nesse objetivo.
Por conta disso, há de se ter um equilíbrio em uma visão geral para o inicío do software que dê uma noção do todo, mas com pouco custo para se extrair algo que seja aproveitável pelos desenvolvedores. Já esse material é a base para um conceito de médio prazo de requisitos, que vão se remodelando a medida que as hipóteses são validadas ou não. E, por fim, há um nível micro, trabalhado a cada ciclo de entregas, com os detalhes mais próximos do dia-a-dia do programador.
Considerando tudo isso como uma verdade, entendemos que o sistema evolui com os requisitos e suas comprovações. Entretanto, no desenvolvimento é comum que conceitos e intenções sejam tratadas de maneira equivocadas, exigindo refatorações do design do sistema.
Contornos conceituais
Chegamos, então, no bendito tema do título do post, Domain Driven Design: Contornos Conceituais. Confesso que achei esse um dos mais complicados de se entender, mas vamos lá. À medida que os requisitos avançam, hipóteses são testadas, umas funcionam plenamente, outras parcialmente e outras não funcionam. Através deles o software agrega conceitos e intenções progressivamente compondo a linguagem ubíqua junto ao modelo.
Nem todo o momento temos os conceitos claros e refatorações podem traze-los à tona. Alguns conceitos são menos eficázeis que mesmo após evidenciados no software, será necessário realizar refatorações logo em seguida. Bom conceitos são aqueles que suportam bem as evoluções dos requisitos que ainda vão surgir, pedindo menos refatorações de design. Em outras palavras, bons contornos conceituais melhoram a consistência do domínio em sua evolução.
Isso é fundamental para que o trabalho do desenvolvedor seja mais produtivo ao mesmo tempo que novos programadores conseguem aprender mais facilmente o software e suas intenções.
Tá, mas qual a granularidade ideal?
Já comentamos que um bons contornos conceituais evitam a necessidade de refatorações ligadas ao design, por terem conceitos e intenções claras. A granularidade de uma dada estrutura deve ser aquela que trás o nível de compreensão adequada para aquele domínio, criando bons conceitos.
Por exemplo, uma loja de bolos deve ter sistemas que lidem com os ingredientes, mas não faz sentido quebrar os ingredientes em substância químicas ao mesmo tempo que o objeto bolo pode ser grande demais para viver sem subníveis.
Para ser mais simples e resumido, vamos considerar esses pontos
- As estruturas devem ser compreensíveis para os usuários;
- Os conceitos devem estar estáveis para o domínio e exigirem pouca refatoração de design
- As intenções devem ser fáceis de serem compreendidas e os conceitos que os envolve
- O time técnico deve ser capaz de entender tanto o modelo (logo a linguagem do usuário) quando o sistema (linguagem do desenvolvedor)
- Novos desenvolvedores (experientes e minimamente ambientados com o domínio) devem conseguir facilmente trabalhar com o software já pronto
- O software deve exigir pouca carga cognitiva para quem tenta ler (dentro da complexidade natural do domínio)
Conclusão de Domain Driven Design: Contornos Conceituais
Compreender a importância de como os conceitos do domínio são compreendidos e agregados ao software através dos requisitos é algo fundamental. No artigo Domain Driven Design: Contornos Conceituais, exploramos como a granularidade ideal, impulsionada por uma sólida compreensão dos conceitos e intenções do domínio, cria uma base para estruturas de software flexíveis e resilientes. Ao equilibrar a coesão das intenções com a coerência dos conceitos, os desenvolvedores podem criar sistemas que sabem suportar os novos requisitos que virão na evolução do software.
Ele atua/atuou como Dev Full Stack C# .NET / Angular / Kubernetes e afins. Ele possui certificações Microsoft MCTS (6x), MCPD em Web, ITIL v3 e CKAD (Kubernetes) . Thiago é apaixonado por tecnologia, entusiasta de TI desde a infância bem como amante de aprendizado contínuo.