Skip to content

Commit

Permalink
Fix StructureMap Transient Middleware Resolution (#1619)
Browse files Browse the repository at this point in the history
* Fix StructureMap Transient Middleware Resolution

In StructureMap the Transient lifetime is more akin to MEDI's Scoped lifetime, where a single object instance will be created for each top level call to `Container.GetInstance()`.

Currently, the JustSayingRegistry causes a throw at startup if you don't clear and re-register these middleware as AlwaysUnique.

https://structuremap.github.io/object-lifecycle/supported-lifecycles/

* Add regression test covering StructureMap middleware lifetime
  • Loading branch information
hwoodiwiss authored Jan 18, 2025
1 parent d046a44 commit c632bbf
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ public JustSayingRegistry()
For<IMessageContextAccessor>().Use(context => context.GetInstance<MessageContextAccessor>());
For<IMessageContextReader>().Use(context => context.GetInstance<MessageContextAccessor>());

For<LoggingMiddleware>().Transient();
For<SqsPostProcessorMiddleware>().Transient();
For<LoggingMiddleware>().AlwaysUnique();
For<SqsPostProcessorMiddleware>().AlwaysUnique();

For<IMessageSerializationRegister>()
.Use(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public HandlerMiddlewareBuilder Use<TMiddleware>() where TMiddleware : Middlewar
{
throw new InvalidOperationException(
@"Middlewares must be registered into your DI container such that each resolution creates a new instance.
For StructureMap use Transient(), and for Microsoft.Extensions.DependencyInjection, use AddTransient().
For StructureMap use AlwaysUnique(), and for Microsoft.Extensions.DependencyInjection, use AddTransient().
Please check the documentation for your container for more details.");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
using JustSaying.Fluent;
using JustSaying.Messaging;
using JustSaying.Messaging.MessageHandling;
using JustSaying.Messaging.Middleware;
using JustSaying.Messaging.Middleware.Logging;
using JustSaying.Messaging.Middleware.PostProcessing;
using JustSaying.TestingFramework;
using Microsoft.Extensions.Logging;
using Shouldly;
Expand Down Expand Up @@ -64,4 +68,41 @@ public async Task Can_Create_Messaging_Bus_Fluently_For_A_Queue()
// Assert
handler.ReceivedMessages.ShouldContain(x => x.GetType() == typeof(SimpleMessage));
}

[AwsFact]
public void Registers_Middleware_With_The_Correct_Lifetime()
{
// Arrange
using var container = new Container(
(registry) =>
{
registry.For<ILoggerFactory>()
.Use(() => OutputHelper.ToLoggerFactory())
.Singleton();

registry.AddJustSaying(
(builder) =>
{
builder.Client((options) =>
options.WithBasicCredentials("accessKey", "secretKey")
.WithServiceUri(TestEnvironment.SimulatorUrl))
.Messaging((options) => options.WithRegion("eu-west-1"));
});
});

var handlerMiddlewareBuilder = new HandlerMiddlewareBuilder(container.GetInstance<IHandlerResolver>(), container.GetInstance<IServiceResolver>());
var useLoggingMiddlewareTwice = () => UseMiddlewareTwice<LoggingMiddleware>();
var useSqsPostProcessorMiddlewareTwice = () => UseMiddlewareTwice<SqsPostProcessorMiddleware>();

void UseMiddlewareTwice<T>()
where T : MiddlewareBase<HandleMessageContext, bool>
{
handlerMiddlewareBuilder.Use<LoggingMiddleware>();
handlerMiddlewareBuilder.Use<LoggingMiddleware>();
}

// Act + Assert
useLoggingMiddlewareTwice.ShouldNotThrow();
useSqsPostProcessorMiddlewareTwice.ShouldNotThrow();
}
}
17 changes: 11 additions & 6 deletions tests/JustSaying.TestingFramework/Patiently.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,20 @@ public static async Task AssertThatAsync(
ITestOutputHelper output,
Func<bool> func,
[System.Runtime.CompilerServices.CallerMemberName]
string memberName = "")
=> await AssertThatAsyncInner(output, func, 5.Seconds(), memberName).ConfigureAwait(false);
string memberName = "",
[System.Runtime.CompilerServices.CallerArgumentExpression("func")]
string assertionExpression = "")
=> await AssertThatAsyncInner(output, func, 5.Seconds(), memberName, assertionExpression).ConfigureAwait(false);

public static async Task AssertThatAsync(
ITestOutputHelper output,
Func<bool> func,
TimeSpan timeout,
[System.Runtime.CompilerServices.CallerMemberName]
string memberName = "")
=> await AssertThatAsyncInner(output, func, timeout, memberName).ConfigureAwait(false);
string memberName = "",
[System.Runtime.CompilerServices.CallerArgumentExpression("func")]
string assertionExpression = "")
=> await AssertThatAsyncInner(output, func, timeout, memberName, assertionExpression).ConfigureAwait(false);

public static async Task AssertThatAsync(ITestOutputHelper output, Func<Task<bool>> func) =>
await AssertThatAsync(output, func, 5.Seconds()).ConfigureAwait(false);
Expand Down Expand Up @@ -80,7 +84,8 @@ private static async Task AssertThatAsyncInner(
ITestOutputHelper output,
Func<bool> func,
TimeSpan timeout,
string description)
string description,
string assertionExpression)
{
var watch = new Stopwatch();
watch.Start();
Expand All @@ -106,7 +111,7 @@ private static async Task AssertThatAsyncInner(
$"Waiting for {watch.Elapsed.TotalMilliseconds} ms - Still waiting for {description}.");
} while (watch.Elapsed < timeout);

func.Invoke().ShouldBeTrue();
func.Invoke().ShouldBeTrue($"Failed to assert that {assertionExpression} within {timeout}");
}
}

Expand Down

0 comments on commit c632bbf

Please sign in to comment.