0
votes

During development of my application, I found that I need to emit some events that actually don't modify the state of the aggregate, but they are needed in order to update read models (transient events?). e.g. if in my code (domain model) I hold state of hierarchy of numbers in layers like:

1    4    7
     5    8
3    9

and the read model is doing projection of events like (top number from left to right):

1
5
3

then, when I trigger event in aggregate root RemovedNumber(1), and if this is the only event I trigger (since it is enough to update aggregate state), read model will not know that it needs to replace number 1 with 4.

? <--- SHOULD BE 4 SINCE 4 IS UNDER 1
5
3

So here basically, I need to trigger additionally: NowShowNumber(4 instead of 1), and then read model will know to project:

4
5
3

Event RemovedNumber(1) should be kept in event store, since it affects internal state of aggregate. Event NowShowNumber(4 instead of 1) should also be stored in event store since it is affecting read model (and should be replayed on re-projecting it), but it should probably not be used during reconstruction of aggregate root from event stream.

Is this standard practice in CQRS/Event Sourcing systems? Is there some alternative solution?

4
First of all, you need to decide if the logic that deduces (put 4 instead of 1) is purely display logic or domain logic. If it affects the way in which the user can modify the Aggregate later, then it's probably not just read model stuff. The algorithm should be in the Domain and its decision reflected in the event. See @EbenRoux's answer. - guillaume31

4 Answers

1
votes

Why doesn't the Read model know to show number 4? Didn't the Aggregate emit an AddNumber(4) prior to AddNumber(1)? Then the Read model has the necessary state replicated on his part, basically a stack with numbers, in order to pull the previous number and to show it.

In CQRS, in order to help the Read models, when a state changes and an Event is emitted, the Aggregate include bits of the previous state in the Event also.

In your case, the emitted Event could have the following signature RemovedNumber( theRemovedNumber, theNewCurrentNumber), and in particular RemovedNumber(1, 4).

1
votes

I call these events out of band events and save them to a different stream than I hydrate aggregates with. Haven't heard anyone else doing it but haven't heard any good arguments to not do it - especially if you have a legitimate case for posting events that have no effect at all on the aggregate.

In your case if I understand your problem well enough I would just have the domain write a TopLevelNumberChanged event which the read model would see and process. And obviously it would not read that event when hydrating.

0
votes

I cannot see that it is at all an issue having events that don't effect changes in your projections. Depending on the projection it may be that the projection ignores many events.

That being said, if you are saying that these two events go hand-in-hand you may need to have another look at the design / intention. How do you know to call the second command? Would a single command not perhaps do the trick? The event could return the full change:

NumberReplacedEvent ReplaceNumber(1, 4);

The event would contain all the state:

public class NumberReplacedEvent
{
    int ReplacedNumber { get; set; }
    int WithNumber { get; set;
}
0
votes

From my understanding, there's no single correct answers. CQRS / Event Sourcing is just a tool for helping you to model your data flow. But it's still your data, your business rules, your use case. In other words: Some other company could use the exact same data model, but have a different event structure, because it fits better for their use case.

Some example:

Let's imagine we have an online shop. And every time a customer buys a product, we decrease the inStock value for that product. If the customer sends the product back, we increase the value.

The command is pretty simple: BuyProduct(id: "123", amount: 4)

For the resulting event we have (at least) 2 options:

  1. ProductBuyed(id: "123", amount: 4) (delta value)
  2. ProductBuyed(id: "123", newInStockValue: 996) (new total value)

(you could also publish 4 times a simple ProductBuyed(id: "123") event)

Or you can have multiple resulting events at the same time:

  • ProductBuyed(id: "123", amount: 4)
  • InStockValueForProductChanged(id: "123", newValue: 996)

An online shop will possibly have multiple read models that are interested in these events. The Product Page wants to display only 996 items left!. And the Shop Statistics Page wants to display sold 4 items today. Though both options (total and delta) can be useful.

But also both Pages could work if there's only one of both events. Then the read side must do the calculation: oldTotal - newTotal = delta or oldTotal - delta = newTotal.

There are even more possible solutions. For example:

  1. Checkout Service publishes ProductBuyed(id: "123", amount: 4) event
  2. Stock Service receives this event, decreases the stock and then publishes the InStockValueForProductChanged(id: "123", newValue: 996) event

It really depends on the needs of your business.

My suggestions:

  • I prefer when the write model is only responsible for managing the business rules. Get Command, validate it, publish event(s) which look pretty similar to the command contents.
  • And the read model should be as simple as possible, too. Get Event, update model.
  • If calculations have to be done, there are a few options:
    1. The calculation is part of a business rule? Then your write side has to compute the result anyway. In this case you already have written the algorithm, the CPU has done its work, and you have the resulting value for free. (Just include the result with the published event)
    2. The calculation is really complex and/or there are multiple event consumers that need the result. Then it might be better to compute it once and include the result in an event, instead of computing it n times for every involved event consumer. Complex could mean:
      • Takes a lot of time
      • Very CPU / memory intensive
      • Needs special / huge external libs (imagine you had to include some Image Processing library with every read service)
    3. The calculation is the result of a combination of a lot of different events (i.e. it's getting complex): Build an external service, which is responsible for the calculation. This way you can easily scale out by providing multiple instances of this service.
    4. If the calculation is not part of a business rule and it's simple and only a single service needs the result or if it's only relevant for the read model: Place it in the read side.

In the end it's a tradeoff:

  • Duplicate algorithms? You could have multiple event consumers written with different programming languages. Do you want to implement the algorithm multiple times?
  • More network traffic / bigger event store? If you include the calculation result with the event, there's more data to store and transfer between the services. Can your infrastructure handle that?
  • Can your write / read service take the additional load?