Um detalhe importante no entendimento de contas baseadas em transações: o balance
atributo de account
é 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 inserir transaction
.
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:
O agregado é um limite lógico para coisas que podem mudar em uma transação comercial de um determinado contexto. Um agregado pode ser representado por uma única classe ou por várias classes. Se mais de uma classe constitui um agregado, uma delas é a chamada classe ou entidade raiz. Todo o acesso ao agregado de fora deve acontecer através da classe raiz.
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).