@@ -51,8 +51,9 @@ the [dos and don'ts](./Documentation/DoesAndDonts.md) and the
51
51
52
52
### Examples
53
53
54
- * **[Simple](#simple-example):** Shows the key concepts of EventFlow in a few
55
- lines of code
54
+ * **[Complete](#complete-example):** Shows a complete example on how to use
55
+ EventFlow with in-memory event store and read models in a relatively few lines
56
+ of code
56
57
* **Shipping:** To get a more complete example of how EventFlow _could_ be used,
57
58
have a look at the shipping example found here in the code base. The example
58
59
is based on the shipping example from the book "Domain-Driven Design -
@@ -113,37 +114,159 @@ to the documentation.
113
114
EventFlow can be swapped with a custom implementation through the embedded
114
115
IoC container.
115
116
116
- ## Simple example
117
- Here's an example on how to use the in-memory event store (default)
118
- and a in-memory read model store.
117
+ ## Complete example
118
+ Here's a complete example on how to use the default in-memory event store
119
+ along with an in-memory read model.
120
+
121
+ The example consists of the following classes, each shown below
122
+
123
+ - `ExampleAggregate`: The aggregate root
124
+ - `ExampleId`: Value object representing the identity of the aggregate root
125
+ - `ExampleEvent`: Event emitted by the aggregate root
126
+ - `ExampleCommand`: Value object defining a command that can be published to the
127
+ aggregate root
128
+ - `ExampleCommandHandler`: Command handler which EventFlow resolves using its IoC
129
+ container and defines how the command specific is applied to the aggregate root
130
+ - `ExampleReadModel`: In-memory read model providing easy access to the current
131
+ state
132
+
133
+ **Note:** This example is part of the EventFlow test suite, so checkout the
134
+ code and give it a go.
119
135
120
136
```csharp
121
- using (var resolver = EventFlowOptions.New
122
- .AddEvents(typeof (TestAggregate).Assembly)
123
- .AddCommandHandlers(typeof (TestAggregate).Assembly)
124
- .UseInMemoryReadStoreFor<TestAggregate, TestReadModel>()
137
+ [Test]
138
+ public async Task Example()
139
+ {
140
+ // We wire up EventFlow with all of our classes. Instead of adding events,
141
+ // commands, etc. explicitly, we could have used the the simpler
142
+ // AddDefaults(Assembly) instead.
143
+ using (var resolver = EventFlowOptions.New
144
+ .AddEvents(typeof(ExampleEvent))
145
+ .AddCommands(typeof(ExampleCommand))
146
+ .AddCommandHandlers(typeof(ExampleCommandHandler))
147
+ .UseInMemoryReadStoreFor<ExampleReadModel>()
125
148
.CreateResolver())
149
+ {
150
+ // Create a new identity for our aggregate root
151
+ var exampleId = ExampleId.New;
152
+
153
+ // Resolve the command bus and use it to publish a command
154
+ var commandBus = resolver.Resolve<ICommandBus>();
155
+ await commandBus.PublishAsync(
156
+ new ExampleCommand(exampleId, 42), CancellationToken.None)
157
+ .ConfigureAwait(false);
158
+
159
+ // Resolve the query handler and use the built-in query for fetching
160
+ // read models by identity to get our read model representing the
161
+ // state of our aggregate root
162
+ var queryProcessor = resolver.Resolve<IQueryProcessor>();
163
+ var exampleReadModel = await queryProcessor.ProcessAsync(
164
+ new ReadModelByIdQuery<ExampleReadModel>(exampleId), CancellationToken.None)
165
+ .ConfigureAwait(false);
166
+
167
+ // Verify that the read model has the expected magic number
168
+ exampleReadModel.MagicNumber.Should().Be(42);
169
+ }
170
+ }
171
+ ```
172
+
173
+ ```csharp
174
+ // The aggregate root
175
+ public class ExampleAggrenate : AggregateRoot<ExampleAggrenate, ExampleId>,
176
+ IEmit<ExampleEvent>
126
177
{
127
- var commandBus = resolver.Resolve<ICommandBus>();
128
- var eventStore = resolver.Resolve<IEventStore>();
129
- var readModelStore = resolver.Resolve<IInMemoryReadModelStore<
130
- TestAggregate,
131
- TestReadModel>>();
132
- var id = TestId.New;
178
+ private int? _magicNumber;
179
+
180
+ public ExampleAggrenate(ExampleId id) : base(id) { }
181
+
182
+ // Method invoked by our command
183
+ public void SetMagicNumer(int magicNumber)
184
+ {
185
+ if (_magicNumber.HasValue)
186
+ throw DomainError.With("Magic number already set");
187
+
188
+ Emit(new ExampleEvent(magicNumber));
189
+ }
190
+
191
+ // We apply the event as part of the event sourcing system. EventFlow
192
+ // provides several different methods for doing this, e.g. state objects,
193
+ // the Apply method is merely the simplest
194
+ public void Apply(ExampleEvent aggregateEvent)
195
+ {
196
+ _magicNumber = aggregateEvent.MagicNumber;
197
+ }
198
+ }
199
+ ```
133
200
134
- // Publish a command
135
- await commandBus.PublishAsync(new PingCommand(id));
201
+ ```csharp
202
+ // Represents the aggregate identity (ID)
203
+ public class ExampleId : Identity<ExampleId>
204
+ {
205
+ public ExampleId(string value) : base(value) { }
206
+ }
207
+ ```
136
208
137
- // Load aggregate
138
- var testAggregate = await eventStore.LoadAggregateAsync<TestAggregate>(id);
209
+ ```csharp
210
+ // A basic event containing some information
211
+ public class ExampleEvent : AggregateEvent<ExampleAggrenate, ExampleId>
212
+ {
213
+ public ExampleEvent(int magicNumber)
214
+ {
215
+ MagicNumber = magicNumber;
216
+ }
139
217
140
- // Get read model from in-memory read store
141
- var testReadModel = await readModelStore.GetAsync(id);
218
+ public int MagicNumber { get; }
219
+ }
220
+ ```
221
+
222
+ ```csharp
223
+ // Command for update magic number
224
+ public class ExampleCommand : Command<ExampleAggrenate, ExampleId>
225
+ {
226
+ public ExampleCommand(
227
+ ExampleId aggregateId,
228
+ int magicNumber)
229
+ : base(aggregateId)
230
+ {
231
+ MagicNumber = magicNumber;
232
+ }
233
+
234
+ public int MagicNumber { get; }
235
+ }
236
+ ```
237
+
238
+ ```csharp
239
+ // Command handler for our command
240
+ public class ExampleCommandHandler
241
+ : CommandHandler<ExampleAggrenate, ExampleId, ExampleCommand>
242
+ {
243
+ public override Task ExecuteAsync(
244
+ ExampleAggrenate aggregate,
245
+ ExampleCommand command,
246
+ CancellationToken cancellationToken)
247
+ {
248
+ aggregate.SetMagicNumer(command.MagicNumber);
249
+ return Task.FromResult(0);
250
+ }
251
+ }
252
+ ```
253
+
254
+ ```csharp
255
+ // Read model for our aggregate
256
+ public class ExampleReadModel : IReadModel,
257
+ IAmReadModelFor<ExampleAggrenate, ExampleId, ExampleEvent>
258
+ {
259
+ public int MagicNumber { get; private set; }
260
+
261
+ public void Apply(
262
+ IReadModelContext context,
263
+ IDomainEvent<ExampleAggrenate, ExampleId, ExampleEvent> domainEvent)
264
+ {
265
+ MagicNumber = domainEvent.AggregateEvent.MagicNumber;
266
+ }
142
267
}
143
268
```
144
269
145
- Note: `.ConfigureAwait(false)` and use of `CancellationToken` is omitted in
146
- the above example to ease reading.
147
270
148
271
## State of EventFlow
149
272
0 commit comments