Here's the rating on BetterCodeHub. The rating is 9/10 because everything is one assembly but being a kata, it's not really an issue.
This project came up as a personal challenge. This is a simple ToDoList Rest Api with CRUD and Event Sourcing like many others on GitHub. This is not a production-ready solution as data stores are both in-memory but just a fun personal project.
Still, every single implementation I saw were using either Reflection or 'switch' (sometimes Dictionary<IEvent, Action> but the problem remains the same) to construct an aggregate based on previous events. I wanted to try a cleaner approach. I do not pretend my implementation is perfect, I was just able to remove what I consider being "code smells".
Here are a couple of remediation for those issues :
private static T CreateAggregate(IEnumerable<Option<IIntegrationEvent<T>>> events)
{
T aggregate = new();
events
.ToList()
.ForEach(listItem => listItem.IfSome(value => ApplyEvent(aggregate, value)));
return aggregate;
}
private static void ApplyEvent(T aggregate, IIntegrationEvent<T> integrationEvent) => integrationEvent.Apply(aggregate);
public interface IIntegrationEvent<T> : IIntegrationEvent
where T : IAggregate
{
void Apply(Option<T> aggregate);
}
public class InMemoryReadService : IReadService, INotificationHandler<CreatedItemEvent>,
INotificationHandler<UpdatedItemEvent>, INotificationHandler<DeletedItemEvent>
{
}
public interface IAggregate
{
Guid Id { get; }
Option<IEnumerable<IIntegrationEvent>> GetIntegrationEvents();
}
public class Item : IAggregate
{
private readonly AggregateBase baseAggregate = new();
}
public class CreatedItemEvent : IIntegrationEvent<Item>
{
private readonly ItemBaseEvent baseEvent = new();
}
My proposal comes with a compromise on the workflow: an event applies its logic on an aggregate. This is not really a bad compromise as it would allow us to creates more events without affecting the aggregate (OCP). Keeping that in mind, I kept the logic in the Aggregate.Apply method for encapsulation reasons: an event calls the aggregate when passing himself as a parameter and triggers a change on the aggregate.
Thanks for reading, I hope you'll find this implementation interesting.
Don't hesitate to provide feedbacks.