public IEnumerable <IAggregateRootEvent> GetAggregateHistory(Guid id) { lock (_lockObject) { return(SingleAggregateInstanceEventStreamMutator.MutateCompleteAggregateHistory(_migrationFactories, _events.Where(e => e.AggregateRootId == id).ToList()) .ToList());; } }
public void When_there_are_no_migrations_mutation_takes_less_than_a_millisecond() { var eventMigrations = Seq.Create <IEventMigration>().ToArray(); TimeAsserter.Execute( maxAverage: 1.Milliseconds(), iterations: 10, description: "load aggregate in isolated scope", timeFormat: "ss\\.fff", action: () => { SingleAggregateInstanceEventStreamMutator.MutateCompleteAggregateHistory(eventMigrations, _history); }); }
public void SaveEvents(IEnumerable <IAggregateRootEvent> events) { _usageGuard.AssertNoContextChangeOccurred(this); _schemaManager.SetupSchemaIfDatabaseUnInitialized(); events = events.ToList(); var updatedAggregates = events.Select(@event => @event.AggregateRootId).Distinct(); _aggregatesWithEventsAddedByThisInstance.AddRange(updatedAggregates); _eventWriter.Insert(events.Cast <AggregateRootEvent>()); //todo: move this to the event store session. foreach (var aggregateId in updatedAggregates) { var completeAggregateHistory = _cache.GetCopy(aggregateId).Events.Concat(events.Where(@event => @event.AggregateRootId == aggregateId)).Cast <AggregateRootEvent>().ToArray(); SingleAggregateInstanceEventStreamMutator.AssertMigrationsAreIdempotent(_migrationFactories, completeAggregateHistory); } }
public void With_four_migrations_that_change_nothing_mutation_takes_less_than_10_milliseconds() { var eventMigrations = Seq.Create <IEventMigration>( Before <E3> .Insert <E1>(), Before <E5> .Insert <E1>(), Before <E7> .Insert <E1>(), Before <E9> .Insert <E1>() ).ToArray(); var maxAverage = 10.Milliseconds().AdjustRuntimeForNCrunch(boost: 6); TimeAsserter.Execute( maxTotal: maxAverage, maxTries: 10, description: "load aggregate in isolated scope", timeFormat: "ss\\.fff", action: () => { SingleAggregateInstanceEventStreamMutator.MutateCompleteAggregateHistory(eventMigrations, _history); }); }
public void With_four_migrations_mutation_that_all_actually_changes_things_migration_takes_less_than_15_milliseconds() { var eventMigrations = Seq.Create <IEventMigration>( Before <E2> .Insert <E3>(), Before <E4> .Insert <E5>(), Before <E6> .Insert <E7>(), Before <E8> .Insert <E9>() ).ToArray(); var maxAverage = TestEnvironmentPerformance.AdjustRuntime(15.Milliseconds(), boost: 2); TimeAsserter.Execute( maxTotal: maxAverage, description: "load aggregate in isolated scope", maxTries: 10, timeFormat: "ss\\.fff", action: () => { SingleAggregateInstanceEventStreamMutator.MutateCompleteAggregateHistory(eventMigrations, _history); }); }
private IEnumerable <IAggregateRootEvent> GetAggregateHistoryInternal(Guid aggregateId, bool takeWriteLock) { _usageGuard.AssertNoContextChangeOccurred(this); _schemaManager.SetupSchemaIfDatabaseUnInitialized(); lock (AggregateLockManager.GetAggregateLockObject(aggregateId)) { var cachedAggregateHistory = _cache.GetCopy(aggregateId); var newEventsFromDatabase = _eventReader.GetAggregateHistory( aggregateId: aggregateId, startAfterInsertedVersion: cachedAggregateHistory.MaxSeenInsertedVersion, takeWriteLock: takeWriteLock); var containsRefactoringEvents = newEventsFromDatabase.Where(IsRefactoringEvent).Any(); if (containsRefactoringEvents && cachedAggregateHistory.MaxSeenInsertedVersion > 0) { _cache.Remove(aggregateId); return(GetAggregateHistoryInternal(aggregateId: aggregateId, takeWriteLock: takeWriteLock)); } var currentHistory = cachedAggregateHistory.Events.Count == 0 ? SingleAggregateInstanceEventStreamMutator.MutateCompleteAggregateHistory(_migrationFactories, newEventsFromDatabase) : cachedAggregateHistory.Events.Concat(newEventsFromDatabase).ToList(); //Should within a transaction a process write events, read them, then fail to commit we will have cached events that are not persisted unless we refuse to cache them here. if (!_aggregatesWithEventsAddedByThisInstance.Contains(aggregateId)) { var maxSeenInsertedVersion = newEventsFromDatabase.Any() ? newEventsFromDatabase.Max(@event => @event.InsertedVersion) : cachedAggregateHistory.MaxSeenInsertedVersion; _cache.Store( aggregateId, new SqlServerEventStoreEventsCache.Entry(events: currentHistory, maxSeenInsertedVersion: maxSeenInsertedVersion)); } return(currentHistory); } }
public void PersistMigrations() { this.Log().Warn($"Starting to persist migrations"); long migratedAggregates = 0; long updatedAggregates = 0; long newEventCount = 0; var logInterval = 1.Minutes(); var lastLogTime = DateTime.Now; const int recoverableErrorRetriesToMake = 5; var aggregateIdsInCreationOrder = StreamAggregateIdsInCreationOrder().ToList(); foreach (var aggregateId in aggregateIdsInCreationOrder) { try { var succeeded = false; int retries = 0; while (!succeeded) { try { //todo: Look at batching the inserting of events in a way that let's us avoid taking a lock for a long time as we do now. This might be a problem in production. using (var transaction = new TransactionScope(TransactionScopeOption.Required, scopeTimeout: 10.Minutes())) { lock (AggregateLockManager.GetAggregateLockObject(aggregateId)) { var updatedThisAggregate = false; var original = _eventReader.GetAggregateHistory(aggregateId: aggregateId, takeWriteLock: true).ToList(); var startInsertingWithVersion = original.Max(@event => @event.InsertedVersion) + 1; var updatedAggregatesBeforeMigrationOfThisAggregate = updatedAggregates; SingleAggregateInstanceEventStreamMutator.MutateCompleteAggregateHistory( _migrationFactories, original, newEvents => { //Make sure we don't try to insert into an occupied InsertedVersion newEvents.ForEach(@event => @event.InsertedVersion = startInsertingWithVersion++); //Save all new events so they get an InsertionOrder for the next refactoring to work with in case it acts relative to any of these events _eventWriter.InsertRefactoringEvents(newEvents); updatedAggregates = updatedAggregatesBeforeMigrationOfThisAggregate + 1; newEventCount += newEvents.Count(); updatedThisAggregate = true; }); if (updatedThisAggregate) { _eventWriter.FixManualVersions(aggregateId); } transaction.Complete(); _cache.Remove(aggregateId); } migratedAggregates++; succeeded = true; } } catch (Exception e) when(IsRecoverableSqlException(e) && ++retries <= recoverableErrorRetriesToMake) { this.Log().Warn($"Failed to persist migrations for aggregate: {aggregateId}. Exception appers to be recoverable so running retry {retries} out of {recoverableErrorRetriesToMake}", e); } } } catch (Exception exception) { this.Log().Error($"Failed to persist migrations for aggregate: {aggregateId}", exception: exception); } if (logInterval < DateTime.Now - lastLogTime) { lastLogTime = DateTime.Now; Func <int> percentDone = () => (int)(((double)migratedAggregates / aggregateIdsInCreationOrder.Count) * 100); this.Log().Info($"{percentDone()}% done. Inspected: {migratedAggregates} / {aggregateIdsInCreationOrder.Count}, Updated: {updatedAggregates}, New Events: {newEventCount}"); } } this.Log().Warn($"Done persisting migrations."); this.Log().Info($"Inspected: {migratedAggregates} , Updated: {updatedAggregates}, New Events: {newEventCount}"); }
private static void RunScenarioWithEventStoreType (MigrationScenario scenario, Type eventStoreType, WindsorContainer container, IList <IEventMigration> migrations, int indexOfScenarioInBatch) { var startingMigrations = migrations.ToList(); migrations.Clear(); var timeSource = container.Resolve <DummyTimeSource>(); IReadOnlyList <IAggregateRootEvent> eventsInStoreAtStart; using (container.BeginScope()) //Why is this needed? It fails without it but I do not understand why... { var eventStore = container.Resolve <IEventStore>(); eventsInStoreAtStart = eventStore.ListAllEventsForTestingPurposesAbsolutelyNotUsableForARealEventStoreOfAnySize(); } Console.WriteLine($"\n########Running Scenario {indexOfScenarioInBatch}"); var original = TestAggregate.FromEvents(DummyTimeSource.Now, scenario.AggregateId, scenario.OriginalHistory).History.ToList(); Console.WriteLine($"Original History: "); original.ForEach(e => Console.WriteLine($" {e}")); Console.WriteLine(); var initialAggregate = TestAggregate.FromEvents(timeSource, scenario.AggregateId, scenario.OriginalHistory); var expected = TestAggregate.FromEvents(timeSource, scenario.AggregateId, scenario.ExpectedHistory).History.ToList(); var expectedCompleteEventstoreStream = eventsInStoreAtStart.Concat(expected).ToList(); Console.WriteLine($"Expected History: "); expected.ForEach(e => Console.WriteLine($" {e}")); Console.WriteLine(); var initialAggregate2 = TestAggregate.FromEvents(timeSource, scenario.AggregateId, scenario.OriginalHistory); timeSource.UtcNow += 1.Hours();//Bump clock to ensure that times will be be wrong unless the time from the original events are used.. Console.WriteLine("Doing pure in memory "); IReadOnlyList <IAggregateRootEvent> otherHistory = SingleAggregateInstanceEventStreamMutator.MutateCompleteAggregateHistory( scenario.Migrations, initialAggregate2.History.Cast <AggregateRootEvent>().ToList()); AssertStreamsAreIdentical(expected, otherHistory, $"Direct call to SingleAggregateInstanceEventStreamMutator.MutateCompleteAggregateHistory"); container.ExecuteUnitOfWorkInIsolatedScope(() => container.Resolve <IEventStoreSession>().Save(initialAggregate)); migrations.AddRange(startingMigrations); var migratedHistory = container.ExecuteUnitOfWorkInIsolatedScope(() => container.Resolve <IEventStoreSession>().Get <TestAggregate>(initialAggregate.Id)).History; AssertStreamsAreIdentical(expected, migratedHistory, "Loaded un-cached aggregate"); var migratedCachedHistory = container.ExecuteUnitOfWorkInIsolatedScope(() => container.Resolve <IEventStoreSession>().Get <TestAggregate>(initialAggregate.Id)).History; AssertStreamsAreIdentical(expected, migratedCachedHistory, "Loaded cached aggregate"); Console.WriteLine(" Streaming all events in store"); var streamedEvents = container.ExecuteUnitOfWorkInIsolatedScope(() => container.Resolve <IEventStore>().ListAllEventsForTestingPurposesAbsolutelyNotUsableForARealEventStoreOfAnySize().ToList()); AssertStreamsAreIdentical(expectedCompleteEventstoreStream, streamedEvents, "Streaming all events in store"); Console.WriteLine(" Persisting migrations"); using (container.BeginScope()) { container.Resolve <IEventStore>().PersistMigrations(); } migratedHistory = container.ExecuteUnitOfWorkInIsolatedScope(() => container.Resolve <IEventStoreSession>().Get <TestAggregate>(initialAggregate.Id)).History; AssertStreamsAreIdentical(expected, migratedHistory, "Loaded aggregate"); Console.WriteLine("Streaming all events in store"); streamedEvents = container.ExecuteUnitOfWorkInIsolatedScope(() => container.Resolve <IEventStore>().ListAllEventsForTestingPurposesAbsolutelyNotUsableForARealEventStoreOfAnySize().ToList()); AssertStreamsAreIdentical(expectedCompleteEventstoreStream, streamedEvents, "Streaming all events in store"); Console.WriteLine(" Disable all migrations so that none are used when reading from the event stores"); migrations.Clear(); migratedHistory = container.ExecuteUnitOfWorkInIsolatedScope(() => container.Resolve <IEventStoreSession>().Get <TestAggregate>(initialAggregate.Id)).History; AssertStreamsAreIdentical(expected, migratedHistory, "loaded aggregate"); Console.WriteLine("Streaming all events in store"); streamedEvents = container.ExecuteUnitOfWorkInIsolatedScope(() => container.Resolve <IEventStore>().ListAllEventsForTestingPurposesAbsolutelyNotUsableForARealEventStoreOfAnySize().ToList()); AssertStreamsAreIdentical(expectedCompleteEventstoreStream, streamedEvents, "Streaming all events in store"); if (eventStoreType == typeof(SqlServerEventStore)) { Console.WriteLine("Clearing sql server eventstore cache"); container.ExecuteUnitOfWorkInIsolatedScope(() => ((SqlServerEventStore)container.Resolve <IEventStore>()).ClearCache()); migratedHistory = container.ExecuteUnitOfWorkInIsolatedScope(() => container.Resolve <IEventStoreSession>().Get <TestAggregate>(initialAggregate.Id)).History; AssertStreamsAreIdentical(expected, migratedHistory, "Loaded aggregate"); Console.WriteLine("Streaming all events in store"); streamedEvents = container.ExecuteUnitOfWorkInIsolatedScope(() => container.Resolve <IEventStore>().ListAllEventsForTestingPurposesAbsolutelyNotUsableForARealEventStoreOfAnySize().ToList()); AssertStreamsAreIdentical(expectedCompleteEventstoreStream, streamedEvents, "Streaming all events in store"); } }