Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
rasmus committed Sep 6, 2016
2 parents 0dccfc7 + 136dfba commit e508a88
Show file tree
Hide file tree
Showing 11 changed files with 540 additions and 65 deletions.
169 changes: 146 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ the [dos and don'ts](./Documentation/DoesAndDonts.md) and the

### Examples

* **[Simple](#simple-example):** Shows the key concepts of EventFlow in a few
lines of code
* **[Complete](#complete-example):** Shows a complete example on how to use
EventFlow with in-memory event store and read models in a relatively few lines
of code
* **Shipping:** To get a more complete example of how EventFlow _could_ be used,
have a look at the shipping example found here in the code base. The example
is based on the shipping example from the book "Domain-Driven Design -
Expand Down Expand Up @@ -113,37 +114,159 @@ to the documentation.
EventFlow can be swapped with a custom implementation through the embedded
IoC container.

## Simple example
Here's an example on how to use the in-memory event store (default)
and a in-memory read model store.
## Complete example
Here's a complete example on how to use the default in-memory event store
along with an in-memory read model.

The example consists of the following classes, each shown below

- `ExampleAggregate`: The aggregate root
- `ExampleId`: Value object representing the identity of the aggregate root
- `ExampleEvent`: Event emitted by the aggregate root
- `ExampleCommand`: Value object defining a command that can be published to the
aggregate root
- `ExampleCommandHandler`: Command handler which EventFlow resolves using its IoC
container and defines how the command specific is applied to the aggregate root
- `ExampleReadModel`: In-memory read model providing easy access to the current
state

**Note:** This example is part of the EventFlow test suite, so checkout the
code and give it a go.

```csharp
using (var resolver = EventFlowOptions.New
.AddEvents(typeof (TestAggregate).Assembly)
.AddCommandHandlers(typeof (TestAggregate).Assembly)
.UseInMemoryReadStoreFor<TestAggregate, TestReadModel>()
[Test]
public async Task Example()
{
// We wire up EventFlow with all of our classes. Instead of adding events,
// commands, etc. explicitly, we could have used the the simpler
// AddDefaults(Assembly) instead.
using (var resolver = EventFlowOptions.New
.AddEvents(typeof(ExampleEvent))
.AddCommands(typeof(ExampleCommand))
.AddCommandHandlers(typeof(ExampleCommandHandler))
.UseInMemoryReadStoreFor<ExampleReadModel>()
.CreateResolver())
{
// Create a new identity for our aggregate root
var exampleId = ExampleId.New;

// Resolve the command bus and use it to publish a command
var commandBus = resolver.Resolve<ICommandBus>();
await commandBus.PublishAsync(
new ExampleCommand(exampleId, 42), CancellationToken.None)
.ConfigureAwait(false);

// Resolve the query handler and use the built-in query for fetching
// read models by identity to get our read model representing the
// state of our aggregate root
var queryProcessor = resolver.Resolve<IQueryProcessor>();
var exampleReadModel = await queryProcessor.ProcessAsync(
new ReadModelByIdQuery<ExampleReadModel>(exampleId), CancellationToken.None)
.ConfigureAwait(false);

// Verify that the read model has the expected magic number
exampleReadModel.MagicNumber.Should().Be(42);
}
}
```

```csharp
// The aggregate root
public class ExampleAggrenate : AggregateRoot<ExampleAggrenate, ExampleId>,
IEmit<ExampleEvent>
{
var commandBus = resolver.Resolve<ICommandBus>();
var eventStore = resolver.Resolve<IEventStore>();
var readModelStore = resolver.Resolve<IInMemoryReadModelStore<
TestAggregate,
TestReadModel>>();
var id = TestId.New;
private int? _magicNumber;

public ExampleAggrenate(ExampleId id) : base(id) { }

// Method invoked by our command
public void SetMagicNumer(int magicNumber)
{
if (_magicNumber.HasValue)
throw DomainError.With("Magic number already set");

Emit(new ExampleEvent(magicNumber));
}

// We apply the event as part of the event sourcing system. EventFlow
// provides several different methods for doing this, e.g. state objects,
// the Apply method is merely the simplest
public void Apply(ExampleEvent aggregateEvent)
{
_magicNumber = aggregateEvent.MagicNumber;
}
}
```

// Publish a command
await commandBus.PublishAsync(new PingCommand(id));
```csharp
// Represents the aggregate identity (ID)
public class ExampleId : Identity<ExampleId>
{
public ExampleId(string value) : base(value) { }
}
```

// Load aggregate
var testAggregate = await eventStore.LoadAggregateAsync<TestAggregate>(id);
```csharp
// A basic event containing some information
public class ExampleEvent : AggregateEvent<ExampleAggrenate, ExampleId>
{
public ExampleEvent(int magicNumber)
{
MagicNumber = magicNumber;
}

// Get read model from in-memory read store
var testReadModel = await readModelStore.GetAsync(id);
public int MagicNumber { get; }
}
```

```csharp
// Command for update magic number
public class ExampleCommand : Command<ExampleAggrenate, ExampleId>
{
public ExampleCommand(
ExampleId aggregateId,
int magicNumber)
: base(aggregateId)
{
MagicNumber = magicNumber;
}

public int MagicNumber { get; }
}
```

```csharp
// Command handler for our command
public class ExampleCommandHandler
: CommandHandler<ExampleAggrenate, ExampleId, ExampleCommand>
{
public override Task ExecuteAsync(
ExampleAggrenate aggregate,
ExampleCommand command,
CancellationToken cancellationToken)
{
aggregate.SetMagicNumer(command.MagicNumber);
return Task.FromResult(0);
}
}
```

```csharp
// Read model for our aggregate
public class ExampleReadModel : IReadModel,
IAmReadModelFor<ExampleAggrenate, ExampleId, ExampleEvent>
{
public int MagicNumber { get; private set; }

public void Apply(
IReadModelContext context,
IDomainEvent<ExampleAggrenate, ExampleId, ExampleEvent> domainEvent)
{
MagicNumber = domainEvent.AggregateEvent.MagicNumber;
}
}
```

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

## State of EventFlow

Expand Down
10 changes: 9 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
### New in 0.34 (not released yet)
### New in 0.35 (not released yet)

* Fixed: `IAggregateStore.UpdateAsync` and `StoreAsync` now publishes committed
events as expected. This basically means that its now possible to circumvent the
command and command handler pattern and use the `IAggregateStore.UpdateAsync`
directly to modify an aggregate root
* Fixed: Domain events emitted from aggregate sagas are now published

### New in 0.34.2221 (released 2016-08-23)

* **New core feature:** EventFlow now support sagas, also known as process
managers. The use of sagas is opt-in. Currently EventFlow only supports sagas
Expand Down
1 change: 1 addition & 0 deletions Source/EventFlow.Tests/EventFlow.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
<Compile Include="IntegrationTests\ReadStores\ReadModels\InMemoryThingyMessageReadModel.cs" />
<Compile Include="IntegrationTests\ReadStores\ReadModels\InMemoryThingyReadModel.cs" />
<Compile Include="IntegrationTests\ResolverTests.cs" />
<Compile Include="IntegrationTests\CompleteExampleTests.cs" />
<Compile Include="IntegrationTests\SnapshotStores\InMemorySnapshotStoreTests.cs" />
<Compile Include="UnitTests\Aggregates\AggregateRootNameTests.cs" />
<Compile Include="UnitTests\Aggregates\AggregateRootApplyEventTests.cs" />
Expand Down
162 changes: 162 additions & 0 deletions Source/EventFlow.Tests/IntegrationTests/CompleteExampleTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// The MIT License (MIT)
//
// Copyright (c) 2015-2016 Rasmus Mikkelsen
// Copyright (c) 2015-2016 eBay Software Foundation
// https://github.com/rasmus/EventFlow
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System.Threading;
using System.Threading.Tasks;
using EventFlow.Aggregates;
using EventFlow.Commands;
using EventFlow.Core;
using EventFlow.Exceptions;
using EventFlow.Extensions;
using EventFlow.Queries;
using EventFlow.ReadStores;
using EventFlow.TestHelpers;
using FluentAssertions;
using NUnit.Framework;

namespace EventFlow.Tests.IntegrationTests
{
[Category(Categories.Integration)]
public class CompleteExampleTests
{
[Test]
public async Task Example()
{
// We wire up EventFlow with all of our classes. Instead of adding events,
// commands, etc. explicitly, we could have used the the simpler
// AddDefaults(Assembly) instead.
using (var resolver = EventFlowOptions.New
.AddEvents(typeof(ExampleEvent))
.AddCommands(typeof(ExampleCommand))
.AddCommandHandlers(typeof(ExampleCommandHandler))
.UseInMemoryReadStoreFor<ExampleReadModel>()
.CreateResolver())
{
// Create a new identity for our aggregate root
var simpleId = ExampleId.New;

// Resolve the command bus and use it to publish a command
var commandBus = resolver.Resolve<ICommandBus>();
await commandBus.PublishAsync(
new ExampleCommand(simpleId, 42), CancellationToken.None)
.ConfigureAwait(false);

// Resolve the query handler and use the built-in query for fetching
// read models by identity to get our read model representing the
// state of our aggregate root
var queryProcessor = resolver.Resolve<IQueryProcessor>();
var simpleReadModel = await queryProcessor.ProcessAsync(
new ReadModelByIdQuery<ExampleReadModel>(simpleId), CancellationToken.None)
.ConfigureAwait(false);

// Verify that the read model has the expected magic number
simpleReadModel.MagicNumber.Should().Be(42);
}
}

// Represents the aggregate identity (ID)
public class ExampleId : Identity<ExampleId>
{
public ExampleId(string value) : base(value) { }
}

// The aggregate root
public class ExampleAggrenate : AggregateRoot<ExampleAggrenate, ExampleId>,
IEmit<ExampleEvent>
{
private int? _magicNumber;

public ExampleAggrenate(ExampleId id) : base(id) { }

// Method invoked by our command
public void SetMagicNumer(int magicNumber)
{
if (_magicNumber.HasValue)
throw DomainError.With("Magic number already set");

Emit(new ExampleEvent(magicNumber));
}

// We apply the event as part of the event sourcing system. EventFlow
// provides several different methods for doing this, e.g. state objects,
// the Apply method is merely the simplest
public void Apply(ExampleEvent aggregateEvent)
{
_magicNumber = aggregateEvent.MagicNumber;
}
}

// A basic event containing some information
public class ExampleEvent : AggregateEvent<ExampleAggrenate, ExampleId>
{
public ExampleEvent(int magicNumber)
{
MagicNumber = magicNumber;
}

public int MagicNumber { get; }
}

// Command for update magic number
public class ExampleCommand : Command<ExampleAggrenate, ExampleId>
{
public ExampleCommand(
ExampleId aggregateId,
int magicNumber)
: base(aggregateId)
{
MagicNumber = magicNumber;
}

public int MagicNumber { get; }
}

// Command handler for our command
public class ExampleCommandHandler : CommandHandler<ExampleAggrenate, ExampleId, ExampleCommand>
{
public override Task ExecuteAsync(
ExampleAggrenate aggregate,
ExampleCommand command,
CancellationToken cancellationToken)
{
aggregate.SetMagicNumer(command.MagicNumber);
return Task.FromResult(0);
}
}

// Read model for our aggregate
public class ExampleReadModel : IReadModel,
IAmReadModelFor<ExampleAggrenate, ExampleId, ExampleEvent>
{
public int MagicNumber { get; private set; }

public void Apply(
IReadModelContext context,
IDomainEvent<ExampleAggrenate, ExampleId, ExampleEvent> domainEvent)
{
MagicNumber = domainEvent.AggregateEvent.MagicNumber;
}
}
}
}
Loading

0 comments on commit e508a88

Please sign in to comment.