No CQRS / ES, um comando pode criar outro comando?

10

No CQRS / ES, um comando é enviado do cliente para o servidor e roteado para o manipulador de comando apropriado. Esse manipulador de comando carrega um agregado de seu repositório, chama algum método e salva de volta no repositório. Eventos são gerados. Um manipulador de eventos / saga / gerenciador de processos pode ouvir esses eventos para emitir comandos.

Portanto, os comandos (entrada) produzem eventos (saída), que podem então retornar ao sistema mais comandos (entrada). Agora, é prática comum para um comando não emitir nenhum evento, mas enfileirar outro comando? Essa abordagem pode ser usada para forçar a execução em um processo externo.

EDITAR:

O caso de uso específico que tenho em mente é o processamento dos detalhes do pagamento. O cliente envia um PayInvoicecomando cuja carga útil inclui os detalhes do cartão de crédito do usuário. O PayInvoiceHandlerpassa um MakeInvoicePaymentcomando para um processo separado, responsável pela interação com o gateway de pagamento. Se o pagamento for bem sucedido, um InvoicePaidevento será gerado. Se, por algum motivo, o sistema travar após o PayInvoicecomando persistir, mas antes do MakeInvoicePaymentcomando persistir, podemos rastrear isso manualmente (nenhum pagamento será realizado). Se o sistema travar após o MakeInvoicePaymentcomando persistir, mas antes doInvoicePaidSe o evento persistir, podemos ter uma situação em que o cartão de crédito do usuário é cobrado, mas a fatura não é sinalizada como paga. Nesse caso, a situação teria que ser investigada manualmente e a fatura marcada manualmente como paga.

magnus
fonte

Respostas:

11

Em retrospecto, acho que estava complicando a questão.

Em geral, os comandos devem lançar uma exceção ou gerar um ou mais eventos.

Se eu pudesse resumir a arquitetura do Event Sourcing, seria a seguinte:

  • Comandos são entradas que representam instruções para fazer algo.
  • Eventos são saídas que representam fatos históricos do que foi feito.
  • Os manipuladores de eventos podem escutar eventos para emitir comandos, ajudando a coordenar as diferentes partes do sistema.

Fazer um comando criar outro comando causa ambiguidade no significado geral de comandos e eventos: se um comando "aciona" outro comando, isso implica que um comando é "um fato histórico do que foi feito". Isso contradiz a intenção desses dois tipos de mensagens e pode se tornar um caminho escorregadio, pois outros desenvolvedores podem acionar eventos de eventos, o que pode levar a dados corrompidos e eventual inconsistência .

Em relação ao cenário específico que eu propus, o fator complicador foi que eu não queria que o encadeamento principal interagisse com o gateway de pagamento, pois (sendo um processo persistente e de encadeamento único), isso não permitiria que outros comandos fossem processados . A solução simples aqui é gerar outro thread / processo para lidar com o pagamento.

Para ilustrar, o cliente envia um PayInvoicecomando. O PayInvoiceHandlerprocesso inicia um novo processo e passa os detalhes do pagamento. O novo processo se comunica com o gateway de pagamento. Se o pagamento foi bem-sucedido, ele liga invoice.markAsPaid()com o número do recibo (que produz o InvoicePaidevento). Se o pagamento não tiver êxito, ele chama invoice.paymentFailed()com passa um código de referência para uma investigação mais aprofundada (que produz o InvoicePaymentFailedevento). Portanto, apesar do envolvimento / processo separado, o padrão permanece Command -> Event.

Em relação à falha, há três cenários: o sistema pode falhar após a PayInvoicepersistência do comando, mas antes da persistência dos eventos InvoicePaidou InvoicePaymentFailed. Nesse caso, não sabemos se o cartão de crédito do usuário foi cobrado. Nesse caso, o usuário notará a cobrança no cartão de crédito e fará uma reclamação; nesse caso, um membro da equipe poderá investigar o problema e marcar manualmente a fatura como paga.

magnus
fonte
1
Muito obrigado pela pergunta e pela resposta, boa comida para pensar. No geral, ainda existem alguns pontos que acho confusos: (1) Não vi muitas pessoas falando sobre a noção de manipuladores de eventos. (2) Minha interpretação de sua visão muda a maior parte do trabalho para os manipuladores de eventos, a ponto de o manipulador de comandos se tornar uma função de tradução pura, levantando a questão se a separação em comandos / eventos é necessária. Consulte esta pergunta de acompanhamento se estiver interessado.
bluenote10
3

Você obterá um sistema que é arquitetonicamente mais frouxamente acoplado se você emitir apenas eventos de um comando. Em outras palavras, um comando não precisa saber quais outros comandos externos devem ser emitidos; isso deve ser de responsabilidade da parte externa (que deve se inscrever no evento e pode ser, como você mencionou, um gerente de saga com responsabilidades de coordenação ou apenas outro módulo que depende desses eventos).

Erik Eidt
fonte
2

Visualização recomendada: Udi Dahan em Reliable Messaging - não é exatamente o que você está descrevendo, mas está intimamente relacionado.

Agora, é prática comum para um comando não emitir nenhum evento, mas enfileirar outro comando?

Não vi ninguém recomendando essa prática.

Resposta curta: se você não salvar algum estado, não poderá recuperar o comando enfileirado se travar após confirmar que o recebeu.

Se você decidir salvar o estado, não está claro que haja grande vantagem em agendar o segundo comando desde o primeiro, em vez de usar um manipulador de eventos.

VoiceOfUnreason
fonte