Estou tentando aprender maneiras de DDD e assuntos relacionados. Eu tive uma idéia de um contexto limitado simples para implementar o "banco": existem contas, o dinheiro pode ser depositado, sacado e transferido entre eles. Também é importante manter o histórico de mudanças.
Identifiquei a entidade da conta e que a fonte do evento seria boa para acompanhar as alterações nela. Outras entidades ou objetos de valor são irrelevantes para o problema, portanto, não os mencionarei.
Ao considerar depósitos e saques - é relativamente simples, porque há apenas um agregado modificado.
Ao transferir é diferente - duas agregações devem ser modificadas por um evento MoneyTransferred . O DDD descontinua a modificação de vários agregados em uma transação. Por outro lado, a regra da fonte de eventos é aplicar eventos às entidades e modificar o estado com base nelas. Se o evento pudesse ser armazenado simplesmente no banco de dados, não haveria problema. Mas, para impedir a modificação simultânea de entidades originadas por eventos, devemos implementar alguma versão do fluxo de eventos de cada agregado (para manter seus limites de transação). Com o controle de versão, surge outro problema - não posso usar estruturas simples para armazenar eventos e lê-los novamente para aplicá-los à agregação.
Minha pergunta é: como posso reunir esses três princípios: "uma transação agregada", "evento-> alteração no agregado" e "prevenção de modificação simultânea"?
fonte
Um detalhe importante no entendimento de contas baseadas em transações: o
balance
atributo deaccount
é na verdade uma instância de desnormalização. Está lá por conveniência. Na realidade, o saldo de uma conta é a soma de suas transações, e você realmente não precisa que a própria conta tenha um saldo.Tendo isso em mente, o ato de transferir um dinheiro não deve ser atualizar,
account
mas inserirtransaction
.Dito isto, há outra regra importante: o ato de adicionar a
transaction
deve ser atômico com uma atualização do (campo de equilíbrio desnormalizado de)account
.Agora, se eu entendo o conceito DDD de agregados, o seguinte parece relevante:
Então, em termos de design DDD, eu sugeriria:
Há um agregado para representar a transferência
O agregado é composto pelos seguintes objetos: a transferência (o objeto raiz); o objeto raiz está vinculado a duas listas de transações (uma para cada conta); e cada lista de transações está vinculada a uma conta.
Todo o acesso à transferência deve ser meditado pelo objeto raiz (the
transfer
).Se você estiver tentando implementar o suporte à transferência assíncrona, seu código principal deve se preocupar apenas em criar a transferência, no status "pendente". Você pode ter outro segmento ou um trabalho que realmente mova o dinheiro (inserindo no histórico de transações e, portanto, atualizando saldos) e defina a transferência como "lançada".
Se você deseja implementar uma transação de transferência de bloqueio em tempo real, a lógica de negócios deve criar uma
transfer
e esse objeto coordenaria as outras atividades em tempo real.Em termos de prevenção de problemas de simultaneidade, a primeira ordem do dia deve ser inserir a transação de débito na lista de transações da conta de origem (atualizar o saldo, é claro). Isso teria que ser executado atomicamente no nível do banco de dados (através de um procedimento armazenado). Após a ocorrência do débito, o restante da transferência deverá ser bem-sucedido, independentemente de problemas de simultaneidade, pois não deve haver nenhuma regra comercial que impeça um crédito na conta de destino.
(No mundo real, as contas bancárias têm o conceito de uma postagem de memorando que suporta o conceito de confirmação lenta de duas fases. A criação da postagem de memorando é leve e fácil, e também pode ser revertida sem problemas. A mensagem de memorando para uma postagem difícil é quando o dinheiro realmente se move - isso não pode ser revertido - e representa a segunda fase do commit de duas fases, ocorrendo somente após todas as regras de validação terem sido verificadas).
fonte
Atualmente, também estou na fase de aprendizado. Do ponto de vista da implementação, é assim que sinto que você executará essa ação.
Dispatch TransferMoneyCommand que gera os seguintes eventos [MoneyTransferEvent, AccountDebitedEvent]
Observe que antes de gerar esses eventos, será necessário executar a validação superficial do comando e a lógica do domínio, ou seja, a conta possui saldo suficiente?
Persista os eventos (com controle de versão) para garantir que não haja problemas de consistência. Observe que pode haver outro comando simultâneo (como retirar todo o dinheiro) que conseguiu obter êxito e salvar eventos anteriores a esse, portanto o estado atual do agregado pode estar desatualizado e, portanto, os eventos são gerados no estado antigo e estão incorretos. Se o salvamento dos eventos falhar, você precisará tentar novamente o comando desde o início.
Depois que os eventos são salvos com sucesso no banco de dados, você pode publicar os dois eventos que foram gerados.
O AccountDebitedEvent removerá o dinheiro da conta do pagador (atualiza o estado agregado e quaisquer modelos de exibição / projeção relacionados)
MoneyTransferEvent inicia o Saga / Process Manager.
O trabalho do gerente da saga / processo será tentar creditar a conta do beneficiário; se falhar, será necessário creditar o saldo de volta ao pagador.
O gerente da Saga / Process publicará um CreditAccountCommand que será aplicado à conta do beneficiário e, se for bem-sucedido, o AccountCreditedEvent será gerado.
Do ponto de vista da fonte de eventos, se você quiser reverter essa ação, todos os eventos nesta transação terão o ID de correlação / causalidade como o TransferMoneyCommand original, que você pode usar para gerar eventos para operações de desfazer / reversões.
Sinta-se à vontade para sugerir problemas ou melhorias em potencial no que foi dito acima.
fonte