A few libraries that can be combined together to make build systems based on DDD, CQRS, and EventSourcing patterns. There is support for SQL Server, RavenDB, EventStoreDB, and Azure Table Storage (experimental) for your event streams. Credit for the design that became the AggregateRoot implementation has to go to Greg Young.
"Why'd you call it "Inforigami.Regalo"?" Well it's an event sourcing framework, and events tell a story. You might "regale" someone with a story, and I just swopped the trailing "e" for an "o". Hence "Inforigami.Regalo". I pronounce it "regarlo", in case you're wondering.
All projects that you would use to implement your application are available on nuget.org. See Getting Started for more information.
There are a number of related patterns that are so closely related that they are often used interchangably to mean the same thing:
The basic principles of trying to keep your raw "business logic"/"domain logic" code away from "infrastructure" concerns like HTTP/REST, queuing, databases, email sending, etc are valid goals, but the terminology and understanding are not always consistent.
The following is brief explanation of how Regalo implements each of these related patterns:
| Regalo | Domain-Driven Design | Hexagonal/Ports-and-Adapters Architecture | Clean Architecture |
|---|---|---|---|
Inforigami.Regalo.EventSourcing.AggregateRoot |
Domain Model, Aggregate Root, Entity | Business Logic | Entities |
Inforigami.Regalo.Interfaces.ICommand/Command |
Domain Model, Command | Business Logic | Entities |
Inforigami.Regalo.Interfaces.IEvent/Event |
Domain Model, Event | Business Logic | Entities |
Inforigami.Regalo.Messaging.ICommandHandler<T>/IEventHandler<T> |
Domain Model, Application Service | Port | Use-Cases |
| Framework e.g. ASP.NET Controller, NServiceBus Handler | Infrastructure Layer | Adapter | Presenters, Gateways, Controllers |
Note:
I recommend you keep related aggregates and their domain Commands, Events, and Application Services together in namespaces.
- Choose from the available persistence options for your event streams below:
- SQL Server
- EventStoreDB
- RavenDB
- Azure Table Storage (experimental)
- Create a new class library project for your domain model and delete the scaffolded
Class1.cs. - Install your chosen persistence package.
- Choose your first Command to implement. This will be informed by the tasks to be carried out by your use-cases.
- Refer to the guidance for Creating a domain command.
- Refer to the guidance for Creating a domain event.
- Refer to the guidance for Building an Aggregate Root.
- Refer to the guidance for Creating an Application Service.
- If your domain events need to leave your system and be consumed by another system (e.g. in a microservices environment) then I recommend creating a new class library project that contains POCOs for those events, and build some mapping to them from your "true" domain events. This means consumers of your events don't have to take a dependency on your domain model project and therefore any other dependencies like Regalo that it would bring.
Any given use-case may be made up of multiple tasks. Creating a shopping basket, adding items to it, removing items, adding voucher codes, etc are all tasks. Think of Commands as representing the various tasks. You'll have a Command, a Command Handler (see guidance for Creating an Application Service), a method on an AggregateRoot, one or more Events that can be generated as a result of invoking that aggregate's behaviour, and zero or more Event Handlers (also Application Services) that may do something with those events.
- Create a class representing a Command that inherits
Inforigami.Regalo.Interfaces.Command. - Create a
public Guid Id { get; private set; }property to store the aggregate's ID. This step is optional for Commands that create aggregates. - Create a
public int Version { get; private set; }property to store the version of the aggregate that the command applies to. This step is optional for Commands that create aggregates. - Create similar properties for any other values that apply to the command.
- Create a constructor that initialises all the properties.
- Create a class representing an Event that inherits
Inforigami.Regalo.Interfaces.Event. - Create a
public Guid Id { get; private set; }property to store the aggregate's ID. - Create a
public int Version { get; private set; }property to store the version of the aggregate that the command applies to. - Create similar properties for any other values that apply to the command.
- Create a constructor that initialises all the properties.
- Aggregate Roots may implement business rules, invariants and such. They MUST NOT use infrastructure (e.g. databases, email servers, send messages, etc). The command or event handler that loaded/created the aggregate root object and invoked it's behaviour is responsible for providing it everything it needs to do it's work.
- The public API for an aggregate root will consist entirely of
public voidmethods that are named for Commands in your domain relating to that aggregate. There are to be no public properties or fields. - Each public "command" method is only allowed to perform three duties:
- Validate parameters.
- Assert invariant logic (this is the actual "business logic" of your domain).
- Record new events by calling
base.Record().
- Public "command" methods should NOT modify private state of the aggregate.
- For each event that a "command" method may raise, there can be a corresponding
private void Apply([Event] evt)method. - Each private "apply event" method is only allowed to modify private state of the aggregate.
- Private "apply event" methods MUST NOT perform invariant logic, validation, or record further events.
- Create a class representing an aggregate root, ensuring it inherits
Inforigami.Regalo.EventSourcing.AggregateRoot. E.g.SalesOrder. - Create an "initialisation" method that represents the aggregate's "initialisation" Command. E.g.
public void Create(). - In that method, implement command validation, invariant logic, and
Record()any events as necessary.
Note
Use exceptions to reject command execution. - Create the matching
private void Apply(AggregateCreated evt)"apply event" method.
Note
In that method, ensure it setsbase.Idfrom the Id on the event, plus any other private state that might be needed by other invariants in "command" methods on the same aggregate.
- Create a "command" method on your aggregate
- In that method, implement command validation, invariant logic, and
Record()any events as necessary.
Note
Use exceptions to reject command execution. - Create the matching
private void Apply(AggregateCreated evt)"apply event" methods, that will modify any private state that might be needed by other invariants in "command" methods on the same aggregate.
- In Regalo, an Application Service is an implementation of
Inforigami.Regalo.Messaging.ICommandHandler<T>orInforigami.Regalo.Messaging.IEventHandler<T>. - Aggregate Roots may implement business rules, invariants and such. They MUST NOT use infrastructure (e.g. databases, email servers, send messages, etc). The command or event handler that loaded/created the aggregate root object and invoked it's behaviour is responsible for providing it everything it needs to do it's work, and MUST NOT implement any business rules.
- Create a class implementing
Inforigami.Regalo.Messaging.ICommandHandler<T>orInforigami.Regalo.Messaging.IEventHandler<T>.- Tip
Regalo's message-handling supports the entire message type hierarchy. So if you createAllCommandsHandler<Command>it will be invoked for any message that inheritsCommand. If you create type hierarchies for messages (e.g. for updating readstores or logging) you can make use of this feature easily.
- Tip
- Ensure your new handler has a
private readonly IMessageHandlerContext<TEntity> _context;field initialised by the constructor. - In the
public void Handle<T>method:- Obtain a session from the context.
- Create a new AggregateRoot object or load it from the session using values on the message.
- Invoke a "command" method on the aggregate root object.
- Call
session.SaveAndPublishEvents(). - You should have something like the following:
public void Handle(PlaceSalesOrder command) { using (var session = _context.OpenSession(command)) { var order = session.Get(command.SalesOrderId, command.SalesOrderVersion); order.PlaceOrder(); session.SaveAndPublishEvents(order); } }
This will be typically things like sending an email, publishing the message to a queuing service, etc and will most commonly be required for event handling.
- Create a class implementing
Inforigami.Regalo.Messaging.ICommandHandler<T>orInforigami.Regalo.Messaging.IEventHandler<T>. - Implement the
public void Handle<T>method as you see fit, using whatever injected services are required.
These are the basic projects that you are likely to require in any Regalo-based system. You may not need to install them directly as they will arrive as transient dependencies when you install other packages.
As it's name suggests, a library of shared code that is used throughout the other libraries. Classes of note include:
DateTimeOffsetProvider
Useful for writing testable code by making date generation deterministic by avoidingDateTimeOffset.Now.GuidProvider
Useful for writing testable code by making Guid generation deterministic by avoidingGuid.NewGuid().Resolver
A basic Service Locator used when new objects are required after the composition root (e.g. WebAPI controller) has already been initialised. E.g. For initialising message handlers ( classes you provide implementingInforigami.Regalo.Messaging.IMessageHandler<TMessage>). You will need to tell theResolverhow to use your chosen inversion of control container by callingResolver.Configure()when your application is started.
The key to this project is the AggregateRoot base class that you would use for all your aggregate root entities. Persistence-specific implementations are available and documented in the next section.
These are marker interfaces and base classes for domain events. For the events that leave your system you should have lightweight implementations, and appropriate mapping, so that external systems using your message contracts do not require even this lightweight dependency.
A lightweight mediator pattern implementation. Command and Event handlers are the Application Services of your domain and will interact with your entities. Note that an ASP.NET Controller or NServiceBus/MassTransit handler would delegate to these classes if you choose to use this library. See notes above on how the various distributed systems patterns relate to each other and are implemented by Regalo.
You can use this library to perform a recursive property-based comparison of two objects. This is "dog-fooded" in the Inforigami.Regalo.Testing project.
A library you can use to make unit testing of your Application Services (handler implementations of Inforigami.Regalo.Messaging) and how they interact with your domain objects (implementations of Inforigami.Regalo.EventSourcing.AggregateRoot).
Depending on how you wish to store your event streams, you can choose one of the following:
An Azure Table Storage-based implementation of Inforigami.Regalo.EventSourcing.IEventStore.
An EventStoreDB-based implementation of Inforigami.Regalo.EventSourcing.IEventStore.
A RavenDB-based implementation of Inforigami.Regalo.EventSourcing.IEventStore.
A SQL Server-based implementation of Inforigami.Regalo.EventSourcing.IEventStore.
A test-harness for the basic console logging provided by Inforigami.Regalo.Core.ConsoleLogger.
A sample domain model that is also used by Regalo's own tests.
These libraries have certain opinions:
AggregateRootfollows the principle that aggregate roots do not have any public properties. They have only public methods, where each method should relate to part of a business transaction or use-case. The library will collect domain events that are generated when behaviours are invoked on- You need a reference to one of these assemblies in your Domain Model projects. There's really no problem here. You've made a decision to use this library, and you're not going to just swap it out for another one - you'd have to re-write everything anyway. It's opinionated, if you go with it you'll be fine.
- Having a reference in your Domain Commands/Events. This is also fine. It seems like it would mean that you'd have to reference Inforigami.Regalo.Interfaces in consuming projects to, which would be bad. In fact, don't do that. Instead create proper inter-service events and an anti-corruption layer instead. Your domain events then stay inside your domain and your externally-published events are free to be happy little POCOs, just as intended.