CQRS async query from command handler

asynchronous-programmingcqrsmessagingmicroservices

In a CQRS/async microservices context, I have a command handler e.g. SendOrderConfirmationEmailCommandHandler implemented in a communications service.

This depends on information from the orders service, which can be retrieved via an GetOrderDetailsQueryHandler.

The sequence of messages for this process is:

SendOrderConfirmationEmailCommand -> GetOrderDetailsQuery -> OrderDetailsRetrievedEvent

Since all messages (including the GetOrderDetailsQuery) are asynchronous by necessity the process executing the SendOrderEmailCommandHandler would exit after dispatching an GetOrderDetailsQuery and then would somehow need to resume where it left off once the OrderDetailsRetrievedEvent is handled.

What sort of pattern can be implemented to deal with bridging the gap between firing off a query and the response message being received such that we are able to retain and link back to the original command message instance for which we have retrieved order details?

Can or should this be handled in the CommandHandler or elsewhere instead?

Best Answer

You are most likely to see analogous examples if you search for "process manager" or "saga".

Speaking broadly, what you are describing is a protocol, with at least three participants; the command source, the order details database, and the email service itself.

The responsibility of the protocol is to keep track; it is doing the book keeping, which is to say it is recording the events that are part of this interaction. Here, the events would be

CommandReceived
OrderDetailsRetrieved
EmailSent

(Choose better spellings if you can). When one of these things happens, the process manager writes it down. When it is the process manager's turn to act, the manager looks at the events it has seen, computes what actions (if any) should be taken next, and reports/executes those actions.

Report vs execution is a matter of style

List<Action> actions = processManager.actions()

Here, the process manager gives you an in memory representation of the actions, and the consumer can decide how to deal with them.

processManager.act(orderDetailsClient, emailClient)

Here, the process manager computes the action to take, and invokes the appropriate method on the appropriate client.

Cory Benfield's talk Building Protocol Libraries the Right Way is a good starting point.

Related Topic