Disclaimler: I'm only taking my first steps in the CQRS world, but I can offer my current understanding of the matter and we'll see if others confirm. All I write below has an underlying "as I see it" theme, and is not authoritative.
The 80% case
To answer your question, commands are indeed a point-to-point affair. When a command enters a controller (MVC webapp), that controller then asks a command dispatcher to find one and only one apropriate command handler, and delegates the work to that handler.
Why not publish?
It's a question of responsibility. If something sends a command, it entails expectation that it will be fulfilled. If you simply publish and hope that something somewhere picks it up and acts on it, there is no guarantee that this will be the case. By extrapolation, you also don't know if multiple handlers don't decide to act on a command, possibly resulting in the same change being applied more than once.
Events, on the other hand, are informative in nature, and it's reasonable to expect zero, two, or more components to be interested in a particular event. We don't really care in the scope of making the requested change.
Example
This could be compared to real life. If you have three children, walk into a room and simply shout "Clean the bathroom," you have no guarantee that someone will, and perhaphs if it won't be done twice (if you have obedient children that is ;-) You should fare better if you assign a specific child to do what you want done.
When that child finishes its job however, it's convenient if it shouts out "bathroom has been cleaned," so that everyone who wants to brush their teeth knows they can now do so.
Review what Rinat Abdullin wrote about evolving business process. In particular, notice his recommendation for developing a business process in a fast changing environment -- a process manager is "just" an automated replacement for a human being staring at a screen.
My own mental model of a process manager is that it is an event sourced projection that you can query for a list of pending commands.
Do I need to persist the process manager? It seems like I do, but I'm not sure
It's a read model. You can rebuild the process manager from the history of events each time you need it; or you can treat it like a snapshot that you update.
If I do, I need to save the events for the process manager.
No - the process manager is a manager. It doesn't do anything useful on its own; instead it tells aggregates to do work (ie, make changes to the book of record).
How do I know what basket the ProductReserved events are for? Is it OK to have a BasketId on those too, or is that leaking info
Sure. Note: in most "real" shopping domains, you wouldn't insist on reserving inventory before processing an order; it adds unnecessary contention to the business. It's more likely that your business would want to accept the order, then apologize in the rare case that the order can't be fulfilled in the required time.
How do I keep a relationship between events, how do I know which ItemAdded produced which ProductReserved event?
Messages have meta data - in particular, you can include a causationIdentifier (so you can identify which commands produced which events) and a correlationIdentifier, to generally track the conversation.
For instance, the process manager writes its own id as the correlationId in the command; the events produced by a copy the correlation id of the command, and your process manager subscribes to all events that have its own correlationId.
Should I implement the Basket as a process manager instead of a simple aggregate?
My recommendation is no. But Udi Dahan has a different opinion that you should review; which is that CQRS only makes sense if your aggregates are sagas -- Udi used saga in the place where process manager has become the dominant spelling.
should process managers retrieve aggregates?
Not really? Process managers are primarily concerned with orchestration, not domain state. An instance of a process will have "state", in the form of a history of all of the events that they have observed -- the correct thing to do in response to event Z depends on whether or not we have seen events X and Y. So you may need to be able to store and load a representation of that state (which could be something flat, or could be the history of observed events).
(I say "not really" because aggregate is defined vaguely enough that it's not completely wrong to claim that list of observed events is an "aggregate". The differences are more semantic than implementation -- we load process state and then decide what messages to send to the parts of the system responsible for domain state. There's a bit of hand waving going on here.)
So the PM does not need to use one type of state management over another because it is only responsible for doing stuff live and never during replays?
Not quite - state management isn't a do-er, it's a keeper tracker of-er. In circumstances where the process manger shouldn't emit any signals, you give it inert connections to the world. In other words, dispatch(command)
is a no-op.
Best Answer
In retrospect, I think I was complicating the issue.
In general, commands should either throw an exception or raise one or more events.
If I could summarise the architecture of Event Sourcing it would be as follows:
Having one command create another command causes ambiguity in the general meaning of commands and events: if one command "triggers" another command, it is implying that a command is "an historical fact of what was done". This contradicts the intent of these two types of messages, and could become a slippery path, in that other developers could trigger events from events, which could lead to corrupt data and eventual inconsistency.
In regards to the specific scenario I posed, the complicating factor was that I didn't want the main thread to interact with the payment gateway, as (being a persistent, single-threaded process), this would not allow other commands to be processed. The simple solution here is to spawn another thread/process to handle the payment.
To illustrate, the client sends a
PayInvoice
command. ThePayInvoiceHandler
starts a new process and passes it the payment details. The new process communicates with the payment gateway. If the payment was successful, it callsinvoice.markAsPaid()
with the receipt number (which produces theInvoicePaid
event). If the payment was unsuccessful, it callsinvoice.paymentFailed()
with passes a reference code for further investigation (which produces theInvoicePaymentFailed
event). So despite the fact that a separate process/thread is involved, the pattern remainsCommand -> Event
.In regards to failure, there are three scenarios, the system could crash after the
PayInvoice
command is persisted but before theInvoicePaid
orInvoicePaymentFailed
events are persisted. In this case, we don't know if the user's credit card was charged. In such a case, the user will notice the charge on their credit card and make a complaint, in which case a member of staff can investigate the issue and manually mark the invoice as paid.