O Core Domain: Modelando Domínios Ricos

Guilherme Biff Zarelli
Inside PicPay
Published in
7 min readNov 22, 2023

Em diversos modelos arquiteturais como a Hexagonal de Alistair Cockburn e a Clean Architecture de Robert C. Martin o conceito de “Core Domain” é aplicado de maneira mais isolada possível a fim de proteger a parte mais importante do software: as Regras de Negócio.

Mas como estamos construindo essas regras? será que nossos objetos de domínio dizem realmente como nosso sistema pode se comportar ou são apenas classes para trafegar status? Nesse artigo veremos como sair de um "modelo anêmico" para um "modelo rico".

O Core Domain

No Domain-Driven Design (DDD), o “Core Domain” é identificado como a parte mais vital e estratégica do negócio. Ele representa o cerne do conhecimento e expertise da organização, mantendo as regras de negócios e a lógica essencial do sistema.

É crucial identificar, compreender e dedicar esforços significativos para desenvolver e manter o Core Domain da aplicação. Isso pode envolver a criação de modelos ricos em conhecimento do domínio, a colaboração próxima entre especialistas do domínio e desenvolvedores, além do uso de linguagem ubíqua para garantir que a comunicação sobre o domínio seja clara e consistente entre todas as partes envolvidas no projeto.

“The Core Domain is the heart of the business. It must be well understood and well modeled for the application to succeed.” — Eric Evans, author of “Domain-Driven Design: Tackling Complexity in the Heart of Software”

Para entender melhor os próximos tópicos, precisamos saber onde estão os objetos de domínio em um projeto ou onde deveriam estar. Em um dos padrões mais utilizados, o MVC, as regras de negócios deveriam estar nos ‘Models’, em padrões de arquiteturas em camadas como a Clean Architecture, Onion ou Hexagonal elas deveriam estar ‘no centro’, o nome pode variar como: ‘Core’, ‘Entities’, ‘Bussines Model’, ‘Domain’, etc.

Agora que já sabemos para onde olhar, vamos compreender as maneiras de se modelar o domínio de um sistema.

Domínios Anêmicos: Objetos sem comportamento e lógica de negócio em Serviços

Modelos anêmicos referem-se a um anti-patterns de design onde as classes ou objetos que lidam com o "negócio" têm apenas atributos com getters e setters, mas carecem de comportamento, ou seja, possuem poucos ou nenhum método que opera nesses atributos. Em outras palavras, são classes sem ter lógica associada a eles — ou apenas POJOS disfarçados.

O pior desse anti-patterns é que ele é totalmente contrário à ideia básica do design orientado a objetos; que é combinar dados e processos juntos. O modelo de domínio anêmico é na verdade apenas um design de estilo procedural.

Nota-se muitos desenvolvedores usando as famosas classes de Services ou UseCases para escrever as regras de negócio, mas o grande equivoco é que elas deveriam ser apenas orquestradoras do domínio e embora os modelos anêmicos possam parecer simples e diretos, essa prática pode ter algumas desvantagens significativas:

  • Lógica espalhada: Quando a lógica relacionada a um conjunto de dados está espalhada por várias partes do código (por exemplo, em controladores, serviços, etc; em vez de estar nos próprios modelos), pode ser difícil de manter e entender. Isso pode levar a um código mais frágil e propenso a erros;
  • Dificuldade de evolução: Se novas regras de negócio surgirem ou se as regras existentes precisarem ser modificadas, ter a lógica diretamente nos modelos permite uma evolução mais natural e coerente do código. Modelos anêmicos muitas vezes não têm a capacidade de evoluir com facilidade para acomodar novos requisitos sem introduzir complexidade adicional em outras partes do sistema;
  • Quebra do encapsulamento: Em modelos anêmicos, a lógica muitas vezes é colocada em serviços ou controladores externos, o que quebra o princípio de encapsulamento, dificultando a manutenção e o entendimento do sistema como um todo.

No livro de Eric Evans, Domain Driven Design, vemos a seguinte citação sobre a responsabilidade da camada de serviços:

“Camada de Aplicação [seu nome para Camada de Serviço]: Define as tarefas que o software deve executar e direciona os objetos de domínio expressivos para resolver problemas. As tarefas pelas quais essa camada é responsável são significativas para o negócio ou necessárias para a interação com as camadas de aplicação de outros sistemas. Essa camada é mantida fina. Ela não contém regras de negócio ou conhecimento, mas apenas coordena tarefas e delega o trabalho para colaborações de objetos de domínio na camada imediatamente abaixo. Ela não possui um estado que reflita a situação do negócio, mas pode ter um estado que reflita o progresso de uma tarefa para o usuário ou para o programa.” — Domain-Driven Design: Tackling Complexity in the Heart of Software, Eric Evans

E como vimos anteriormente, a camada de domínio é a parte mais fundamental do sistema, então que sentido faz delegarmos seus comportamentos para camadas superiores como de aplicação ou serviços? Construindo modelos anêmicos?

“In general, the more behavior you find in the services, the more likely you are to be robbing yourself of the benefits of a domain model. If all your logic is in services, you’ve robbed yourself blind.” — Martin Fowler in “AnemicDomainModel

Domínios Ricos: Objetos de domínio inteligentes e serviços enxutos

Embora muitos associem esse termo a uma variedade de referências sobre como fazer corretamente e à complexidade da implementação, ‘domínios ricos’ fundamentam-se nos princípios básicos da Programação Orientada a Objetos. Mas ele não se trata apenas disso, mas sim de um profundo entendimento das regras de negócio e como elas se refletem no código.

No geral, a construção de domínios ricos resulta em sistemas de software mais adaptáveis, alinhados com as necessidades do negócio, e mais fáceis de compreender e manter ao longo do tempo. Isso leva a um desenvolvimento mais eficiente e resiliente.

As camadas de serviços/aplicação irão apenas definir as tarefas que o software deve executar e direcionar os objetos de domínio. Como dito anteriormente, seu dever é orquestrar, coordenar.

Essa abordagem promove significativamente a reusabilidade e a flexibilidade. Os modelos de domínio bem definidos possibilitam seu reaproveitamento em várias partes do sistema, evitando a criação de dependências cíclicas entre serviços. Isso simplifica adaptações futuras e fortalece a estabilidade do software.

Como sair do modelo de domínio anêmico para o rico? — Let’s code…

Gif do Jim Carrey no filme “O todo poderoso” digitando no computador

No exemplo a seguir, demonstraremos como adicionar um pagamento em um contexto de pedidos. Inicialmente, exploraremos um modelo anêmico, seguindo uma estrutura simples e amplamente utilizada.

Estrutura comumente utilizada no desenvolvimento de aplicações

Exemplo anêmico

Os modelos anêmicos são caracterizados por classes sem comportamentos e mutáveis, sem validações ou quaisquer outras regras:

anemicsample.domain.model.Order.java
anemicsample.domain.model.Payment.java

As regras de negócio normalmente são implementadas em classes de serviços, com isso, o domínio fica fraco e totalmente dependente delas, pois apesar de conseguirmos adicionar um pagamento com um setPayment, será que seria correto fazer sem depender do seguinte serviço para validar as condições?:

anemicsample.services.OrderService.java

Agora veremos como transforma-lo em um domínio rico mantendo a mesma estrutura.

Exemplo rico

Os modelos ricos, possuem estrutura, validações e comportamentos, deixando-o “forte” e independente.

richsample.domain.model.Order.java
richsample.domain.model.Payment.java

No exemplo acima podemos notar como o modelo passa a ter validações e ser bem mais restritivo, não podendo simplismente adicionar um pagamento sem passar por regras essenciais de negócio.

Adicionamos também ‘exceptions de negócio’ e objetos auxiliares para enriquecer as validações das entidades, denominados de Value Objects, são elas a OrderId.java e PaymentId.java.

A ideia dos ‘Objetos de valores’ é representar algo com significado intrínseco, onde a identidade não importa ( assim como um UUID, String, Boolean, etc). Veja um exemplo de como seria em uma representação do OrderId.java desse exemplo:

richsample.domain.model.vo.OrderId.java

E agora que transformamos aquele domínio anêmico em rico a função das classes de serviço são apenas para coordenar o fluxo:

richsample.services.OrderService.java

Pronto, nosso domínio possui comportamentos e demonstram de fato as regras de negócios, as classes de domínio ficaram mais isoladas e independentes alem de diversos outros ganhos já citados nos tópicos anteriores para garantir uma melhor manutenibilidade de código.

Veja como coordenar o fluxo das entidades usando Use Cases nesse artigo:
O Use Case: Modelando as Interações do Seu Domínio

Conclusão

Nesse artigo vimos que os ‘Domínios Anêmicos’ são caracterizados por objetos sem comportamento, o que vai contra os princípios fundamentais da Orientação a Objetos. Isso resulta em código difícil de manter e evoluir, além de quebrar o encapsulamento e dispersar a lógica pelo sistema.

Por outro lado, os ‘Domínios Ricos’ priorizam a criação de objetos de domínio inteligentes, incorporando a lógica de negócios e deixando as camadas de serviço/aplicação como coordenadoras. Isso gera sistemas mais adaptáveis, alinhados com as demandas do negócio, promovendo reutilização e flexibilidade.

O artigo ressalta a importância de centralizar a lógica de negócios nos modelos de domínio, evitando os modelos anêmicos e favorecendo a construção de domínios ricos. Isso garante sistemas de software mais robustos, compreensíveis e adaptáveis.

“Architecture is important, but architectural influences come and go. Remember to prioritize correctly, placing more emphasis on the domain model, which has greater business value and will be more enduring.”
Vaughn Vernon, Implementing Domain-Driven Design

Referências

--

--