/// <summary>
        /// Enable the Dafda outbox producer implementation, configurable using the <paramref name="options"/>
        /// </summary>
        /// <param name="services">The service collection</param>
        /// <param name="options">Configure the <see cref="OutboxProducerOptions"/></param>
        public static void AddOutboxProducer(this IServiceCollection services, Action <OutboxProducerOptions> options)
        {
            var builder = new ProducerConfigurationBuilder();
            var outboxProducerOptions = new OutboxProducerOptions(builder, services);

            options?.Invoke(outboxProducerOptions);
            var configuration = builder.Build();

            var outboxListener = outboxProducerOptions.OutboxListener;

            if (outboxListener == null)
            {
                throw new InvalidConfigurationException($"No {nameof(IOutboxListener)} was registered. Please use the {nameof(OutboxProducerOptions.WithListener)} in the {nameof(AddOutboxProducer)} configuration.");
            }

            services.AddTransient <IHostedService, OutboxDispatcherHostedService>(provider =>
            {
                var outboxUnitOfWorkFactory = provider.GetRequiredService <IOutboxUnitOfWorkFactory>();
                var loggerFactory           = provider.GetRequiredService <ILoggerFactory>();
                var kafkaProducer           = configuration.KafkaProducerFactory(loggerFactory);
                var producer         = new OutboxProducer(kafkaProducer);
                var outboxDispatcher = new OutboxDispatcher(loggerFactory, outboxUnitOfWorkFactory, producer);

                return(new OutboxDispatcherHostedService(outboxListener, outboxDispatcher));
            });
        }
 public OutboxDispatcherHostedService(IOutboxUnitOfWorkFactory unitOfWorkFactory, OutboxProducer producer, IOutboxNotification outboxNotification)
 {
     _outboxNotification = outboxNotification;
     _outboxDispatcher   = new OutboxDispatcher(unitOfWorkFactory, producer);
 }
Ejemplo n.º 3
0
 public OutboxDispatcherHostedService(IOutboxListener outboxListener, OutboxDispatcher outboxDispatcher)
 {
     _outboxListener   = outboxListener;
     _outboxDispatcher = outboxDispatcher;
 }
        public async Task TestTransientErrorBeforeCommit()
        {
            // Arrange

            async Task <TestDomainResponse> Act(bool error, int attempt)
            {
                await using var unitOfWork = await UnitOfWorkManager.Begin("a");

                if (!unitOfWork.Outbox.IsClosed)
                {
                    await unitOfWork.TestEntities.Add(1, attempt.ToString());

                    unitOfWork.Outbox.Publish(new TestDomainEvent {
                        Id = attempt
                    });
                    unitOfWork.Outbox.Send(new TestDomainCommand {
                        Id = attempt * 10
                    });
                    unitOfWork.Outbox.Return(new TestDomainResponse {
                        Id = attempt * 100
                    });

                    if (error)
                    {
                        throw new InvalidOperationException("Some transient error");
                    }

                    await unitOfWork.Commit();
                }

                await unitOfWork.EnsureOutboxDispatched();

                return(unitOfWork.Outbox.GetResponse <TestDomainResponse>());
            }

            // Act 1

            await Act(error : true, attempt : 1).ShouldThrowAsync <InvalidOperationException>();

            // Assert 1

            var readName1 = await EntitiesRepository.GetNameOrDefault(1);

            readName1.ShouldBeNull();

            OutboxDispatcher.PublishedEvents.ShouldBeEmpty();
            OutboxDispatcher.SentCommands.ShouldBeEmpty();

            // Act 2

            OutboxDispatcher.Clear();

            var response2 = await Act(error : false, attempt : 2);

            // Assert 2

            var readName2 = await EntitiesRepository.GetNameOrDefault(1);

            readName2.ShouldBe("2");

            OutboxDispatcher.PublishedEvents.OfType <TestDomainEvent>().ShouldHaveSingleItem();
            OutboxDispatcher.PublishedEvents.OfType <TestDomainEvent>().Single().Id.ShouldBe(2);

            OutboxDispatcher.SentCommands.OfType <TestDomainCommand>().ShouldHaveSingleItem();
            OutboxDispatcher.SentCommands.OfType <TestDomainCommand>().Single().Id.ShouldBe(2 * 10);

            response2.Id.ShouldBe(2 * 100);
        }
        public async Task TestDuplication()
        {
            // Arrange

            async Task <TestDomainResponse> Act(int attempt)
            {
                await using var unitOfWork = await UnitOfWorkManager.Begin("a");

                if (!unitOfWork.Outbox.IsClosed)
                {
                    await unitOfWork.TestEntities.Add(1, attempt.ToString());

                    unitOfWork.Outbox.Publish(new TestDomainEvent {
                        Id = attempt
                    });
                    unitOfWork.Outbox.Send(new TestDomainCommand {
                        Id = attempt * 10
                    });
                    unitOfWork.Outbox.Return(new TestDomainResponse {
                        Id = attempt * 100
                    });

                    await unitOfWork.Commit();
                }

                await unitOfWork.EnsureOutboxDispatched();

                return(unitOfWork.Outbox.GetResponse <TestDomainResponse>());
            }

            // Act 1

            var response1 = await Act(attempt : 1);

            // Assert 1

            var readName1 = await EntitiesRepository.GetNameOrDefault(1);

            readName1.ShouldBe("1");

            OutboxDispatcher.PublishedEvents.OfType <TestDomainEvent>().ShouldHaveSingleItem();
            OutboxDispatcher.PublishedEvents.OfType <TestDomainEvent>().Single().Id.ShouldBe(1);

            OutboxDispatcher.SentCommands.OfType <TestDomainCommand>().ShouldHaveSingleItem();
            OutboxDispatcher.SentCommands.OfType <TestDomainCommand>().Single().Id.ShouldBe(1 * 10);

            response1.Id.ShouldBe(1 * 100);

            // Act 2

            OutboxDispatcher.Clear();

            var response2 = await Act(attempt : 2);

            // Assert 2

            var readName2 = await EntitiesRepository.GetNameOrDefault(1);

            readName2.ShouldBe("1");

            OutboxDispatcher.PublishedEvents.ShouldBeEmpty();
            OutboxDispatcher.SentCommands.ShouldBeEmpty();

            response2.Id.ShouldBe(1 * 100);
        }