Garantindo consistência transacional com DDD

9

Estou começando com o DDD e entendo que as raízes agregadas são usadas para garantir consistência transnacional. Não devemos modificar várias agregações em um serviço de aplicativo.

Gostaria de saber, no entanto, como lidar com a seguinte situação.

Eu tenho uma raiz agregada chamada Produtos.

Há também uma raiz agregada chamada Grupo.

Ambos têm IDs e podem ser editados independentemente.

Vários produtos podem apontar para o mesmo grupo.

Eu tenho um serviço de aplicativo que pode alterar o grupo de um produto:

ProductService.ChangeProductGroup(string productId, string groupId)

  1. O grupo de verificação existe
  2. Obter produto do repositório
  3. Defina seu grupo
  4. Escreva o produto novamente no repositório

Eu também tenho um serviço de aplicativo onde o grupo pode ser excluído:

GroupService.DeleteGroup(string groupId) 1. Obtenha produtos do repositório cujo groupId esteja definido como groupId fornecido, garanta que a contagem seja 0 ou aborte 2. Exclua o grupo do repositório de grupos 3. Salve as alterações

Minha pergunta é o seguinte cenário, o que aconteceria se:

No ProductService.ChangeProductGroup, verificamos se o grupo existe (existe) e, após essa verificação, um usuário separado exclui o productGroup (por meio de outro GroupService.DeleteGroup). Nesse caso, definimos uma referência a um produto que acabou de ser excluído?

Isso é uma falha no meu design, pois eu deveria usar um design de domínio diferente (adicionando elementos adicionais, se necessário) ou precisaria usar transações?

g18c
fonte

Respostas:

2

Temos o mesmo problema.

E não vejo como resolver esse problema, mas com transações ou com verificação de consistência no DB para obter uma exceção no pior dos casos.

Você também pode usar o bloqueio pessimista, bloqueando a raiz agregada ou apenas suas partes para outros clientes até que a transação comercial seja concluída, o que é, em certa medida, equivalente à transação serializável.

O caminho a seguir depende muito do sistema e da lógica de negócios.
A simultaneidade não é uma tarefa fácil.
Mesmo se você puder detectar um problema, como vai resolvê-lo? Apenas cancele a operação ou permita ao usuário 'mesclar' alterações?

Usamos o Entity Framework e o EF6 usa read_commited_snapshot por padrão, portanto, duas leituras consecutivas do repositório podem nos fornecer dados inconsistentes. Apenas lembramos disso no futuro, quando os processos de negócios serão mais claramente delineados e podemos tomar decisões infromed. E sim, ainda verificamos a consistência no nível do modelo, como você faz. Isso pelo menos permite testar BL separadamente do DB.

Também sugiro que você pense sobre a consistência do repositório , caso tenha transações comerciais 'longas'. É bastante complicado, como se viu.

Pavel Voronin
fonte
1

Acho que essa não é uma pergunta específica do Design Orientado a Domínio, mas de gerenciamento de recursos.

Os recursos simplesmente devem ser bloqueados à medida que são adquiridos .

Isso ocorre quando o serviço de aplicativo sabe antecipadamente que o recurso adquirido (o Groupagregado, no seu exemplo) será modificado. No Design Orientado a Domínio, o bloqueio de recursos é um aspecto que pertence ao repositório.

rucamzu
fonte
Não, o bloqueio não é obrigatório. Na verdade, é uma coisa horrível de se fazer com transações de longa duração. É melhor usar a simultaneidade otimista, que todo ORM moderno suporta imediatamente. E o melhor da simultaneidade otimista é que ele também funciona com bancos de dados não transacionais, desde que eles suportem atualizações atômicas.
Aaronaught
11
Concordo com as desvantagens do bloqueio em transações de longa execução, mas o cenário descrito por @ g18c sugere exatamente o oposto, isto é, operações breves sobre agregados individuais.
rucamzu
0

Por que você não armazena os IDs do produto na entidade do grupo? Dessa forma, você está lidando apenas com um único agregado que facilitará as coisas.

Você precisará implementar algum tipo de padrão de simultaneidade, por exemplo, se você optar pela simultaneidade otimista, basta adicionar uma propriedade de versão à entidade do Grupo e lançar uma exceção se as versões não corresponderem na atualização, ou seja,

Adicionar produto a um grupo

  1. Verifique se o produto existe
  2. Obter grupo do repositório (incluindo sua propriedade de identificação de versão)
  3. Group.Add (ProductId)
  4. Salvar grupo usando o repositório (atualize com o novo ID da versão e adicione uma cláusula where para garantir que a versão não seja alterada.)

Muitos ORMs têm simultaneidade otimista incorporada usando IDs de versão ou carimbos de data e hora, mas é fácil criar seus próprios. Aqui está um bom post sobre como isso é feito no Nhibernate http://ayende.com/blog/3946/nhibernate-mapping-concurrency .

Scott Millett
fonte