Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/huysentruitw/aggregator
Managed CQRS/ES fundamentals for dotnet core and dotnet framework
https://github.com/huysentruitw/aggregator
aggregate aggregate-root aggregates autofac command-handlers cqrs cqrs-es ddd dotnet dotnet-core dotnetcore2 dotnetcore3 event-handlers event-sourcing eventstore netstandard20 netstandard21
Last synced: 7 days ago
JSON representation
Managed CQRS/ES fundamentals for dotnet core and dotnet framework
- Host: GitHub
- URL: https://github.com/huysentruitw/aggregator
- Owner: huysentruitw
- License: mit
- Created: 2018-04-06T08:39:14.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2022-12-13T07:16:19.000Z (almost 2 years ago)
- Last Synced: 2024-05-01T14:50:15.264Z (7 months ago)
- Topics: aggregate, aggregate-root, aggregates, autofac, command-handlers, cqrs, cqrs-es, ddd, dotnet, dotnet-core, dotnetcore2, dotnetcore3, event-handlers, event-sourcing, eventstore, netstandard20, netstandard21
- Language: C#
- Homepage:
- Size: 939 KB
- Stars: 31
- Watchers: 3
- Forks: 7
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- Funding: .github/FUNDING.yml
- License: LICENSE.txt
Awesome Lists containing this project
README
# Aggregator
[![Build status](https://ci.appveyor.com/api/projects/status/7uac3qe1fpcl973y/branch/develop?svg=true)](https://ci.appveyor.com/project/huysentruitw/aggregator/branch/develop)
[![codecov](https://codecov.io/gh/huysentruitw/Aggregator/branch/develop/graph/badge.svg)](https://codecov.io/gh/huysentruitw/Aggregator)
![Nuget (with prereleases)](https://img.shields.io/nuget/v/Aggregator)
![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/Aggregator)Aggregator contains some fundamental base classes and interfaces for building a CQRS/ES based application.
Supports .NET Standard 2.0 and 2.1 (.NET Core 2.2 and 3.1).
## Get it on NuGet
The main package:
PM> Install-Package Aggregator
EventStore persistence integration package:
PM> Install-Package Aggregator.Persistence.EventStore
Microsoft DI integration package:
PM> Install-Package Aggregator.Microsoft.DependencyInjection
Autofac DI integration package:
PM> Install-Package Aggregator.Autofac
Testing package:
PM> Install-Package Aggregator.Testing
## Usage
### Registering dependencies
Registering the dependencies in an ASP.NET Core application, using Microsoft.Extensions.DependencyInjection, is pretty simple:
* Install the Aggregator.Microsoft.DependencyInjection package
* Call `app.AddAggregator();` inside the `Configure` method in Startup.csIf you prefer using Autofac, you can use the Aggregator.Autofac package which contains an Autofac module to ease the registration process.
Need support for a different container? Feel free to [open an issue](https://github.com/huysentruitw/Aggregator/issues/new).
### Aggregate roots
The [`AggregateRoot`](./src/Aggregator/AggregateRoot.cs) or [`AggregateRoot`](./src/Aggregator/AggregateRoot.cs) class is an abstract base class that should be used as a base for aggregate roots. It allows registering event handlers, initializing the aggregate by replaying events and keeping track of changes getting applied to the aggregate root.
```csharp
sealed class User : AggregateRoot
{
private string _givenName;
private string _surName;public User()
{
Register(OnCreated);
Register(OnGivenNameUpdated);
// ...
}// When having multiple constructors, always call the one that registers the event handlers
private User(CreatedUserEvent @event) : this()
{
Apply(@event);
}public static User Create(string givenName, string surname)
=> new User(new CreatedUserEvent
{
GivenName = givenName,
Surname = surname,
// ...
});public void SetGivenName(string givenName)
{
if (_givenName.Equals(givenName)) return;Apply(new UpdatedUserGivenNameEvent
{
GivenName = UpdatedInfo.From(_givenName).To(givenName),
Surname = _surname,
// ...
});
}private void OnCreated(CreatedUserEvent @event)
{
_givenName = @event.GivenName;
_surname = @event.Surname;
}private void OnGivenNameUpdated(UpdatedUserGivenNameEvent @event)
{
_givenName = @event.GivenName.NewValue;
}// ...
}
```### Repository
The generic [`Repository`](./src/Aggregator/Persistence/Repository.cs) class is responsible for creating new aggregate roots, loading aggregate roots from the event store and keeping track of changes applied to new or loaded aggregate roots.
This generic repository is registered as a scoped instance (when using one of the DI integration packages) as an instance should only exist for the lifetime of a single command being processed.
### Commands and events
Commands and events can be defined as simple POCO's. All Aggregator related classes have a generic variant which allows you to define a base type for commands and events for those who like type safety.
Example command:
```csharp
class CreateUserCommand
{
public string Id { get; set; }
public string EmailAddress { get; set; }
public string GivenName { get; set; }
public string Surname { get; set; }
}
```Example event:
```csharp
class UserCreatedEvent
{
public string Id { get; set; }
public string EmailAddress { get; set; }
public string GivenName { get; set; }
public string Surname { get; set; }
public DateTimeOffset CreatedAtUtc { get; set; }
}
```### Command handlers
#### Definition
The library contains a generic interface definition [`ICommandHandler`](./src/Aggregator.Abstractions/Command/ICommandHandler.cs) that identifies command handlers.
```csharp
class CreateUserCommandHandler : ICommandHandler
{
public override Task Handle(CreateUserCommand command, CancellationToken cancellationToken)
{
// Process the command...
}
}
```#### Registering command handlers
Command handlers can be registered one-by-one or in one go using [Scrutor](https://github.com/khellang/Scrutor) for Microsoft.Extensions.DependencyInjection or `AsClosedTypesOf` when using Autofac.
#### Add command validation to the command handler
It's a good practice to validate commands before executing them. A great way to do this is to create a base class that uses [FluentValidation](https://github.com/JeremySkinner/FluentValidation).
```csharp
abstract class SafeCommandHandler
: AbstractValidator
, ICommandHandler
{
public async Task Handle(TCommand command, CancellationToken cancellationToken)
{
DefineRules();
this.ValidateAndThrow(command);
await HandleValidatedCommand(command, cancellationToken).ConfigureAwait(false);
}protected abstract void DefineRules();
protected abstract Task HandleValidatedCommand(TCommand command, CancellationToken cancellationToken);
}
```#### Using aggregate roots in a command handler
Since most command handlers will manipulate an aggregate root, you can also create a base class that requires a repository.
```csharp
abstract class PersistentCommandHandler
: SafeCommandHandler
where TAggregateRoot : AggregateRoot, new()
{
protected readonly IRepository Repository;protected PersistentCommandHandler(IRepository repository)
{
Repository = repository;
}
}
```Usage:
```csharp
sealed class UpdateTodoTitleCommandHandler
: PersistentCommandHandler
{
public UpdateTodoTitleCommandHandler(IRepository repository)
: base(repository)
{
}protected override void DefineRules()
{
RuleFor(x => x.Id).NotEmpty();
RuleFor(x => x.Title).NotEmpty();
}protected override async Task HandleValidatedCommand(UpdateTodoTitleCommand command, CancellationToken cancellationToken)
{
var aggregateRoot = await Repository.Get(command.Id.ToString());
aggregateRoot.UpdateTitle(command.Title);
}
}
```### CommandProcessor
The [`CommandProcessor`](./src/Aggregator/Command/CommandProcessor.cs) or [`CommandProcessor`](./src/Aggregator/Command/CommandProcessor.cs) class is responsible for processing commands, which consists of these steps:
* Requests a new `CommandHandlingContext` from the DI container which will maintain the internal unit-of-work
* Execute one or more command handlers that implement the `ICommandHandler` interface
* Store events that were tracked by the unit-of-work
* Dispatch events that were tracked by the unit-of-work in the `CommandHandlingContext`### IServiceScopeFactory / IServiceScope
The implementation of the [`IServiceScopeFactory`](./src/Aggregator/DI/IServiceScopeFactory.cs) interface is responsible for creating a temporary child scope from which the `CommandHandlingContext`, command handlers and event handlers are resolved.
The child scope, which implements [`IServiceScope`](./src/Aggregator/DI/IServiceScope.cs) is only valid for the lifetime of a single command or event being processed/dispatched.
These dedicated interfaces are DI independent. There's an integration NuGet package available for Microsoft.Extensions.DI (`Aggregator.Microsoft.DependencyInjection`) and Autofac (`Aggregator.Autofac`) that can be used out of the box or serve as an example.
### CommandHandlingContext
The [`CommandHandlingContext`](./src/Aggregator/Command/CommandHandlingContext.cs) is resolved from the `IServiceScope` instance by the `CommandProcessor` for the lifetime of one single command being processed. It's a property bag that can be used to store and retrieve properties during the processing of a single command. Internally, this context is also used to store the unit-of-work that is required by the `Repository` class to keep track of changes (events) generated by aggregate root entities.
### Store events
The `CommandProcessor` depends on an implementation of [`IEventStore`](./src/Aggregator/Persistence/IEventStore.cs) which will be used to store one or more events that were generated during the processing of a single command in a transactional manner. When something goes wrong while storing (and dispatching) the event(s), the complete transaction will get rolled back and the exception will bubble up to the caller.
There's an integration NuGet package available for using EventStore (`Aggregator.Persistence.EventStore`) that can be used or serve as an example for a custom event store.
### Dispatch events
The `CommandProcessor` also depends on an implementation of [`IEventDispatcher`](./src/Aggregator/Event/IEventDispatcher.cs) which will be used to dispatch one or more events that were generated during the processing of a single command inside the command domain. The implementation of the `IEventDispatcher` interface is responsible for forwarding events to classes that implement the [`IEventHandler`](./src/Aggregator/Event/IEventHandler.cs) interface inside the command domain. A typical example of classes that listen to one or more events are Process Managers (sometimes referred to as Sagas) that act on events, keep track of some kind of long running state and send out commands depending on that state. Since the work and state of process managers is important, the `CommandProcessor` will also rollback the event store transaction in case something goes wrong during event dispatching.
[`EventDispatcher`](./src/Aggregator/Event/EventDispatcher.cs) is a default implementation that can be used for dispatching events.
## Testing Aggregates
The package Aggregator.Testing can be used to write unit-tests against your aggregate roots.
### Testing static constructors
```csharp
Scenario
.ForConstructor(() => User.Create("John", "Doe"))
.Then(new CreatedUserEvent
{
GivenName = "John",
Surname = "Doe",
})
.Assert();
```### Testing aggregate commands
```csharp
Scenario
.ForCommand(User.Factory)
.Given(
new CreatedUserEvent
{
GivenName = "John",
Surname = "Doe",
})
.When(user => user.SetGivenName("Jon"))
.Then(
new UpdatedUserGivenNameEvent
{
GivenName = UpdatedInfo.From("John").To("Jon"),
Surname = "Doe",
})
.Assert();
```## Contributing
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
## License
This project is licensed under the MIT License - see the [LICENSE.txt](LICENSE.txt) file for detail