HerrGeneral.Core
0.1.13-rc
dotnet add package HerrGeneral.Core --version 0.1.13-rc
NuGet\Install-Package HerrGeneral.Core -Version 0.1.13-rc
<PackageReference Include="HerrGeneral.Core" Version="0.1.13-rc" />
paket add HerrGeneral.Core --version 0.1.13-rc
#r "nuget: HerrGeneral.Core, 0.1.13-rc"
// Install HerrGeneral.Core as a Cake Addin #addin nuget:?package=HerrGeneral.Core&version=0.1.13-rc&prerelease // Install HerrGeneral.Core as a Cake Tool #tool nuget:?package=HerrGeneral.Core&version=0.1.13-rc&prerelease
Herr General
Herr General is a Cqrs implementation with built in debug log for simple modular monolith. (strongly inspired from MediatR)
Send a command :
- Handle the command
- Dispatch events to write side
- Dispatch events to read side
- Return a Result
Implementation choice
One storage for all pro : no eventual consistency cons : no eventual consistency ⇒ doesn't scale
One transaction by command.
ReadModel as singleton.
All ids are System.Guid.
Result pattern but CreationResult return the id of the created object.
Installing Herr General with NuGet
Write side HerrGeneral.WriteSide or HerrGeneral.WriteSide.DDD
Read side HerrGeneral.ReadSide
Application or infrastructure layer HerrGeneral.Core
Registering with IServiceCollection
Herr General supports Microsoft.Extensions.DependencyInjection.Abstractions
directly.
To register write side commands and eventHandlers and read side eventHandlers:
services.UseHerrGeneral(scanner =>
scanner
.OnWriteSide(typeof(Person).Assembly, typeof(Person).Namespace!)
.OnReadSide(typeof(PersonFriendRM).Assembly, typeof(PersonFriendRM).Namespace!));
// Dynamic handlers registration
services.RegisterDynamicHandlers(typeof(CreatePerson).Assembly);
Sample code
// Write Side
// Command + handler
public record record SetFriend(Guid AggregateId, string Friend) : Change<Person>(AggregateId)
{
public class Handler : ChangeAggregateHandler<Person,SetFriend>
{
public Handler(CtorParams ctorParams) : base(ctorParams) { }
protected override Person Handle(Person aggregate, SetFriend command) =>
aggregate.SetFriend(command._friend, command.Id);
}
}
// or
//Command only (use dynamic command handler behind the scene)
public record record SetFriend(Guid AggregateId, string Friend) : Change<Person>(AggregateId);
// Read side
public record PersonFriendRM(Guid PersonId, string Person, string Friend)
{
public class PersonFriendRMRepository : HerrGeneral.ReadSide.IEventHandler<FriendChanged>
{
...
public Task Handle(FriendChanged notification, CancellationToken cancellationToken)
{
...
}
...
}
}
Dynamic command handler
With dynamic command handlers you don't need to write handler.
Nugget
Registration
// Register a dynamic handler for all commands without handler
services.RegisterDynamicHandlers(typeof(CreatePerson).Assembly);
// Register the default IAggregateFactory for aggregate creation
// DefaultAggregateFactory will call the aggregate constructor new(TCreateCommand command, Guid aggregateId)
services.AddTransient<IAggregateFactory<Person>, DefaultAggregateFactory<Person>>();
Conventions
Create (with DefaultAggregateFactory)
The aggregate must have a constructor new(TCreate command, Guid aggregateId).
Change
The aggregate must have a method Execute(TChange command).
Behind the scene
Dynamic handler for create:
- call IAggregateFactory.Create(Create<TAggregate> command, Guid aggregateId)
- save the aggregate
- dispatch events on write side
- dispatch events on read side
Dynamic handler for change:
- get the aggregate
- call the execute method with command as argument
- save the aggregate
- dispatch events on write side
- dispatch events on read side
Debug logger output sample
←------------------ SetFriend <46a0deab-0485-403e-821a-834a96517a7c> thread<1> -------------------> || Publish Write Side on thread<1> HerrGeneral.SampleApplication.WriteSide.FriendChanged
|| Publish Read Side (1 event) on thread<1> HerrGeneral.SampleApplication.WriteSide.FriendChanged -> Handle by HerrGeneral.SampleApplication.ReadSide.PersonFriendRM+PersonFriendRMRepository ←------------------ SetFriend Finished 00:00:00.0021475 -------------------/>
How it works
A user: Hey, I want to change my friend name. Application: Ok, give me the command and I will take care of the rest. I'll send you back a commandResult when I'm done.
In the application black box : Mediator: Anybody to handle this command ? CommandHandler: I"m here. CommandHandler: I'm done and I have some events to publish. WriteSideEventPublisher: That's my job. WriteSideEventPublisher: Anybody to handle this event on the write side ? (for each event). WriteSideEventHandler: Me (and I may have other events to publish). WriteSideEventPublisher: I'm done. CommandHandler: Thank you, now I can transmit all those events to the read side. ReadSideEventPublisher: Anybody to handle this event on the read side ? (for each event). ReadModel: Yes me. ReadSideEventPublisher: I'm done. CommandHandler: I'm done.
Application: Here is your command result.
Result pattern
ChangeResult
Herr general return ChangeResult for ChangeCommand 3 states : Success, DomainError, PanicException
updateResult.Match(() =>
{
...
},
error => ...,
exception => ...);
CreateResult
Herr general return CreationResult for CreationCommand 3 states : Success<Guid>, DomainError, PanicException
createResult.Match(id =>
{
...
},
error => ...,
exception => ...);
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. |
-
net8.0
- HerrGeneral.ReadSide (>= 0.1.13-rc)
- HerrGeneral.WriteSide (>= 0.1.13-rc)
- Microsoft.Extensions.DependencyInjection (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.2)
- Microsoft.SourceLink.GitHub (>= 8.0.0)
- MinVer (>= 6.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on HerrGeneral.Core:
Package | Downloads |
---|---|
HerrGeneral.Test.Extension
Helper methods for HerrGeneral testing |
GitHub repositories
This package is not used by any popular GitHub repositories.