0
votes

In an event sourced system, I have an aggregate root which is of type Order. Let's assume the following events is taking place:

  • OrderPlaced (orderId, placedAt, customerId, orderLines) where OrderLine (lineId, productId, price)
  • OrderAccepted (orderId)

And let's assume we need two different projections:

  1. An projection holding the total price for all accepted orders grouped by year for each customer. Something like this:

     OrdersByCustomer(customerId, summationOnAcceptedOrdersByYear) where SummationOnAcceptedOrdersByYear(year, sum)
    

    The issue here is that OrderAccepted doesn't contain customerId. So when the projection receives an OrderAccepted it has no way of getting the current projection state, as the customerId is the documentId. Worth noting is that I'm storing the projections in Scylla - which is only queryable by the partition key - and the projection state is just a json representation. So not queryable by anything other then the documentId/projectionId. Maybe this is not an ideal choice of technology for projections...?

I'm thinking I have two options if going forward with Scylla:

  • Either pollute OrderAccepted with customerId. But I don't feel this is a good approach - then I would need to incorporate that into all events which would be related to an projection where the projectionId / documentId is not the same as the aggregateId.

  • Or have a separate table which contains a mapping between orderId and customerId, so the projection could query for customerId - this table would probably need to be updated in the command handler of the Order aggregate.

  • Alternatively we could have a projection for CustomerIdByOrderId - but here I think the projections can be in different positions of the event stream - this might be causing issues as well.

  1. An projection which sums up the price of all accepted orders for each product. Something like this

     SummationForProducts (productId, orderSummation)
    

    So here we have an projection which relies on both OrderPlaced and OrderAccepted. And the thing is that since an OrderPlaced may contain multiple orderLines, thus spanning multiple projections - we would need to update multiple projections when receiving OrderPlaced. Is this normal in Event Sourcing Projections - to update multiple projections pr event?

    And the same issue arises here as to OrderAccepted don't include productIds from OrderPlaced event. So here we could probably do with an similar approach to have a table which contains a mapping between orderId and productIds

I wonder how more experienced event sourcerers solve these things...? :) Any input on this is highly appreciated.

1

1 Answers

1
votes

The beauty with event sourced systems is that all the information you need is - or at least should be - encapsulated in the events.

I'm going to assume you are using the CQRS pattern, and from your description the problem is centered around what the projection(s) should look like. In other words, you should not really need to change anything on the command/event side, but rather focus on how to get the data you need from your events to build up the appropriate projections.

I'm not familiar with Scylla, but you are free to build up your projections whichever way works for you. Remember that a projection is just data that you can query built up from events.

The simplest thing I can imagine is to just store each order, including the status. Something like this (in terrible pseudo code):

-- Order Summary event handler
on(orderPlaced):
insert into Order(orderId, datePlaced, customerId, orderLines, status)
values (orderPlaced.orderId, orderPlaced.date, orderPlaced.customerId, orderLines, 'placed')

on(orderAccepted):
update Order
set status = 'accepted'
where orderId = orderAccepted.orderId

-- Sum of accepted orders per customer and year
select customerId, year, sum(orderLines.price) from Orders
where status = 'accepted'
group by customerId, year(datePlaced)

-- Sum per product of accepted orders
select productId, sum(orderLines.price) from Orders
where status = 'accepted'
group by orderLines.productId, year(datePlaced)

Note that the above code assumes events are processed in order.

we would need to update multiple projections when receiving OrderPlaced. Is this normal in Event Sourcing Projections - to update multiple projections pr event?

Sure, it's perfectly normal. You can use a single event to update multiple projections or multiple events to update a single projection, or a combination of both.