persist a basket using event sourcing in csharp

To persist a basket using event sourcing in C#, you will need to follow the Domain-Driven Design approach. Here are the steps to do so:

  1. Define the Basket Domain Object:
main.cs
public class Basket
{
    public BasketId Id { get; private set; }
    public List<Product> Products { get; private set; }

    #region Events

    public class ProductAddedEvent : DomainEvent
    {
        public Product Product { get; }

        public ProductAddedEvent(Product product)
        {
            Product = product;
        }
    }

    public class ProductRemovedEvent : DomainEvent
    {
        public Product Product { get; }

        public ProductRemovedEvent(Product product)
        {
            Product = product;
        }
    }

    #endregion

    //Constructor for the new basket
    public Basket(BasketId id)
    {
        Id = id;
        Products = new List<Product>();
    }

    //Add product to the basket
    public void AddProduct(Product product)
    {
        Products.Add(product);
        AddEvent(new ProductAddedEvent(product));
    }

    //Remove product from the basket
    public void RemoveProduct(Product product)
    {
        Products.Remove(product);
        AddEvent(new ProductRemovedEvent(product));
    }
}
1064 chars
51 lines
  1. Define the Repository Interface:
main.cs
public interface IRepository<TAggregate> where TAggregate : IAggregateRoot
{
    Task<TAggregate> GetByIdAsync(Guid id);
    Task SaveAsync(TAggregate aggregate);
}
165 chars
6 lines
  1. Implement the Repository:
main.cs
public class EventSourcingRepository<TAggregate> : IRepository<TAggregate>
        where TAggregate : AggregateRoot, new()
    {
        private readonly IEventStore eventStore;

        public EventSourcingRepository(IEventStore eventStore)
        {
            this.eventStore = eventStore;
        }

        public async Task<TAggregate> GetByIdAsync(Guid id)
        {
            var events = await eventStore.GetEventsAsync(id);
            var aggregate = new TAggregate();
            aggregate.LoadFromHistory(events);
            return aggregate;
        }

        public async Task SaveAsync(TAggregate aggregate)
        {
            await eventStore.SaveEventsAsync(aggregate.Id, aggregate.UncommitedEvents.ToList());
            aggregate.MarkEventsAsCommitted();
        }
    }
799 chars
25 lines
  1. Define the Event Store Interface:
main.cs
public interface IEventStore
{
    Task<IEnumerable<DomainEvent>> GetEventsAsync(Guid aggregateId);
    Task SaveEventsAsync(Guid aggregateId, IList<DomainEvent> events);
}
173 chars
6 lines
  1. Implement the Event Store:
main.cs
public class SqlEventStore : IEventStore
{
    private readonly IDbConnection connection;

    public SqlEventStore(IDbConnection connection)
    {
        this.connection = connection;
    }

    public async Task<IEnumerable<DomainEvent>> GetEventsAsync(Guid aggregateId)
    {
        await connection.OpenAsync();
        var result = await connection.QueryAsync<EventDto>(
            "SELECT * FROM Event WHERE AggregateId = @AggregateId",
            new { AggregateId = aggregateId });

        return result.Select(d => d.EventData.FromJson<DomainEvent>());
    }

    public async Task SaveEventsAsync(Guid aggregateId, IList<DomainEvent> events)
    {
        await connection.OpenAsync();
        using var transaction = connection.BeginTransaction();

        try
        {
            foreach (var domainEvent in events)
            {
                await connection.ExecuteAsync(
                    "INSERT INTO Event (AggregateId, Version, Type, DataJson) " +
                    "VALUES (@AggregateId, @Version, @Type, @DataJson)",
                    new
                    {
                        AggregateId = aggregateId,
                        Version = domainEvent.Version,
                        Type = domainEvent.GetType().FullName,
                        DataJson = domainEvent.ToJson(),
                    },
                    transaction);
            }

            transaction.Commit();
        }
        catch (Exception)
        {
            transaction.Rollback();
            throw;
        }
    }
}
1548 chars
51 lines
  1. Define the Basket Application Service:
main.cs
public class BasketApplicationService
{
    private readonly IRepository<Basket> repository;

    public BasketApplicationService(IRepository<Basket> repository)
    {
        this.repository = repository;
    }

    public async Task AddProductAsync(Guid basketId, Product product)
    {
        var basket = await repository.GetByIdAsync(basketId);
        basket.AddProduct(product);
        await repository.SaveAsync(basket);
    }

    public async Task RemoveProductAsync(Guid basketId, Product product)
    {
        var basket = await repository.GetByIdAsync(basketId);
        basket.RemoveProduct(product);
        await repository.SaveAsync(basket);
    }
}
670 chars
24 lines
  1. Call the Basket Application Service in your Controller:
main.cs
public class BasketController : Controller
{
    private readonly BasketApplicationService basketService;

    public BasketController(BasketApplicationService basketService)
    {
        this.basketService = basketService;
    }

    [HttpPost]
    public async Task<IActionResult> AddProduct(Guid basketId, Product product)
    {
        await basketService.AddProductAsync(basketId, product);
        return Ok();
    }

    [HttpPost]
    public async Task<IActionResult> RemoveProduct(Guid basketId, Product product)
    {
        await basketService.RemoveProductAsync(basketId, product);
        return Ok();
    }
}
625 chars
24 lines

These are the steps to persist a basket using event sourcing in C#. Event sourcing allows auditing of changes made to the basket, as well as reverting to previous versions if required.

gistlibby LogSnag