Avenue.Commands.EntityFramework.AspNetCore

Package Description


Keywords
DDD, CQRS
License
MIT
Install
Install-Package Avenue.Commands.EntityFramework.AspNetCore -Version 1.1.2.69

Documentation

Acklen Avenue's Event Dispatcher

Installation

dotnet add Avenue.Events

Setup

In order for event handlers to be available to the dispatcher at runtime, you need to provide a list of those handlers to an instance of the dispatcher and then make sure that dispatcher instance is the same that is used to dispatch events later on.

Event handlers have their own dependencies but that can get hairy very quickly. We recommend you use reflection to find all the event handlers in your appDomain and then register them with IoC. Of course, you'll need to register the dependencies as well. Lastly, you'll want to register the event dispatcher itself. In a perfect world, your event dispatcher will instantiate with a complete list of handlers, ready to roll.

Here's an example of how to do that in the Startup.cs file that is used in ASP.NET Core to bootstrap the application:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

    // Add the concrete event dispatcher to IoC.
    services.AddSingleton<IEventDispatcher, WorkerEventDispatcher>();
    // If you want to send events to a bus and defer processing to another worker, use the QueuedEventDispatcher and implement the IEventQueuePublisher interface.

    // Somehow add all the command handlers to IoC so that they will be available
    // when the dispatcher is instantiated.
    RegisterAllEventHandlers(services);
}

static void RegisterAllEventHandlers(IServiceCollection services)
{
    // Here, we're using the "TypeScanner" nuget package as a helper.
    var typeScanner = new TypeScanner();
    var eventHandlerTypes = typeScanner.GetTypesOf<IEventHandler>();
    commandHandlerTypes.ForEach(x => services.AddSingleton(x));
}

Usage

Events: These objects should be simple "data bags". We recommend the name of the command be something reactive (past tense) like "ShoesTakenOff" or "TrashTakenOut". The object should be immutable and not contain any logic or functionality. A good way to think of an event is to consider that it will soon be serialized to JSON and then deserialized back to a C# class. If that's not going to work, then you have something wrong in your command object.

Event Handlers: An event handler is the code that will react to the information in the event object. If the event is "TrashTakenOut" (along with the ID of the trash can), then we could have a "NotifyWasteManagementOnTrashTakenOut" event handler. You can imagine that the waste management company is watching for this event and will keep track of how many times the trash is taken out to send a truck at the right time. An imaginary, yet typical, workflow might be to collect events throughout an operation (e.g. a command handler acting on an entity) and then raise or dispatch events after persisting the entity back to the repository.

In many cases, the event handler will live in the same server, app domain, and codebase as the event and the API. But, if the other pieces of the puzzle are done properly, the event handler could even be in another microservice, codebase or even language.

Here's an example of a typical workflow from command handler to final processing in a event handler:

Domain/Commands/Handlers/TrashTakerOuter.cs

public class TrashTakerOuter : IHandler<TakeOutTheTrash>
{
    readonly IRepository<TrashCan> _trashCanRepo;

    public TrashTakerOuter(IRepository<TrashCan> trashCanRepo)
    {
        _trashCanRepo = trashCanRepo;
    }
    
    public async Task Handle(TakeOutTheTrash command)
    {
        var trashCan = await _trashCanRepo.get(command.TrashCanId);
        // generate and collect events inside the entity however you see fit 
        trashCan.TakeOutTrash();
        // the repo is equipped to dispatch commands after save.
        await _trashCanRepo.save(trashCan);
    }
}

Domain/Entities/TrashCan.cs

public class TrashCan : AggregateRoot
{
    public void TakeOutTrash()
    {
        // do the work, but also generate events and add them to a collection 
        // somewhere on the entity (for later processing)
    }
}

Domain/Events/TrashTakenOut.cs

public class TrashTakenOut
{
    public TrashTakenOut(string trashCanId, DateTime time)
    {
        TrashCanId = trashCanId;
        Time = time;
    }

    public string TrashCanId { get; }
    public DateTime Time { get; }
}

Domain/Events/Handlers/NotifyWasteManagementOnTrashTakenOut.cs

public class NotifyWasteManagementOnTrashTakenOut : IEventHandler<TrashTakenOut>
{
    readonly ITrashIncrementor _trashIncrementor;

    public NotifyWasteManagementOnTrashTakenOut(ITrashIncrementor trashIncrementor)
    {
        _trashIncrementor = trashIncrementor;
    }

    public Task Handle(TrashTakenOut command)
    {
        // by now, the entity has been saved in the repo, the event dispatcher 
        // has matched the event to one or more event handlers, and is currently 
        // iterating those handlers, calling the Handle() method.

        // so, all that's needed here in the handler is to do some work
        _trashIncrementor.Increment(command.TrashCanId, command.Time);
    }
}

Contributing

Acklen Avenue welcomes contributions from the community. Please create an issue describing the problem you are solving and submit a corresponding pull request with your solution.