Coder Social home page Coder Social logo

aggregail's Introduction

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

aggregail's People

Contributors

nillerr avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar

aggregail's Issues

Provide default methods

Currently, there's an experimental API in the AbstractAggregate class, which provides a lot of the boilerplate involved in creating aggregates classes.

This issue tracks that implementation, and aims to make it part of the Aggregate class, if possible.

Event metadata

It is common in Event Sourcing, to have metadata associated with events, which is not part of the event data. This feature can be seen in Event Store.

Some of the interesting metadata can be:

  • Who: The user who performed the action
  • When: When was the action initiated? (This is kind of already supported by the Created) timestamp on the recorded events.
  • Why: Why was the event recorded? In case of program conditions controlling which events are appended.
  • How: What triggered the event being appended? Examples include the endpoint of an HTTP request, or a trigger in response to a message from a message queue.

This can trivially be implemented as a IMetadataFactory supplied to the driver implementation, e.g:

public sealed class MongoEventStoreSettings
{
    public IMetadataFactory MetadataFactory { get; set; } = NoMetadataFactory.Instance;
}

In an ASP.NET Core / MVC application, the metadata can be collected from the HttpContext, while in an Azure Functions app, a context concept would have to be created and added as part of a scoped IServiceContainer.

Setup CI / CD

A CI / CD workflow should be used to automatically build and distribute new versions of each of the NuGet packages, as well as building a docker image for Aggregail.MongoDB.Admin, with proper version management.

This also means a needed change to the "commit straight to the master branch" workflow.

Deleting an aggregate

Deleting an aggregate (stream) is required to support various use cases, one of which is fulfilling GDPR responsibilities.

The proposed API in IEventStore is:

Task DeleteAggregateAsync<TIdentity>(
    TIdentity id,
    IAggregateConfiguration<TIdentity> configuration
)

And in AbstractAggregate:

public static Task DeleteAsync(IEventStore store, TIdentity id) => 
    store.DeleteAggregateAsync(id, Configuration);

Cancellation support

For certain client implementations, such as ones using the MongoDB C# driver, cancellation support is built into the library in the form of CancellationTokens being passed to the Async methods.

In order to support cancellation, the following parameter must be added to every async method in IEventStore:

CancellationToken cancellationToken = default

Client implementations can optionally use this token to support cancellation.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.