Skip to content

2. Eventstore

Simon Heiss edited this page Oct 31, 2019 · 7 revisions

The IEventstore is your persistance layer that offers saving DomainEvents and restoring Entities by EventSourcing. There is a SnapShot function for performance reasons, if you need it. Inject the IEventStore into your desired class and use the functions to save and load entities. The Eventstore uses optimistic concurrency so you have to give him the version of the entity that you are trying to save.

Storing and loading in the IEventstore looks like this:

using Microwave.EventStores;

namespace Application.Users
{
    public class UserCommandHandler
    {
        private readonly IEventStore _eventStore;

        public SeasonCreatedEventHandler(IEventStore eventStore)
        {
            _eventStore = eventStore;
        }
        
        public async Task CreateUser(string userName)
        {
            UserCreatedEvent userCreatedEvent = User.Create(userName);
            var result = await _eventStore.AppendAsync(userCreatedEvent, 0);
            result.Check();
        }
        
        public async Task<User> LoadUser(GuidIdentity userId)
        {
            UserCreatedEvent userCreatedEvent = User.Create(userName);
            var result = await _eventStore.LoadAsync<User>(userId);
            result.Entity;
        }
    }
}

DomainEvents and Identities

To append DomainEvents to the IEventStore you have to implement the IDomainEvent interface on your DomainEvents. The interface forces you to implement the property EntityId so the eventstore can assign the events to the entity. Everything else is up to your choice. The EventStore also generates upcounting versions for the DomainEvents and the GlobalVersion that indicates where the event is placed in the overall EventStore. I would recommend to just forward the EntityId like this, as this bears the least problems with persistance.

An immutable implementation could be:

public class UserCreatedEvent : IDomainEvent
{
    public UserCreatedEvent(
        Guid userId,
        string name)
    {
        Name = name;
    }

    public Guid UserId { get; }
    public Identity EntityId => UserId.ToString();
    public string Name { get; }
}

IApply/Entities

To Load an entity the Entity has to implement the Interface IApply wich takes a list of DomainEvents and forces you to apply them to your entity. There is a class Entity that implements the IApply method in a way, so the entity applies the DomainEvent to the Method that has to be implemented with IApply<T>. Reflection is used so you might want to do it on your own, if you run into performance issues. Example:

public class User : Entity, IApply<UserCreatedEvent>, IApply<UserChangedNameEvent>
{
    public void Apply(UserCreatedEvent domainEvent)
    {
        Id = domainEvent.UserId;
    }

    private void Apply(UserChangedNameEvent domainEvent)
    {
        Name = domainEvent.Name;
    }

    public Guid Id { get; private set; }
    public string Name { get; private set; }
}

// OR with IApply

public class User : IApply
{
    public void Apply(IEnumerable<IDomainEvent> domainEvents)  // this here is basically what is done in the Entity class with reflection
    {
        foreach (var domainEvent in domainEvents)
        {
            switch (domainEvent)
            {
                case UserCreatedEvent ev: Apply(ev);
                case UserChangedNameEvent ev: Apply(ev);

            }
        }
    }

    public void Apply(UserCreatedEvent domainEvent)
    {
        Id = domainEvent.UserId;
    }

    public void Apply(UserChangedNameEvent domainEvent)
    {
        Name = domainEvent.Name;
    }

    public Guid Id { get; private set; }
    public string Name { get; private set; }
}

Snapshots

The IEventstore supports snapshots that lets you save the state of an entity after a certain times of loading it, so the eventstore does not need to apply too much events at once. The eventstore first loads the snapshot and then applies the remaining events on it. The snapshots only get created when the entity is being loaded, so if you never load the entity, the snapshot is not created on the given threshold. You can define this with the SnapShot<T> class. The <T> must be of type IApply and the parameter is the amount of events that have to be inserted before the IEventStore saves a snapshot. To setup an Entity for Snapshots, add the snapshopt configs at the startup class like this:

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddMicrowave(config =>
    {
        config.SnapShots.Add(new SnapShot<User>(10));
    });

    ...
}
Clone this wiki locally