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.
So in the Order Service we store an OrderCreated
event that contains the necessary details for the Inventory Service: a list of Item
instances with their IDs and amounts. Sample code:
public class OrderCreated
{
public int OrderId { get;set; }
public IList<Item> Items { get; set; }
}
public class Item
{
public int Id { get; set; }
public int Amount { get; set; }
}
This event is also sent to the Inventory Service (could be mapped to a shared contract), so the Inventory Service can decrease the stock for the items.
When an order is canceled, we store an OrderCanceled
event. For the Order Service, this only needs to contain an OrderId
, eg:
public class OrderCanceled
{
public int OrderId { get;set; }
}
But how do we now send the items to the Inventory Service?
Option 1
Add the items to the OrderCanceled
event:
public class OrderCanceled
{
public int OrderId { get;set; }
public IList<Item> Items { get; set; }
}
This seems strange to us, because when re-applying the event (ie hydrating), this serves no purpose.
Option 2
Create an Event Handler for the OrderCanceled
event, that retrieves the aggregate and accesses items, eg (not-real-life-code):
public class MyEventHandler : IEventHandler<OrderCanceled>
{
public void Handle(OrderCanceled e)
{
var order = _repository.Get(e.OrderId);
var message = new OrderCanceledMessage
{
OrderId = e.OrderId,
Items = order.Items.Select(x => new ItemMessage(...))
};
_messageSender.Send(message);
}
}
The weird thing here is that we're retrieving the Order
instance, even though we already retrieved it in our command handler previously (i.e. when the user canceled the order, a command was sent to the system). So we retrieved our aggregate twice.
Option 3
The same as option 2, but instead of retrieving the Order
in the Event Handler, we let the Event Handler call the read model to retrieve the items.
But in most articles I read, it is advised not to have the command side call the query side (though often with little argumentation).
Option 4
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.
This means we would actually have components create a read model for their own use, instead of read models for others. In the above example, the Inventory Service would build a model of the orders with its items.
The advantage seems here that you can build a read model purely for your own use. In a situation of microservices, we fear that a component might have to build too many different read models for all the different microservices.
A disadvantage could be that changes/additions of events in the Order Service would require changes in the read model generation of the Inventory Service.
So what am I asking here?
- How do I tackle the issue where an Event Handler needs more information?
- Does a microservices architecture mean I will need lots of read models (aka endpoints) per client?