Skip to content

Nillerr/Aggregail

Repository files navigation

Aggregail

A small framework for implementing Aggregate Roots, which are a critical part of Event Sourcing, DDD and CQRS, backed by an event store.

Provides connectors for Event Store and MongoDB.

Package NuGet
Aggregail NuGet Download
Aggregail.Newtonsoft.Json NuGet Download
Aggregail.System.Text.Json NuGet Download
Aggregail.Testing NuGet Download
Aggregail.MongoDB NuGet Download
Aggregail.MongoDB.Admin NuGet Download
Aggregail.EventStore WIP

A practical example

Goat

The aggregate root in our example will be a goat. Every aggregate needs at least one event to create it, let's call that event GoatCreated:

public class GoatCreated
{
    public static readonly EventType<GoatCreated> EventType = "GoatCreated";

    public string Name { get; set; }
}

It wouldn't be much of an example if we could not update the Aggregate Root after creation, so let's have an event for that GoatUpdated:

public class GoatUpdated
{
    public static readonly EventType<GoatUpdated> EventType = "GoatUpdated";

    public string Name { get; set; }
}

Our goat itself is an example of the minimum amount of code required to make a somewhat interesting Aggregate Root:

public class Goat : AbstractAggregate<Guid, Goat>
{
    static Goat()
    {
        Configuration = new AggregateConfiguration<Guid, Goat>("goat", Guid.Parse)
            .Constructs(GoatCreated.EventType, (id, e) => new Goat(id, e))
            .Applies(GoatUpdated.EventType, (goat, e) => goat.Apply(e));
    }
    
    public static Goat Create(Guid id, string name) => 
        Create(GoatCreated.EventType, new GoatCreated { Name = name }, (id, e) => new Goat(id, e));

    private Goat(Guid id, GoatCreated e) : base(id)
    {
        Name = e.Name;
    }

    public string Name { get; private set; }

    public void Update(string name) => 
        Append(GoatUpdated.EventType, new GoatUpdated { Name = name }, Apply);

    private void Apply(GoatUpdated e)
    {
        Name = e.Name;
    }
}

Commands

The code above defines two commands and two events, and configures the applicator (or constructor) for each event type for the Aggregate Root Goat.

Command Event Applicator
static Goat Create(Guid id, string name) GoatCreated Goat(Guid id, GoatCreated e)
void Update(string name) GoatUpdated void Apply(GoatUpdated e)

Create

public class CreateGoatRequest
{
    public Guid Id { get; set; }
    public string Name { get; set; }
}

[Route("goats")]
public class GoatsController
{
    private readonly IEventStore _store;

    public GoatsController(IEventStore store)
    {
        _store = store;
    }

    [HttpPost]
    public async Task<IActionResult> CreateAsync(CreateGoatRequest request)
    {
        var goat = Goat.Create(request.Id, request.Name);
        await gota.CommitAsync(_store);
        return Created();
    }
}

Fetch

[Route("goats")]
public class GoatsController
{
    private readonly IEventStore _store;

    public GoatsController(IEventStore store)
    {
        _store = store;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetAsync(Guid id)
    {
        var goat = await Goat.FromAsync(_store, id);
        if (goat == null)
        {
            return NotFound();
        }
        
        return Ok(goat);
    }
}

Update

public class UpdateGoatRequest
{
    public string Name { get; set; }
}

[Route("goats")]
public class GoatsController
{
    private readonly IEventStore _store;

    public GoatsController(IEventStore store)
    {
        _store = store;
    }

    [HttpPatch("{id}")]
    public async Task<IActionResult> UpdateAsync(Guid id, UpdateGoatRequest request)
    {
        var goat = await Goat.FromAsync(_store, id);
        if (goat == null)
        {
            return NotFound();
        }
        
        goat.Update(request.Name);
        await goat.CommitAsync(_store);
        return Ok();
    }
}

Delete

Deleting Aggregate Roots, and thus streams is currently not supported by Aggregail, but is actively being worked upon. We know it is a vital part in supporting several use cases due to GDPR. If using MongoDB as an event store, streams can easily be deleted without the use of Aggregail:

db.aggregail.deleteMany({ stream: "goat-a4a4e832-f577-4461-a50c-d9c83342ee6f" }) 

Likewise, when using Event Store, the streams can be deleted using the IEventStoreConnection, or using the UI.

Concepts

Aggregate (Root)

Events

Commands

Configuration

Constructor
Applicator

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published