Abordagem DDD para operações básicas de CRUD em um aplicativo complexo e centrado no domínio

9

Minha empresa está reescrevendo nosso aplicativo Web do zero. É um aplicativo de nível empresarial amplo, com um domínio complexo no setor financeiro.

Estamos usando um ORM (estrutura de entidade) para persistência.

Em essência, metade de nosso aplicativo se concentra em coletar dados brutos do usuário, armazená-los e, em seguida, a outra metade do aplicativo que contém a maior parte de nossa lógica real de domínio usa esses dados brutos para criar nossa imagem de domínio, que difere muito daquelas originais entradas brutas e as passa para um mecanismo de cálculo, executa cálculos e cospe resultados, que são exibidos ao usuário.

Em uma abordagem DDD usando camadas, parece que as operações CRUD passam pela camada de domínio. mas pelo menos no nosso caso, isso não parece fazer sentido.

Quando um usuário acessa a tela de edição para alterar uma conta de investimento, por exemplo, os campos na tela são os campos exatos armazenados no banco de dados, não a representação do domínio usada posteriormente para os cálculos. Então, por que eu carregaria a representação de domínio da conta de investimento quando a tela de edição precisa da representação do banco de dados (entradas brutas)?

Depois que o usuário clica em "Concluído" na tela da conta de investimento e um POST é feito no controlador, o controlador agora tem uma representação exata do banco de dados da conta de investimento que precisa salvar. Mas, por alguma razão, devo carregar a representação do domínio para fazer modificações, em vez de apenas mapear o modelo do controlador diretamente para o modelo de banco de dados (modelo de estrutura de entidades)?

Então, em essência, estou mapeando um modelo de dados para o modelo de domínio, apenas para que ele possa ser mapeado de volta ao modelo de dados para persistir. Como isso faz sentido?

wired_in
fonte

Respostas:

9

Ok, imagine que você implemente a página de criação da sua conta mapeando a postagem do formulário diretamente para um objeto EF que é salvo no banco de dados.

Vamos supor ainda que o banco de dados tenha várias restrições que impedem a inserção de dados completamente errados. As contas sempre têm clientes etc.

Tudo parece funcionar bem. Mas então o negócio faz uma nova regra.

  • Contas criadas na quinta-feira recebem uma taxa de juros de bônus de 2%. (suponha que a taxa de juros seja um dos campos da conta)

Agora você precisa colocar essa lógica em algum lugar e não possui um objeto de domínio para inseri-la.

O DDD assume que você sempre terá esse tipo de regra, e provavelmente o faz. A criação de uma conta deve ter várias verificações, um log de auditoria etc., não será apenas 'escrever uma linha no banco de dados'

Planeje seu domínio, assumindo que não exista persistência ou controladores MVC com entrada extra de lógica. Certifique-se de capturar todos os requisitos e todos eles no modelo de domínio.

Ewan
fonte
3
Essa é uma boa maneira de dizer. Eu odeio encontrar regras de negócios misturadas com detalhes do banco de dados. +1
candied_orange 12/08
Bons pontos, mas e se essas regras de validação se aplicarem apenas durante a criação e atualização das entradas do usuário? Depois que tivermos as entradas do usuário, o modelo criado ao executar cálculos será um modelo completamente diferente. Devemos ter dois modelos de domínio para uma conta de investimento? Um para operações CRUD de entradas brutas para o usuário e outro para quando essas entradas são usadas para criar o modelo de domínio usado nos cálculos?
wired_in
confundindo minhas perguntas. você terá que dar um exemplo completo. Se você possui lógica de domínio, ela deve ser inserida em um objeto de domínio. Isso não significa que você não pode criar outro objeto de domínio depois do primeiro
Ewan
Imagine um mecanismo de cálculo complexo. Uma das entradas necessárias para executar os cálculos é uma conta de investimento, mas toda a conta de investimento é para o mecanismo de cálculo e é um fluxo de receita durante um período de tempo. Esse modelo de domínio de uma conta de investimento é completamente diferente das entradas brutas inseridas pelo usuário para essa conta de investimento. No entanto, quando o usuário está inserindo entradas básicas como Nome, Valor Atual, etc., ainda precisa haver lógica de validação, mas não deve ter nada a ver com o modelo que o mecanismo de calc usa. Existem dois modelos de domínio para uma conta de investimento aqui?
wired_in
..... ou talvez ter um modelo de conta de investimento no domínio é um exagero para operações CRUD e não deve ser apenas alguns validador atributos usados ou algo
wired_in
7

Como isso faz sentido?

Resposta curta: não .

Resposta mais longa: os padrões pesados ​​para o desenvolvimento de um modelo de domínio não se aplicam às partes da sua solução que são apenas um banco de dados.

Udi Dahan teve uma observação interessante que pode ajudar a esclarecer

Dahan considera que um serviço precisa ter algum tipo de funcionalidade e alguns dados. Se não possui dados, é apenas uma função. Se tudo o que faz é executar operações CRUD nos dados, então é o banco de dados.

O objetivo do modelo de domínio, afinal, é garantir que todas as atualizações dos dados mantenham os negócios atuais invariáveis. Ou, em outras palavras, o modelo de domínio é responsável por garantir que o banco de dados que atua como o sistema de registro esteja correto.

Quando você está lidando com um sistema CRUD, geralmente não é o sistema de registro dos dados. O mundo real é o livro de registro e seu banco de dados é apenas uma representação em cache local do mundo real.

Por exemplo, a maioria das informações que aparecem em um perfil de usuário, como um endereço de email ou um número de identificação emitido pelo governo, tem uma fonte de verdade que mora fora da sua empresa - é o administrador de email de outra pessoa que atribui e revoga endereços de email, não seu aplicativo. É o governo que atribui os SSNs, não o seu aplicativo.

Portanto, você normalmente não fará nenhuma validação de domínio nos dados que chegam do mundo exterior; você pode ter verificações para garantir que os dados sejam bem formados e higienizados adequadamente ; mas não são seus dados - seu modelo de domínio não recebe veto.

Em uma abordagem DDD usando camadas, parece que as operações CRUD passam pela camada de domínio. mas pelo menos no nosso caso, isso não parece fazer sentido.

É o caso do caso em que o banco de dados é o livro de registro .

Ouarzy coloca desta maneira .

No entanto, trabalhando em muito código legado, observo erros comuns para identificar o que está dentro do domínio e o que está fora.

Um aplicativo pode ser considerado CRUD apenas se não houver lógica de negócios em torno do modelo de dados. Mesmo nesse caso (raro), seu modelo de dados não é seu modelo de domínio. Significa apenas que, como nenhuma lógica de negócios está envolvida, não precisamos de abstração para gerenciá-la e, portanto, não temos um modelo de domínio.

Usamos o modelo de domínio para gerenciar os dados que pertencem ao domínio; os dados de fora do domínio já são gerenciados em outro lugar - estamos apenas armazenando em cache uma cópia.

Greg Young usa os sistemas de armazém como uma ilustração primária das soluções em que o livro de registro está em outro lugar (ou seja: o piso do armazém). A implementação que ele descreve é ​​muito parecida com a sua - um banco de dados lógico para capturar mensagens recebidas do armazém e, em seguida, um banco de dados lógico separado, armazenando em cache as conclusões tiradas da análise dessas mensagens.

Então, talvez tenhamos dois contextos limitados aqui? Cada um com um modelo diferente para uminvestment account

Talvez. Eu ficaria relutante em classificá-lo como um contexto limitado, porque não está claro o que outras bagagens acompanham. Pode ser que você tenha dois contextos, pode ser um contexto com diferenças sutis na linguagem onipresente que você ainda não entendeu.

Possível teste decisivo: quantos especialistas em domínio você precisa de dois especialistas em domínio para cobrir esse espectro ou apenas um que fale sobre os componentes de maneiras diferentes. Basicamente, você pode adivinhar quantos contextos limitados você tem, trabalhando a lei de Conway de trás para a frente.

Se você considera que os contextos limitados estão alinhados com os serviços, pode ser mais fácil: você deve implantar essas duas peças de funcionalidade independentemente? Sim sugere dois contextos limitados; mas se eles precisam ser sincronizados, talvez seja apenas um.

VoiceOfUnreason
fonte
Bem, existe validação e lógica padrão, mas ela só se aplica ao criar / atualizar as entradas brutas para uma conta de investimento. Em seguida, usamos um modelo muito mais rico para a conta de investimento quando o usamos como entrada para um mecanismo de cálculo. Então, talvez tenhamos dois contextos limitados aqui? Cada um com um modelo diferente para uma conta de investimento ''
wired_in 14/08
Acabei de voltar a isso depois de vários anos, e seu comentário está ressoando agora mais do que antes, por algum motivo. Há muitas coisas boas aqui, mas você poderia esclarecer uma coisa para mim? Você disse: "O objetivo do modelo de domínio, afinal, é garantir que todas as atualizações dos dados mantenham os negócios atuais invariáveis". Isso se aplicaria à parte do nosso aplicativo que salva / atualiza informações. A outra parte é apenas um mecanismo de cálculo. Leva uma representação dos dados como entradas e cospe resultados. Isso não faz parte do modelo de domínio? Como isso não afeta os dados armazenados?
wired_in 28/03
2

No seu domínio, você não precisa saber que o banco de dados existe.

Seu domínio é sobre regras de negócios. O material que precisa sobreviver quando a empresa que criou seu banco de dados encerra suas atividades. Ou seja, se você deseja que sua empresa sobreviva. É muito bom quando essas regras não se importam que você tenha alterado a forma como persiste os dados.

Os detalhes do banco de dados existem e precisam ser tratados. Eles deveriam morar em outro lugar. Coloque-os através de um limite. Controle cuidadosamente como você se comunica através desse limite ou não é um limite.

O tio Bob tem a dizer sobre o que colocar seus dados:

Normalmente, os dados que cruzam os limites são estruturas de dados simples. Você pode usar estruturas básicas ou objetos simples de transferência de dados, se quiser. Ou os dados podem simplesmente ser argumentos em chamadas de função. Ou você pode empacotá-lo em um hashmap ou construí-lo em um objeto.

O importante é que estruturas de dados simples e isoladas sejam transmitidas através dos limites. Não queremos enganar e passar linhas de entidades ou de banco de dados. Não queremos que as estruturas de dados tenham qualquer tipo de dependência que viole a regra de dependência.

[…] Quando passamos dados através de um limite, eles sempre estão na forma mais conveniente para o círculo interno.

Arquitetura Limpa

Ele também explica como suas camadas externas devem ser plugins para suas camadas internas, para que elas nem saibam que existem.

Folha de dicas sobre arquitetura limpa

Siga algo assim e você terá um bom lugar para ignorar o banco de dados, onde pode se preocupar com regras de validação de entrada, regras que devem ser mantidas de alguma forma, regras para executar cálculos, regras para enviar esses resultados para qualquer saída. Na verdade, é mais fácil ler esse tipo de código.

É isso ou você decide que seu domínio é realmente apenas para manipular o banco de dados. Nesse caso, sua linguagem de domínio é SQL. Se estiver bem, mas não espere que sua implementação das regras de negócios sobreviva a uma mudança na persistência. Você precisará reescrevê-las completamente.

candied_orange
fonte
Como estamos usando um ORM (Entity Framework), nosso banco de dados já está abstraído, mas os modelos de dados (classes de estrutura de entidades) naturalmente são praticamente de 1 para 1 com as tabelas do banco de dados. O problema é que, em algumas partes de nosso aplicativo, o usuário está apenas atualizando o modelo de dados (a tela é apenas uma lista de caixas de texto em que cada caixa de texto é um campo no banco de dados (modelo de dados).
wired_in
Portanto, não vejo uma razão para não usar apenas representações dos dados brutos (modelo de dados) ao executar operações CRUD. Temos uma representação de domínio complexa usada para cálculos, que é o que vejo como nosso modelo de domínio, mas não vejo por que carregaria essa imagem na parte CRUD do nosso aplicativo.
Wired_in 12/08/19
Defina o que você quer dizer com "usar representações dos dados brutos". Os dados são inseridos, os dados são validados de acordo com as regras do domínio, os dados são persistentes de alguma forma, os dados são calculados e os resultados são enviados para o que for. Estou esquecendo de algo?
Candied_orange 12/08
Estou tentando dizer que os dados brutos que obtemos do usuário para uma conta de investimento não são como representamos essa conta de investimento nas principais partes de nosso aplicativo, como quando são usados ​​para cálculos. Por exemplo, podemos ter uma entrada booleana que salvamos no banco de dados chamado IsManagedAccount. O usuário nos fornece isso através de um botão de opção na tela de edição. Portanto, a representação do banco de dados até a tela é de 1 a 1. Quando criamos nosso modelo de domínio posteriormente no aplicativo, podemos ter uma classe ManagedAccount, portanto, nenhuma propriedade booleana. As duas estruturas são muito diferentes.
wired_in
Portanto, quando o usuário está apenas editando as entradas brutas em uma tela de edição, por que carregaria a imagem do domínio e adicionaria muita complexidade para mapear de alguma forma a classe ManagedAccount de tipo fortemente digitado de volta para uma representação plana que é apenas uma classe com uma IsManagedAccount propriedade?
wired_in
1

Aplicando a teoria DDD:

Existem dois contextos limitados nesse domínio:

  • Os cálculos da conta de investimento. O modelo matemático da conta de investimento é um elemento, talvez um agregado.
  • Finanças principais. A conta de investimento do cliente é uma das entidades.

Cada contexto limitado pode ter um design arquitetônico diferente.

Exemplo:

A conta de investimento do cliente é uma entidade (talvez um agregado, depende do domínio) e a persistência dos dados é feita pelo repositório da entidade (RDB ou outro tipo de banco de dados, como um banco de dados OO).

Não há uma abordagem DDD para operações CRUD. Ter um campo DB vinculado aos dados de um objeto quebra os princípios de design.

Derek
fonte