Skip to content

Tr00d/CleanEventSourcing

Repository files navigation

CleanEventSourcing

Quality Gate Status

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.

BCH compliance

Summary

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.

The "problem"

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 :

No switch when creating aggregate

 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); }

No switch when projecting changes

 public class InMemoryReadService : IReadService, INotificationHandler<CreatedItemEvent>, INotificationHandler<UpdatedItemEvent>, INotificationHandler<DeletedItemEvent> { }

No inheritance on aggregates - Composition instead

 public interface IAggregate { Guid Id { get; } Option<IEnumerable<IIntegrationEvent>> GetIntegrationEvents(); } public class Item : IAggregate { private readonly AggregateBase baseAggregate = new(); }

No inheritance on events - Composition instead

 public class CreatedItemEvent : IIntegrationEvent<Item> { private readonly ItemBaseEvent baseEvent = new(); }

Trade-offs

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.

Conclusion

Thanks for reading, I hope you'll find this implementation interesting.

Don't hesitate to provide feedbacks.

Releases

No releases published

Packages

No packages published