public async Task PublishPendingEvents_does_not_fails_even_if_all_events_persisted()
        {
            // Arrange
            var userId = Guid.NewGuid();

            var userCreated     = fixture.Create <FakeUserCreated>();
            var usernameChanged = fixture.Create <FakeUsernameChanged>();
            var domainEvents    = new DomainEvent[] { userCreated, usernameChanged };

            RaiseEvents(userId, domainEvents);

            var envelopes = new List <Envelope>(domainEvents.Select(e => new Envelope(e)));

            var batchOperation = new TableBatchOperation();

            envelopes
            .Select(e => PendingEventTableEntity.FromEnvelope <FakeUser>(e, serializer))
            .ForEach(batchOperation.Insert);
            await s_eventTable.ExecuteBatchAsync(batchOperation);

            batchOperation.Clear();
            envelopes
            .Select(e => EventTableEntity.FromEnvelope <FakeUser>(e, serializer))
            .ForEach(batchOperation.Insert);
            await s_eventTable.ExecuteBatchAsync(batchOperation);

            // Act
            Func <Task> action = () => sut.PublishPendingEvents <FakeUser>(userId, CancellationToken.None);

            // Assert
            action.ShouldNotThrow();
        }
Example #2
0
        private async Task <IEnumerable <IDomainEvent> > Load <T>(
            Guid sourceId,
            int afterVersion,
            CancellationToken cancellationToken)
            where T : class, IEventSourced
        {
            string filter = CombineFilters(
                GenerateFilterCondition(
                    nameof(ITableEntity.PartitionKey),
                    Equal,
                    EventTableEntity.GetPartitionKey(typeof(T), sourceId)),
                And,
                GenerateFilterCondition(
                    nameof(ITableEntity.RowKey),
                    GreaterThan,
                    EventTableEntity.GetRowKey(afterVersion)));

            var query = new TableQuery <EventTableEntity>().Where(filter);

            IEnumerable <EventTableEntity> events = await _eventTable
                                                    .ExecuteQuery(query, cancellationToken)
                                                    .ConfigureAwait(false);

            return(new List <IDomainEvent>(events
                                           .Select(e => e.EventJson)
                                           .Select(_serializer.Deserialize)
                                           .Cast <IDomainEvent>()));
        }
Example #3
0
        public void GetPartitionKey_returns_the_same_as_EventTableEntity()
        {
            var    sourceType = typeof(FakeUser);
            var    sourceId   = Guid.NewGuid();
            string actual     = CorrelationTableEntity.GetPartitionKey(sourceType, sourceId);

            actual.Should().Be(EventTableEntity.GetPartitionKey(sourceType, sourceId));
        }
Example #4
0
        public void FromEnvelope_sets_EventType_correctly()
        {
            var domainEvent = fixture.Create <FakeUserCreated>();
            var envelope    = new Envelope(domainEvent);

            EventTableEntity entity =
                FromEnvelope <FakeUser>(envelope, serializer);

            entity.EventType.Should().Be(typeof(FakeUserCreated).FullName);
        }
Example #5
0
        public void FromEnvelope_sets_Version_correctly()
        {
            var domainEvent = fixture.Create <FakeUserCreated>();
            var envelope    = new Envelope(domainEvent);

            EventTableEntity entity =
                FromEnvelope <FakeUser>(envelope, serializer);

            entity.Version.Should().Be(domainEvent.Version);
        }
Example #6
0
        public void FromEnvelope_sets_PartitionKey_correctly()
        {
            var domainEvent = fixture.Create <FakeUserCreated>();
            var envelope    = new Envelope(domainEvent);

            EventTableEntity entity =
                FromEnvelope <FakeUser>(envelope, serializer);

            entity.PartitionKey.Should().Be(
                GetPartitionKey(typeof(FakeUser), domainEvent.SourceId));
        }
Example #7
0
        public void FromEnvelope_sets_CorrelationId_correctly()
        {
            var domainEvent   = fixture.Create <FakeUserCreated>();
            var correlationId = Guid.NewGuid();
            var envelope      = new Envelope(correlationId, domainEvent);

            EventTableEntity entity =
                FromEnvelope <FakeUser>(envelope, serializer);

            entity.CorrelationId.Should().Be(correlationId);
        }
        public async Task PublishAllEvents_sends_pending_events()
        {
            // Arrange
            var domainEvents = new List <DomainEvent>();

            List <Guid> users = fixture.CreateMany <Guid>().ToList();

            foreach (Guid userId in users)
            {
                var userCreated     = fixture.Create <FakeUserCreated>();
                var usernameChanged = fixture.Create <FakeUsernameChanged>();
                var events          = new DomainEvent[] { userCreated, usernameChanged };
                RaiseEvents(userId, events);

                var envelopes = new List <Envelope>(events.Select(e => new Envelope(e)));

                var batchOperation = new TableBatchOperation();
                envelopes
                .Select(e => PendingEventTableEntity.FromEnvelope <FakeUser>(e, serializer))
                .ForEach(batchOperation.Insert);
                await s_eventTable.ExecuteBatchAsync(batchOperation);

                batchOperation.Clear();
                envelopes
                .Select(e => EventTableEntity.FromEnvelope <FakeUser>(e, serializer))
                .ForEach(batchOperation.Insert);
                await s_eventTable.ExecuteBatchAsync(batchOperation);

                domainEvents.AddRange(events);
            }

            var messages = new List <IDomainEvent>();

            Mock.Get(messageBus)
            .Setup(
                x =>
                x.SendBatch(
                    It.IsAny <IEnumerable <Envelope> >(),
                    It.IsAny <CancellationToken>()))
            .Callback <IEnumerable <Envelope>, CancellationToken>(
                (batch, cancellationToken) =>
                messages.AddRange(batch
                                  .Select(b => b.Message)
                                  .OfType <IDomainEvent>()
                                  .Where(m => users.Contains(m.SourceId))))
            .Returns(Task.FromResult(true));

            // Act
            await sut.PublishAllEvents(CancellationToken.None);

            // Assert
            messages.Should().OnlyContain(e => e is IDomainEvent);
            messages.ShouldAllBeEquivalentTo(domainEvents);
        }
Example #9
0
        public void FromEnvelope_sets_PersistentPartition_correctly()
        {
            var domainEvent = fixture.Create <FakeUserCreated>();
            var envelope    = new Envelope(domainEvent);

            PendingEventTableEntity actual =
                FromEnvelope <FakeUser>(envelope, serializer);

            actual.PersistentPartition.Should().Be(
                EventTableEntity.GetPartitionKey(
                    typeof(FakeUser), domainEvent.SourceId));
        }
Example #10
0
        public void FromEnvelope_sets_EventJson_correctly()
        {
            var domainEvent = fixture.Create <FakeUserCreated>();
            var envelope    = new Envelope(domainEvent);

            EventTableEntity entity =
                FromEnvelope <FakeUser>(envelope, serializer);

            object actual = serializer.Deserialize(entity.EventJson);

            actual.Should().BeOfType <FakeUserCreated>();
            actual.ShouldBeEquivalentTo(domainEvent);
        }
        public async Task PublishPendingEvents_does_not_delete_pending_events_if_fails_to_send()
        {
            // Arrange
            var userId = Guid.NewGuid();

            var userCreated     = fixture.Create <FakeUserCreated>();
            var usernameChanged = fixture.Create <FakeUsernameChanged>();
            var domainEvents    = new DomainEvent[] { userCreated, usernameChanged };

            RaiseEvents(userId, domainEvents);

            var envelopes = new List <Envelope>(domainEvents.Select(e => new Envelope(e)));

            var batchOperation = new TableBatchOperation();
            var pendingEvents  = new List <PendingEventTableEntity>(
                envelopes.Select(e => PendingEventTableEntity.FromEnvelope <FakeUser>(e, serializer)));

            pendingEvents.ForEach(batchOperation.Insert);
            await s_eventTable.ExecuteBatchAsync(batchOperation);

            batchOperation.Clear();
            envelopes
            .Take(1)
            .Select(e => EventTableEntity.FromEnvelope <FakeUser>(e, serializer))
            .ForEach(batchOperation.Insert);
            await s_eventTable.ExecuteBatchAsync(batchOperation);

            Mock.Get(messageBus)
            .Setup(
                x =>
                x.SendBatch(
                    It.IsAny <IEnumerable <Envelope> >(),
                    It.IsAny <CancellationToken>()))
            .Throws(new InvalidOperationException());

            // Act
            try
            {
                await sut.PublishPendingEvents <FakeUser>(userId, CancellationToken.None);
            }
            catch (InvalidOperationException)
            {
            }

            // Assert
            string partitionKey         = PendingEventTableEntity.GetPartitionKey(typeof(FakeUser), userId);
            var    query                = new TableQuery <PendingEventTableEntity>().Where($"PartitionKey eq '{partitionKey}'");
            IEnumerable <object> actual = s_eventTable.ExecuteQuery(query).Select(e => e.RowKey);

            actual.ShouldAllBeEquivalentTo(pendingEvents.Select(e => e.RowKey));
        }
        public async Task PublishPendingEventss_sends_only_persisted_pending_events()
        {
            // Arrange
            var userId = Guid.NewGuid();

            var userCreated     = fixture.Create <FakeUserCreated>();
            var usernameChanged = fixture.Create <FakeUsernameChanged>();
            var domainEvents    = new DomainEvent[] { userCreated, usernameChanged };

            RaiseEvents(userId, domainEvents);

            var envelopes = new List <Envelope>(domainEvents.Select(e => new Envelope(e)));

            var batchOperation = new TableBatchOperation();

            envelopes
            .Select(e => PendingEventTableEntity.FromEnvelope <FakeUser>(e, serializer))
            .ForEach(batchOperation.Insert);
            await s_eventTable.ExecuteBatchAsync(batchOperation);

            batchOperation.Clear();
            envelopes
            .Take(1)
            .Select(e => EventTableEntity.FromEnvelope <FakeUser>(e, serializer))
            .ForEach(batchOperation.Insert);
            await s_eventTable.ExecuteBatchAsync(batchOperation);

            List <Envelope> batch = null;

            Mock.Get(messageBus)
            .Setup(
                x =>
                x.SendBatch(
                    It.IsAny <IEnumerable <Envelope> >(),
                    It.IsAny <CancellationToken>()))
            .Callback <IEnumerable <Envelope>, CancellationToken>((b, t) => batch = b.ToList())
            .Returns(Task.FromResult(true));

            // Act
            await sut.PublishPendingEvents <FakeUser>(userId, CancellationToken.None);

            // Assert
            Mock.Get(messageBus).Verify(
                x =>
                x.SendBatch(
                    It.IsAny <IEnumerable <Envelope> >(),
                    CancellationToken.None),
                Times.Once());
            batch.ShouldAllBeEquivalentTo(envelopes.Take(1), opts => opts.RespectingRuntimeTypes());
        }
Example #13
0
        private async Task InsertEventsAndCorrelation <T>(
            List <Envelope> envelopes,
            Guid?correlationId,
            CancellationToken cancellationToken)
            where T : class, IEventSourced
        {
            var batch = new TableBatchOperation();

            var  firstEvent = (IDomainEvent)envelopes.First().Message;
            Guid sourceId   = firstEvent.SourceId;

            foreach (Envelope envelope in envelopes)
            {
                batch.Insert(EventTableEntity.FromEnvelope <T>(envelope, _serializer));
            }

            if (correlationId.HasValue)
            {
                batch.Insert(CorrelationTableEntity.Create(typeof(T), sourceId, correlationId.Value));
            }

            try
            {
                await _eventTable.ExecuteBatchAsync(batch, cancellationToken).ConfigureAwait(false);
            }
            catch (StorageException exception) when(correlationId.HasValue)
            {
                string filter = CorrelationTableEntity.GetFilter(typeof(T), sourceId, correlationId.Value);
                var    query  = new TableQuery <CorrelationTableEntity>().Where(filter);

                if (await _eventTable.Any(query, cancellationToken))
                {
                    throw new DuplicateCorrelationException(
                              typeof(T),
                              sourceId,
                              correlationId.Value,
                              exception);
                }

                throw;
            }
        }
Example #14
0
        public async Task SaveEvents_inserts_event_entities_correctly()
        {
            // Arrange
            var created         = fixture.Create <FakeUserCreated>();
            var usernameChanged = fixture.Create <FakeUsernameChanged>();
            var events          = new DomainEvent[] { created, usernameChanged };
            var correlationId   = Guid.NewGuid();

            RaiseEvents(userId, events);

            // Act
            await sut.SaveEvents <FakeUser>(events, correlationId);

            // Assert
            string partitionKey = EventTableEntity.GetPartitionKey(typeof(FakeUser), userId);
            var    query        = new TableQuery <EventTableEntity>().Where($"PartitionKey eq '{partitionKey}'");
            IEnumerable <EventTableEntity> persistentEvents = s_eventTable.ExecuteQuery(query);

            foreach (var t in persistentEvents.Zip(events, (persistent, source)
                                                   => new { Persistent = persistent, Source = source }))
            {
                var actual = new
                {
                    t.Persistent.RowKey,
                    t.Persistent.Version,
                    t.Persistent.EventType,
                    t.Persistent.CorrelationId,
                    Event = (DomainEvent)serializer.Deserialize(t.Persistent.EventJson),
                    t.Persistent.RaisedAt
                };
                actual.ShouldBeEquivalentTo(new
                {
                    RowKey = EventTableEntity.GetRowKey(t.Source.Version),
                    t.Source.Version,
                    EventType     = t.Source.GetType().FullName,
                    CorrelationId = correlationId,
                    Event         = t.Source,
                    t.Source.RaisedAt
                });
            }
        }
Example #15
0
        public async Task SaveEvents_inserts_pending_event_entities_correctly()
        {
            // Arrange
            var created         = fixture.Create <FakeUserCreated>();
            var usernameChanged = fixture.Create <FakeUsernameChanged>();
            var events          = new DomainEvent[] { created, usernameChanged };
            var correlationId   = Guid.NewGuid();

            RaiseEvents(userId, events);

            // Act
            await sut.SaveEvents <FakeUser>(events, correlationId);

            // Assert
            string partitionKey = PendingEventTableEntity.GetPartitionKey(typeof(FakeUser), userId);
            var    query        = new TableQuery <PendingEventTableEntity>().Where($"PartitionKey eq '{partitionKey}'");
            IEnumerable <PendingEventTableEntity> pendingEvents = s_eventTable.ExecuteQuery(query);

            foreach (var t in pendingEvents.Zip(events, (pending, source) =>
                                                new { Pending = pending, Source = source }))
            {
                var actual = new
                {
                    t.Pending.RowKey,
                    t.Pending.PersistentPartition,
                    t.Pending.Version,
                    t.Pending.CorrelationId,
                    Message = serializer.Deserialize(t.Pending.EventJson)
                };
                actual.ShouldBeEquivalentTo(new
                {
                    RowKey = PendingEventTableEntity.GetRowKey(t.Source.Version),
                    PersistentPartition = EventTableEntity.GetPartitionKey(typeof(FakeUser), userId),
                    t.Source.Version,
                    CorrelationId = correlationId,
                    Message       = t.Source
                },
                                            opts => opts.RespectingRuntimeTypes());
            }
        }
Example #16
0
        private async Task <List <EventTableEntity> > GetPersistentEvents(
            string persistentPartition,
            int version,
            CancellationToken cancellationToken)
        {
            var query = new TableQuery <EventTableEntity>();

            string filter = CombineFilters(
                GenerateFilterCondition(
                    nameof(ITableEntity.PartitionKey),
                    Equal,
                    persistentPartition),
                And,
                GenerateFilterCondition(
                    nameof(ITableEntity.RowKey),
                    GreaterThanOrEqual,
                    EventTableEntity.GetRowKey(version)));

            return(new List <EventTableEntity>(await _eventTable
                                               .ExecuteQuery(query.Where(filter), cancellationToken)
                                               .ConfigureAwait(false)));
        }
        public static PendingEventTableEntity FromEnvelope <T>(
            Envelope envelope,
            IMessageSerializer serializer)
            where T : class, IEventSourced
        {
            if (envelope == null)
            {
                throw new ArgumentNullException(nameof(envelope));
            }

            if (serializer == null)
            {
                throw new ArgumentNullException(nameof(serializer));
            }

            var domainEvent = envelope.Message as IDomainEvent;

            if (domainEvent == null)
            {
                throw new ArgumentException(
                          $"{nameof(envelope)}.{nameof(envelope.Message)} must be an {nameof(IDomainEvent)}.",
                          nameof(envelope));
            }

            string persistentPartition = EventTableEntity.GetPartitionKey(
                typeof(T), domainEvent.SourceId);

            return(new PendingEventTableEntity
            {
                PartitionKey = GetPartitionKey(typeof(T), domainEvent.SourceId),
                RowKey = GetRowKey(domainEvent.Version),
                PersistentPartition = persistentPartition,
                Version = domainEvent.Version,
                MessageId = envelope.MessageId,
                CorrelationId = envelope.CorrelationId,
                EventJson = serializer.Serialize(domainEvent)
            });
        }
        public async Task PublishPendingEvents_deletes_all_pending_events()
        {
            // Arrange
            var userId = Guid.NewGuid();

            var userCreated     = fixture.Create <FakeUserCreated>();
            var usernameChanged = fixture.Create <FakeUsernameChanged>();
            var domainEvents    = new DomainEvent[] { userCreated, usernameChanged };

            RaiseEvents(userId, domainEvents);

            var envelopes = new List <Envelope>(domainEvents.Select(e => new Envelope(e)));

            var batchOperation = new TableBatchOperation();

            envelopes
            .Select(e => PendingEventTableEntity.FromEnvelope <FakeUser>(e, serializer))
            .ForEach(batchOperation.Insert);
            await s_eventTable.ExecuteBatchAsync(batchOperation);

            batchOperation.Clear();
            envelopes
            .Take(1)
            .Select(e => EventTableEntity.FromEnvelope <FakeUser>(e, serializer))
            .ForEach(batchOperation.Insert);
            await s_eventTable.ExecuteBatchAsync(batchOperation);

            // Act
            await sut.PublishPendingEvents <FakeUser>(userId, CancellationToken.None);

            // Assert
            string partitionKey = PendingEventTableEntity.GetPartitionKey(typeof(FakeUser), userId);
            var    query        = new TableQuery <PendingEventTableEntity>().Where($"PartitionKey eq '{partitionKey}'");
            List <PendingEventTableEntity> actual = s_eventTable.ExecuteQuery(query).ToList();

            actual.Should().BeEmpty();
        }
 public static string GetPartitionKey(Type sourceType, Guid sourceId)
 => EventTableEntity.GetPartitionKey(sourceType, sourceId);