DDD - Resignificando conceitos: Entidades
Você já ouviu falar do experimento dos macacos e da escada? Nele, um grupo de macacos é colocado em uma sala com uma escada que leva a uma penca de banana. Toda vez que um macaco tenta subir a escada, todos os macacos são molhados com água fria. Com o tempo, cada macaco que subia a escada era punido com pancadas dos outros macacos para evitar o banho gelado. O interessante é que quando os pesquisadores iam substituindo os macacos originais por novos que não conheciam a punição, estes ainda acabavam impedindo uns aos outros de subir a escada, sem saber o motivo. Este experimento destaca como comportamentos são perpetuados em grupos, mesmo sem entendermos sua origem. Já se sentiu nesse paradigma?
Você já parou para pensar por que, ao lidar com um projeto simples onde, por exemplo, criamos uma entidade que é uma classe para representar uma tabela do banco de dados, preenchendo-a com os mesmos atributos e adicionamos getters e setters? Aposto que você já passou por isso, assim como eu. É um daqueles padrões que parecem simplesmente enraizados em nós, algo que fazemos sem realmente questionar o por quê.
Para entendermos melhor o conceito de Domain Driven Design (DDD), precisamos resignificar tais conceitos enraizados em nós, como o de entidades citados anteriormente e mais alguns outros, que serão abordados futuramente. Antes de nos aprofundarmos nessa metodologia, é importante compreendermos esses elementos básicos que mencionei. Afinal, só assim conseguiremos aplicar os padrões do DDD de forma mais consciente e eficaz.
Este é o primeiro de 6 posts sobre o assunto Padrões e Modelagem Tática do DDD, onde iremos abordar conceitos de Entidades, Objetos de Valor e Agregados, Domain Services, Repositories, Domain Events, Módulos e Factories e espero que você embarque junto nessa jornada de conhecimento!
Entidades
Vamos esquecer tudo que ouvimos sobre entidades no passado e vamos entender o que é uma entidade de verdade.
Segundo Eric Evans em "Domain-Driven Design: Atacando as Complexidades no Coração do Software" (o famoso livro azul), uma entidade é:
Um objeto com uma identidade única e distinta, que pode ser rastreada ao longo do tempo, independentemente de mudanças em seus atributos.
Por exemplo, no contexto de um sistema de gerenciamento de clientes, apesar de existir a possibilidade de clientes possuirem nomes iguais, contatos iguais, endereços iguais, eles não são os mesmos. Cada cliente individual seria considerado uma entidade única, permitindo o acompanhamento de suas interações e alterações ao longo do tempo. Isso significa que, mesmo que um cliente mude de endereço ou número de telefone, sua identidade como cliente permanece constante. É isso que significa possuir uma identidade! Em outras palavras, uma entidade é algo que possui uma continuidade em seu ciclo de vida e pode ser distinguida independentemente dos atributos que são importantes para a aplicação do usuário.
Entidades Anêmicas
Entidades desempenham um papel crucial na modelagem de domínios de problemas, garantindo uma representação precisa e flexível dos objetos no software.
Para exemplificar, vamos criar neste primeiro momento um sistema de gerenciamento de clientes. Vamos utilizar a linguagem Python (por que não?), mas poderia ser qualquer outra que seja orientada a objetos:
# src/domain/customer/entity/customer.py
class Customer:
def __init__(self, id: str, name: str, address: str):
self._id = id
self._name = name
self._address = address
@property
def id(self) -> str:
return self._id
@id.setter
def id(self, value: str):
self._id = value
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, value: str):
self._name = value
@property
def address(self) -> str:
return self._address
@address.setter
def address(self, value: str):
self._address = value
Aqui podemos identificar a classe Customer
acima como uma entidade, pois possuem certas características que conhecemos que representam uma entidade:
- Temos propriedades
id
,name
eaddress
, todos como strings. - Os métodos getter e setter são definidos para cada propriedade com os decorators, permitindo o acesso e a modificação dos valores de forma controlada.
- No construtor
__init__
, os valores são inicializados.
Ao observarmos mais de perto, identificamos que estamos lidando com uma entidade anêmica, semelhante a DTOs, que consiste apenas em dados e carece de comportamento específico ou lógicas de negócio. Seu principal propósito é a transferência de dados entre diferentes partes do sistema. No entanto, é crucial lembrar que uma verdadeira entidade possui um id único, valores que mudam com o tempo e, o mais importante, comportamento e regras de negócios, elementos essenciais que caracterizam o DDD. São essas regras de negócios que capacitam as entidades a manipular o cerne da aplicação.
Portanto, é essencial reconceituar a entidade não apenas como uma estrutura de dados conectada a um banco de dados, mas como um elemento que incorpora as regras e lógicas vitais do sistema. É aqui que ocorrem as modificações no estado do domínio conforme as regras de negócios são aplicadas.
Mas, o que exatamente são essas regras de negócios? Em suma, são métodos de alterar o comportamento da entidade, incluindo validações, fórmulas ou qualquer outra lógica que atenda às necessidades específicas do sistema. Vamos então acrescentar algumas regras de negócio para dar um pouco de sustância à nossa entidade Customer.
Regras de Negócio e Consistência
Vamos instanciar um objeto da classe Customer
acima da seguinte forma:
customer = Customer("001","","")
Criamos um cliente e deixamos para completar o nome e o endereço dele depois utilizando o setter:
customer.name("Fulano")
customer.address("Rua das Acácias, 22")
O que é totalmente possível, já que o inicializador da classe permite que essas propriedades sejam do tipo strings. Mas falando em modelagem de domínios ricos, isso simplesmente não existe (ou não deveria). Porque sempre que for preciso consultar essa entidade, devo garantir que esses dados estarão consistentes e que seu estado atual é confiável em 100% das vezes. Um cliente sem nome é uma entidade inconsistente.
Neste caso, para garantir que a entidade seja sempre consistente e confiável, devemos ter uma regra de negócio que valide qualquer obrigatoriedade (de acordo com a necessidade do negócio) no ato da construção ou modificação de um objeto Customer, ou seja, uma entidade por padrão, deve sempre se auto-validar.
Auto-validação
Um objeto deve ser auto-validado no ato que ele for construído para atender as regras de negócio e garantir a consistência, então vamos criar uma validação simples para nossa classe Customer
e também criar um método para mudança de nome que diferencie de um setter e aplique essa validação. Também seria interessante termos uma regra onde só poderíamos ativar o cliente se o endereço estiver preenchido:
# Method to validate the attributes
def validate(self):
if len(self._id) == 0:
raise ValueError("Id is required")
if len(self._name) == 0:
raise ValueError("Name is required")
# Method to change the customer's name
def change_name(self, name: str):
self._name = name
self.validate()
# Method to activate the customer
def activate(self):
if self._address is None:
raise ValueError("Address is mandatory to activate a customer")
self._active = True
# Method to deactivate the customer
def deactivate(self):
self._active = False
Assim, nossa entidade vai ficando mais consistente e menos anêmica:
class Customer:
# Constructor of the class
def __init__(self, id: str, name: str):
# Initialization of class attributes
self._id = id
self._name = name
self._address = None
self._active = False
# Validation of attributes
self.validate()
# Properties of the class
@property
def id(self) -> str:
return self._id
@property
def name(self) -> str:
return self._name
# Method to validate the attributes
def validate(self):
if len(self._id) == 0:
raise ValueError("Id is required")
if len(self._name) == 0:
raise ValueError("Name is required")
# Method to change the customer's name
def change_name(self, name: str):
self._name = name
self.validate()
# Method to check if the customer is active
def is_active(self) -> bool:
return self._active
# Method to activate the customer
def activate(self):
if self._address is None:
raise ValueError("Address is mandatory to activate a customer")
self._active = True
# Method to deactivate the customer
def deactivate(self):
self._active = False
Então…
Agora podemos concluir que a modelagem de um domínio vai muito além de configurar getters e setters. Agora sim, começamos a modelar um domínio rico e expressivo, que atenda os comportamentos e necessidades do negócio.
No próximo post, vamos dar um passo a mais na modelagem tática do DDD, falando dos Objetos de Valor. Não deixe de conferir!
... Este artigo é resultado de estudos pessoais e foi profundamente inspirado pelo conteúdo apresentado no curso Full Cycle 3.0. Agradeço à equipe da Full Cycle pela excelente qualidade do material educacional fornecido. Todos os créditos e reconhecimentos vão para a FullCycle pela sua contribuição significativa para o meu aprendizado.
Fonte: https://fullcycle.com.br/