|
1 | | -What is it? |
2 | | -=========== |
3 | | -A simple event sourcing "framework" based on Greg Young's work in DDD, CQRS and Event Sourcing. In truth, as you'll see, it's barely a "framework" as much of a bunch of reusable code. :) |
| 1 | +# What is it? |
4 | 2 |
|
5 | | -Why'd you call it "Inforigami.Regalo"? |
6 | | -=========================== |
7 | | -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" to make it sound cool. Hence "Inforigami.Regalo". I pronounce it "regarlo", in case you're wondering. |
| 3 | +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](https://x.com/gregyoung). |
8 | 4 |
|
9 | | -How do I use it? |
10 | | -================ |
11 | | -Inforigami.Regalo comes in two significant parts - there's _Regalo.Core_ itself. This provides a bunch of interfaces and a Greg Young-inspired `AggregateRoot` class to derive your... aggregate roots from. You also get an event sourcing repository implementation. What's missing from that picture is the actual persistence. That's where _Inforigami.Regalo.RavenDB_ (and at some point _Inforigami.Regalo.EventStore_ and _Inforigami.Regalo.SqlServer_) come in. They provide an event store implementation. |
| 5 | +_"Why'd you call it "Inforigami.Regalo"?"_ |
| 6 | +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. |
12 | 7 |
|
13 | | -Getting started is a case of installing one of the event store implementation packages via nuget.org e.g. `install-package Inforigami.Regalo.Ravendb`, then configuring a few dependencies (all Inforigami.Regalo libraries rely on the Dependency Inversion principle). |
| 8 | +# How do I get it? |
14 | 9 |
|
15 | | -I'll try to build a "getting started" page on the wiki asap. |
| 10 | +All projects that you would use to implement your application are available on nuget.org. See [Getting Started](#getting-started) for more information. |
16 | 11 |
|
17 | | -Full Disclosure |
18 | | -=============== |
19 | | -There are a couple of things you probably won't like... |
| 12 | +# Patterns |
20 | 13 |
|
21 | | -1. Having 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. |
| 14 | +There are a number of related patterns that are so closely related that they are often used interchangably to mean the same thing: |
| 15 | + |
| 16 | +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. |
| 17 | + |
| 18 | +The following is brief explanation of how Regalo implements each of these related patterns: |
| 19 | + |
| 20 | +| Regalo | Domain-Driven Design | Hexagonal/Ports-and-Adapters Architecture | Clean Architecture | |
| 21 | +|--------|----------------------|------------------------|--------------------| |
| 22 | +| `Inforigami.Regalo.EventSourcing.AggregateRoot` | Domain Model, Aggregate Root, Entity | Business Logic | Entities | |
| 23 | +| `Inforigami.Regalo.Interfaces.ICommand/Command` | Domain Model, Command | Business Logic | Entities | |
| 24 | +| `Inforigami.Regalo.Interfaces.IEvent/Event` | Domain Model, Event | Business Logic | Entities | |
| 25 | +| `Inforigami.Regalo.Messaging.ICommandHandler<T>/IEventHandler<T>` | Domain Model, Application Service | Port | Use-Cases | |
| 26 | +| Framework e.g. ASP.NET Controller, NServiceBus Handler | Infrastructure Layer | Adapter | Presenters, Gateways, Controllers | |
| 27 | + |
| 28 | + |
| 29 | +# Getting Started |
| 30 | + |
| 31 | +## Creating a domain model project |
| 32 | + |
| 33 | +**Note:** |
| 34 | +I recommend you keep related aggregates and their domain Commands, Events, and Application Services together in namespaces. |
| 35 | + |
| 36 | +1. Choose from the available persistence options for your event streams below: |
| 37 | + * [SQL Server](#inforigamiregalosqlserver) |
| 38 | + * [EventStoreDB](#inforigamiregaloeventstore) |
| 39 | + * [RavenDB](#inforigamiregaloravendb) |
| 40 | + * [Azure Table Storage](#inforigamiregaloazuretablestorage) (experimental) |
| 41 | +1. Create a new class library project for your domain model and delete the scaffolded `Class1.cs`. |
| 42 | +1. Install your chosen persistence package. |
| 43 | +1. Choose your first Command to implement. This will be informed by the tasks to be carried out by your use-cases. |
| 44 | +1. Refer to the guidance for [Creating a domain command](#creating-a-domain-command). |
| 45 | +1. Refer to the guidance for [Creating a domain event](#creating-a-domain-event). |
| 46 | +1. Refer to the guidance for [Building an Aggregate Root](#building-an-aggregate-root). |
| 47 | +1. Refer to the guidance for [Creating an Application Service](#creating-an-application-service). |
| 48 | +1. 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. |
| 49 | + |
| 50 | +## Creating a domain command |
| 51 | + |
| 52 | +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](#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. |
| 53 | + |
| 54 | +1. Create a class representing a Command that inherits `Inforigami.Regalo.Interfaces.Command`. |
| 55 | +1. Create a `public Guid Id { get; private set; }` property to store the aggregate's ID. This step is optional for Commands that create aggregates. |
| 56 | +1. 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. |
| 57 | +1. Create similar properties for any other values that apply to the command. |
| 58 | +1. Create a constructor that initialises all the properties. |
| 59 | + |
| 60 | +## Creating a domain event |
| 61 | + |
| 62 | +1. Create a class representing an Event that inherits `Inforigami.Regalo.Interfaces.Event`. |
| 63 | +1. Create a `public Guid Id { get; private set; }` property to store the aggregate's ID. |
| 64 | +1. Create a `public int Version { get; private set; }` property to store the version of the aggregate that the command applies to. |
| 65 | +1. Create similar properties for any other values that apply to the command. |
| 66 | +1. Create a constructor that initialises all the properties. |
| 67 | + |
| 68 | +## Building an Aggregate Root |
| 69 | + |
| 70 | +* 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. |
| 71 | +* The public API for an aggregate root will consist entirely of `public void` methods that are named for Commands in your domain relating to that aggregate. There are to be no public properties or fields. |
| 72 | +* Each public "command" method is only allowed to perform three duties: |
| 73 | + 1. Validate parameters. |
| 74 | + 1. Assert invariant logic (this is the actual "business logic" of your domain). |
| 75 | + 1. Record new events by calling `base.Record()`. |
| 76 | +* Public "command" methods should NOT modify private state of the aggregate. |
| 77 | +* For each event that a "command" method may raise, there can be a corresponding `private void Apply([Event] evt)` method. |
| 78 | +* Each private "apply event" method is only allowed to modify private state of the aggregate. |
| 79 | +* Private "apply event" methods MUST NOT perform invariant logic, validation, or record further events. |
| 80 | + |
| 81 | +#### To begin a new aggregate root |
| 82 | + |
| 83 | +1. Create a class representing an aggregate root, ensuring it inherits `Inforigami.Regalo.EventSourcing.AggregateRoot`. E.g. `SalesOrder`. |
| 84 | +1. Create an "initialisation" method that represents the aggregate's "initialisation" Command. E.g. `public void Create()`. |
| 85 | +1. In that method, implement command validation, invariant logic, and `Record()` any events as necessary. |
| 86 | + **Note** |
| 87 | + Use exceptions to reject command execution. |
| 88 | +1. Create the matching `private void Apply(AggregateCreated evt)` "apply event" method. |
| 89 | + **Note** |
| 90 | + In that method, ensure it sets `base.Id` from the Id on the event, plus any other private state that might be needed by other invariants in "command" methods on the same aggregate. |
| 91 | + |
| 92 | +#### To add new command methods to an existing aggregate root |
| 93 | + |
| 94 | +1. Create a "command" method on your aggregate |
| 95 | +1. In that method, implement command validation, invariant logic, and `Record()` any events as necessary. |
| 96 | + **Note** |
| 97 | + Use exceptions to reject command execution. |
| 98 | +1. 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. |
| 99 | + |
| 100 | +### Creating an Application Service |
| 101 | + |
| 102 | +* In Regalo, an Application Service is an implementation of `Inforigami.Regalo.Messaging.ICommandHandler<T>` or `Inforigami.Regalo.Messaging.IEventHandler<T>`. |
| 103 | +* 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. |
| 104 | + |
| 105 | +#### To create an application service that invokes business logic |
| 106 | + |
| 107 | +1. Create a class implementing `Inforigami.Regalo.Messaging.ICommandHandler<T>` or `Inforigami.Regalo.Messaging.IEventHandler<T>`. |
| 108 | + * **Tip** |
| 109 | + Regalo's message-handling supports the entire message type hierarchy. So if you create `AllCommandsHandler<Command>` it will be invoked for any message that inherits `Command`. If you create type hierarchies for messages (e.g. for updating readstores or logging) you can make use of this feature easily. |
| 110 | +1. Ensure your new handler has a `private readonly IMessageHandlerContext<TEntity> _context;` field initialised by the constructor. |
| 111 | +1. In the `public void Handle<T>` method: |
| 112 | + 1. Obtain a session from the context. |
| 113 | + 1. Create a new AggregateRoot object or load it from the session using values on the message. |
| 114 | + 1. Invoke a "command" method on the aggregate root object. |
| 115 | + 1. Call `session.SaveAndPublishEvents()`. |
| 116 | + 1. You should have something like the following: |
| 117 | + ``` csharp |
| 118 | + public void Handle(PlaceSalesOrder command) |
| 119 | + { |
| 120 | + using (var session = _context.OpenSession(command)) |
| 121 | + { |
| 122 | + var order = session.Get(command.SalesOrderId, command.SalesOrderVersion); |
| 123 | + order.PlaceOrder(); |
| 124 | + session.SaveAndPublishEvents(order); |
| 125 | + } |
| 126 | + } |
| 127 | + ``` |
| 128 | + |
| 129 | +#### To create an application service that performs infrastructure tasks |
| 130 | + |
| 131 | +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. |
| 132 | + |
| 133 | +1. Create a class implementing `Inforigami.Regalo.Messaging.ICommandHandler<T>` or `Inforigami.Regalo.Messaging.IEventHandler<T>`. |
| 134 | +1. Implement the `public void Handle<T>` method as you see fit, using whatever injected services are required. |
| 135 | + |
| 136 | + |
| 137 | +# What does each project do? |
| 138 | + |
| 139 | +## Projects you consume in your applications |
| 140 | + |
| 141 | +### Core projects |
| 142 | + |
| 143 | +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. |
| 144 | + |
| 145 | +#### Inforigami.Regalo.Core |
| 146 | + |
| 147 | +As it's name suggests, a library of shared code that is used throughout the other libraries. Classes of note include: |
| 148 | + |
| 149 | +* `DateTimeOffsetProvider` |
| 150 | + Useful for writing testable code by making date generation deterministic by avoiding `DateTimeOffset.Now`. |
| 151 | +* `GuidProvider` |
| 152 | + Useful for writing testable code by making Guid generation deterministic by avoiding `Guid.NewGuid()`. |
| 153 | +* `Resolver` |
| 154 | + 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 implementing `Inforigami.Regalo.Messaging.IMessageHandler<TMessage>`). You will need to tell the `Resolver` how to use your chosen inversion of control container by calling `Resolver.Configure()` when your application is started. |
| 155 | + |
| 156 | +#### Inforigami.Regalo.EventSourcing |
| 157 | + |
| 158 | +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. |
| 159 | + |
| 160 | +#### Inforigami.Regalo.Interfaces |
| 161 | + |
| 162 | +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. |
| 163 | + |
| 164 | +#### Inforigami.Regalo.Messaging |
| 165 | + |
| 166 | +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. |
| 167 | + |
| 168 | +#### Inforigami.Regalo.ObjectCompare |
| 169 | + |
| 170 | +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. |
| 171 | + |
| 172 | +#### Inforigami.Regalo.Testing |
| 173 | + |
| 174 | +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`). |
| 175 | + |
| 176 | +### Event stream persistence options |
| 177 | + |
| 178 | +Depending on how you wish to store your event streams, you can choose one of the following: |
| 179 | + |
| 180 | +#### Inforigami.Regalo.AzureTableStorage |
| 181 | + An Azure Table Storage-based implementation of `Inforigami.Regalo.EventSourcing.IEventStore`. |
| 182 | + |
| 183 | +#### Inforigami.Regalo.EventStore |
| 184 | + An EventStoreDB-based implementation of `Inforigami.Regalo.EventSourcing.IEventStore`. |
| 185 | + |
| 186 | +#### Inforigami.Regalo.RavenDB |
| 187 | + A RavenDB-based implementation of `Inforigami.Regalo.EventSourcing.IEventStore`. |
| 188 | + |
| 189 | +#### Inforigami.Regalo.SqlServer |
| 190 | + A SQL Server-based implementation of `Inforigami.Regalo.EventSourcing.IEventStore`. |
| 191 | + |
| 192 | +## Projects used only in the development of Regalo |
| 193 | + |
| 194 | +#### Inforigami.Regalo.Core.ConsoleLoggerTest |
| 195 | + A test-harness for the basic console logging provided by `Inforigami.Regalo.Core.ConsoleLogger`. |
| 196 | + |
| 197 | +#### Inforigami.Regalo.Core.Tests.DomainModel |
| 198 | + A sample domain model that is also used by Regalo's own tests. |
| 199 | + |
| 200 | +# Principles |
| 201 | + |
| 202 | +These libraries have certain opinions: |
| 203 | + |
| 204 | +1. `AggregateRoot` follows 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 |
| 205 | +1. 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. |
22 | 206 | 2. 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. |
0 commit comments