implement event sourcing for persisting a basket in csharp

Implementing Event sourcing in C# involves modeling your system behavior as a stream of events. To persist a basket using event sourcing, you will need to create an event store that keeps track of all events that occur for a basket. Each event represents a change in the state of the basket. Event sourcing is mostly used in combination with Domain Driven Design.

  1. Define the events: Start by defining the events that your basket can emit. Examples of Basket events could be ItemAddedToBasket, ItemRemovedFromBasket, and OrderPlaced.
public class ItemAddedToBasket
{
    public Guid BasketId { get; private set; } // unique identifier for the basket
    public Guid ProductId { get; private set; } // unique identifier for the product
    public string ProductName { get; private set; }
    public decimal Price { get; private set; }
    public int Quantity { get; private set; }
 
    public ItemAddedToBasket(Guid basketId, Guid productId, string productName, decimal price, int quantity)
    {
        BasketId = basketId;
        ProductId = productId;
        ProductName = productName;
        Price = price;
        Quantity = quantity;
    }
}

public class ItemRemovedFromBasket
{
    public Guid BasketId { get; private set; }
    public Guid ProductId { get; private set; }
    public int Quantity { get; private set; }
 
    public ItemRemovedFromBasket(Guid basketId, Guid productId, int quantity)
    {
        BasketId = basketId;
        ProductId = productId;
        Quantity = quantity;
    }
}

public class OrderPlaced
{
    public Guid BasketId { get; private set; }
 
    public OrderPlaced(Guid basketId)
    {
        BasketId = basketId;
    }
}
1138 chars
42 lines
  1. Create the basket entity: Create the basket entity and implement the events.
public class Basket
{
    private readonly List<ItemAddedToBasket> _itemsAddedToBasket = new List<ItemAddedToBasket>();
    private readonly List<ItemRemovedFromBasket> _itemsRemovedFromBasket = new List<ItemRemovedFromBasket>();
    private readonly List<OrderPlaced> _ordersPlaced = new List<OrderPlaced>();
 
    public Guid Id { get; private set; }
    public decimal TotalAmount { get; private set; }
 
    public Basket()
    {
        Id = Guid.NewGuid();
        TotalAmount = 0;
    }
 
    public void AddItem(Guid productId, string productName, decimal price, int quantity)
    {
        _itemsAddedToBasket.Add(new ItemAddedToBasket(Id, productId, productName, price, quantity));
        TotalAmount += price * quantity;
    }
 
    public void RemoveItem(Guid productId, int quantity)
    {
        _itemsRemovedFromBasket.Add(new ItemRemovedFromBasket(Id, productId, quantity));
    }
 
    public void PlaceOrder()
    {
        _ordersPlaced.Add(new OrderPlaced(Id));
    }
 
    public List<Guid> GetOrderIds()
    {
        return _ordersPlaced.Select(o => o.BasketId).ToList();
    }
}
1105 chars
37 lines
  1. Implement the Event Store: Events can be saved in a database or in-memory, depending on the architecture of your application. You can use messaging queues to reliably dispatch events.
public interface IEventStore
{
    void SaveEvent(Guid aggregateId, object @event);
    List<object> GetEvents(Guid aggregateId);
}

public class InMemoryEventStore : IEventStore
{
    private readonly Dictionary<Guid, List<object>> _eventStore = new Dictionary<Guid, List<object>>();
 
    public void SaveEvent(Guid aggregateId, object @event)
    {
        if (!_eventStore.ContainsKey(aggregateId))
            _eventStore[aggregateId] = new List<object>();
        _eventStore[aggregateId].Add(@event);
    }
 
    public List<object> GetEvents(Guid aggregateId)
    {
        if (!_eventStore.ContainsKey(aggregateId))
            return new List<object>();
        return _eventStore[aggregateId];
    }
}
713 chars
25 lines
  1. Apply the Events: Apply the events from the Event Store to the Basket entity to rebuild the state of the entity.
public class BasketRepository
{
    private readonly IEventStore _eventStore;
 
    public BasketRepository(IEventStore eventStore)
    {
        _eventStore = eventStore;
    }
 
    public Basket GetBasket(Guid basketId)
    {
        var basket = new Basket();
        var events = _eventStore.GetEvents(basketId);
        foreach (var @event in events)
        {
            ApplyEvent(basket, @event);
        }
        return basket;
    }
 
    private void ApplyEvent(Basket basket, object @event)
    {
        switch (@event)
        {
            case ItemAddedToBasket added:
                basket.AddItem(added.ProductId, added.ProductName, added.Price, added.Quantity);
                break;
            case ItemRemovedFromBasket removed:
                basket.RemoveItem(removed.ProductId, removed.Quantity);
                break;
            case OrderPlaced placed:
                basket.PlaceOrder();
                break;
            default:
                throw new ArgumentException($"Unexpected event type {@event.GetType().Name}");
        }
    }
}
1082 chars
39 lines

Now, you can manipulate the Basket entity by adding items, removing them or placing an order, and all changes will be saved in the Event Store. By applying those DDD patterns and principles, you can create a flexible codebase that can be easily maintained and adapted to your changing requirements.

gistlibby LogSnag