DDD + NHibernate

74 views
Skip to first unread message

Cassio Pinheiro Almeron

unread,
Jan 7, 2013, 8:44:03 AM1/7/13
to de...@googlegroups.com
Bom dia.

Alguem da comunidade utiliza DDD com NHibernate, ou outro ORM?

Eu uso e vou compartilhar minha experiência.

Tenho a camada de Domínio, onde estão as entidades com as regras de negócio (modelo rico).
Estas estidades são totalmente puristas em relação a tecnologia, usando o que há de mais básico no .NET.
As classes de domínio nunca entram em "Estado inválido", assim, todas as informações necessárias entram pelo construtor, e qualquer valor inválido é barrado com uma exception.

Para que um modelo possa ser usado pelo NHibernate, é necessário que haja um construtor padrão, e todas as propriedades com virtual.

Porém, qualquer alteração no Domínio com objetivo de se adaptar a uma tecnologia é quebra de conceito, e ainda pode ser brecha para erros de lógica de negócio.

O que tenho feito é ter uma modelo anêmico na camada de repositório, representando as tabelas do banco.
E estes sim possuem virtual, e são mapeados pelo NHibernate.

Na implementação dos contratos de repositório, para persistir, é convertido uma entidade do dominio para um modelo anêmico com AutoMapper, e o processo inverso para Obter do repositório.

Conceitualmente ficou bastante desacoplado e flexível, porém é um esforço maior.

Alguem implementou de uma forma diferente?

Grato
Cássio Almeron

Bernardo Bosak de Rezende

unread,
Jan 7, 2013, 6:14:49 PM1/7/13
to de...@googlegroups.com
Oi Cássio,

Achei pertinente o tópico. Meus dois centavos sobre isso:

Achei muito purista e meio YAGNI a estrutura de ter, além do domínio, um "modelo anêmico" apenas para representar as tabelas do banco. Confesso que já trabalhei assim, mas façamos uma reflexão: 

Na prática, qual é o real benefício disto?

1) Você ganhou classes a mais para manter, ou seja, getters / setters para todos os lados (mesmo tendo o excelente AutoMapper para ajudar).
2) Se você escreve/escreverá testes para os repositórios terá que se preocupar em, no mínimo, mockar estas novas classes.
3) Potencial explosão de código repetido.
4) Você tem motivos a mais para os desenvolvedores da equipe ficarem divagando entre classes, sem realmente saberem o que estão fazendo (algo como "qual a diferença entre OrderBillingAddressType e OrderBillingAddressTypeDTO ?") 
5) Motivos a mais para a lógica de negócio aparecer nestas classes anêmicas e não no domínio. Dificulta a manutenção e a facilidade com que percorremos o código. É shift+f12, ctrl+shift+f e f12 para todos os lados.

Com DDD ou sem DDD, precisamos considerar que é extremamente difícil conseguir Persistence Ignorance sem alterar a forma como escrevemos nossos POCOs. Isso vale não só para a forma como persistimos os objetos, mas até na forma como os validamos (se você utilizar DataAnnotations estaria igualmente quebrando este princípio).

É de se louvar que utilizando o NHibernate precisemos APENAS decorar os membros públicos com virtual e ter um default constructor com modificador de acesso protected. Dependendo da convenção de mapeamento teus objetos ainda terão de ter uma propriedade Id. Isso não impede em nada que escrevamos testes para os nossos objetos e repositórios sem nem sequer olhar para o banco.

Nos termos acima, teríamos UMA dll com a regra de negócio inteira, o "coração do software" (como diz a capa do livro do Evans), aquilo que deve permanecer por 5, 10 anos, independente das ViewModels do MVC, da persistência no MongoDB, dos UserControls do Silverlight ou de qualquer camada de aplicação/infraestrutura.

Para este tipo de decisão me inspiro no Rails/ActiveRecord, onde por exemplo nos focamos na construção de uma classe com propriedades e comportamento, a base da OO. No fundo sabemos que aquilo vai parar em um banco, pois tivemos que colocar ao lado do nome da classe um "< ActiveRecord::Base".

Para concluir, precisamos focar no que importa (negócio) e aceitar tecnologias que diminuam nossa dor de cabeça na hora de jogar as coisas para um banco, mesmo que isto custe um public virtual ou um protected NomeDaClasse().

[]s
Bernardo

2013/1/7 Cassio Pinheiro Almeron <cassio...@gmail.com>

--
--
Acesse nosso blog http://devrs.net/
Siga-nos no twitter! http://twitter.com/DevRsNet
--
Antes de criar um novo post, para maior organização do grupo, procure seguir as regras de TAGS: http://devrs.net/regras-da-lista
Para postar para o grupo, envie email para de...@googlegroups.com
Para sair do grupo, envie email para devrs+un...@googlegroups.com



--
Bernardo Bosak de Rezende
http://bernardorezende.net/ - Arquitetura e Desenvolvimento
https://github.com/bernardobrezende/ - GitHub

"The weather-cock on the church spire, though made of iron, would soon be broken by the storm-wind if it did not understand the noble art of turning to every wind."
Heinrich Heine

Cassio Pinheiro Almeron

unread,
Jan 8, 2013, 7:10:38 AM1/8/13
to de...@googlegroups.com
Bernardo.

Justamente, dessa forma está dando mais trabalho em implementar os adaptadores, mesmo com AutoMapper, do que a própria regra de negócio, fora os testes unitários necessários.
Dessa forma, um simples cadastro tem dado bastante trabalho em implementa-lo de ponta a ponta.

O que pode se concluir é que pode se flexibilizar um pouco o modelo, em relação a teoria, desde que não amarre a nenhuma tecnologia.

Em relação ao NHibernate, além do construtor protected, e dos virtuals e listas com IList, você sabe se existe alguma outra restrição para o modelo?

Grato
Cássio Almeron

Bernardo Bosak de Rezende

unread,
Jan 8, 2013, 7:21:45 AM1/8/13
to de...@googlegroups.com
Cassio,

Quando uso o NHibernate com mapeamento automático (sem Fluent nem XML) o que costumo fazer é:

1) Ter uma classe abstrata (ex: Entidade) com uma propriedade virtual Id (dependendo da versão do NH o set da propriedade pode ser protected, na versão atual é preciso ser public :().
2) Todas minhas entidades "mapeáveis" herdam de Entidade.
3) Membros públicos são virtual.
4) As entidades "mapeáveis" possuem um construtor protected.

As limitações 3 e 4 se devem à necessidade de termos Lazy Loading, o que é fundamental hoje em dia em qualquer ORM.


2013/1/8 Cassio Pinheiro Almeron <cassio...@gmail.com>

Cassio Pinheiro Almeron

unread,
Jan 8, 2013, 7:34:53 AM1/8/13
to de...@googlegroups.com
Olá Bernardo,

Venho pensando em uma situação, como por exemplo em uma Nota Fiscal.

A entidade NotaFiscal possui uma lista de Items.
na classe NotaFiscal, a lista é privada, para adicionar items na nota, existe um método AdicionarItem(Produto).
Este método além de adicionar o item, atualiza a propriedade ValorTotal, que é private set.

Se eu deixar a lista publica, o método Add da lista vai ficar exposto, e assim, permitindo que o ValorTotal fique inconsistente.

Este tipo de situação que não sei se o ORM se comporta bem.

Obrigado pela atenção
Cássio Almeron

Bernardo Bosak de Rezende

unread,
Jan 8, 2013, 7:40:24 AM1/8/13
to de...@googlegroups.com
Vamos lá:

https://gist.github.com/4483415

Funciona perfeitamente aqui.

Bernardo Bosak de Rezende

unread,
Jan 8, 2013, 7:50:37 AM1/8/13
to de...@googlegroups.com
Havia esquecido de atualizar o ValorTotal.

https://gist.github.com/4483451



2013/1/8 Bernardo Bosak de Rezende <bernard...@gmail.com>

Cassio Pinheiro Almeron

unread,
Jan 8, 2013, 8:11:33 AM1/8/13
to de...@googlegroups.com
Quero contribuir para esse código também.

Bernardo Bosak de Rezende

unread,
Jan 8, 2013, 8:32:09 AM1/8/13
to de...@googlegroups.com
Faz um fork lá ou clona o atual.

Leonardo Lima

unread,
Jan 8, 2013, 6:44:51 PM1/8/13
to de...@googlegroups.com
Ola Cassio,


    estou de acordo com o Bernardo quanto a inclusão de complexidade no projeto usando automapper etc, etc.
    mas tenho serias restrições ao modelo proposto no exemplo do git.

Vamos "conceitualizar".  modelo purista, então não temos "int ID" nas nossas entidades? O nome/descrição/referencia é chave porque é representativo ao dominio?
Imagino que não, me corrija se eu estiver errado, mas o ID inteiro numa classe serve apenas para o banco de dados e para nossas consultas, por questões de performance ou costume do uso do mesmo no banco.
Então, vejo que da mesma forma que o Modelo anêmico equivalente ao banco + DDD são complicadores, ficar dando voltas com propriedades, métodos e campos apenas para tratar visibilidade das mesmas é fazer o mesmo dentro de uma classe.

Então, nem oito nem oitenta.
Uso EF por opção, o NH em muitas características é superior ao EF, mas o EF tem as mesmas restrições.
O que possibilita a quebra, assim como quando usamos IoC e precisamos em alguns assemblys manter coisas visiveis somente para dentro dele, impossibilitando os testes.

Uso um atributo no arquivo assemblyinfo chamado "InternalsVisibleToAttribute" 
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute.aspx

Exemplo:
[assembly: InternalsVisibleToAttribute("Assembly.que.enxerga.internals.deste")]

Nas minhas entidades uso o construtor padrão como protected internal.

Isso resolve o problema, porque pensando que tu tens uma camada de domínio, e uma de infra-estrutura, a de infra estrutura deve ganhar o privilegio de enxergar os internals do meu dominio para resolver as exigencias do ORM.
As demais camadas da aplicação não devem enxergar. E assim, alem de usar o ORM de forma adequada mapeando o que é necessário, eliminando o modelo anêmico, e mantendo a entidade limpa tu impede a quebra das regras de negocio na criação e manipulação das tuas entidades.

Quanto a preocupação de manter o domínio isento de tecnologia e etc, etc não teremos esta preocupação usando este atributo.



Espero ter ajudado.


Bernardo Bosak de Rezende

unread,
Jan 8, 2013, 7:03:14 PM1/8/13
to de...@googlegroups.com
2013/1/8 Leonardo Lima <leoli...@gmail.com>


Então, nem oito nem oitenta.
Uso EF por opção, o NH em muitas características é superior ao EF, mas o EF tem as mesmas restrições.
O que possibilita a quebra, assim como quando usamos IoC e precisamos em alguns assemblys manter coisas visiveis somente para dentro dele, impossibilitando os testes.


Leonardo,

Você poderia exemplificar pq vc acha que a classe do gist impossibilita os testes?

OBS: o atributo ID, citado por vc, é mais uma das "restrições" impostas pelo mapeamento com chave identificadora. Esta convenção pode ser facilmente alterada sem precisar os POCOs, basta sobre-escrever a convenção do NHibernate. Todo ORM tem um pitfall.

Leonardo Lima

unread,
Jan 9, 2013, 6:13:09 AM1/9/13
to de...@googlegroups.com
Então Bernardo, 

não disse que a classe do gist impossibilita testes, mas sim que por vezes em alguns assemblys temos que manter a visibilidade interna, e que isso impossibilita testes.
E não, o ID não tem relação nenhuma com ORM e etc, o que eu disse é que se você for purista o teu "identificador" de entidade será algo representativo ao dominio, por exemplo uma pessoa não vai ter um ID do tipo inteiro como identificador porque isso não diz nada para o domínio, mas sim um email, cfp ou outro campo único. Mas por causa do banco de dados utilizamos o ID como inteiro por questões de performance ou convenção.
Um exemplo pratico é simples, porque teriamos um campo ID do tipo inteiro se persisticemos em um arquivo de texto?????
E justamente disse também que não precisamos ser puristas a este nível, não tem problema de usar o ID inteiro como identificador, foi apenas um exemplo para mostrar que já estamos atrelados a tecnologia do banco de dados e que usar public virtual para satisfazer o ORM não seria um problema.


--

Cristiano Nascimento

unread,
Jan 9, 2013, 12:32:14 PM1/9/13
to de...@googlegroups.com
Pessoal, aproveitando o gancho do NHibernate, alguém sabe fazer um CAST em uma HQL?
--
-------------------------------------
 Cristiano Nascimento
 Esteio - RS
 crist...@gmail.com
 http://about.me/cristiano.nascimento
-------------------------------------

Cassio Pinheiro Almeron

unread,
Jan 12, 2013, 8:19:54 AM1/12/13
to de...@googlegroups.com
Bernardo

Apliquei em um dos agregados do Domínio do meu sistema, alterando as entidades para a forma como você me explicou e colocou naquele exemplo.
E posso afirmar que mudei e opinião e você está totalmente certo.

Com uma pequena quebra de conceito, não há impacto ou perda nenhuma nos modelos do domínio, estou conseguindo obter os mesmos resultados com muito menos trabalho.
E essa idéia de expor a lista como IEnumerable, para não expor o Add é uma legítima jogada de mestre.

É uma solução simples e objetiva. até mesmo óbiva, mas existe um ditato "O obvio tem que ser dito", e até agora estou me perguntado "como não pensei nisso antes".

Muito Obrigado.

Semana que vem quero criar um repositório no GitHub, com um exemplo específico para esta solução, pois acredito que outras pessoas possam estar passando por esse problema.

Cássio Almeron

Bruno Teixeira

unread,
Jan 14, 2013, 6:45:35 AM1/14/13
to de...@googlegroups.com
Boa Cássio!
Reply all
Reply to author
Forward
0 new messages