Przemysław Hojnacki – IT Blog

Event-driven Santa’s Gift House

In this article, I’d like to unwrap the joys of the event-driven systems. I’d like to answer a question in which situations is it more beneficial to maintain an Entity as a log of events, rather than solely it’s current state. What do we gain? Should we actually just overwrite the current state with new data in database when something changes or maintain the events (changes) to a given Entity and discover it’s current state by replaying all of the events from the start.

Before I try to come up with particular scenario proving that we need events ;), let me use the following Christmas example 😉

Imagine Santa’s Gift House where all those Items (LEGO, toys, puzzles, etc…) are being packed in Boxes before they are sent to kids. Santa asked us to implement a system that tracks current Gift status in his House during packing process.

Requirements

  1. The Items are being packed into Boxes by Elves. Every time Elves pack an Item into a Box there is a new message being pushed to a ItemPack Queue. Our system needs to consume that message and update current state of BoxEntity in database.
  2. When Box is accidentally damaged, we need to transfer all it’s contents to another Box. There is a message being pushed then by Controlling Department to BoxDamaged Queue.

Our system consumes those messages and persists the current state of a Boxes that are present within Santa’s House.

Sample scenario

Suppose our system consumed following messages in the following order:

  1. Item 1 packed into Box 1 at 9:00
  2. Item 2 packed into Box 1 at 10:00
  3. Box 1 damaged at 11:00
  4. Item 3 packed into Box 1 at 10:30

(4) happened before (3) but our system consumed those two messages in reverse order because of some dalay (is it possible? yes it is, and even it happens often, please remember that ItemPackQueue and BoxDamagedQueue are separate queues).

„Current-state-only” design

Now, let’s consider how the BoxEntity looks like after each scenario step in current-state-only approach:

(1) -> (2) Item 1 and Item 2 packed into Box 1

{
  Id : "Box1"
  Items : ["Item1", "Item2"]
}

(3) Box 1 damaged, we need to transfer all it’s contents into Box 2

{
  Id : "Box1"
  Items : [ ]
}
{
  Id : "Box2"
  Items : ["Item1", "Item2"]
}

(4) Item 3 packed into Box 1

{
  Id : "Box1"
  Items : ["Item3"]
}
{
  Id : "Box2"
  Items : ["Item1", "Item2"]
}

Because in reality Item3 has been packed before BoxDamaged and all its contents have been transferred into Box2, the correct form of data is:

{
  Id : "Box1"
  Items : [ ]
}
{
  Id : "Box2"
  Items : ["Item1", "Item2", "Item3"]
}

So we have an error in our current-state data. We could obviously fix that implementing additional logic that detects if message was delayed and if so repairs Items status in both Boxes. Imagine how many possible combinations are there in terms of delayed messages consumption. The logic that fixes current state can expand rapidly.

„Event-driven” design

Now instead of reacting on each incoming message and updating current state in database. Let’s store each message as the event in the EventStore. If asked about current state we will replay all events sorted by the date they happened in reality (not date they were consumed) building the final BoxEntity.

  1. Item 1 packed into Box 1 at 9:00
  2. Item 2 packed into Box 1 at 10:00
  3. Item 3 packed into Box 1 at 10:30
  4. Box 1 damaged at 11:00

That way we end up with correct BoxEntity even if delayed message comes (in our case Item 3 packed into Box 1 at 10:30) because we order events by real-life date now and replay all events from the beginning.

Draft of implementation

Events:

public class ItemPackedEvent: IEvent
{
    public Guid ItemId { get; set; }
    public Guid TargetBoxId { get; set; } 

}

public class BoxContentsTransferredEvent: IEvent
{
    public Guid SourceBoxId { get; set; }
    public Guid TargetBoxId { get; set; }
    public string List<string> TransferredItems { get; set; }
}

BoxEntity which is able co recalculate it’s state each time when replaying events from the beginning:

public class BoxEntity
{
    public Guid BoxId { get; private set; }
    public List<string> Items { get; private set; }

    public void Apply(ItemPackedEvent @event)
    {
        Items.Add(@event.Item);
    }

    public void Apply(BoxContentsTransferredEvent @event)
    {
        if (BoxId == @event.TargetBoxId)
        {
            Items.AddRange(@event.TransferredItems);
        }
        else if (BoxId == @event.SourceBoxId)
        {
            Items.RemoveAll(item =>             
                                @event.TransferredItems.Contains(item));
        }
    }
}

Links

https://www.eventstore.com/blog/how-to-get-the-current-entity-state-from-events



Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *