Com todos esses serviços, como posso não ser anêmico?

90

Onde traçamos a linha entre delegação e encapsulamento da lógica de negócios? Parece-me que quanto mais delegamos, mais anêmicos nos tornamos. No entanto, a delegação também promove a reutilização e o diretor DRY. Então, o que é apropriado delegar e o que deve permanecer em nossos modelos de domínio?

Tome as seguintes preocupações como exemplos:

Autorização . O objeto de domínio deve ser responsável por manter suas regras de controle de acesso (como uma propriedade CanEdit) ou deve ser delegado a outro componente / serviço exclusivamente responsável pelo gerenciamento do acesso, por exemplo, IAuthorizationService.CanEdit (objeto)? Ou deveria ser uma combinação dos dois? Talvez o objeto de domínio tenha uma propriedade CanEdit que delegue para um IAuthorizationService interno para executar o trabalho real?

Validação . A mesma discussão acima se refere à validação. Quem mantém as regras e quem é responsável por avaliá-las? Por um lado, o estado do objeto deve pertencer a esse objeto e a validade é um estado, mas não queremos reescrever o código usado para avaliar regras para cada objeto de domínio. Nós poderia usar a herança neste caso ...

Criação de Objetos . Classe de fábrica versus métodos de fábrica versus 'atualização' de uma instância. Se usarmos uma classe de fábrica separada, somos capazes de isolar e encapsular a lógica de criação, mas à custa de abrir o estado do nosso objeto para a fábrica. Isso pode ser gerenciado se nossa camada de domínio estiver em um assembly separado, expondo um construtor interno usado pela fábrica, mas isso se tornará um problema se houver vários padrões de criação. E, se tudo o que a fábrica está fazendo é chamar o construtor certo, qual é o sentido de ter a fábrica?

Os métodos de fábrica na classe eliminam o problema com a abertura do estado interno do objeto, mas, como são estáticos, não podemos quebrar dependências através da injeção de uma interface de fábrica, como podemos com uma classe de fábrica separada.

Persistência . Alguém poderia argumentar que, se nosso objeto de domínio vai expor o CanEdit ao delegar a responsabilidade de executar a verificação de autorização para outra parte (IAuthorizationService), por que não ter um método Save no objeto de domínio que faça a mesma coisa? Isso nos permitiria avaliar o estado interno do objeto para determinar se a operação pode ser executada sem interromper o encapsulamento. É claro que exige que injetemos a instância do repositório em nosso objeto de domínio, o que me cheira um pouco, então aumentamos um evento de domínio e permitimos que um manipulador executasse a operação de persistência?

Veja para onde estou indo com isso?

Rockford Lhotka tem uma ótima discussão sobre suas razões para seguir a rota Class-in-Charge para sua estrutura CSLA, e eu tenho um pouco de história com essa estrutura e posso ver sua ideia de objetos de negócios paralelos a objetos de domínio de várias maneiras. Mas, tentando me tornar mais aderente aos bons ideais de DDD, estou me perguntando quando a colaboração se torna demais.

Se eu terminar com um IAuthorizationService, IValidator, IFactory e IRepository para minha raiz agregada, o que resta? Ter um método Publish que altera o estado do objeto de Rascunho para Publicado é suficiente para considerar a classe como um objeto de domínio não anêmico?

Seus pensamentos?

SonOfPirate
fonte
Ótima pergunta. Eu não tenho uma resposta para você, pois quase sempre acabo completamente anêmico no design pelo mesmo motivo - usando / consumindo / expondo serviços de / para muitos contextos ou aplicativos diferentes.
Hrmanko
Ótima pergunta, adoraria ver unclebob, martinfowler, ericevans et al a comentar isso. Agora, para que eu vá embora e tenha um longo pensamento #
Martijn Verburg
Eu me pego evoluindo para um modelo anêmico o tempo todo; e então estou lutando com exatamente a mesma coisa. Ótima pergunta!
L-Four
Essa também foi minha experiência com DDD. Estamos fazendo isso onde trabalho, e sempre acabamos anêmicos. Eu anteriormente (e ainda na verdade) uso o Csla. Nosso arquiteto não gosta de sermos anêmicos, mas não foi capaz de me dar uma boa resposta para o que o objeto deve fazer se todas as coisas que você indicar não puderem ser feitas dentro do objeto. No final das contas, tentar ser DDD purista parece criar mais trabalho do que vale a pena. Pessoalmente, acho que Csla e DDD se dão bem (eles parecem idênticos em princípios), se você estiver disposto a deixar o dogma do DDD para trás.
Andy
Aqui é um exemplo de algumas técnicas utilizadas na modelagem de domínio do ponto de vista comportamental (-não-dados centrados): medium.com/@wrong.about/...
Zapadlo

Respostas:

66

A maior parte da confusão parece estar relacionada à funcionalidade que não deveria existir no modelo de domínio:

  • A persistência nunca deve estar no modelo de domínio. Jamais. Essa é a razão pela qual você confia em tipos abstratos, como IRepositoryse parte do modelo precisar fazer algo como recuperar uma parte diferente do modelo e usar injeção de dependência ou alguma técnica semelhante para conectar a implementação. Então, considere isso a partir do registro.

  • A autorização geralmente não faz parte do seu modelo de domínio, a menos que faça parte do domínio, por exemplo, se você estiver escrevendo um software de segurança. A mecânica de quem tem permissão para executar o que em um aplicativo é normalmente tratada na "borda" da camada de negócios / domínio, as partes públicas com as quais a interface do usuário e as partes de integração realmente podem conversar - o Controller no MVC, os Serviços ou o próprio sistema de mensagens em uma SOA ... você entendeu.

  • Fábricas (e suponho que você queira dizer fábricas abstratas aqui) não são exatamente ruins para ter em um modelo de domínio, mas são quase sempre desnecessárias. Normalmente, você só tem uma fábrica quando a mecânica interna da criação de objetos pode mudar. Mas você só tem uma implementação do modelo de domínio, o que significa que sempre haverá um tipo de fábrica que sempre chama os mesmos construtores e outro código de inicialização.

    Você pode ter fábricas de "conveniência", se quiser - classes que encapsulam combinações comuns de parâmetros de construtores e assim por diante - mas, honestamente, de um modo geral, se você tiver muitas fábricas no seu modelo de domínio, estará apenas desperdiçando linhas de código.

Então, depois de definir tudo isso, isso deixa a validação. Esse é o único que é meio complicado.

A validação faz parte do seu modelo de domínio, mas também faz parte de todos os outros componentes do aplicativo. Sua interface do usuário e banco de dados terão suas próprias regras de validação semelhantes, porém diferentes, com base em um modelo conceitual semelhante, porém diferente. Não está realmente especificado se os objetos precisam ou não ter um Validatemétodo, mas mesmo se o fizerem, eles geralmente o delegarão para uma classe validadora (não interface - a validação não é abstrata no modelo de domínio, é fundamental).

Lembre-se de que o validador ainda faz parte tecnicamente do modelo; ele não precisa ser anexado a uma raiz agregada porque não contém nenhum dado ou estado. Modelos de domínio são coisas conceituais, geralmente traduzindo fisicamente para uma montagem ou uma coleção de montagens. Não se estresse com o problema "anêmico" se o código de delegação reside muito próximo ao modelo de objeto; ainda conta.

O que isso tudo vem realmente para baixo é que se você está indo fazer DDD, você tem que entender o que o domínio é . Se você ainda está falando sobre coisas como persistência e autorização, está no caminho errado. O domínio representa o estado de execução de um sistema - os objetos e atributos físicos e conceituais. Qualquer coisa que não seja diretamente relevante para os objetos e relacionamentos em si não pertence ao modelo de domínio, ponto final.

Como regra geral, ao considerar se algo pertence ou não ao modelo de domínio, faça a si mesmo a seguinte pergunta:

"Essa funcionalidade pode mudar por razões puramente técnicas?" Em outras palavras, não devido a qualquer alteração observável nos negócios ou no domínio do mundo real?

Se a resposta for "sim", ela não pertence ao modelo de domínio. Não faz parte do domínio.

Há uma chance muito boa de que, algum dia, você altere suas infraestruturas de persistência e autorização. Portanto, eles não fazem parte do domínio, fazem parte do aplicativo. Isso também se aplica a algoritmos, como classificação e pesquisa; você não deve inserir uma implementação de código de pesquisa binária em seu modelo de domínio, porque seu domínio se preocupa apenas com o conceito abstrato de uma pesquisa, não como ela funciona.

Se, depois de remover todas as coisas que não importam, você descobrir que o modelo de domínio é realmente anêmico , isso deve servir como uma boa indicação de que o DDD é simplesmente o paradigma errado para o seu projeto.

Alguns domínios são realmente anêmicos. Os aplicativos de bookmarking social realmente não têm muito "domínio" para falar; todos os seus objetos são basicamente apenas dados sem funcionalidade. Um sistema de vendas e CRM, por outro lado, tem um domínio bastante pesado; quando você carrega uma Rateentidade, existe uma expectativa razoável de que você possa realmente executar tarefas com essa taxa, como aplicá-las a uma quantidade do pedido e descobrir os descontos por volume e os códigos promocionais e todas essas coisas divertidas.

Objetos de domínio que só armazenam dados normalmente não significa que você tem um modelo de domínio anêmico, mas isso não necessariamente significa que você criou um design ruim - ele só poderia significar que o domínio em si é anêmico e que você deve estar usando um metodologia diferente.

Aaronaught
fonte
2
Além disso, @SonOfPirate, não é totalmente improvável que você queira alterar todo o seu modelo de segurança em algum momento; a segurança baseada em função geralmente é obsoleta em favor da segurança baseada em reivindicações ou direitos ou talvez você queira uma segurança em nível de campo. Imagine tentar refazer todo o seu modelo de domínio, se isso acontecer.
precisa saber é o seguinte
1
@SonOfPirate: Parece que você ainda está um pouco preso ao antigo modelo de "camada de negócios", em que havia uma "camada intermediária" que era basicamente uma camada fina sobre a camada de dados, implementando várias regras de negócios e também geralmente regras de segurança . Não é isso que é a camada de domínio. O domínio é o que tudo o resto depende, representa os objetos do mundo real e as relações que o sistema se destina a administrar.
precisa saber é o seguinte
1
@ LaurentBourgault-Roy: Desculpe, eu não acredito em você. Toda empresa poderia dizer isso sobre cada aplicativo; afinal, alterar um banco de dados é difícil. Isso não faz parte do seu domínio, e a lógica comercial associada à camada de persistência significa apenas uma abstração ruim. Um modelo de domínio é focado no comportamento, que é exatamente o que a persistência não é . Este não é um assunto sobre o qual as pessoas possam inventar suas próprias definições; é claramente escrito em DDD. Geralmente, você não precisa de um modelo de domínio para aplicativos CRUD ou de relatório, mas também não deve alegar que possui um quando não precisa.
Aaronaught 23/01
1
A autorização pertence absolutamente à camada de domínio. Quem decide quais permissões existem? O negócio faz. Quem decide quem pode fazer o que? O negócio faz. Acabamos de receber uma solicitação de recurso há algumas semanas para alterar a autorização necessária para editar um objeto específico em nosso sistema. Se um modelo era baseado em um modelo mestre, eram necessários privilégios mais altos para editar (substituir os valores do mestre) o modelo do que você normalmente precisaria. Onde essa lógica pertence se não estiver no domínio?
21717 Andy
1
Outro tipo de autorização pode ser o limite da conta do cliente. O pessoal normal de atendimento ao cliente pode elevar esse nível a um certo ponto, mas talvez limites mais altos precisem de aprovação da gerência. Essa é a lógica de autorização.
21417 Andy
6

Autorização. O objeto do domínio deve ser responsável por manter suas regras de controle de acesso

Não. A autorização é uma preocupação em si mesma. Os comandos que não seriam válidos devido à falta de permissões devem ser rejeitados antes do domínio, o mais cedo possível - o que significa que muitas vezes queremos verificar a autorização de um comando em potencial para criar a interface do usuário (para não até mostrar ao usuário a opção de edição).

Compartilhar estratégias de autorização entre camadas (na interface do usuário e posteriormente em um manipulador de serviço ou comando) é mais fácil quando a autorização é componente separada do modelo de domínio.

Uma parte complicada que pode ser encontrada é a autorização contextual, em que um comando pode ou não ser permitido não apenas com base nas funções do usuário, mas também nos dados / regras de negócios.

Validação. A mesma discussão acima se refere à validação.

Eu também diria que não, não no domínio (principalmente). A validação ocorre em diferentes contextos, e as regras de validação geralmente diferem entre os contextos. Raramente existe um senso simples e absoluto de válido ou não válido ao considerar os dados encapsulados por um agregado.

Além disso, como a autorização, utilizamos a lógica de validação em todas as camadas - na interface do usuário, no manipulador de serviço ou comando, etc. Mais uma vez, é mais fácil empregar DRY com validação se for um componente separado. Do ponto de vista prático, a validação (principalmente ao usar estruturas) requer a exposição de dados que, de outra forma, deveriam ser encapsulados e geralmente requer que atributos personalizados sejam anexados a campos e propriedades. Eu prefiro que elas estejam em outras classes além dos meus modelos de domínio.

Prefiro duplicar algumas propriedades em algumas classes semelhantes do que tentar forçar as exigências de uma estrutura de validação para minhas entidades. Isso inevitavelmente acaba atrapalhando as classes da entidade.

Criação de Objetos. Classe de fábrica versus métodos de fábrica versus 'atualização' de uma instância.

Eu uso uma camada de indireção. Nos meus projetos mais recentes, este é um comando + manipulador para criar algo, ou seja CreateNewAccountCommand. Uma alternativa seria sempre usar uma fábrica (embora isso possa ser estranho se o restante da operação de uma entidade for exposto por uma classe de serviço separada da classe da fábrica).

Em geral, porém, tento ser mais flexível com as opções de design para criação de objetos. newé fácil e familiar, mas nem sempre é suficiente. Penso que é importante usar o julgamento aqui e permitir que diferentes partes de um sistema usem estratégias diferentes, conforme necessário.

Persistência. ... por que não ter um método Save em nosso objeto de domínio

Isso raramente é uma boa ideia; Eu acho que há muita experiência compartilhada para apoiar isso.

Se eu terminar com um IAuthorizationService, IValidator, IFactory e IRepository para minha raiz agregada, o que resta? Ter um método Publish que altera o estado do objeto de Rascunho para Publicado é suficiente para considerar a classe como um objeto de domínio não anêmico ???

Talvez um modelo de domínio não seja a escolha correta para esta parte do seu aplicativo.

quentin-starin
fonte
2
"Uma parte complicada que pode ser encontrada é a autorização contextual, onde um comando pode ou não ser permitido não apenas com base nas funções do usuário, mas também nos dados / regras de negócios." - E como você aborda isso? Mais vezes do que não, para mim, pelo menos, nossas regras de autorização são uma combinação de função e estado atual.
SonOfPirate
@SonOfPirate: manipuladores de eventos que escutam eventos do domínio e atualizam tabelas muito específicas para as necessidades das consultas que verificam a autorização. Lógica que analisa o estado e determina se uma função ou indivíduo está autorizado ou não mora no manipulador de eventos (portanto, as tabelas são quase sempre um sim / não simples, tornando a verificação de autenticação uma consulta simples). Além disso, assim que qualquer dessas lógicas é usada em mais de um local, ela é refatorada do manipulador para um serviço compartilhado.
Quentin-starin
1
Em geral, nos últimos anos, me afastei cada vez mais da tentativa de consolidar tudo em um domínio ou objeto de negócios. Minha experiência parece ser que tornar as coisas mais granulares e menos acopladas é uma vitória a longo prazo. Portanto, embora de uma perspectiva esse método esteja colocando a lógica de negócios fora do domínio (até certo ponto), ele também suporta modificações ágeis posteriormente. É tudo sobre encontrar um equilíbrio.
Quentin-starin
4
Referenciando a resposta aqui: Encontrei o Padrão de Estado muito útil ao lidar com validações dependentes de permissões e regras de negócios. Crie um estado abstrato que seja injetado no modelo de domínio e exponha métodos que tomam o objeto de domínio como parâmetro e validam ações específicas do domínio. Dessa forma, se suas regras de permissão mudarem (como quase sempre), você nunca precisará mexer no modelo para acomodá-lo, porque as implementações de estado residem no seu componente de segurança.
Aaronaught
1
Permanecendo no assunto da autorização, deixe-me dar um exemplo tangível sobre a mesa para ver como você (ambos) lidaria com isso. Eu tenho uma operação de publicação no meu objeto de domínio que exige que o usuário esteja na função Publicador E que o objeto esteja em um determinado estado. Quero ocultar ou desativar o botão "Publicar" na interface do usuário. Como você faria isso?
SonOfPirate
4

OK, aqui vai para mim. Vou evitar isso dizendo o seguinte:

  • A otimização prematura (e isso inclui o design) geralmente pode causar problemas.

  • IANMF (não sou Martin Fowler);)

  • Um pequeno segredo sujo é que, em projetos de tamanho pequeno (mesmo os de tamanho médio), é a consistência da sua abordagem que importa.

Autorização

Para mim, autenticação e autorização são sempre uma preocupação transversal. No meu feliz e pequeno mundo Java, isso é delegado à segurança Spring ou à estrutura Apache Shiro.

Validação Para mim, a validação faz parte do objeto, como eu vejo como definindo o que é o objeto.

por exemplo, um objeto Car tem 4 rodas (OK, existem algumas exceções estranhas, mas vamos ignorar o carro de 3 rodas estranho por enquanto). Um carro simplesmente não é válido, a menos que tenha 4 (no meu mundo), de modo que a validação faz parte do que é a definição de um carro. Isso não significa que você não pode ter aulas de validação auxiliar.

No meu feliz mundo Java, uso estruturas de validação de Bean e uso anotações simples na maioria dos meus campos de Bean. É fácil validar seu objeto, independentemente da camada em que você está.

Criação de Objetos

Eu vejo aulas de fábrica com cautela. Muitas vezes eu tenho visto a xyxFactoryFactoryclasse;)

Costumo criar apenas um newobjeto, conforme necessário, até encontrar um caso em que a Injeção de Dependência é justificada (e como tento seguir uma abordagem de TDD, isso ocorre com mais frequência).

No meu feliz mundo Java, que é cada vez mais Guice, mas a primavera ainda é o rei aqui.

Persistência

Então esse é um debate que continua em círculos e rotatórias e eu sempre estou em dúvida sobre isso.

Alguns dizem que se você olhar para o objeto de uma maneira 'pura', a persistência não é uma propriedade essencial, é apenas uma preocupação externa.

Outros consideram que os objetos do seu domínio implementam implicitamente uma interface 'persistente' (sim, eu sei que estou estendendo aqui). Portanto, não há problema em ter vários métodos save, deleteetc. Isso é visto como uma abordagem pragmática e muitas tecnologias ORM (JPA no meu feliz mundo Java) lidam com objetos dessa maneira.

Por uma questão de segurança transversal, certifico-me de que as permissões de edição / exclusão / adição / qualquer que seja sejam definidas corretamente no serviço que chama o método de salvar / atualizar / excluir no objeto. Se eu sou realmente paranóico, posso até definir as permissões no próprio objeto de domínio.

HTH!

Martijn Verburg
fonte
2

Jimmy Nilsson aborda esse tópico em seu livro sobre DDD. Ele começou com um modelo anêmico, passou a modelos não anêmicos em um projeto posterior e finalmente se estabeleceu em modelos anêmicos. Seu raciocínio era que os modelos anêmicos poderiam ser reutilizados em vários serviços com lógica de negócios diferente.

O trade-off é a falta de capacidade de descoberta. Os métodos que você pode usar para operar em seus modelos anêmicos estão espalhados por um conjunto de serviços localizados em outros lugares.

Todd Smith
fonte
Parece um requisito específico - a reutilização da estrutura de dados (estresse 'dados') - leva à parte comum reduzida a DTOs simples.
Victor Sergienko
Modelos anêmicos permitem melhor reutilização? Parece mais um DTO, e eu pessoalmente não dou a mínima para reutilizar definições de propriedade. Prefiro reutilizar comportamentos.
Andy
@ Andy - Eu concordo, no entanto, se seus comportamentos estão dentro dos Serviços de Domínio e eles operam em objetos anêmicos (ok DTOs, se desejar), isso não aumenta a reutilização desses comportamentos? Apenas brincando de advogado do diabo.
jpierson
@ jpierson Eu descobri que os comportamentos são tipicamente específicos para um caso de uso específico. Se houver reutilização, isso pertenceria a outra classe, mas o consumidor não usaria essas classes, usaria aquelas específicas para o caso de uso. Portanto, qualquer reutilização é "nos bastidores", por assim dizer. Além disso, a tentativa de reutilizar modelos normalmente dificulta o uso do consumidor, portanto você cria modelos de visualização / edição na camada da interface do usuário. Freqüentemente, você acaba violando o DRY para proporcionar uma experiência mais rica ao usuário (por exemplo, DataAnnotations of edit models).
Andy
1
Prefiro módulos de domínio criados para o caso de uso, reutilizados onde faz sentido (ou seja, a reutilização pode ser feita sem modificar muito ou quase todo o comportamento). Portanto, em vez do modelo de domínio anêmico, classe de serviço e modelo de edição, você tem um único modelo de domínio editável inteligente. Descobri muito mais fácil de usar e manter.
Andy
2

Esta pergunta foi feita há muito tempo, mas está marcada com Design Orientado a Domínio. Eu acho que a pergunta em si contém um mal-entendido fundamental de toda a prática e as respostas, incluindo a resposta aceita, perpetuam um mal-entendido fundamental.

Não há "o modelo de domínio" em uma arquitetura DDD.

Vamos usar a Autorização como exemplo. Deixe-me pedir que você pense em uma pergunta: imagine dois usuários diferentes se autenticando no seu sistema. Um usuário tem permissão para alterar uma determinada entidade, mas o outro não. Por que não?

Eu odeio exemplos simples e inventados, porque muitas vezes confundem mais do que esclarecem. Mas vamos fingir que temos dois domínios diferentes. Primeiro, é uma plataforma CMS para uma agência de marketing. Esta agência tem muitos clientes, todos com conteúdo on-line que precisa ser gerenciado por redatores e artistas gráficos. O conteúdo inclui postagens em blogs e páginas de destino para diferentes clientes.

O outro domínio é o gerenciamento de inventário de uma empresa de calçados. O sistema gerencia o estoque a partir de quando chega do fabricante na França, aos centros de distribuição nos EUA continentais, às lojas de varejo nos mercados locais e, finalmente, ao cliente que compra os sapatos no varejo.

Se você acha que as regras de autorização são as mesmas para as duas empresas, sim, isso seria um bom candidato para um serviço fora do domínio. Mas duvido que as regras de autorização sejam as mesmas. Até os conceitos por trás dos usuários seriam diferentes. Certamente o idioma seria diferente. A agência de marketing provavelmente tem funções como autor do post e proprietário do ativo, enquanto a empresa de calçados provavelmente tem funções como auxiliar de expedição ou gerente de armazém ou gerente de loja.

Esses conceitos provavelmente têm todos os tipos de regras de permissão associadas a eles que precisam ser modelados no domínio. Mas isso não significa que todos fazem parte do mesmo modelo, mesmo dentro do mesmo aplicativo. Porque lembre-se de que existem diferentes contextos limitados.

Portanto, talvez se possa considerar um modelo de domínio não anêmico no contexto da Autorização diferente do contexto de encaminhamento de remessas de calçados para lojas com pouco estoque ou encaminhamento de visitantes do site para a página de destino apropriada, dependendo do anúncio em que clicaram.

Se você se deparar com modelos de domínio anêmicos, talvez precise gastar mais tempo no mapeamento de contexto antes de começar a escrever o código.

RibaldEddie
fonte