A lightweight event sourcing implementation in .NET 6
You should install EasySourcing with NuGet (Now only EF Core implementation):
Install-Package EasySourcing.EntityFrameworkCore
public class PostCreatedEvent : IVersionedEvent
{
public PostCreatedEvent(Guid sourcedId, int version, string title, string content, Guid authorId)
{
SourcedId = sourcedId;
Version = version;
Title = title;
Content = content;
AuthorId = authorId;
}
public Guid SourcedId { get; }
public int Version { get; }
public string Title { get; }
public string Content { get; }
public Guid AuthorId { get; }
}
public class PostEditedEvent : IVersionedEvent
{
public PostEditedEvent(Guid sourcedId, int version, string title, string content)
{
SourcedId = sourcedId;
Version = version;
Title = title;
Content = content;
}
public Guid SourcedId { get; }
public int Version { get; }
public string Title { get; }
public string Content { get; }
}
public class Post : EventSourced
{
private Guid _authorId;
private string _title;
private string _content;
private Post(Guid id) : base(id)
{
}
public static Post Initialize(Guid authorId, string title, string content)
{
var post = new Post(Guid.NewGuid());
post.ApplyChange(new PostCreatedEvent(post.Id, post.GetNextVersion(), title, content, authorId));
return post;
}
public void Edit(string title, string content)
{
ApplyChange(new PostEditedEvent(Id, GetNextVersion(), title, content));
}
protected override void Apply(IVersionedEvent @event)
{
this.When((dynamic) @event);
}
private void When(PostCreatedEvent @event)
{
_authorId = @event.AuthorId;
_title = @event.Title;
_content = @event.Content;
}
private void When(PostEditedEvent @event)
{
_title = @event.Title;
_content = @event.Content;
}
}
var services = new ServiceCollection();
//You can register different database contexts
services.AddDbContext<SampleDbContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("Default"));
});
//Optional
services.Configure<EventSourcingOptions>(options =>
{
options.TakeEachSnapshotVersion = 5;
});
services.AddEasySourcing(typeof(PostReadModelGenerator).Assembly,
builder =>
{
builder.UseEfCoreStore<SampleDbContext>();
});
public class PostService : IPostService
{
private readonly IEventSourcedRepository<Post> _postRepository;
public PostService(IEventSourcedRepository<Post> postRepository)
{
_postRepository = postRepository;
}
public async Task CreatePostAsync(Guid authorId, string title, string content)
{
var post = Post.Initialize(authorId, title, content);
await _postRepository.SaveAsync(post);
}
public async Task EditPostAsync(Guid postId, string title, string content)
{
var post = await _postRepository.GetAsync(postId);
post.Edit(title, content);
await _postRepository.SaveAsync(post);
}
}
Read model generator:
public class PostReadModelGenerator : IEventHandler<PostCreatedEvent>, IEventHandler<PostEditedEvent>
{
private readonly PostReadModelDbContext _context;
public PostReadModelGenerator(PostReadModelDbContext context)
{
_context = context;
}
public async Task HandleAsync(PostCreatedEvent @event, CancellationToken cancellationToken)
{
await _context.Set<PostDetail>().AddAsync(new PostDetail
{
Id = @event.SourcedId,
Content = @event.Content,
Title = @event.Title,
AuthorId = @event.AuthorId,
CreationTime = DateTime.UtcNow
}, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
}
public async Task HandleAsync(PostEditedEvent @event, CancellationToken cancellationToken)
{
var post = await _context.Set<PostDetail>().SingleAsync(p => p.Id == @event.SourcedId, cancellationToken);
var postHistory = new PostHistory
{
Id = Guid.NewGuid(),
PostId = post.Id,
AuthorId = post.AuthorId,
Title = post.Title,
Content = post.Content,
CreationTime = DateTime.UtcNow
};
await _context.AddAsync(postHistory, cancellationToken);
post.Title = @event.Title;
post.Content = @event.Content;
_context.Update(post);
await _context.SaveChangesAsync(cancellationToken);
}
}