Oskar Dudycz

Pragmatic about programming

Should you throw an exception when rebuilding the state from events?

2022-06-08 oskar dudyczEvent Sourcing

cover

Event Sourcing is not a difficult pattern to understand. Event stores are not conceptually complicated databases (although implementation may be quite complex). We have two data structures in them:

  • event, a fact that has occurred in our system. For instance: the order has been started, the shopping cart has been confirmed, a cinema seat has been reserved,
  • events stream, an ordered sequence of events. The stream conceptually corresponds to the record/entity/aggregate in the classical approach. Typically, one record (e.g., one reservation, one order) is represented by one stream. In other words, these are all facts recorded for a given object.

Additionally, we have two types of operations:

  • append an event to the end of the selected stream,
  • read all events for the stream.

That’s all, and that’s enough to implement the simplest event store. I even showed how to do it in one hour!.

In Event Sourcing, each business operation ends with the registration of its results, i.e. a new event. To execute business logic, we need to know its current state. In Event Sourcing, the state is events. We don’t have to make a revolution, and we can still use entities and a “flattened” current entity state. We need to turn the sequence of events into an object to get it. How to do it? I wrote in detail in How to get the current entity state from events?. A short TLDR:

  • we read all events from the stream in the order of occurrence,
  • create a default entity object (e.g. using the default constructor),
  • for each event, we apply it to the entity state to get the current one.

This can be done, for example, by adding the Apply methods for each type of event. For example, a method that applies an event to remove a product from the cart could look like this:

public void Apply(ProductRemoved @event)
{
    var productItemToBeRemoved = @event.ProductItem;

    var existingProductItem = FindProductItemMatchingWith(@event.ProductItem);

    if (existingProductItem == null)
        return;

    if (existingProductItem.HasTheSameQuantity(productItemToBeRemoved))
    {
        ProductItems.Remove(existingProductItem);
        return;
    }

    ProductItems.Replace(
        existingProductItem,
        existingProductItem.Subtract(productItemToBeRemoved)
    );
}

In short, when we zero the number of products in the shopping cart, we remove the whole product item. If we are removing only a part, we reduce the number. What if the product, for some reason, does not exist in the cart? I wanted to talk about that case today.

Take a look at the snippet that just ignores this scenario.

if (existingProductItem == null)
        return;

Wouldn’t you be tempted to throw an exception here? Yes, I would also be tempted. So why didn’t I do it?

Let us recall once again. Events are facts. They happened. What has been seen will not be displayed. The events were stored in our database because our business logic stated that rules are fulfilled, and we can perform the operation and record the event as a result. If we trust our business logic (read: we have it tested), we should also trust the recorded event. Yes, I know control is the basis of trust, plus I don’t trust myself either.

So what if we forgot some IF in our logic, or we misspelt the event? What if our logic in the past allowed us to remove more products than existed in the shopping cart? Sounds absurd? Perhaps, but I would like to remind you that most bank accounts have an overdraft facility, the amount of which can be changed. Moreover, there may be a legal rule prohibiting banks from using overdrafts. If we change our application method, forgetting that it was possible in the past, we will cause errors in the system’s operation.

So am I suggesting that we ignore data errors? God forbid! Mistakes need to be fixed (not only those in the code). I wrote about it in What texting your Ex has to do with Event-Driven Design?. The best way to correct a mistake is to regret your sins and promise correction. Well, the best thing is actually improving. In Event-Driven Architecture, this is best done by adding another event. Thanks to that, we can be sure that even in a distributed system, information about the compensation will be propagated between independent services.

What could be a compensating event? If, for instance, the cashier in a department store charged us with the same product twice, the money will be refunded, and a new receipt will be generated. Do you think that’s a hack? Let me dispel this way of thinking. This is not a hack; this is how business works, especially where paper is involved. You do not scribble on the contract; you print a new one and then sign it. It is also worth thinking about compensating actions before the end of development. The more we give the user the chance to fix errors on their own, the better. We can even consider introducing administrative events so that we can correct our mistakes without redeploying the application.

When we publish a compensating event, it will be appended like any other: to the current end of the stream. So, if we throw an exception on an event that had a bug, we won’t give ourselves a chance to fix the state. We won’t reach the compensating event because each time we try to apply the “wrong event” on the state, we will throw an exception.

On the other hand, even if we did not have compensating events, should the actual recovery be verifying the business rules? Business logic should be checking them. If our state is wrong, it should pick up the inconsistency. Therefore, you can simply check whether the state is consistent and meets the business rules after restoring the state from events and then run the appropriate business logic. Of course, we will not capture everything this way because the state may meet the business rules and not be correct (e.g. with double charges made by the cashier), but that’s another story.

So, do I suggest that you always ignore mistakes? It depends. In my opinion, throwing exceptions may make sense in the initial development of functionality when the functionality is stabilizing. At such a stage, we may want to know as soon as possible that something went wrong. We can also assume that only when there is the first need to introduce a compensating operation then, we remove code that throws exceptions to unblock it. However, we must remember one thing. If we decide to do so, the error will be corrected only after deployment. Which can mean in the extreme situation of a wake-up call in the middle of the night if you are the unfortunate one on the shift.

One IF, and so much fun, isn’t it?

Cheers!

Oskar

p.s. Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help. You can help in various ways, for instance, directly helping refugees, spreading awareness, putting pressure on your local government or companies. You can also support Ukraine by donating e.g. to Red Cross, Ukraine humanitarian organisation or donate Ambulances for Ukraine.

👋 If you found this article helpful and want to get notification about the next one, subscribe to Architecture Weekly.

✉️ Join over 11500 subscribers, get the best resources to boost your skills, and stay updated with Software Architecture trends!

Loading...
Event-Driven by Oskar Dudycz

cover

Through my window, I see the result of good plans but poor execution. Opposite my flat, there is a partially completed construction place. Buildings were supposed to be eye-catching Mediterranean style apartments. Delivery date? Two years ago. Actual? More and more unknown.

Some time ago, I heard that using Event Sourcing makes creating Event-Driven Architecture easier. The arguments were correct, that if we’re already publishing events to trigger business workflows, then at some point, we may want to also store events to not lose information. Agreed. However, I also heard that keeping the state as events will simplify things. We’ll have a source of truth with a record of the system behaviour. This will allow, e.g. to confront the results of the operations with the recorded state. I’d agree with that, with one distinction. It’s easier as long as you already know Event Sourcing.

Many people in the DDD community claim that the essential is to properly break down the system into autonomous parts called bounded contexts. Once we have it, the rest is secondary and will sort itself out. For sure.

Many seasoned programmers speak similarly about new technologies. They claim that they can translate past experience into new technologies. That’s true that by analogy, they can catch the big picture quicker. But isn’t it a bold assumption to say that Win.Forms specialist will learn Angular quickly?

The end result may differ a lot from the initial ideas. I saw the plan of those buildings next to me. Now I can see the effects of the execution. Or actually, the lack.

I believe that we should carefully acknowledge not only the point of view of our authorities but also their seating point. If we want to find out how to form a wall, do we ask an architect or a foreman? An architect may know the theory, but the practice is what we’re looking for. On the other hand, if you want to know where to put the wall, you prefer the architect to do measurements. At least if you don’t want to have the roof falling to your head.

After I had torn a ligament in my knee, I went to two qualified orthopedists. One said I should have surgery and do a reconstruction. The second stated that there is no need for that; rehabilitation should be enough. Guess which one had a specialization in surgery and which in rehabilitation?

People usually give us advice from the point where they’re currently standing. They are entitled to a biased view. An architect who rarely does programming will tend to downplay the value of implementation and tactical patterns. Midlevel developers will focus on technicalities instead of the global system impact. The team manager or consultant will emphasize the importance of soft skills (or esoteric techniques known only to them).

The truth is that we need all of them. The excellent plan will fall on the bad execution. The best execution for the wrong case will be just a waste of time. We should carefully evaluate the advice considering what we need and what an expert can give us.

Therefore, when we’re reading an article, watching a talk, let’s also pay attention to the place where the person is standing. The perspective from there may be much different from where we are right now. That can be good, as it may push us in the right direction. But it may also be misleading, as we accidentally take biases of this person without understanding the tradeoffs. Personally, I prefer to follow not only people from pedestal but also those that are closer to my position. A bit further in the journey, but not too far. That helps me to calibrate my view as those people are more relative to my daily struggles.

Polish historical leader Józef Piłsudzki reportedly used to say: “Right is like an ass, everyone has its own”.

Cheers!

Oskar