public void Aggregate_entity_has_index_with_AggregateId()
        {
            var         context = new EventStoreDbContext(_dbContextOptions);
            IEntityType sut     = context.Model.FindEntityType(typeof(Aggregate));
            IIndex      actual  = sut.FindIndex(sut.FindProperty("AggregateId"));

            actual.Should().NotBeNull();
            actual.IsUnique.Should().BeTrue();
        }
 public void TestCleanup()
 {
     using (EventStoreDbContext db = CreateDbContext())
     {
         db.Database.ExecuteSqlCommand("DELETE FROM Aggregates");
         db.Database.ExecuteSqlCommand("DELETE FROM PersistentEvents");
         db.Database.ExecuteSqlCommand("DELETE FROM PendingEvents");
         db.Database.ExecuteSqlCommand("DELETE FROM UniqueIndexedProperties");
     }
 }
Example #3
0
        private async Task UpdateUniqueIndexedProperties <T>(
            EventStoreDbContext context,
            Guid sourceId,
            List <IDomainEvent> events,
            CancellationToken cancellationToken)
            where T : class, IEventSourced
        {
            Dictionary <string, UniqueIndexedProperty> restored = await context
                                                                  .UniqueIndexedProperties
                                                                  .Where(
                p =>
                p.AggregateType == typeof(T).FullName &&
                p.AggregateId == sourceId)
                                                                  .ToDictionaryAsync(p => p.PropertyName, cancellationToken)
                                                                  .ConfigureAwait(false);

            var properties = new List <UniqueIndexedProperty>();

            foreach (var indexedEvent in events.OfType <IUniqueIndexedDomainEvent>())
            {
                foreach (string name in
                         indexedEvent.UniqueIndexedProperties.Keys)
                {
                    UniqueIndexedProperty property;
                    if (restored.TryGetValue(name, out property))
                    {
                        context.UniqueIndexedProperties.Remove(property);
                    }

                    property = properties.Find(p => p.PropertyName == name);
                    if (property != null)
                    {
                        properties.Remove(property);
                    }

                    string value = indexedEvent.UniqueIndexedProperties[name];
                    if (value == null)
                    {
                        continue;
                    }

                    property = new UniqueIndexedProperty
                    {
                        AggregateType = typeof(T).FullName,
                        PropertyName  = name,
                        PropertyValue = value,
                        AggregateId   = sourceId,
                        Version       = indexedEvent.Version
                    };
                    properties.Add(property);
                }
            }

            context.UniqueIndexedProperties.AddRange(properties);
        }
Example #4
0
        private static Task <List <PendingEvent> > LoadEvents(
            EventStoreDbContext context,
            Guid sourceId,
            CancellationToken cancellationToken)
        {
            IQueryable <PendingEvent> query = from e in context.PendingEvents
                                              where e.AggregateId == sourceId
                                              orderby e.Version
                                              select e;

            return(query.ToListAsync(cancellationToken));
        }
Example #5
0
        private void InsertEvent(
            EventStoreDbContext context,
            IDomainEvent domainEvent,
            string operationId,
            Guid?correlationId,
            string contributor)
        {
            var envelope = new Envelope(Guid.NewGuid(), domainEvent, operationId, correlationId, contributor);

            context.PersistentEvents.Add(PersistentEvent.FromEnvelope(envelope, _serializer));
            context.PendingEvents.Add(PendingEvent.FromEnvelope(envelope, _serializer));
        }
Example #6
0
 private void InsertEvents(
     EventStoreDbContext context,
     List <IDomainEvent> events,
     string operationId,
     Guid?correlationId,
     string contributor)
 {
     foreach (IDomainEvent domainEvent in events)
     {
         InsertEvent(context, domainEvent, operationId, correlationId, contributor);
     }
 }
Example #7
0
 private void InsertEvents <T>(
     EventStoreDbContext context,
     List <IDomainEvent> events,
     string operationId,
     Guid?correlationId,
     string contributor)
     where T : class, IEventSourced
 {
     foreach (IDomainEvent domainEvent in events)
     {
         InsertEvent <T>(context, domainEvent, operationId, correlationId, contributor);
     }
 }
        public void PersistentEvent_entity_has_index_with_AggregateId_Version()
        {
            var         context = new EventStoreDbContext(_dbContextOptions);
            IEntityType sut     = context.Model.FindEntityType(typeof(PersistentEvent));
            IIndex      actual  = sut.FindIndex(new[]
            {
                sut.FindProperty("AggregateId"),
                sut.FindProperty("Version"),
            });

            actual.Should().NotBeNull();
            actual.IsUnique.Should().BeTrue();
        }
        public void PendingEvent_entity_has_primary_key_with_AggregateId_Version()
        {
            var         context = new EventStoreDbContext(_dbContextOptions);
            IEntityType sut     = context.Model.FindEntityType(typeof(PendingEvent));
            IKey        actual  = sut.FindPrimaryKey();

            actual.Should().NotBeNull();
            actual.Properties.Should().Equal(new[]
            {
                sut.FindProperty("AggregateId"),
                sut.FindProperty("Version"),
            });
        }
        public void UniqueIndexedProperty_entity_has_index_with_AggregateId_PropertyName()
        {
            var         context = new EventStoreDbContext(_dbContextOptions);
            IEntityType sut     = context.Model.FindEntityType(typeof(UniqueIndexedProperty));
            IIndex      actual  = sut.FindIndex(new[]
            {
                sut.FindProperty("AggregateType"),
                sut.FindProperty("AggregateId"),
                sut.FindProperty("PropertyName"),
            });

            actual.Should().NotBeNull();
            actual.IsUnique.Should().BeTrue();
        }
        public void UniqueIndexedProperty_entity_has_primary_key_with_AggregateType_PropertyName_PropertyValue()
        {
            var         context = new EventStoreDbContext(_dbContextOptions);
            IEntityType sut     = context.Model.FindEntityType(typeof(UniqueIndexedProperty));
            IKey        actual  = sut.FindPrimaryKey();

            actual.Should().NotBeNull();
            actual.Properties.Should().Equal(new[]
            {
                sut.FindProperty("AggregateType"),
                sut.FindProperty("PropertyName"),
                sut.FindProperty("PropertyValue"),
            });
        }
        public void Correlation_entity_has_primary_key_with_AggregateType_AggregateId_and_CorrelationId()
        {
            var         context = new EventStoreDbContext(_dbContextOptions);
            IEntityType sut     = context.Model.FindEntityType(typeof(Correlation));
            IKey        actual  = sut.FindPrimaryKey();

            actual.Should().NotBeNull();
            actual.Properties.Should().Equal(new[]
            {
                sut.FindProperty("AggregateType"),
                sut.FindProperty("AggregateId"),
                sut.FindProperty("CorrelationId"),
            });
        }
Example #13
0
 private void InsertEvents(
     EventStoreDbContext context,
     List <IDomainEvent> events,
     Guid?correlationId)
 {
     foreach (IDomainEvent domainEvent in events)
     {
         var envelope =
             correlationId == null
             ? new Envelope(domainEvent)
             : new Envelope(correlationId.Value, domainEvent);
         context.PersistentEvents.Add(PersistentEvent.FromEnvelope(envelope, _serializer));
         context.PendingEvents.Add(PendingEvent.FromEnvelope(envelope, _serializer));
     }
 }
Example #14
0
        private async Task RunFlushPendingEvents(
            Guid sourceId,
            CancellationToken cancellationToken)
        {
            using (EventStoreDbContext context = _dbContextFactory.Invoke())
            {
                List <PendingEvent> pendingEvents = await LoadEvents(context, sourceId, cancellationToken).ConfigureAwait(false);

                if (pendingEvents.Any())
                {
                    await SendEvents(pendingEvents, cancellationToken).ConfigureAwait(false);
                    await RemoveEvents(context, pendingEvents, cancellationToken).ConfigureAwait(false);
                }
            }
        }
 public async Task PublishAllPendingEvents(CancellationToken cancellationToken)
 {
     using (EventStoreDbContext context = _dbContextFactory.Invoke())
     {
         foreach (Guid sourceId in await context
                  .PendingEvents
                  .Select(e => e.AggregateId)
                  .Distinct()
                  .ToListAsync(cancellationToken)
                  .ConfigureAwait(false))
         {
             await PublishEvents(sourceId, cancellationToken).ConfigureAwait(false);
         }
     }
 }
Example #16
0
        private void InsertCorrelation(
            Guid sourceId,
            Guid?correlationId,
            EventStoreDbContext context)
        {
            if (correlationId == null)
            {
                return;
            }

            context.Correlations.Add(new Correlation
            {
                AggregateId   = sourceId,
                CorrelationId = correlationId.Value,
                HandledAt     = DateTimeOffset.Now
            });
        }
Example #17
0
 private static async Task RemoveEvents(
     EventStoreDbContext context,
     List <PendingEvent> pendingEvents,
     CancellationToken cancellationToken)
 {
     foreach (PendingEvent pendingEvent in pendingEvents)
     {
         try
         {
             context.PendingEvents.Remove(pendingEvent);
             await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
         }
         catch (DbUpdateConcurrencyException)
         {
             context.Entry(pendingEvent).State = EntityState.Detached;
         }
     }
 }
Example #18
0
        public async Task FlushPendingEvents_absorbs_exception_caused_by_that_some_pending_event_already_deleted_since_loaded()
        {
            // Arrange
            var         completionSource = new TaskCompletionSource <bool>();
            IMessageBus messageBus       = new AwaitingMessageBus(completionSource.Task);
            var         fixture          = new Fixture();
            FakeUser    user             = fixture.Create <FakeUser>();

            user.ChangeUsername(fixture.Create(nameof(user.Username)));
            var eventStore = new SqlEventStore(CreateDbContext, _serializer);
            await eventStore.SaveEvents <FakeUser>(user.FlushPendingEvents());

            var sut = new SqlEventPublisher(CreateDbContext, _serializer, messageBus);

            // Act
            Func <Task> action = async() =>
            {
                Task flushTask = sut.FlushPendingEvents(user.Id, CancellationToken.None);

                using (EventStoreDbContext db = CreateDbContext())
                {
                    List <PendingEvent> pendingEvents = await db
                                                        .PendingEvents
                                                        .Where(e => e.AggregateId == user.Id)
                                                        .OrderBy(e => e.Version)
                                                        .Take(1)
                                                        .ToListAsync();

                    db.PendingEvents.RemoveRange(pendingEvents);
                    await db.SaveChangesAsync();
                }

                completionSource.SetResult(true);
                await flushTask;
            };

            // Assert
            action.ShouldNotThrow();
            using (EventStoreDbContext db = CreateDbContext())
            {
                (await db.PendingEvents.AnyAsync(e => e.AggregateId == user.Id))
                .Should().BeFalse("all pending events should be deleted");
            }
        }
Example #19
0
        private async Task Save <T>(
            Guid sourceId,
            List <IDomainEvent> events,
            Guid?correlationId,
            CancellationToken cancellationToken)
            where T : class, IEventSourced
        {
            using (EventStoreDbContext context = _dbContextFactory.Invoke())
            {
                await UpsertAggregate <T>(context, sourceId, events, cancellationToken).ConfigureAwait(false);

                InsertEvents(context, events, correlationId);
                await UpdateUniqueIndexedProperties <T>(context, sourceId, events, cancellationToken).ConfigureAwait(false);

                InsertCorrelation(sourceId, correlationId, context);

                await SaveChanges <T>(context, sourceId, cancellationToken);
            }
        }
Example #20
0
        private void InsertCorrelation <T>(
            Guid sourceId,
            Guid?correlationId,
            EventStoreDbContext context)
            where T : class, IEventSourced
        {
            if (correlationId == null)
            {
                return;
            }

            context.Correlations.Add(new Correlation
            {
                AggregateType = typeof(T).FullName,
                AggregateId   = sourceId,
                CorrelationId = correlationId.Value,
                HandledAt     = DateTime.UtcNow,
            });
        }
Example #21
0
        public SqlEventStore_features(ITestOutputHelper output)
        {
            this.output = output;

            fixture = new Fixture().Customize(new AutoMoqCustomization());
            fixture.Inject <Func <EventStoreDbContext> >(() => new DataContext());

            userId = Guid.NewGuid();

            serializer = new JsonMessageSerializer();
            fixture.Inject(serializer);

            sut = fixture.Create <SqlEventStore>();

            mockDbContext = Mock.Of <EventStoreDbContext>(
                x => x.SaveChangesAsync() == Task.FromResult(default(int)));

            mockDbContext.Aggregates = Mock.Of <DbSet <Aggregate> >();
            Mock.Get(mockDbContext.Aggregates).SetupData();

            mockDbContext.PersistentEvents = Mock.Of <DbSet <PersistentEvent> >();
            Mock.Get(mockDbContext.PersistentEvents).SetupData();

            mockDbContext.PendingEvents = Mock.Of <DbSet <PendingEvent> >();
            Mock.Get(mockDbContext.PendingEvents).SetupData();

            mockDbContext.UniqueIndexedProperties = Mock.Of <DbSet <UniqueIndexedProperty> >();
            Mock.Get(mockDbContext.UniqueIndexedProperties).SetupData();

            using (var db = new DataContext())
            {
                db.Database.Log = output.WriteLine;
                db.Database.ExecuteSqlCommand("DELETE FROM Aggregates");
                db.Database.ExecuteSqlCommand("DELETE FROM PersistentEvents");
                db.Database.ExecuteSqlCommand("DELETE FROM PendingEvents");
                db.Database.ExecuteSqlCommand("DELETE FROM UniqueIndexedProperties");
            }
        }
Example #22
0
        private async Task <Guid?> FindIdByProperty <T>(
            string name,
            string value,
            CancellationToken cancellationToken)
            where T : class, IEventSourced
        {
            using (EventStoreDbContext context = _dbContextFactory.Invoke())
            {
                IQueryable <UniqueIndexedProperty> query =
                    from p in context.UniqueIndexedProperties
                    where
                    p.AggregateType == typeof(T).FullName &&
                    p.PropertyName == name &&
                    p.PropertyValue == value
                    select p;

                UniqueIndexedProperty property = await query
                                                 .SingleOrDefaultAsync(cancellationToken)
                                                 .ConfigureAwait(false);

                return(property?.AggregateId);
            }
        }
Example #23
0
        private static async Task RemoveEvents(
            EventStoreDbContext context,
            List <PendingEvent> pendingEvents,
            CancellationToken cancellationToken)
        {
            foreach (PendingEvent pendingEvent in pendingEvents)
            {
                try
                {
                    context.PendingEvents.Remove(pendingEvent);
                    await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
                }
#if NETSTANDARD2_0
                catch (DbUpdateConcurrencyException)
#else
                catch (DbUpdateConcurrencyException exception)
                    when(exception.InnerException is OptimisticConcurrencyException)
#endif
                {
                    context.Entry(pendingEvent).State = EntityState.Detached;
                }
            }
        }
        public void TestInitialize()
        {
            _fixture = new Fixture().Customize(new AutoMoqCustomization());
            _fixture.Inject <Func <EventStoreDbContext> >(() => new DataContext());

            _userId = Guid.NewGuid();

            _serializer = new JsonMessageSerializer();
            _fixture.Inject(_serializer);

            _sut = _fixture.Create <SqlEventStore>();

            _mockDbContext = Mock.Of <EventStoreDbContext>(
                x => x.SaveChangesAsync() == Task.FromResult(default(int)));

            _mockDbContext.Aggregates = Mock.Of <DbSet <Aggregate> >();
            Mock.Get(_mockDbContext.Aggregates).SetupData();

            _mockDbContext.PersistentEvents = Mock.Of <DbSet <PersistentEvent> >();
            Mock.Get(_mockDbContext.PersistentEvents).SetupData();

            _mockDbContext.PendingEvents = Mock.Of <DbSet <PendingEvent> >();
            Mock.Get(_mockDbContext.PendingEvents).SetupData();

            _mockDbContext.UniqueIndexedProperties = Mock.Of <DbSet <UniqueIndexedProperty> >();
            Mock.Get(_mockDbContext.UniqueIndexedProperties).SetupData();

            using (var db = new DataContext())
            {
                db.Database.Log = m => TestContext?.WriteLine(m);
                db.Database.ExecuteSqlCommand("DELETE FROM Aggregates");
                db.Database.ExecuteSqlCommand("DELETE FROM PersistentEvents");
                db.Database.ExecuteSqlCommand("DELETE FROM PendingEvents");
                db.Database.ExecuteSqlCommand("DELETE FROM UniqueIndexedProperties");
            }
        }
Example #25
0
        public async Task FlushAllPendingEvents(CancellationToken cancellationToken)
        {
            using (EventStoreDbContext context = _dbContextFactory.Invoke())
            {
Loop:

                IEnumerable <Guid> source = await context
                                            .PendingEvents
                                            .OrderBy(e => e.AggregateId)
                                            .Select(e => e.AggregateId)
                                            .Take(1000)
                                            .Distinct()
                                            .ToListAsync(cancellationToken)
                                            .ConfigureAwait(false);

                Task[] tasks = source.Select(sourceId => FlushPendingEvents(sourceId, cancellationToken)).ToArray();
                await Task.WhenAll(tasks).ConfigureAwait(false);

                if (source.Any())
                {
                    goto Loop;
                }
            }
        }
Example #26
0
        private async Task <IEnumerable <IDomainEvent> > Load(
            Guid sourceId,
            int afterVersion,
            CancellationToken cancellationToken)
        {
            using (EventStoreDbContext context = _dbContextFactory.Invoke())
            {
                List <PersistentEvent> events = await context
                                                .PersistentEvents
                                                .Where(e => e.AggregateId == sourceId)
                                                .Where(e => e.Version > afterVersion)
                                                .OrderBy(e => e.Version)
                                                .ToListAsync(cancellationToken)
                                                .ConfigureAwait(false);

                List <IDomainEvent> domainEvents = events
                                                   .Select(e => e.EventJson)
                                                   .Select(_serializer.Deserialize)
                                                   .Cast <IDomainEvent>()
                                                   .ToList();

                return(domainEvents);
            }
        }
Example #27
0
        private async Task SaveChanges <T>(
            EventStoreDbContext context,
            Guid sourceId,
            Guid?correlationId,
            CancellationToken cancellationToken)
            where T : class, IEventSourced
        {
#if NETSTANDARD2_0
            try
            {
                using (IDbContextTransaction transaction = await
                                                           context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false))
                {
                    await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);

                    transaction.Commit();
                }
            }
            catch (DbUpdateException exception) when(correlationId.HasValue)
            {
                IQueryable <Correlation> query = from c in context.Correlations
                                                 where c.AggregateId == sourceId && c.CorrelationId == correlationId
                                                 select c;

                if (await query.AnyAsync().ConfigureAwait(false))
                {
                    throw new DuplicateCorrelationException(
                              typeof(T),
                              sourceId,
                              correlationId.Value,
                              exception);
                }

                throw;
            }
#else
            try
            {
                await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
            }
            catch (DbUpdateException exception)
                when(exception.InnerException is UpdateException)
                {
                    var updateException = (UpdateException)exception.InnerException;

                    Correlation correlation = updateException
                                              .StateEntries
                                              .Select(s => s.Entity)
                                              .OfType <Correlation>()
                                              .FirstOrDefault();

                    if (correlation != null)
                    {
                        throw new DuplicateCorrelationException(
                                  typeof(T),
                                  sourceId,
                                  correlation.CorrelationId,
                                  exception);
                    }

                    throw;
                }
#endif
        }