Estou mergulhando nos conceitos do Domain-Driven Design (DDD) e achei alguns princípios estranhos, especialmente no que diz respeito ao isolamento do domínio e do modelo de persistência. Aqui está o meu entendimento básico:
- Um serviço na camada de aplicativo (fornecendo um conjunto de recursos) solicita objetos de domínio de um repositório necessário para executar sua função.
- A implementação concreta desse repositório busca dados do armazenamento para o qual foi implementado
- O serviço informa ao objeto de domínio, que encapsula a lógica de negócios, para executar determinadas tarefas que modificam seu estado.
- O serviço informa ao repositório para persistir o objeto de domínio modificado.
- O repositório precisa mapear o objeto de domínio de volta para a representação correspondente no armazenamento.
Agora, dadas as suposições acima, o seguinte parece estranho:
Anúncio 2:
O modelo de domínio parece carregar o objeto de domínio inteiro (incluindo todos os campos e referências), mesmo que não sejam necessários para a função que o solicitou. Carregar completamente pode nem ser possível se outros objetos de domínio forem referenciados, a menos que você carregue também esses objetos de domínio e todos os objetos a que se referem, e assim por diante. O carregamento lento vem à mente, o que significa que você começa a consultar seus objetos de domínio, que devem ser de responsabilidade do repositório.
Dado esse problema, a maneira "correta" de carregar objetos do domínio parece ter uma função de carregamento dedicada para cada caso de uso. Essas funções dedicadas carregariam apenas os dados exigidos pelo caso de uso para o qual foram projetados. Aqui é onde o embaraço entra em jogo: primeiro, eu precisaria manter uma quantidade considerável de funções de carregamento para cada implementação do repositório, e os objetos de domínio acabariam em estados incompletos, carregando null
em seus campos. Tecnicamente, este último não deve ser um problema porque, se um valor não foi carregado, não deve ser exigido pela funcionalidade que o solicitou. Ainda é estranho e um risco potencial.
Anúncio 3:
Como um objeto de domínio verificaria restrições de exclusividade na construção, se ele não tem nenhuma noção do repositório? Por exemplo, se eu quisesse criar um novo User
com um número de previdência social exclusivo (fornecido), o conflito mais antigo ocorreria ao solicitar ao repositório que salve o objeto, apenas se houver uma restrição de exclusividade definida no banco de dados. Caso contrário, eu poderia procurar um User
com a previdência social fornecida e relatar um erro, caso exista, antes de criar uma nova. Mas as verificações de restrição permaneceriam no serviço e não no objeto de domínio ao qual pertencem. Acabei de perceber que os objetos de domínio estão muito bem autorizados a usar repositórios (injetados) para validação.
Anúncio 5:
Eu percebo o mapeamento de objetos de domínio para um back-end de armazenamento como um processo intensivo de trabalho, em comparação com os objetos de domínio modificando diretamente os dados subjacentes. É, obviamente, um pré-requisito essencial para dissociar a implementação concreta do armazenamento do código de domínio. No entanto, isso realmente tem um custo tão alto?
Aparentemente, você tem a opção de usar ferramentas ORM para fazer o mapeamento para você. No entanto, isso geralmente exige que você projete o modelo de domínio de acordo com as restrições do ORM ou introduza uma dependência do domínio para a camada de infraestrutura (usando anotações do ORM nos objetos de domínio, por exemplo). Também li que os ORMs introduzem uma sobrecarga computacional considerável.
No caso de bancos de dados NoSQL, para os quais quase não existem conceitos semelhantes a ORM, como você monitora quais propriedades foram alteradas nos modelos de domínio save()
?
Editar : Além disso, para que um repositório acesse o estado do objeto de domínio (ou seja, o valor de cada campo), o objeto de domínio precisa revelar seu estado interno que quebra o encapsulamento.
Em geral:
- Para onde iria a lógica transacional? Isso é certamente persistência específica. Algumas infra-estruturas de armazenamento podem nem mesmo suportar transações (como repositórios simulados na memória).
- Para operações em massa que modificam vários objetos, eu precisaria carregar, modificar e armazenar cada objeto individualmente para passar pela lógica de validação encapsulada do objeto? Isso se opõe à execução de uma única consulta diretamente no banco de dados.
Gostaria de receber alguns esclarecimentos sobre este tópico. Minhas suposições estão corretas? Caso contrário, qual é a maneira correta de resolver esses problemas?
fonte
Respostas:
Seu entendimento básico está correto e a arquitetura que você esboça é boa e funciona bem.
Lendo nas entrelinhas, parece que você está vindo de um estilo de programação de registro ativo mais centrado no banco de dados? Para chegar a uma implementação funcional, eu diria que você precisa
1: Os objetos do domínio não precisam incluir o gráfico inteiro do objeto. Por exemplo, eu poderia ter:
Endereço e Cliente só precisam fazer parte da mesma agregação se você tiver alguma lógica, como "o nome do cliente só pode começar com a mesma letra que o nome da casa". Você tem razão para evitar o carregamento lento e as versões 'Lite' dos objetos.
2: As restrições de exclusividade geralmente são da competência do repositório e não do objeto do domínio. Não injete repositórios nos Objetos de Domínio, isso é um retorno ao registro ativo, simplesmente erro quando o serviço tenta salvar.
A regra de negócios não é "Não existem duas instâncias de Usuário com o mesmo SocialSecurityNumber ao mesmo tempo"
É que eles não podem existir no mesmo repositório.
3: Não é difícil escrever repositórios em vez de métodos de atualização de propriedades individuais. De fato, você descobrirá que possui praticamente o mesmo código de qualquer maneira. É exatamente em qual classe você a coloca.
Atualmente, os ORMs são fáceis e não têm restrições extras no seu código. Dito isto, pessoalmente, prefiro simplesmente pôr em marcha o SQL. Não é tão difícil, você nunca enfrenta problemas com os recursos do ORM e pode otimizar quando necessário.
Realmente não há necessidade de acompanhar quais propriedades foram alteradas quando você salvou. Mantenha seus Objetos de Domínio pequenos e simplesmente substitua a versão antiga.
Questões gerais
A lógica de transação entra no repositório. Mas você não deve ter muito ou nada. Certamente, você precisará de algumas se tiver tabelas filho nas quais está colocando os objetos filhos do agregado, mas isso será totalmente encapsulado no método de repositório SaveMyObject.
Atualizações em massa. Sim, você deve alterar individualmente cada objeto e adicionar um método SaveMyObjects (List objects) ao seu repositório, para fazer a atualização em massa.
Você deseja que o objeto de domínio ou o serviço de domínio contenha a lógica. Não é o banco de dados. Isso significa que você não pode simplesmente "atualizar o nome do conjunto de clientes = x onde y", porque, apesar de tudo, você conhece o objeto Customer ou CustomerUpdateService faz 20 outras coisas estranhas ao alterar o nome.
fonte
AddressId
vez deAddress
) contradizem os princípios OO?Resposta curta: Seu entendimento está correto e as perguntas que você aponta para problemas válidos para os quais as soluções não são diretas nem universalmente aceitas.
Ponto 2 .: (carregando gráficos de objetos completos)
Não sou o primeiro a apontar que as ORMs nem sempre são uma boa solução. O principal problema é que os ORMs não sabem nada sobre o caso de uso real, portanto, não têm idéia do que carregar ou como otimizar. Isto é um problema.
Como você disse, a solução óbvia é ter métodos de persistência para cada caso de uso. Mas se você ainda usar um ORM para isso, o ORM forçará você a compactar tudo em objetos de dados. Que, além de não ser realmente orientado a objetos, novamente não é o melhor design para alguns casos de uso.
E se eu apenas quiser atualizar em massa alguns registros? Por que eu precisaria de uma representação de objeto para todos os registros? Etc.
Portanto, a solução para isso é simplesmente não usar um ORM para casos de uso para os quais não é um bom ajuste. Implemente um caso de uso "naturalmente" como ele é, que às vezes não requer uma "abstração" adicional dos dados em si (objetos de dados) nem uma abstração sobre as "tabelas" (repositórios).
Ter objetos de dados meio preenchidos ou substituir referências de objetos por "ids" são soluções alternativas, na melhor das hipóteses, não são bons projetos, como você apontou.
Ponto 3 .: (verificando restrições)
Se a persistência não for abstraída, cada caso de uso poderá obviamente verificar qualquer restrição que desejar facilmente. O requisito de que os objetos não conheçam o "repositório" é completamente artificial e não é um problema da tecnologia.
Ponto 5 .: (ORMs)
Não, não faz. Existem muitas outras maneiras de ter persistência. O problema é que o ORM é visto como "a" solução a ser usada sempre (pelo menos para bancos de dados relacionais). Tentar sugerir não usá-lo para alguns casos de uso em um projeto é inútil e, dependendo do próprio ORM, às vezes até impossível, pois às vezes os caches e a execução tardia são usados por essas ferramentas.
Pergunta geral 1: (transações)
Eu não acho que exista uma solução única. Se seu design for orientado a objetos, haverá um método "top" para cada caso de uso. A transação deve estar lá.
Qualquer outra restrição é completamente artificial.
Pergunta geral 2: (operações a granel)
Com um ORM, você (para a maioria dos ORMs que eu conheço) é forçado a passar por objetos individuais. Isso é completamente desnecessário e provavelmente não seria o seu design se sua mão não fosse atada pelo ORM.
O requisito para separar a "lógica" do SQL vem dos ORMs. Eles têm que dizer isso, porque não podem suportar. Não é inerentemente "ruim".
Sumário
Acho que meu argumento é que os ORMs nem sempre são a melhor ferramenta para um projeto e, mesmo que seja, é altamente improvável que seja o melhor para todos os casos de uso em um projeto.
Da mesma forma, a abstração do repositório de objeto de dados do DDD também nem sempre é a melhor. Eu diria até agora, eles raramente são o design ideal.
Isso não nos deixa com uma solução única, então teríamos que pensar em soluções para cada caso de uso individualmente, o que não é uma boa notícia e, obviamente, dificulta nosso trabalho :)
fonte