Most* Message Oriented Middleware platforms provide a mechanism for the schematic validation (i.e. against a schema) of incoming messages as they are enqueued. This is logic that checks for things like missing/null fields, values outside of certain ranges, bad formatting, etc. But, as these checks are being done by the message broker, and not the end system - cannot check references such as customer ID exists, order exists, etc.
* "Most" of the strongly-typed message brokers, not just systems that queue dumb strings.
For example, in JMS a 'Validator' method can be assigned: https://docs.spring.io/spring/docs/4.1.1.RELEASE/spring-framework-reference/html/jms.html#jms-annotated-method-signature
I believe WCF has something similar, closest thing I could find was this article.
As for your options, I believe 4 is probably the most common. There are reasons (to do with scalability) why this response channel is preferable to a request-response driven system (I can validate your message and send you the Exception whenever, rather than tying up a socket + process/thread on each end while you wait for me to do it live, etc).
We're building two microservices that use CQRS and Event Sourcing: an Order Service and an Inventory Service. When an order is made, the inventory of that item must be decreased by the amount ordered. An order can also be canceled. In that case, the inventory must be increased again.
You may be getting yourself tripped up because you are conflating manipulation of the order with reservation of the inventory.
Hint: creating an order doesn't deplete your stock; shipping product depletes stock.
Sometimes the patterns don't fit because they are trying to tell you that the model is broken.
Send a message with only the OrderId but have the Inventory Service listen to all necessary messages of the Order Service and build its own read model of the orders.
I want to call this out explicitly as a bad idea -- the inventory service should not be trying to build its own read model of orders; that couples the inventory service to the internals of the order model, and means that you can't easily change the order model without also re-deploying the inventory service.
But... it's perfectly reasonable for the inventory service to have its own model for "reserved inventory" that it builds up because of the messages that it receives from the order service.
In other words, during cancellation the inventory service doesn't care what the order items are, it just cares what stock was reserved for that order. It can look in its own store for that....
How do I tackle the issue where an Event Handler needs more information?
One thing to keep in mind: by the time an event reaches an event handler, the event is stale.
If the Inventory Service receives an event, and needs additional information to act upon it, then it queries the authority for the information it needs. So if the inventory service needs to know the state of the order because the Order was cancelled, then it sends a query to the order service asking for the information that it needs.
Note the separation - you establish an api contract that allows the inventory service to ask for what it needs, and a schema for the messages exchanged. The two services can independently change in any way that respects that API.
So you might imagine an exchange where the inventory service gets a thin notification that an order was cancelled, and then queries the order service to get a fatter representation of the order at that time.
That query doesn't necessarily need to be synchronous -- the result of the query is just another input message to the inventory service.
Does a microservices architecture mean I will need lots of read models (aka endpoints) per client?
Write only databases aren't very interesting, so you are going to need endpoints that let you get the data back out. If you are careful about your api designs, you won't necessarily need more read models than you would in a monolith. But you will sometimes need to support more than one read model for a given capability, so that legacy and upgraded clients can both access the information that the need.
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
(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
Here, the process manager gives you an in memory representation of the actions, and the consumer can decide how to deal with them.
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.