Skip to content

Commit 42509e5

Browse files
committed
Merge branch 'develop'
2 parents fa87cc5 + 7b5cf02 commit 42509e5

File tree

84 files changed

+1785
-1210
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

84 files changed

+1785
-1210
lines changed

Documentation/EventUpgrade.md

+9
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,20 @@ EventFlow event upgraders are invoked whenever the event stream is loaded from
1111
the event store. Each event upgrader receives the entire event stream one event
1212
at a time.
1313

14+
A new instance of a event upgrader is created each time an aggregate is loaded.
15+
This enables you to store information from previous events on the upgrader
16+
instance to be used later, e.g. to determine an action to take on a event
17+
or provide additional information for a new event.
18+
1419
Note that the _ordering_ of event upgraders is important as you might implement
1520
two upgraders, one upgrade a event from V1 to V2 and then another upgrading V2
1621
to V3. EventFlow orders the event upgraders by name before starting the event
1722
upgrade.
1823

24+
**Be careful** if working with event upgraders that return zero or more than one
25+
event, as this have an influence on the aggregate version and you need to make
26+
sure that the aggregate sequence number on upgraded events have a valid value.
27+
1928
## Example - removing a damaged event
2029

2130
To remove an event, simply check and only return the event if its no the event

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,4 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
105105
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
106106
SOFTWARE.
107107
```
108+

RELEASE_NOTES.md

+41-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,44 @@
1-
### New in 0.7 (not released yet)
1+
### New in 0.8 (not released yet)
2+
3+
* Breaking: Remove _all_ functionality related to global sequence
4+
numbers as it proved problematic to maintain. It also matches this
5+
quote:
6+
7+
> Order is only assured per a handler within an aggregate root
8+
> boundary. There is no assurance of order between handlers or
9+
> between aggregates. Trying to provide those things leads to
10+
> the dark side.
11+
>> Greg Young
12+
13+
- If you use a MSSQL read store, be sure to delete the
14+
`LastGlobalSequenceNumber` column during update, or set it to
15+
default `NULL`
16+
- `IDomainEvent.GlobalSequenceNumber` removed
17+
- `IEventStore.LoadEventsAsync` and `IEventStore.LoadEvents` taking
18+
a `GlobalSequenceNumberRange` removed
19+
* Breaking: Remove the concept of event caches. If you really need this
20+
then implement it by registering a decorator for `IEventStore`
21+
* Breaking: Moved `IDomainEvent.BatchId` to metadata and created
22+
`MetadataKeys.BatchId` to help access it
23+
* New: `IEventStore.DeleteAggregateAsync` to delete an entire aggregate
24+
stream. Please consider carefully if you really want to use it. Storage
25+
might be cheaper than the historic knowledge within your events
26+
* New: `IReadModelPopulator` is new and enables you to both purge and
27+
populate read models by going though the entire event store. Currently
28+
its only basic functionality, but more will be added
29+
* New: `IEventStore` now has `LoadAllEventsAsync` and `LoadAllEvents` that
30+
enables you to load all events in the event store a few at a time.
31+
* New: `IMetadata.TimestampEpoch` contains the Unix timestamp version
32+
of `IMetadata.Timestamp`. Also, an additional metadata key
33+
`timestamp_epoch` is added to events containing the same data. Note,
34+
the `TimestampEpoch` on `IMetadata` handles cases in which the
35+
`timestamp_epoch` is not present by using the existing timestamp
36+
* Fixed: `AggregateRoot<>` now reads the aggregate version from
37+
domain events applied during aggregate load. This resolves an issue
38+
for when an `IEventUpgrader` removed events from the event stream
39+
* Fixed: `InMemoryReadModelStore<,>` is now thread safe
40+
41+
### New in 0.7.481 (released 2015-05-22)
242

343
* New: EventFlow now includes a `IQueryProcessor` that enables you to implement
444
queries and query handlers in a structure manner. EventFlow ships with two

Source/EventFlow.EventStores.MsSql/MssqlEventStore.cs

+46-25
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
using System.Threading.Tasks;
2929
using EventFlow.Aggregates;
3030
using EventFlow.Core;
31-
using EventFlow.EventCaches;
3231
using EventFlow.Exceptions;
3332
using EventFlow.Logs;
3433
using EventFlow.MsSql;
@@ -56,13 +55,43 @@ public MsSqlEventStore(
5655
IEventJsonSerializer eventJsonSerializer,
5756
IEventUpgradeManager eventUpgradeManager,
5857
IEnumerable<IMetadataProvider> metadataProviders,
59-
IEventCache eventCache,
6058
IMsSqlConnection connection)
61-
: base(log, aggregateFactory, eventJsonSerializer, eventCache, eventUpgradeManager, metadataProviders)
59+
: base(log, aggregateFactory, eventJsonSerializer, eventUpgradeManager, metadataProviders)
6260
{
6361
_connection = connection;
6462
}
6563

64+
protected override async Task<AllCommittedEventsPage> LoadAllCommittedDomainEvents(
65+
long startPostion,
66+
long endPosition,
67+
CancellationToken cancellationToken)
68+
{
69+
const string sql = @"
70+
SELECT
71+
GlobalSequenceNumber, BatchId, AggregateId, AggregateName, Data, Metadata, AggregateSequenceNumber
72+
FROM EventFlow
73+
WHERE
74+
GlobalSequenceNumber >= @FromId AND GlobalSequenceNumber <= @ToId
75+
ORDER BY
76+
GlobalSequenceNumber ASC";
77+
var eventDataModels = await _connection.QueryAsync<EventDataModel>(
78+
Label.Named("mssql-fetch-events"),
79+
cancellationToken,
80+
sql,
81+
new
82+
{
83+
FromId = startPostion,
84+
ToId = endPosition,
85+
})
86+
.ConfigureAwait(false);
87+
88+
var nextPosition = eventDataModels.Any()
89+
? eventDataModels.Max(e => e.GlobalSequenceNumber) + 1
90+
: startPostion;
91+
92+
return new AllCommittedEventsPage(nextPosition, eventDataModels);
93+
}
94+
6695
protected override async Task<IReadOnlyCollection<ICommittedDomainEvent>> CommitEventsAsync<TAggregate, TIdentity>(
6796
TIdentity id,
6897
IReadOnlyCollection<SerializedEvent> serializedEvents,
@@ -73,15 +102,13 @@ protected override async Task<IReadOnlyCollection<ICommittedDomainEvent>> Commit
73102
return new ICommittedDomainEvent[] {};
74103
}
75104

76-
var batchId = Guid.NewGuid();
77105
var aggregateType = typeof(TAggregate);
78-
var aggregateName = aggregateType.Name.Replace("Aggregate", string.Empty);
79106
var eventDataModels = serializedEvents
80107
.Select((e, i) => new EventDataModel
81108
{
82109
AggregateId = id.Value,
83-
AggregateName = aggregateName,
84-
BatchId = batchId,
110+
AggregateName = e.Metadata[MetadataKeys.AggregateName],
111+
BatchId = Guid.Parse(e.Metadata[MetadataKeys.BatchId]),
85112
Data = e.Data,
86113
Metadata = e.Meta,
87114
AggregateSequenceNumber = e.AggregateSequenceNumber,
@@ -166,29 +193,23 @@ ORDER BY
166193
return eventDataModels;
167194
}
168195

169-
protected override async Task<IReadOnlyCollection<ICommittedDomainEvent>> LoadCommittedEventsAsync(
170-
GlobalSequenceNumberRange globalSequenceNumberRange,
196+
public override async Task DeleteAggregateAsync<TAggregate, TIdentity>(
197+
TIdentity id,
171198
CancellationToken cancellationToken)
172199
{
173-
const string sql = @"
174-
SELECT
175-
GlobalSequenceNumber, BatchId, AggregateId, AggregateName, Data, Metadata, AggregateSequenceNumber
176-
FROM EventFlow
177-
WHERE
178-
GlobalSequenceNumber >= @FromId AND GlobalSequenceNumber <= @ToId
179-
ORDER BY
180-
GlobalSequenceNumber ASC";
181-
var eventDataModels = await _connection.QueryAsync<EventDataModel>(
182-
Label.Named("mssql-fetch-events"),
200+
const string sql = @"DELETE FROM EventFlow WHERE AggregateId = @AggregateId";
201+
var affectedRows = await _connection.ExecuteAsync(
202+
Label.Named("mssql-delete-aggregate"),
183203
cancellationToken,
184204
sql,
185-
new
186-
{
187-
FromId = globalSequenceNumberRange.From,
188-
ToId = globalSequenceNumberRange.To,
189-
})
205+
new {AggregateId = id.Value})
190206
.ConfigureAwait(false);
191-
return eventDataModels;
207+
208+
Log.Verbose(
209+
"Deleted aggregate '{0}' with ID '{1}' by deleting all of its {2} events",
210+
typeof(TAggregate).Name,
211+
id,
212+
affectedRows);
192213
}
193214
}
194215
}

Source/EventFlow.MsSql.Tests/IntegrationTests/MsSqlIntegrationTestConfiguration.cs

+14-2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public class MsSqlIntegrationTestConfiguration : IntegrationTestConfiguration
4444
protected ITestDatabase TestDatabase { get; private set; }
4545
protected IMsSqlConnection MsSqlConnection { get; private set; }
4646
protected IReadModelSqlGenerator ReadModelSqlGenerator { get; private set; }
47+
protected IReadModelPopulator ReadModelPopulator { get; private set; }
4748

4849
public override IRootResolver CreateRootResolver(EventFlowOptions eventFlowOptions)
4950
{
@@ -52,11 +53,12 @@ public override IRootResolver CreateRootResolver(EventFlowOptions eventFlowOptio
5253
var resolver = eventFlowOptions
5354
.ConfigureMsSql(MsSqlConfiguration.New.SetConnectionString(TestDatabase.ConnectionString))
5455
.UseEventStore<MsSqlEventStore>()
55-
.UseMssqlReadModel<MsSqlTestAggregateReadModel, ILocateByAggregateId>()
56+
.UseMssqlReadModel<MsSqlTestAggregateReadModel>()
5657
.CreateResolver();
5758

5859
MsSqlConnection = resolver.Resolve<IMsSqlConnection>();
5960
ReadModelSqlGenerator = resolver.Resolve<IReadModelSqlGenerator>();
61+
ReadModelPopulator = resolver.Resolve<IReadModelPopulator>();
6062

6163
var databaseMigrator = resolver.Resolve<IMsSqlDatabaseMigrator>();
6264
EventFlowEventStoresMsSql.MigrateDatabase(databaseMigrator);
@@ -65,7 +67,7 @@ public override IRootResolver CreateRootResolver(EventFlowOptions eventFlowOptio
6567
return resolver;
6668
}
6769

68-
public override async Task<ITestAggregateReadModel> GetTestAggregateReadModel(IIdentity id)
70+
public override async Task<ITestAggregateReadModel> GetTestAggregateReadModelAsync(IIdentity id)
6971
{
7072
var sql = ReadModelSqlGenerator.CreateSelectSql<MsSqlTestAggregateReadModel>();
7173
var readModels = await MsSqlConnection.QueryAsync<MsSqlTestAggregateReadModel>(
@@ -77,6 +79,16 @@ public override async Task<ITestAggregateReadModel> GetTestAggregateReadModel(II
7779
return readModels.SingleOrDefault();
7880
}
7981

82+
public override Task PurgeTestAggregateReadModelAsync()
83+
{
84+
return ReadModelPopulator.PurgeAsync<MsSqlTestAggregateReadModel>(CancellationToken.None);
85+
}
86+
87+
public override Task PopulateTestAggregateReadModelAsync()
88+
{
89+
return ReadModelPopulator.PopulateAsync<MsSqlTestAggregateReadModel>(CancellationToken.None);
90+
}
91+
8092
public override void TearDown()
8193
{
8294
TestDatabase.Dispose();

Source/EventFlow.MsSql.Tests/Scripts/0001 - Create table ReadModel-TestAggregate.sql

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
[CreateTime] [datetimeoffset](7) NOT NULL,
99
[UpdatedTime] [datetimeoffset](7) NOT NULL,
1010
[LastAggregateSequenceNumber] [int] NOT NULL,
11-
[LastGlobalSequenceNumber] [bigint] NOT NULL,
1211
CONSTRAINT [PK_ReadModel-TestAggregate] PRIMARY KEY CLUSTERED
1312
(
1413
[Id] ASC

Source/EventFlow.MsSql.Tests/UnitTests/ReadModels/ReadModelSqlGeneratorTests.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ public void CreateInsertSql_ProducesCorrectSql()
4343
// Assert
4444
sql.Should().Be(
4545
"INSERT INTO [ReadModel-TestAggregate] " +
46-
"(AggregateId, CreateTime, DomainErrorAfterFirstReceived, LastAggregateSequenceNumber, LastGlobalSequenceNumber, PingsReceived, UpdatedTime) " +
46+
"(AggregateId, CreateTime, DomainErrorAfterFirstReceived, LastAggregateSequenceNumber, PingsReceived, UpdatedTime) " +
4747
"VALUES " +
48-
"(@AggregateId, @CreateTime, @DomainErrorAfterFirstReceived, @LastAggregateSequenceNumber, @LastGlobalSequenceNumber, @PingsReceived, @UpdatedTime)");
48+
"(@AggregateId, @CreateTime, @DomainErrorAfterFirstReceived, @LastAggregateSequenceNumber, @PingsReceived, @UpdatedTime)");
4949
}
5050

5151
[Test]
@@ -58,7 +58,7 @@ public void CreateUpdateSql_ProducesCorrectSql()
5858
sql.Should().Be(
5959
"UPDATE [ReadModel-TestAggregate] SET " +
6060
"CreateTime = @CreateTime, DomainErrorAfterFirstReceived = @DomainErrorAfterFirstReceived, " +
61-
"LastAggregateSequenceNumber = @LastAggregateSequenceNumber, LastGlobalSequenceNumber = @LastGlobalSequenceNumber, " +
61+
"LastAggregateSequenceNumber = @LastAggregateSequenceNumber, " +
6262
"PingsReceived = @PingsReceived, UpdatedTime = @UpdatedTime " +
6363
"WHERE AggregateId = @AggregateId");
6464
}

Source/EventFlow.ReadStores.MsSql/EventFlow.ReadStores.MsSql.csproj

-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252
<Compile Include="MssqlReadModel.cs" />
5353
<Compile Include="MssqlReadModelStore.cs" />
5454
<Compile Include="Properties\AssemblyInfo.cs" />
55-
<Compile Include="Queries\MsSqlReadModelByIdQueryHandler.cs" />
5655
<Compile Include="ReadModelSqlGenerator.cs" />
5756
</ItemGroup>
5857
<ItemGroup>

Source/EventFlow.ReadStores.MsSql/Extensions/EventFlowOptionsExtensions.cs

+29-8
Original file line numberDiff line numberDiff line change
@@ -21,26 +21,47 @@
2121
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2222

2323
using EventFlow.Configuration.Registrations;
24-
using EventFlow.Queries;
25-
using EventFlow.ReadStores.MsSql.Queries;
24+
using EventFlow.Extensions;
2625

2726
namespace EventFlow.ReadStores.MsSql.Extensions
2827
{
2928
public static class EventFlowOptionsExtensions
3029
{
31-
public static EventFlowOptions UseMssqlReadModel<TReadModel, TReadModelLocator>(this EventFlowOptions eventFlowOptions)
32-
where TReadModel : IMssqlReadModel, new()
30+
public static EventFlowOptions UseMssqlReadModel<TReadModel, TReadModelLocator>(
31+
this EventFlowOptions eventFlowOptions)
32+
where TReadModel : class, IMssqlReadModel, new()
3333
where TReadModelLocator : IReadModelLocator
3434
{
35-
eventFlowOptions.RegisterServices(f =>
35+
eventFlowOptions
36+
.RegisterServices(f =>
37+
{
38+
if (!f.HasRegistrationFor<IReadModelSqlGenerator>())
39+
{
40+
f.Register<IReadModelSqlGenerator, ReadModelSqlGenerator>(Lifetime.Singleton);
41+
}
42+
f.Register<IMssqlReadModelStore<TReadModel>, MssqlReadModelStore<TReadModel>>();
43+
f.Register<IReadModelStore<TReadModel>>(r => r.Resolver.Resolve<IMssqlReadModelStore<TReadModel>>());
44+
})
45+
.UseReadStoreFor<IMssqlReadModelStore<TReadModel>, TReadModel, TReadModelLocator>();
46+
47+
return eventFlowOptions;
48+
}
49+
50+
public static EventFlowOptions UseMssqlReadModel<TReadModel>(
51+
this EventFlowOptions eventFlowOptions)
52+
where TReadModel : class, IMssqlReadModel, new()
53+
{
54+
eventFlowOptions
55+
.RegisterServices(f =>
3656
{
3757
if (!f.HasRegistrationFor<IReadModelSqlGenerator>())
3858
{
3959
f.Register<IReadModelSqlGenerator, ReadModelSqlGenerator>(Lifetime.Singleton);
4060
}
41-
f.Register<IReadModelStore, MssqlReadModelStore<TReadModel, TReadModelLocator>>();
42-
f.Register<IQueryHandler<ReadModelByIdQuery<TReadModel>, TReadModel>, MsSqlReadModelByIdQueryHandler<TReadModel>>();
43-
});
61+
f.Register<IMssqlReadModelStore<TReadModel>, MssqlReadModelStore<TReadModel>>();
62+
f.Register<IReadModelStore<TReadModel>>(r => r.Resolver.Resolve<IMssqlReadModelStore<TReadModel>>());
63+
})
64+
.UseReadStoreFor<IMssqlReadModelStore<TReadModel>, TReadModel>();
4465

4566
return eventFlowOptions;
4667
}

Source/EventFlow.ReadStores.MsSql/IMssqlReadModel.cs

-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,5 @@ public interface IMssqlReadModel : IReadModel
3030
DateTimeOffset CreateTime { get; set; }
3131
DateTimeOffset UpdatedTime { get; set; }
3232
int LastAggregateSequenceNumber { get; set; }
33-
long LastGlobalSequenceNumber { get; set; }
3433
}
3534
}

Source/EventFlow.ReadStores.MsSql/IMssqlReadModelStore.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222

2323
namespace EventFlow.ReadStores.MsSql
2424
{
25-
public interface IMssqlReadModelStore<TReadModel> : IReadModelStore
26-
where TReadModel : IMssqlReadModel, new()
25+
public interface IMssqlReadModelStore<TReadModel> : IReadModelStore<TReadModel>
26+
where TReadModel : class, IMssqlReadModel, new()
2727
{
2828
}
2929
}

Source/EventFlow.ReadStores.MsSql/IReadModelSqlGenerator.cs

+3
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,8 @@ string CreateSelectSql<TReadModel>()
3232

3333
string CreateUpdateSql<TReadModel>()
3434
where TReadModel : IMssqlReadModel;
35+
36+
string CreatePurgeSql<TReadModel>()
37+
where TReadModel : IReadModel;
3538
}
3639
}

Source/EventFlow.ReadStores.MsSql/MssqlReadModel.cs

+1-3
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,13 @@ public abstract class MssqlReadModel : IMssqlReadModel
3030
public DateTimeOffset CreateTime { get; set; }
3131
public DateTimeOffset UpdatedTime { get; set; }
3232
public int LastAggregateSequenceNumber { get; set; }
33-
public long LastGlobalSequenceNumber { get; set; }
3433

3534
public override string ToString()
3635
{
3736
return string.Format(
38-
"Read model '{0}' for '{1} ({2}/{3}'",
37+
"Read model '{0}' for '{1} v{2}'",
3938
GetType().Name,
4039
AggregateId,
41-
LastGlobalSequenceNumber,
4240
LastAggregateSequenceNumber);
4341
}
4442
}

0 commit comments

Comments
 (0)