Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
rasmus committed May 22, 2015
2 parents 407670f + b5c2ef5 commit fa87cc5
Show file tree
Hide file tree
Showing 34 changed files with 793 additions and 48 deletions.
64 changes: 64 additions & 0 deletions Documentation/EventUpgrade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Event upgrade
At some point you might find the need to replace a event with zero or more
events. Some use cases might be

* A previous application version introduced a domain error in the form of a
wrong event being emitted from the aggregate
* Domain has changed, either from a change in requirements or simply from a
better understanding of the domain

EventFlow event upgraders are invoked whenever the event stream is loaded from
the event store. Each event upgrader receives the entire event stream one event
at a time.

Note that the _ordering_ of event upgraders is important as you might implement
two upgraders, one upgrade a event from V1 to V2 and then another upgrading V2
to V3. EventFlow orders the event upgraders by name before starting the event
upgrade.

## Example - removing a damaged event

To remove an event, simply check and only return the event if its no the event
you want to remove.

```csharp
public class DamagedEventRemover : IEventUpgrader<MyAggregate, MyId>
{
public IEnumerable<IDomainEvent<TestAggregate, TestId>> Upgrade(
IDomainEvent<TestAggregate, TestId> domainEvent)
{
var damagedEvent = domainEvent as IDomainEvent<MyAggregate, MyId, DamagedEvent>;
if (damagedEvent == null)
{
yield return domainEvent;
}
}
}
```

## Example - replace event

To one event to another, you should use the `IDomainEventFactory.Upgrade` to
help migrate meta data and create the new event.

```csharp
public class UpgradeMyEventV1ToMyEventV2 : IEventUpgrader<MyAggregate, MyId>
{
private readonly IDomainEventFactory _domainEventFactory;

public UpgradeTestEventV1ToTestEventV2(IDomainEventFactory domainEventFactory)
{
_domainEventFactory = domainEventFactory;
}

public IEnumerable<IDomainEvent<TestAggregate, TestId>> Upgrade(
IDomainEvent<TestAggregate, TestId> domainEvent)
{
var myEventV1 = domainEvent as IDomainEvent<MyAggregate, MyId, MyEventV1>;
yield return myEventV1 == null
? domainEvent
: _domainEventFactory.Upgrade<MyAggregate, MyId>(
domainEvent, new MyEventV2());
}
}
```
76 changes: 76 additions & 0 deletions Documentation/Queries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Queries

Creating queries in EventFlow is simple.

First create a value object that contains the data required for the query. In
this example we want to search for users based on their username.

```csharp
public class GetUserByUsernameQuery : IQuery<User>
{
public string Username { get; private set; }

public GetUserByUsernameQuery(string username)
{
Username = username;
}
}
```

Next create a query handler that implements how the query is processed.

```csharp
public class GetUserByUsernameQueryHandler :
IQueryHandler<GetUserByUsernameQuery, User>
{
private IUserReadModelRepository _userReadModelRepository;

public GetUserByUsernameQueryHandler(
IUserReadModelRepository userReadModelRepository)
{
_userReadModelRepository = userReadModelRepository;
}

Task<User> ExecuteQueryAsync(
GetUserByUsernameQuery query,
CancellationToken cancellationToken)
{
return _userReadModelRepository.GetByUsernameAsync(
query.Username,
cancellationToken)
}
}
```

Last step is to register the query handler in EventFlow. Here we show the
simple, but cumbersome version, you should use one of the overloads that
scans an entire assembly.

```csharp
...
EventFlowOptions.New
.AddQueryHandler<GetUserByUsernameQueryHandler, GetUserByUsernameQuery, User>()
...
```

Then in order to use the query in your application, you need a reference to
the `IQueryProcessor`, which in our case is stored in the `_queryProcessor`
field.

```csharp
...
var user = await _queryProcessor.ProcessAsync(
new GetUserByUsernameQuery("root")
cancellationToken)
.ConfigureAwait(false);
...
```

## Queries shipped with EventFlow

* `ReadModelByIdQuery<TReadModel>`: Supported by both the in-memory and MSSQL
read model stores automatically as soon as you define the read model use
using the EventFlow options for that store
* `InMemoryQuery<TReadModel>`: Takes a `Predicate<TReadModel>` and returns
`IEnumerable<TReadModel>`, making it possible to search all your in-memory
read models based on any predicate
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ Have a look at our [Getting started guide](./Documentation/GettingStarted.md).
* Highly configurable and extendable
* Easy to use
* No use of threads or background workers making it "web friendly"
* Cancellation

### Overview

Here is a list of the EventFlow concepts. Use the links to navigate
to the documentation.

* [**Aggregates:**](./Documentation/Aggregates.md) Domains object
* [**Aggregates**](./Documentation/Aggregates.md): Domains object
that guarantees the consistency of changes being made within
each aggregate
* **Command bus:** Entry point for all command execution.
Expand All @@ -35,7 +36,12 @@ to the documentation.
read model storage types.
* In-memory - only for test
* Microsoft SQL Server
* [**Metadata:**](./Documentation/Metadata.md)
* [**Queries**](./Documentation/Queries.md): Value objects that represent
a query without specifying how its executed, that is let to a query handler
* [**Event upgrade**](./Documentation/EventUpgrade.md): As events committed to
the event store is never changed, EventFlow uses the concept of event upgraders
to deprecate events and replace them with new during aggregate load.
* [**Metadata**](./Documentation/Metadata.md):
Additional information for each aggregate event, e.g. the IP of
the user behind the event being emitted. EventFlow ships with
several providers ready to use used.
Expand Down
12 changes: 11 additions & 1 deletion RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
### New in 0.6 (not released yet)
### New in 0.7 (not released yet)

* New: EventFlow now includes a `IQueryProcessor` that enables you to implement
queries and query handlers in a structure manner. EventFlow ships with two
ready-to-use queries and related handlers
- `ReadModelByIdQuery<TReadModel>`: Supported by in-memory and MSSQL read
model stores
- `InMemoryQuery<TReadModel>`: Only supported by in-memory read model store,
but lets you search for any read model based on a `Predicate<TReadModel>`

### New in 0.6.456 (released 2015-05-18)

* Breaking: Read models have been significantly improved as they can now
subscribe to events from multiple aggregates. Use a custom
Expand Down
2 changes: 1 addition & 1 deletion Source/EventFlow.MsSql.Tests/EventFlow.MsSql.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
<Compile Include="IntegrationTests\MsSqlIntegrationTestConfiguration.cs" />
<Compile Include="IntegrationTests\MssqlReadModelStoreTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ReadModels\TestAggregateReadModel.cs" />
<Compile Include="ReadModels\MsSqlTestAggregateReadModel.cs" />
<Compile Include="UnitTests\ReadModels\ReadModelSqlGeneratorTests.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@
using EventFlow.Extensions;
using EventFlow.MsSql.Extensions;
using EventFlow.MsSql.Tests.Helpers;
using EventFlow.MsSql.Tests.ReadModels;
using EventFlow.ReadStores;
using EventFlow.ReadStores.MsSql;
using EventFlow.ReadStores.MsSql.Extensions;
using EventFlow.TestHelpers;
using EventFlow.TestHelpers.Aggregates.Test.ReadModels;
using TestAggregateReadModel = EventFlow.MsSql.Tests.ReadModels.TestAggregateReadModel;

namespace EventFlow.MsSql.Tests.IntegrationTests
{
Expand All @@ -52,7 +52,7 @@ public override IRootResolver CreateRootResolver(EventFlowOptions eventFlowOptio
var resolver = eventFlowOptions
.ConfigureMsSql(MsSqlConfiguration.New.SetConnectionString(TestDatabase.ConnectionString))
.UseEventStore<MsSqlEventStore>()
.UseMssqlReadModel<TestAggregateReadModel, ILocateByAggregateId>()
.UseMssqlReadModel<MsSqlTestAggregateReadModel, ILocateByAggregateId>()
.CreateResolver();

MsSqlConnection = resolver.Resolve<IMsSqlConnection>();
Expand All @@ -67,8 +67,8 @@ public override IRootResolver CreateRootResolver(EventFlowOptions eventFlowOptio

public override async Task<ITestAggregateReadModel> GetTestAggregateReadModel(IIdentity id)
{
var sql = ReadModelSqlGenerator.CreateSelectSql<TestAggregateReadModel>();
var readModels = await MsSqlConnection.QueryAsync<TestAggregateReadModel>(
var sql = ReadModelSqlGenerator.CreateSelectSql<MsSqlTestAggregateReadModel>();
var readModels = await MsSqlConnection.QueryAsync<MsSqlTestAggregateReadModel>(
Label.Named("mssql-fetch-test-read-model"),
CancellationToken.None,
sql,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
// 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.ComponentModel.DataAnnotations.Schema;
using EventFlow.Aggregates;
using EventFlow.ReadStores;
using EventFlow.ReadStores.MsSql;
Expand All @@ -29,7 +30,8 @@

namespace EventFlow.MsSql.Tests.ReadModels
{
public class TestAggregateReadModel : MssqlReadModel, ITestAggregateReadModel
[Table("ReadModel-TestAggregate")]
public class MsSqlTestAggregateReadModel : MssqlReadModel, ITestAggregateReadModel
{
public bool DomainErrorAfterFirstReceived { get; set; }
public int PingsReceived { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class TestTableAttribute : MssqlReadModel { }
public void CreateInsertSql_ProducesCorrectSql()
{
// Act
var sql = Sut.CreateInsertSql<TestAggregateReadModel>();
var sql = Sut.CreateInsertSql<MsSqlTestAggregateReadModel>();

// Assert
sql.Should().Be(
Expand All @@ -52,7 +52,7 @@ public void CreateInsertSql_ProducesCorrectSql()
public void CreateUpdateSql_ProducesCorrectSql()
{
// Act
var sql = Sut.CreateUpdateSql<TestAggregateReadModel>();
var sql = Sut.CreateUpdateSql<MsSqlTestAggregateReadModel>();

// Assert
sql.Should().Be(
Expand All @@ -67,7 +67,7 @@ public void CreateUpdateSql_ProducesCorrectSql()
public void CreateSelectSql_ProducesCorrectSql()
{
// Act
var sql = Sut.CreateSelectSql<TestAggregateReadModel>();
var sql = Sut.CreateSelectSql<MsSqlTestAggregateReadModel>();

// Assert
sql.Should().Be("SELECT * FROM [ReadModel-TestAggregate] WHERE AggregateId = @AggregateId");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
<Compile Include="MssqlReadModel.cs" />
<Compile Include="MssqlReadModelStore.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Queries\MsSqlReadModelByIdQueryHandler.cs" />
<Compile Include="ReadModelSqlGenerator.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using EventFlow.Configuration.Registrations;
using EventFlow.Queries;
using EventFlow.ReadStores.MsSql.Queries;

namespace EventFlow.ReadStores.MsSql.Extensions
{
Expand All @@ -37,6 +39,7 @@ public static EventFlowOptions UseMssqlReadModel<TReadModel, TReadModelLocator>(
f.Register<IReadModelSqlGenerator, ReadModelSqlGenerator>(Lifetime.Singleton);
}
f.Register<IReadModelStore, MssqlReadModelStore<TReadModel, TReadModelLocator>>();
f.Register<IQueryHandler<ReadModelByIdQuery<TReadModel>, TReadModel>, MsSqlReadModelByIdQueryHandler<TReadModel>>();
});

return eventFlowOptions;
Expand Down
25 changes: 16 additions & 9 deletions Source/EventFlow.ReadStores.MsSql/MssqlReadModelStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
using EventFlow.Core;
using EventFlow.Logs;
using EventFlow.MsSql;
using EventFlow.Queries;

namespace EventFlow.ReadStores.MsSql
{
Expand All @@ -38,17 +39,20 @@ public class MssqlReadModelStore<TReadModel, TReadModelLocator> :
where TReadModelLocator : IReadModelLocator
{
private readonly IMsSqlConnection _connection;
private readonly IQueryProcessor _queryProcessor;
private readonly IReadModelSqlGenerator _readModelSqlGenerator;

public MssqlReadModelStore(
ILog log,
TReadModelLocator readModelLocator,
IReadModelFactory readModelFactory,
IMsSqlConnection connection,
IQueryProcessor queryProcessor,
IReadModelSqlGenerator readModelSqlGenerator)
: base(log, readModelLocator, readModelFactory)
{
_connection = connection;
_queryProcessor = queryProcessor;
_readModelSqlGenerator = readModelSqlGenerator;
}

Expand All @@ -59,14 +63,7 @@ private async Task UpdateReadModelAsync(
CancellationToken cancellationToken)
{
var readModelNameLowerCased = typeof (TReadModel).Name.ToLowerInvariant();
var selectSql = _readModelSqlGenerator.CreateSelectSql<TReadModel>();
var readModels = await _connection.QueryAsync<TReadModel>(
Label.Named(string.Format("mssql-fetch-read-model-{0}", readModelNameLowerCased)),
cancellationToken,
selectSql,
new { AggregateId = id })
.ConfigureAwait(false);
var readModel = readModels.SingleOrDefault();
var readModel = await GetByIdAsync(id, cancellationToken).ConfigureAwait(false);
var isNew = false;
if (readModel == null)
{
Expand Down Expand Up @@ -105,7 +102,17 @@ await _connection.ExecuteAsync(
readModel).ConfigureAwait(false);
}

protected override Task UpdateReadModelsAsync(IReadOnlyCollection<ReadModelUpdate> readModelUpdates, IReadModelContext readModelContext, CancellationToken cancellationToken)
public override Task<TReadModel> GetByIdAsync(
string id,
CancellationToken cancellationToken)
{
return _queryProcessor.ProcessAsync(new ReadModelByIdQuery<TReadModel>(id), cancellationToken);
}

protected override Task UpdateReadModelsAsync(
IReadOnlyCollection<ReadModelUpdate> readModelUpdates,
IReadModelContext readModelContext,
CancellationToken cancellationToken)
{
var updateTasks = readModelUpdates
.Select(rmu => UpdateReadModelAsync(rmu.ReadModelId, rmu.DomainEvents, readModelContext, cancellationToken));
Expand Down
Loading

0 comments on commit fa87cc5

Please sign in to comment.