DDD: são objetos que representam “raízes agregadas” devem ser exclusivas (como singleton)

5

Estou desenvolvendo meu primeiro aplicativo usando o DDD e o sourcing de eventos.

Pelo que entendi, o agregado encapsula a funcionalidade específica do domínio com uma ou mais invariantes associadas. Os invariantes são traduzidos em restrições de consistência - em qualquer momento, o estado do agregado deve ser consistente (invariantes preservados).

Até aí tudo bem.

A maioria dos tutoriais que eu vi sugerem que as raízes agregadas devem ser obtidas desta maneira:

UserAggregate user = mUsersRepository.getUser(userId);

Nesse ponto, fico confuso.

Vamos supor que cada chamada para UsersRepository retorne uma nova instância de UserAggregate ("approach 1"). Isso pode ser benéfico porque elimina a necessidade de garantir a segurança do thread (no código). No entanto, se vários objetos UserAggregate forem usados simultaneamente, esses objetos poderão ficar fora de sincronia um com o outro (enquanto permanecerem consistentes individualmente). Tenho a sensação de que ter várias instâncias "não sincronizadas" do mesmo agregado pode levar a todos os tipos de erros desagradáveis, mas não posso ter certeza sobre isso.

Agora vamos supor que cada chamada para UsersRepository retorne o mesmo UserAggregate object ("approach 2"). Em seguida, a instanciação e o armazenamento em cache de UserAggregate dentro de UserRepository precisam ser feitos com segurança de encadeamento. Além disso, o armazenamento em cache deve se basear em referências fracas; caso contrário, todo o banco de dados de usuários será mantido na memória. Minha principal preocupação, no entanto, é que neste cenário o UserAggregate deve se tornar seguro para o thread. Parece que será um grande PITA para garantir a segurança do thread de todas as raízes agregadas. Também pode se tornar um problema de desempenho. Não mencionando o risco associado a erros multi-threading ...

Se isso fosse tudo, eu provavelmente iria com a "abordagem 2" porque, pelo menos, eu sei como torná-la segura. No entanto, existe essa noção de bloqueio otimista com base na versão agregada que (no meu entender) se aplica à "abordagem 1".

Se eu entendi corretamente, cada agregado recebe uma versão associada a ele. Em cada atualização do estado do agregado, a versão é verificada e, se a versão no banco de dados não for igual à versão do agregado em cache, a atualização falhará. Em seguida, o estado agregado pode ser sincronizado do banco de dados e a nova tentativa de atualização. Isso parece resolver meus medos relacionados à inconsistência entre diferentes instâncias do agregado. No entanto, parece muito trabalho - cada método que atualiza o agregado pode falhar, portanto, o código que o usa deve ser capaz de tentar novamente a operação.

Minhas perguntas são:

  1. O meu entendimento (resumido acima) está correto?
  2. Existem problemas adicionais associados à abordagem 1/2 que não vejo?
  3. Existem abordagens adicionais que eu não vejo?
  4. Existe uma abordagem padrão que a comunidade DDD usa?
por Vasiliy 04.06.2017 / 16:49
fonte

1 resposta

4
UserAggregate user = mUsersRepository.getUser(userId);

At this point I become confused.

Sim, isso é um pouco complicado.

However, if multiple UserAggregate objects will be used simultaneously, these objects might get out of sync with each other (while staying consistent individually). I get a feeling that having several "non-synchronized" instances of the same aggregate can lead to all kinds of nasty bugs, but I can't really be sure about it.

Existem duas preocupações.

A primeira é que as operações que lêem o estado do agregado podem estar lendo uma cópia antiga. Na maior parte, a literatura reconhece que isso simplesmente significa que as leituras são "eventualmente consistentes". É análogo a trabalhar com recursos mutáveis na Web - o Alice POST é uma alteração no recurso, mas Bob não vê essa alteração imediatamente porque ele tem uma cópia em cache.

O padrão CQRS chega ao ponto de levar o suporte para consultas (leituras) completamente fora do modelo - o O papel de suas raízes agregadas é garantir que a consistência seja mantida durante as gravações.

A segunda é que, se tivermos vários escritores (dois threads, possivelmente em processos diferentes, possivelmente em hosts diferentes) executando um write ao mesmo tempo , podemos acabar com um cérebro partido; "o" agregado em dois estados diferentes, dependendo de onde você olha, e podemos perder as edições quando um é salvo em cima do outro.

Esse é o problema que é "real" e precisa ser abordado.

Uma resposta é restringir-se a um único escritor. A arquitetura LMAX é um exemplo disso - todos os comandos são linearizados, publicando-os em uma fila, e "o" escritor então consome a fila e gerencia todas as gravações. Então, você resolve o problema dos conflitos não tendo nenhum - ou, mais corretamente, aceita um problema de eleição do líder em troca do problema de conflito.

Com vários escritores; A resposta usual é reconhecer que o banco de dados (seja um RDBMS ou uma coisa sem SQL ou um sistema de arquivos) é o livro de registros - é o que você vai usar como "verdade" se a energia for desligada. . Portanto, seus escritores estão compartilhando o banco de dados e você precisa impor a integridade da gravação nele.

Eu só sei de duas respostas - seus escritores correm para pegar uma fechadura, e o vencedor ganha autoridade de escrita até que a fechadura seja liberada, ou seus escritores correm para comparar e trocar.

If I understand it correctly, each aggregate gets a version associated with it. On each update of aggregate's state the version is checked, and, if the version in DB is not the same as the version of the cached aggregate, the update fails.

Isso é "compare e troque", pelo menos logicamente. Você pode precisar jogar jogos com números de versão, ou posições de fluxo, ou algo parecido, para obter uma implementação funcional, mas logicamente eles são variações de "alterar o estado atual daquele localizado lá para aquele localizado aqui ". PUT condicional, por assim dizer.

However, it feels like a lot of work - each method that updates the aggregate can fail, therefore the code that uses it must be capable of retrying the operation.

Aqui estão as notícias "boas" - em um sistema distribuído, você precisava se preocupar com o problema mesmo assim . O banco de dados pode não estar disponível, pode estar sobrecarregado, o sistema de arquivos pode estar cheio etc. É um trabalho, mas não é um trabalho extra .

Is there a standard approach that DDD community uses?

A maioria das pessoas que estão fazendo o sourcing de eventos tendem a usar comparar e trocar, especialmente quando usam armazenamentos de eventos NoSql que suportam gravações idempotentes.

Mas meu sentimento é que ES ainda é uma minoria relativa no espaço; é menos claro para mim qual abordagem as pessoas salvando o estado agregado estão usando.

    
por 05.06.2017 / 06:11
fonte