Os conceitos de Entity (Entidade) e Value Object (Objeto de Valor) são cada vez mais empregados para descrever e também dar significado a um conjunto de classes dentro de um projeto de software.
As nomenclaturas ganharam notoriedade com o advento do DDD (Domain Driven Design) e são comumente associados aos conceitos de identidade e atributo. Entretanto, o uso equivocado dos termos acaba gerando confusão a respeito do que exatamente é cada um dos dois conceitos, e por consequência conflitos são gerados juntamente com complexidade desnecessária.
Vamos então abordar os aspectos de cada um dos dois conceitos, e como eles se relacionam, com a intenção de entender e identificar todas as suas características.
O que é uma entidade?
Uma entidade remete ao conceito de indivíduo, ou seja, existe a preocupação de se considerar de forma isolada diante de um conjunto de objetos um objeto em específico, separando-o da coletividade. Por exemplo, geralmente enquanto pessoas vivendo em sociedade recebemos um nome dos nossos pais, mas várias pessoas podem possuir nomes parecidos ou até mesmo idênticos, dessa forma então foi necessário a criação de um mecanismo que seja único para representar cada pessoa, daí vem CPF, RG, Passaporte, entre diversos outros mecanismos.
Segundo Vernon (2014) “projetamos o conceito de domínio como uma Entidade quando nos preocupamos com sua individualidade, e diferenciá-la de todos os outros em um sistema é uma restrição obrigatória”, ou seja, assim como governos criam mecanismos para nos individualizar, precisamos fazer o mesmo.
Evans (2003) aborda algumas questões importantes que devem ser levadas em consideração no momento de determinar a modelagem entidades e objetos de valor, que são:
O objeto representa alguma idéia continua com identidade dentro do software?
Este objeto poderá possuir diversos estados através de diferentes implementações?; ou
Trata-se de um atributo que descreve o estado?
Estes questionamentos levantados por Evans ajudam na discussão da equipe sobre os objetos que estamos lidando, destaco principalmente o terceiro ponto — Trata-se de um atributo que descreve o estado? — E por que considero o mais importante? Pois basicamente define que alguns objetos não são definidos pelos seus atributos, mas sim pela identidade ao longo do tempo, o que nos faz voltar aos conceitos abordados logo no início do texto, sobre identidade.
Quais técnicas podemos usar para gerar estas identidades?
Técnicas simples para geração de identidade
Devemos então construir nossas entidades com o mínimo possível de atributos, e focar no aspecto de identidade da mesma. Outro detalhe importante é que acompanhar a evolução do dado é uma preocupação latente, ou seja, se não existe hoje provavelmente pode ser considerado no futuro.
Existem várias técnicas que podemos abordar para criação de uma identidade única, pode-se utilizar um identificador único universal (UUID), GUID, determinar identificadores baseados em contextos como Pessoa-GUID, números sequenciais únicos e etc. Além do atributo de identidade, devemos nos preocupar sempre em reduzir ao máximo a quantidade de propriedades contidos dentro de uma entidade, com a intenção de simplificar as diferentes validações a serem aplicadas para garantir a integridade do estado do objeto.
Das técnicas citadas anteriormente, o uso de números sequenciais é a mais complicada e ineficiente de todas. Apesar de ser convencionalmente utilizada há muitos anos uma vez que o mecanismo em geral já é entregue pelos mecanismos de banco de dados, o valor sequencial provavelmente vai existir em diversas outras entidades no sistema, o que acaba deixando frágil qualquer método de comparação de objetos. Duas entidades são diferentes mesmo que todas as suas propriedades sejam iguais, dado o valor da identidade do objeto, caso a mesma seja diferente então temos duas entidades distintas.
Se uma entidade deve possuir apenas uma propriedade para identificação (ou uma chave complexa como falamos anteriormente), e o mínimo possível de propriedades que são utilizadas para recuperação da entidade, o que fazer com todas as demais propriedades da entidade?
O que é um Objeto de Valor?
Segundo Evans, quando você se importa apenas com os atributos de um modelo, classifique-o como um Objeto de Valor, ou seja, a identidade não é importante e sim o valor da propriedade.
Por exemplo, quando avaliamos a propriedade CPF não estamos preocupados com a identidade do CPF, ou a rastreabilidade do CPF, o que importa para nós é o valor da propriedade CPF por causa de alguma regra de negócio. Objetos de Valor trazem consigo um leque gigante de possibilidades de modelagem para o software. Eles podem ser mutáveis ou imutáveis, dependendo de algumas condições.
Por exemplo, quando lidamos com Objetos de Valor que são imutáveis, o objetivo é reduzir o espaço necessário para o armazenamento das informações compartilhando o mesmo objeto entre diferentes entidades que possuem as mesmas propriedades. Dessa forma, as propriedades nunca devem ser alteradas. Entretanto quando o espaço de armazenamento não é necessariamente um problema, e são previstos diversas alterações, o Objeto de Valor pode ser mutável, mas nunca compartilhado.
Objetos de Valor então armazenam atributos que dão característica a uma entidade, e isso é diferente de dizer que um Objeto de Valor é algo simples, o nível de simplicidade é dado pelos aspectos e necessidades do seu projeto, não pela classificação da informação enquanto Entidade ou Objeto de Valor.
Para ajudar a classificar um conceito de Domínio como objeto de valor, Vernon determina algumas condições:
- Ele mede, quantifica ou descreve uma coisa no domínio.
- Ele pode ser mantido imutável.
- Ele modela um todo conceitual compondo atributos relacionados como uma unidade integral
- Ele é completamente substituível quando a medição ou descrição muda
- Ele pode ser comparado com outros usando a igualdade de valor
- Ele fornece para os colaboradores comportamento livre de efeitos colaterais.
Outro aspecto que está intimamente relacionado a imutabilidade de Objetos de Valor é a construção de métodos que são livres de efeitos colaterais, ou seja, sua execução não causa alteração no estado do objeto, caso o método mude o estado, a imutabilidade será quebrada e não haverá mais controle sobre as consequências da sua execução. Mas como garantir que o estado não será corrompido, e qual o papel da imutabilidade neste processo?
Níveis de acesso e imutabilidade do objeto
Tornar um objeto de valor imutável requer também uma restrição do nível de acesso às propriedades, ou seja, não devemos permitir que uma propriedade tenha nível de acesso público possibilitando setar o valor de uma propriedade, e o objeto deve possuir um construtor que obriga que os dados sejam informados logo que a instância de objeto seja criada. Dessa forma, a alteração de um Objeto de Valor significa que todo o objeto será substituído.
Como foi mencionado anteriormente, existe a possibilidade de encarar um Objeto de Valor como um objeto mutável. Evans deixou aberta esta possibilidade no lançamento do livro em 2003, mas esta propriedade já foi abolida nas interpretações mais recentes do DDD, o caso é que o espaço em disco atualmente está muito mais barato em razão do que era naquela época, e a capacidade de processamento também aumentou muito, dessa forma a possibilidade de existirem Objetos de Valor mutáveis foi dispensada.
Algo que confunde bastante quando o domínio está sendo avaliado é como devem ser persistidos Entidades e Objetos de Valor. Ambos fazem parte da mesma estrutura de armazenamento no banco, ou seja, o mesmo documento quando fala-se de bancos NoSql ou a mesma linha quando fala-se de bancos relacionais.
A complexidade aumenta um pouco nos bancos relacionais quando existe algum empecilho no armazenamento desnormalizado, ou seja, existe algum departamento ou regra que obriga a manter entidades e objetos de valor relacionando-se por meio de mapeamentos de chave estrangeira, dependendo da ferramenta utilizada para mapear o banco para a aplicação, a complexidade de se manter a modelagem de domínio sem influências do modelo de armazenamento pode ser incrementada em alguns níveis, entretanto, não deve se mudar a forma como entidades e objetos de valor são modelados meramente por causa de problemas relacionados a ferramentas externas ao domínio.
Agora que já falamos bastante sobre entidades e objetos de valor, que tal aplicarmos tudo isso?
O contra-exemplo de uma Entidade
Agora que os conceitos de Entidade e Objetos de valor foram explicados em detalhes, é importante o exercício de utilizar os conceitos apresentados aplicando-os para reforçar o aprendizado. Vamos então refatorar uma entidade que facilmente encontramos em diversos projetos, como por exemplo a entidade Pessoa e transformá-la em um modelo mais rico.
A classe pessoa apresentada na listagem 1 exibe uma modelagem bastante comum para o conceito de Pessoa dentro de um sistema. Observe que todas as propriedades são públicas não apenas para os métodos de GET, mas também para os métodos SET, fazendo com que a qualquer momento seja possível alterar o valor da propriedade. Todos os tipos utilizados na classe são primitivos da própria linguagem, que no caso do exemplo é C#, mas poderia muito bem representar uma classe em Java, a tradução do código entre as duas linguagens é bem simples.
Não existem construtores para inicialização, o que significa que a instância é criada sem que suas propriedades sejam preenchidas. No momento em que a classe Pessoa é instanciada no código, ela não representa nada dentro do domínio, e agora começa o desafio. Primeiro devemos recapitular os conceitos de entidade apresentados no início do artigo, o dado que queremos modelar será mantido ao longo do tempo? Neste caso sim, uma pessoa deve ser armazenada no banco pois a aplicação deverá apresentar uma listagem de pessoas ao usuário, que poderá então editar os dados de um registro, ou inserir novos registros. Existe a necessidade de se identificar uma pessoa dentro de diversas outras? Sim, queremos poder associar esta pessoa com outras entidades, e precisamos poder atribuir a uma pessoa algumas responsabilidades.
Em algum momento do passado os atributos que qualificam uma Pessoa foram discutidos entre a equipe de desenvolvimento e o analista de requisitos/product owner da funcionalidade, então sabemos que as informações listadas são necessárias para se modelar a entidade Pessoa, mas a forma como eles estão estruturados faz sentido? Outro questionamento que devemos fazer é com relação ao tipo das propriedades da classe, todas as propriedades são primitivas, o que significa que nenhuma delas traz consigo nenhum significado, nenhuma cobertura de estado e nenhuma funcionalidade.
Por exemplo, diga-se que um dos pré requisitos para se cadastrar uma pessoa seja a validade do CPF informado, como fazer essa validação? Da forma como a entidade está modelada, ela não garante que o valor atribuído a propriedade CPF está válida, podemos simplesmente fazer isso:
Claramente o cpf informado não é válido, possui a quantidade errada de caracteres, inclusive algumas letras são encontradas entre os números, o dígito verificador não faz sentido e enfim, não é um estado válido para a propriedade cpf, mas nada impede que a propriedade seja alterada para o valor informado. É muito comum encontrar classes de validação que possuem diversos métodos estáticos que são utilizados ao longo do sistema, e geralmente o nome dessas classes começa ou termina com Util, por exemplo UtilValidator, Utilities ou apenas Util. Continuando o exemplo de como “não fazer” o desenvolvedor decide criar uma classe de utilidade e colocar um método lá dentro que faça a validação do cpf, e retorna true/false em razão da validade ou não do valor informado, e dependendo do resultado acontece a atribuição.
Agora garante-se que o cpf atribuído a pessoa sempre será válido! E de que quebra ainda construiu um método que pode ser utilizado em todo o resto do sistema sempre que necessário validar um cpf, basta chamar a classe Util e o método ValidaCpf e tudo está resolvido! Mas será mesmo?
O que acabou de ser criado aqui foi um pequeno monstro, a entidade não tem controle sobre os valores que são atribuídos às propriedades, a alteração do estado pode acontecer de forma a violar quaisquer regras estabelecidas no sistema. O que aconteceria se por exemplo outro desenvolvedor no futuro ao precisar criar uma entidade Pessoa esquecer de validar o cpf com a classe Util? Mesmo que o cpf agora seja validado como apresentado na listagem 3, ele será persistido com os caracteres “.” e “,”? Quem garante que sim ou que não? Como escrever um teste que garanta que uma entidade de Pessoa mantém seu estado válido?
O design apresentado até agora só garante problemas, uma quantidade desnecessária de código de validação e estruturas de decisão encadeadas para ficar validando cada propriedade (um grandioso hadouken de if’s e else’s). O próximo passo então é refatorar esta entidade para que ela deixe de ser um problema, e passe a ser parte da solução, e este será o tema do nosso próximo artigo.
Créditos
Learn: Photo by Pixabay on Pexels
Pencil: Photo by Lex Photography from Pexels
Question: Photo by Pixabay on Pexels
Restricted: Photo by Nguyen Nguyen from Pexels
NotEnter: Photo by Jaymantri on Pexels
Sobre o Autor
0 Comentários