Skip to content

Commit e508a88

Browse files
committed
Merge branch 'develop'
2 parents 0dccfc7 + 136dfba commit e508a88

File tree

11 files changed

+540
-65
lines changed

11 files changed

+540
-65
lines changed

README.md

Lines changed: 146 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ the [dos and don'ts](./Documentation/DoesAndDonts.md) and the
5151

5252
### Examples
5353

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
5657
* **Shipping:** To get a more complete example of how EventFlow _could_ be used,
5758
have a look at the shipping example found here in the code base. The example
5859
is based on the shipping example from the book "Domain-Driven Design -
@@ -113,37 +114,159 @@ to the documentation.
113114
EventFlow can be swapped with a custom implementation through the embedded
114115
IoC container.
115116

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.
119135

120136
```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>()
125148
.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>
126177
{
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+
```
133200

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+
```
136208

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+
}
139217

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+
}
142267
}
143268
```
144269

145-
Note: `.ConfigureAwait(false)` and use of `CancellationToken` is omitted in
146-
the above example to ease reading.
147270

148271
## State of EventFlow
149272

RELEASE_NOTES.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
### New in 0.34 (not released yet)
1+
### New in 0.35 (not released yet)
2+
3+
* Fixed: `IAggregateStore.UpdateAsync` and `StoreAsync` now publishes committed
4+
events as expected. This basically means that its now possible to circumvent the
5+
command and command handler pattern and use the `IAggregateStore.UpdateAsync`
6+
directly to modify an aggregate root
7+
* Fixed: Domain events emitted from aggregate sagas are now published
8+
9+
### New in 0.34.2221 (released 2016-08-23)
210

311
* **New core feature:** EventFlow now support sagas, also known as process
412
managers. The use of sagas is opt-in. Currently EventFlow only supports sagas

Source/EventFlow.Tests/EventFlow.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
<Compile Include="IntegrationTests\ReadStores\ReadModels\InMemoryThingyMessageReadModel.cs" />
5959
<Compile Include="IntegrationTests\ReadStores\ReadModels\InMemoryThingyReadModel.cs" />
6060
<Compile Include="IntegrationTests\ResolverTests.cs" />
61+
<Compile Include="IntegrationTests\CompleteExampleTests.cs" />
6162
<Compile Include="IntegrationTests\SnapshotStores\InMemorySnapshotStoreTests.cs" />
6263
<Compile Include="UnitTests\Aggregates\AggregateRootNameTests.cs" />
6364
<Compile Include="UnitTests\Aggregates\AggregateRootApplyEventTests.cs" />
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// The MIT License (MIT)
2+
//
3+
// Copyright (c) 2015-2016 Rasmus Mikkelsen
4+
// Copyright (c) 2015-2016 eBay Software Foundation
5+
// https://github.com/rasmus/EventFlow
6+
//
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy of
8+
// this software and associated documentation files (the "Software"), to deal in
9+
// the Software without restriction, including without limitation the rights to
10+
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
11+
// the Software, and to permit persons to whom the Software is furnished to do so,
12+
// subject to the following conditions:
13+
//
14+
// The above copyright notice and this permission notice shall be included in all
15+
// copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
19+
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
20+
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
21+
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22+
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+
//
24+
25+
using System.Threading;
26+
using System.Threading.Tasks;
27+
using EventFlow.Aggregates;
28+
using EventFlow.Commands;
29+
using EventFlow.Core;
30+
using EventFlow.Exceptions;
31+
using EventFlow.Extensions;
32+
using EventFlow.Queries;
33+
using EventFlow.ReadStores;
34+
using EventFlow.TestHelpers;
35+
using FluentAssertions;
36+
using NUnit.Framework;
37+
38+
namespace EventFlow.Tests.IntegrationTests
39+
{
40+
[Category(Categories.Integration)]
41+
public class CompleteExampleTests
42+
{
43+
[Test]
44+
public async Task Example()
45+
{
46+
// We wire up EventFlow with all of our classes. Instead of adding events,
47+
// commands, etc. explicitly, we could have used the the simpler
48+
// AddDefaults(Assembly) instead.
49+
using (var resolver = EventFlowOptions.New
50+
.AddEvents(typeof(ExampleEvent))
51+
.AddCommands(typeof(ExampleCommand))
52+
.AddCommandHandlers(typeof(ExampleCommandHandler))
53+
.UseInMemoryReadStoreFor<ExampleReadModel>()
54+
.CreateResolver())
55+
{
56+
// Create a new identity for our aggregate root
57+
var simpleId = ExampleId.New;
58+
59+
// Resolve the command bus and use it to publish a command
60+
var commandBus = resolver.Resolve<ICommandBus>();
61+
await commandBus.PublishAsync(
62+
new ExampleCommand(simpleId, 42), CancellationToken.None)
63+
.ConfigureAwait(false);
64+
65+
// Resolve the query handler and use the built-in query for fetching
66+
// read models by identity to get our read model representing the
67+
// state of our aggregate root
68+
var queryProcessor = resolver.Resolve<IQueryProcessor>();
69+
var simpleReadModel = await queryProcessor.ProcessAsync(
70+
new ReadModelByIdQuery<ExampleReadModel>(simpleId), CancellationToken.None)
71+
.ConfigureAwait(false);
72+
73+
// Verify that the read model has the expected magic number
74+
simpleReadModel.MagicNumber.Should().Be(42);
75+
}
76+
}
77+
78+
// Represents the aggregate identity (ID)
79+
public class ExampleId : Identity<ExampleId>
80+
{
81+
public ExampleId(string value) : base(value) { }
82+
}
83+
84+
// The aggregate root
85+
public class ExampleAggrenate : AggregateRoot<ExampleAggrenate, ExampleId>,
86+
IEmit<ExampleEvent>
87+
{
88+
private int? _magicNumber;
89+
90+
public ExampleAggrenate(ExampleId id) : base(id) { }
91+
92+
// Method invoked by our command
93+
public void SetMagicNumer(int magicNumber)
94+
{
95+
if (_magicNumber.HasValue)
96+
throw DomainError.With("Magic number already set");
97+
98+
Emit(new ExampleEvent(magicNumber));
99+
}
100+
101+
// We apply the event as part of the event sourcing system. EventFlow
102+
// provides several different methods for doing this, e.g. state objects,
103+
// the Apply method is merely the simplest
104+
public void Apply(ExampleEvent aggregateEvent)
105+
{
106+
_magicNumber = aggregateEvent.MagicNumber;
107+
}
108+
}
109+
110+
// A basic event containing some information
111+
public class ExampleEvent : AggregateEvent<ExampleAggrenate, ExampleId>
112+
{
113+
public ExampleEvent(int magicNumber)
114+
{
115+
MagicNumber = magicNumber;
116+
}
117+
118+
public int MagicNumber { get; }
119+
}
120+
121+
// Command for update magic number
122+
public class ExampleCommand : Command<ExampleAggrenate, ExampleId>
123+
{
124+
public ExampleCommand(
125+
ExampleId aggregateId,
126+
int magicNumber)
127+
: base(aggregateId)
128+
{
129+
MagicNumber = magicNumber;
130+
}
131+
132+
public int MagicNumber { get; }
133+
}
134+
135+
// Command handler for our command
136+
public class ExampleCommandHandler : CommandHandler<ExampleAggrenate, ExampleId, ExampleCommand>
137+
{
138+
public override Task ExecuteAsync(
139+
ExampleAggrenate aggregate,
140+
ExampleCommand command,
141+
CancellationToken cancellationToken)
142+
{
143+
aggregate.SetMagicNumer(command.MagicNumber);
144+
return Task.FromResult(0);
145+
}
146+
}
147+
148+
// Read model for our aggregate
149+
public class ExampleReadModel : IReadModel,
150+
IAmReadModelFor<ExampleAggrenate, ExampleId, ExampleEvent>
151+
{
152+
public int MagicNumber { get; private set; }
153+
154+
public void Apply(
155+
IReadModelContext context,
156+
IDomainEvent<ExampleAggrenate, ExampleId, ExampleEvent> domainEvent)
157+
{
158+
MagicNumber = domainEvent.AggregateEvent.MagicNumber;
159+
}
160+
}
161+
}
162+
}

0 commit comments

Comments
 (0)