Skip to content

A lightweight event sourcing implementation in .NET

License

Notifications You must be signed in to change notification settings

linwenda/EasySourcing

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EasySourcing

CI Nuget

A lightweight event sourcing implementation in .NET 6

Installation

You should install EasySourcing with NuGet (Now only EF Core implementation):

Install-Package EasySourcing.EntityFrameworkCore

Quick start:

1. Create event messages
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; }
}
2. Create event sourced entities
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;
    }
}
3. DI Setup
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>();
    });
4. Use case
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);
    }
}

About

A lightweight event sourcing implementation in .NET

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages