Skip to content

Commit

Permalink
fix: use lifetimescope when dispatching multiple commands for municip…
Browse files Browse the repository at this point in the history
…ality proposal
  • Loading branch information
rikdepeuter authored and ArneD committed Sep 4, 2024
1 parent f05a88f commit 965a6e6
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ protected override IServiceProvider ConfigureServices(IServiceCollection service
.AsSelf()
.InstancePerLifetimeScope();

builder.RegisterType<ScopedIdempotentCommandHandler>()
.As<IScopedIdempotentCommandHandler>()
.AsSelf()
.InstancePerLifetimeScope();

services.ConfigureIdempotency(
configuration.GetSection(IdempotencyConfiguration.Section).Get<IdempotencyConfiguration>()
.ConnectionString,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace StreetNameRegistry.Api.BackOffice.Handlers.Lambda.Handlers
using Be.Vlaanderen.Basisregisters.Sqs.Lambda.Infrastructure;
using Be.Vlaanderen.Basisregisters.Sqs.Responses;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Municipality;
using Municipality.Commands;
using Municipality.Exceptions;
Expand All @@ -16,14 +17,17 @@ namespace StreetNameRegistry.Api.BackOffice.Handlers.Lambda.Handlers
public sealed class ProposeStreetNameForMunicipalityMergerHandler : StreetNameLambdaHandler<ProposeStreetNamesForMunicipalityMergerLambdaRequest>
{
private readonly BackOfficeContext _backOfficeContext;
private readonly ILogger _logger;

public ProposeStreetNameForMunicipalityMergerHandler(
IConfiguration configuration,
ICustomRetryPolicy retryPolicy,
ITicketing ticketing,
IIdempotentCommandHandler idempotentCommandHandler,
IScopedIdempotentCommandHandler idempotentCommandHandler,
BackOfficeContext backOfficeContext,
IMunicipalities municipalities)
IMunicipalities municipalities,
ILoggerFactory loggerFactory
)
: base(
configuration,
retryPolicy,
Expand All @@ -32,6 +36,7 @@ public ProposeStreetNameForMunicipalityMergerHandler(
idempotentCommandHandler)
{
_backOfficeContext = backOfficeContext;
_logger = loggerFactory.CreateLogger(GetType());
}

protected override async Task<object> InnerHandle(
Expand All @@ -40,20 +45,24 @@ protected override async Task<object> InnerHandle(
{
var commands = await BuildCommands(request, cancellationToken);

try
foreach (var command in commands)
{
foreach (var command in commands)
_logger.LogDebug($"Handling {command.GetType().FullName}");

try
{
await IdempotentCommandHandler.Dispatch(
command.CreateCommandId(),
command,
request.Metadata!,
cancellationToken);
cancellationToken: cancellationToken);
_logger.LogDebug($"Handled {command.GetType().FullName}");
}
catch (IdempotencyException)
{
// Idempotent: Do Nothing return last etag
_logger.LogDebug($"Skipped due to idempotency {command.GetType().FullName}");
}
}
catch (IdempotencyException)
{
// Idempotent: Do Nothing return last etag
}

var etagResponses = new List<ETagResponse>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace StreetNameRegistry.Api.BackOffice.Handlers.Lambda
{
using Autofac;
using Be.Vlaanderen.Basisregisters.CommandHandling.Idempotency;

public interface IScopedIdempotentCommandHandler: IIdempotentCommandHandler
{
}

public class ScopedIdempotentCommandHandler : IScopedIdempotentCommandHandler
{
private readonly ILifetimeScope _container;

public ScopedIdempotentCommandHandler(ILifetimeScope container)
{
_container = container;
}

public async Task<long> Dispatch(Guid? commandId, object command, IDictionary<string, object> metadata, CancellationToken cancellationToken)
{
await using var scope = _container.BeginLifetimeScope();

var resolver = scope.Resolve<IIdempotentCommandHandler>();
return await resolver.Dispatch(
commandId,
command,
metadata,
cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ namespace StreetNameRegistry.Tests.BackOffice.Lambda
using Municipality;
using Municipality.Commands;
using Newtonsoft.Json;
using StreetNameRegistry.Api.BackOffice.Handlers.Lambda;
using StreetNameRegistry.Api.BackOffice.Handlers.Lambda.Handlers;
using Testing;
using TicketingService.Abstractions;
using Xunit.Abstractions;
Expand Down Expand Up @@ -51,6 +53,28 @@ protected Mock<IIdempotentCommandHandler> MockExceptionIdempotentCommandHandler<
return idempotentCommandHandler;
}

protected Mock<IScopedIdempotentCommandHandler> MockExceptionScopedIdempotentCommandHandler<TException>()
where TException : Exception, new()
{
var idempotentCommandHandler = new Mock<IScopedIdempotentCommandHandler>();
idempotentCommandHandler
.Setup(x => x.Dispatch(It.IsAny<Guid>(), It.IsAny<object>(),
It.IsAny<IDictionary<string, object>>(), CancellationToken.None))
.Throws<TException>();
return idempotentCommandHandler;
}

protected Mock<IScopedIdempotentCommandHandler> MockExceptionScopedIdempotentCommandHandler<TException>(Func<TException> exceptionFactory)
where TException : Exception
{
var idempotentCommandHandler = new Mock<IScopedIdempotentCommandHandler>();
idempotentCommandHandler
.Setup(x => x.Dispatch(It.IsAny<Guid>(), It.IsAny<object>(),
It.IsAny<IDictionary<string, object>>(), CancellationToken.None))
.Throws(exceptionFactory());
return idempotentCommandHandler;
}

protected Mock<ITicketing> MockTicketing(Action<ETagResponse> ticketingCompleteCallback)
{
var ticketing = new Mock<ITicketing>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ namespace StreetNameRegistry.Tests.BackOffice.Lambda.WhenProposingStreetNameForM
using FluentAssertions;
using global::AutoFixture;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Municipality;
using Municipality.Exceptions;
using Newtonsoft.Json;
using SqlStreamStore;
using SqlStreamStore.Streams;
using StreetNameRegistry.Api.BackOffice.Abstractions.SqsRequests;
using StreetNameRegistry.Api.BackOffice.Handlers.Lambda;
using StreetNameRegistry.Api.BackOffice.Handlers.Lambda.Handlers;
using StreetNameRegistry.Api.BackOffice.Handlers.Lambda.Requests;
using Municipality;
using Municipality.Exceptions;
using TicketingService.Abstractions;
using Xunit;
using Xunit.Abstractions;
Expand Down Expand Up @@ -68,9 +70,10 @@ public async Task ThenTheStreetNameIsProposed()
Container.Resolve<IConfiguration>(),
new FakeRetryPolicy(),
ticketing.Object,
new IdempotentCommandHandler(Container.Resolve<ICommandHandlerResolver>(), _idempotencyContext),
new FakeScopedIdemponentCommandHandler(() => new IdempotentCommandHandler(Container.Resolve<ICommandHandlerResolver>(), _idempotencyContext)),
_backOfficeContext,
Container.Resolve<IMunicipalities>());
Container.Resolve<IMunicipalities>(),
new NullLoggerFactory());

//Act
await handler.Handle(new ProposeStreetNamesForMunicipalityMergerLambdaRequest(newMunicipalityId,
Expand Down Expand Up @@ -120,9 +123,10 @@ public async Task WhenStreetNameNameAlreadyExistsException_ThenTicketingErrorIsE
Container.Resolve<IConfiguration>(),
new FakeRetryPolicy(),
ticketing.Object,
MockExceptionIdempotentCommandHandler(() => new StreetNameNameAlreadyExistsException(streetname)).Object,
MockExceptionScopedIdempotentCommandHandler(() => new StreetNameNameAlreadyExistsException(streetname)).Object,
_backOfficeContext,
Mock.Of<IMunicipalities>());
Mock.Of<IMunicipalities>(),
new NullLoggerFactory());

await sut.Handle(new ProposeStreetNamesForMunicipalityMergerLambdaRequest(newMunicipalityId,
new ProposeStreetNamesForMunicipalityMergerSqsRequest
Expand Down Expand Up @@ -167,9 +171,10 @@ public async Task WhenMunicipalityHasInvalidStatusException_ThenTicketingErrorIs
Container.Resolve<IConfiguration>(),
new FakeRetryPolicy(),
ticketing.Object,
MockExceptionIdempotentCommandHandler<MunicipalityHasInvalidStatusException>().Object,
MockExceptionScopedIdempotentCommandHandler<MunicipalityHasInvalidStatusException>().Object,
_backOfficeContext,
Mock.Of<IMunicipalities>());
Mock.Of<IMunicipalities>(),
new NullLoggerFactory());

await sut.Handle(new ProposeStreetNamesForMunicipalityMergerLambdaRequest(newMunicipalityId,
new ProposeStreetNamesForMunicipalityMergerSqsRequest
Expand Down Expand Up @@ -213,9 +218,10 @@ public async Task WhenStreetNameNameLanguageIsNotSupportedException_ThenTicketin
Container.Resolve<IConfiguration>(),
new FakeRetryPolicy(),
ticketing.Object,
MockExceptionIdempotentCommandHandler<StreetNameNameLanguageIsNotSupportedException>().Object,
MockExceptionScopedIdempotentCommandHandler<StreetNameNameLanguageIsNotSupportedException>().Object,
_backOfficeContext,
Mock.Of<IMunicipalities>());
Mock.Of<IMunicipalities>(),
new NullLoggerFactory());

// Act
var municipalityId = Guid.NewGuid().ToString();
Expand Down Expand Up @@ -262,9 +268,10 @@ public async Task WhenStreetNameIsMissingALanguageException_ThenTicketingErrorIs
Container.Resolve<IConfiguration>(),
new FakeRetryPolicy(),
ticketing.Object,
MockExceptionIdempotentCommandHandler<StreetNameIsMissingALanguageException>().Object,
MockExceptionScopedIdempotentCommandHandler<StreetNameIsMissingALanguageException>().Object,
_backOfficeContext,
Mock.Of<IMunicipalities>());
Mock.Of<IMunicipalities>(),
new NullLoggerFactory());

// Act
await sut.Handle(new ProposeStreetNamesForMunicipalityMergerLambdaRequest(newMunicipalityId,
Expand Down Expand Up @@ -311,9 +318,10 @@ public async Task WhenMergedStreetNamePersistentLocalIdsAreMissingException_Then
Container.Resolve<IConfiguration>(),
new FakeRetryPolicy(),
ticketing.Object,
MockExceptionIdempotentCommandHandler<MergedStreetNamePersistentLocalIdsAreMissingException>().Object,
MockExceptionScopedIdempotentCommandHandler<MergedStreetNamePersistentLocalIdsAreMissingException>().Object,
_backOfficeContext,
Mock.Of<IMunicipalities>());
Mock.Of<IMunicipalities>(),
new NullLoggerFactory());

// Act
await sut.Handle(new ProposeStreetNamesForMunicipalityMergerLambdaRequest(newMunicipalityId,
Expand Down Expand Up @@ -360,9 +368,10 @@ public async Task WhenMergedStreetNamePersistentLocalIdsAreNotUniqueException_Th
Container.Resolve<IConfiguration>(),
new FakeRetryPolicy(),
ticketing.Object,
MockExceptionIdempotentCommandHandler<MergedStreetNamePersistentLocalIdsAreNotUniqueException>().Object,
MockExceptionScopedIdempotentCommandHandler<MergedStreetNamePersistentLocalIdsAreNotUniqueException>().Object,
_backOfficeContext,
Mock.Of<IMunicipalities>());
Mock.Of<IMunicipalities>(),
new NullLoggerFactory());

// Act
await sut.Handle(new ProposeStreetNamesForMunicipalityMergerLambdaRequest(newMunicipalityId,
Expand Down Expand Up @@ -409,9 +418,10 @@ public async Task WhenStreetNameHasInvalidDesiredStatusException_ThenTicketingEr
Container.Resolve<IConfiguration>(),
new FakeRetryPolicy(),
ticketing.Object,
MockExceptionIdempotentCommandHandler<StreetNameHasInvalidDesiredStatusException>().Object,
MockExceptionScopedIdempotentCommandHandler<StreetNameHasInvalidDesiredStatusException>().Object,
_backOfficeContext,
Mock.Of<IMunicipalities>());
Mock.Of<IMunicipalities>(),
new NullLoggerFactory());

// Act
await sut.Handle(new ProposeStreetNamesForMunicipalityMergerLambdaRequest(newMunicipalityId,
Expand Down Expand Up @@ -479,9 +489,10 @@ public async Task WhenIdempotencyException_ThenTicketingCompleteIsExpected()
Container.Resolve<IConfiguration>(),
new FakeRetryPolicy(),
ticketing.Object,
MockExceptionIdempotentCommandHandler(() => new IdempotencyException(string.Empty)).Object,
MockExceptionScopedIdempotentCommandHandler(() => new IdempotencyException(string.Empty)).Object,
_backOfficeContext,
municipalities);
municipalities,
new NullLoggerFactory());

// Act
await sut.Handle(new ProposeStreetNamesForMunicipalityMergerLambdaRequest(newMunicipalityId,
Expand Down Expand Up @@ -545,9 +556,10 @@ public async Task GivenRetryingRequest_ThenTicketingCompleteIsExpected()
Container.Resolve<IConfiguration>(),
new FakeRetryPolicy(),
ticketing.Object,
new IdempotentCommandHandler(Container.Resolve<ICommandHandlerResolver>(), _idempotencyContext),
new FakeScopedIdemponentCommandHandler(() => new IdempotentCommandHandler(Container.Resolve<ICommandHandlerResolver>(), _idempotencyContext)),
_backOfficeContext,
municipalities);
municipalities,
new NullLoggerFactory());

var request = new ProposeStreetNamesForMunicipalityMergerLambdaRequest(newMunicipalityId,
new ProposeStreetNamesForMunicipalityMergerSqsRequest
Expand Down Expand Up @@ -589,5 +601,21 @@ public async Task GivenRetryingRequest_ThenTicketingCompleteIsExpected()
}),
CancellationToken.None));
}

private class FakeScopedIdemponentCommandHandler : IScopedIdempotentCommandHandler
{
private readonly Func<IIdempotentCommandHandler> _idempotentCommandHandlerResolver;

public FakeScopedIdemponentCommandHandler(Func<IIdempotentCommandHandler> idempotentCommandHandlerResolver)
{
_idempotentCommandHandlerResolver = idempotentCommandHandlerResolver;
}

public Task<long> Dispatch(Guid? commandId, object command, IDictionary<string, object> metadata, CancellationToken cancellationToken)
{
var handler = _idempotentCommandHandlerResolver();
return handler.Dispatch(commandId, command, metadata, cancellationToken);
}
}
}
}

0 comments on commit 965a6e6

Please sign in to comment.