コード例 #1
0
        /// <summary>
        /// Atomically persists multiple event entries representing a single version.
        /// </summary>
        public virtual async Task <IList <EventEntry> > Persist(
            IEnumerable <EventEntry> eventTransaction,
            long?alreadyLockedVersionNumber = null)
        {
            // Copy the list before modifying it
            var events = new List <EventEntry>(eventTransaction);
            // Take the version number, they all use the same
            var versionNumber = events[0].VersionNumber;
            // Add the commit event at the end in order to prevent mixing multiple transactions
            var commit = new TransactionCommitEventEntry
            {
                VersionNumber = versionNumber
            };

            events.Add(commit);
            // Double-check validate them all to have the same version number, and assign the same current time
            var now = CurrentTime();

            foreach (var eventEntry in events)
            {
                if (eventEntry.VersionNumber != versionNumber)
                {
                    throw new Exception(
                              $"Integrity error, attempted to persist a transaction consisting of events having different version numbers: expected {versionNumber.ToString()}, actual {eventEntry.VersionNumber.ToString()}");
                }

                eventEntry.EntryTime = now;
            }

            // Take the semaphore, so that no other action can use the current version as it's about to change
            var versionNumberOutdatedAlready = false;

            void InsertLambda(long currentDatabaseVersionNumber)
            {
                // Make sure version number has not changed, as we can just save ourselves the useless effort otherwise
                if (currentDatabaseVersionNumber + 1 != versionNumber)
                {
                    versionNumberOutdatedAlready = true;
                    return;
                }

                // We are under synchronization, so we can double-check that we are ahead against other services
                if (EventHistoryRepository.Events()
                    .Find(e => e.VersionNumber > currentDatabaseVersionNumber)
                    .CountDocuments() != 0)
                {
                    versionNumberOutdatedAlready = true;
                    return;
                }

                // Attempt to atomically insert all entries
                EventHistoryRepository.Events().InsertMany(events, new InsertManyOptions {
                    IsOrdered = true
                });
            }

            if (alreadyLockedVersionNumber.HasValue)
            {
                InsertLambda(alreadyLockedVersionNumber.Value);
            }
            else
            {
                VersionControl.ExecuteUsingFixedVersion(InsertLambda);
            }

            if (versionNumberOutdatedAlready)
            {
                // Prematurely aborted insertion
                _logger.LogError($"Reason for event @ version number {versionNumber} retry: already outdated");
                return(null);
            }

            // Make sure it was inserted with the version number first without other same-versioned concurrent attempts
            var foundEventsCursor = await EventHistoryRepository.Events().FindAsync(
                EventHistoryRepository.VersionEqFilter(versionNumber)
                );

            var foundEvents = await foundEventsCursor.ToListAsync();

            var failedEntries  = new List <EventEntry>();
            var foundCommit    = false;
            var thisSuccessful = false;

            foreach (var foundEvent in foundEvents)
            {
                if (foundCommit)
                {
                    failedEntries.Add(foundEvent);
                }
                else if (foundEvent is TransactionCommitEventEntry)
                {
                    foundCommit    = true;
                    thisSuccessful = foundEvent.Id.Equals(commit.Id);
                }
            }

            // A nasty workaround to clean up invalid events. They won't be processed, so it's not vital for operation.
            // This is not guaranteed to execute though, so TODO change or make another cleanup!
            foreach (var failedEntry in failedEntries)
            {
                _logger.LogError(
                    $"Note: removing duplicate (uncommitted) failed event entry {failedEntry.GetType().Name} @ version number {versionNumber}");
                // Re-written to be sequential, as there were issues with DeleteMany LINQ selector
                await EventHistoryRepository.Events().DeleteOneAsync(
                    e => failedEntry.Id.Equals(e.Id)
                    );
            }

            // Return null if the attempted transaction was not the first group of events with the same version number,
            // which means it was deemed invalid and then removed
            return(thisSuccessful ? events : null);
        }
コード例 #2
0
 public void ProcessEvent(TransactionCommitEventEntry eventEntry)
 {
     // Ignored
 }