/// <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); }
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); }