Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move scopes inside #3454

Merged
merged 8 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 23. Scoping dependencies inline with lifetime scope

Date: 2025-01-03

## Status

Proposed

## Context

As it stands dependency Lifetime Scopes are wrapped around the `Command Processor` meaning that all options performed by an instance of the Command processor will share the same scope, this becomes problematic when using the `Publish` method as this allows for multiple `Request Handlers` to be subscribed to a single Event, this will mean that all handlers share dependencies in the same scope which is unexpected behavior.

## Decision

When the Handler Factories are configured to not be a singleton Scopes will be created for each Lifetime, and a new lifetime will be given for each registered subscriber.

## Consequences

We will no longer require a `Command processor provider` as this was only created for scoping, and Handler factories will require the lifetime scope to be passed in to all methods.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ THE SOFTWARE. */
#endregion

using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;

namespace Paramore.Brighter.Extensions.DependencyInjection
Expand All @@ -34,6 +35,7 @@ public class ServiceProviderHandlerFactory : IAmAHandlerFactorySync, IAmAHandler
{
private readonly IServiceProvider _serviceProvider;
private readonly bool _isTransient;
private readonly Dictionary<IAmALifetime, IServiceScope> _scopes = new Dictionary<IAmALifetime, IServiceScope>();

/// <summary>
/// Constructs a factory that uses the .NET IoC container as the factory
Expand All @@ -51,41 +53,68 @@ public ServiceProviderHandlerFactory(IServiceProvider serviceProvider)
/// Lifetime is set during registration
/// </summary>
/// <param name="handlerType">The type of handler to request</param>
/// <param name="lifetime">The brighter Handler lifetime</param>
/// <returns>An instantiated request handler</returns>
IHandleRequests IAmAHandlerFactorySync.Create(Type handlerType)
IHandleRequests IAmAHandlerFactorySync.Create(Type handlerType, IAmALifetime lifetime)
{
return (IHandleRequests)_serviceProvider.GetService(handlerType);
if (!_isTransient)
return (IHandleRequests)_serviceProvider.GetService(handlerType);

if (!_scopes.ContainsKey(lifetime))
_scopes.Add(lifetime, _serviceProvider.CreateScope());

return (IHandleRequests)_scopes[lifetime].ServiceProvider.GetService(handlerType);
}

/// <summary>
/// Creates an instance of the request handler
/// Lifetime is set during registration
/// </summary>
/// <param name="handlerType">The type of handler to request</param>
/// <param name="lifetime">The brighter Handler lifetime</param>
/// <returns>An instantiated request handler</returns>
IHandleRequestsAsync IAmAHandlerFactoryAsync.Create(Type handlerType)
IHandleRequestsAsync IAmAHandlerFactoryAsync.Create(Type handlerType, IAmALifetime lifetime)
{
return (IHandleRequestsAsync)_serviceProvider.GetService(handlerType);
if (!_isTransient)
return (IHandleRequestsAsync)_serviceProvider.GetService(handlerType);

preardon marked this conversation as resolved.
Show resolved Hide resolved
if (!_scopes.ContainsKey(lifetime))
_scopes.Add(lifetime, _serviceProvider.CreateScope());

return (IHandleRequestsAsync)_scopes[lifetime].ServiceProvider.GetService(handlerType);
}

/// <summary>
/// Release the request handler - actual behavior depends on lifetime, we only dispose if we are transient
/// </summary>
/// <param name="handler"></param>
public void Release(IHandleRequests handler)
/// <param name="lifetime">The brighter Handler lifetime</param>
public void Release(IHandleRequests handler, IAmALifetime lifetime)
{
if (!_isTransient) return;

var disposal = handler as IDisposable;
disposal?.Dispose();

ReleaseScope(lifetime);
}

public void Release(IHandleRequestsAsync handler)
public void Release(IHandleRequestsAsync handler, IAmALifetime lifetime)
{
if (!_isTransient) return;

var disposal = handler as IDisposable;
disposal?.Dispose();
ReleaseScope(lifetime);
}

private void ReleaseScope(IAmALifetime lifetime)
{
if(!_scopes.TryGetValue(lifetime, out IServiceScope scope))
return;

scope.Dispose();
_scopes.Remove(lifetime);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,9 @@ private void SendHeartbeat(object? state)
{
_logger.LogInformation("Sending Heartbeat");

var factory = ((Dispatcher)_dispatcher).CommandProcessorFactory.Invoke();
var commandProcessor = ((Dispatcher)_dispatcher).CommandProcessor;

factory.CreateScope();

HeartBeatSender.Send(factory.Get(), _dispatcher);
HeartBeatSender.Send(commandProcessor, _dispatcher);

factory.ReleaseScope();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -54,23 +54,13 @@ private static Dispatcher BuildDispatcher(IServiceProvider serviceProvider)

var options = serviceProvider.GetService<IServiceActivatorOptions>();

Func<IAmACommandProcessorProvider> providerFactory;

if (options.UseScoped)
{
providerFactory = () => new ScopedCommandProcessorProvider(serviceProvider);
}
else
{
var commandProcessor = serviceProvider.GetService<IAmACommandProcessor>();
providerFactory = () => new CommandProcessorProvider(commandProcessor);
}
var commandProcessor = serviceProvider.GetService<IAmACommandProcessor>();

var requestContextFactory = serviceProvider.GetService<IAmARequestContextFactory>();

var dispatcherBuilder = DispatchBuilder
.StartNew()
.CommandProcessorFactory(providerFactory, requestContextFactory);
.CommandProcessor(commandProcessor, requestContextFactory);

var messageMapperRegistry = ServiceCollectionExtensions.MessageMapperRegistry(serviceProvider);
var messageTransformFactory = ServiceCollectionExtensions.TransformFactory(serviceProvider);
Expand Down

This file was deleted.

14 changes: 7 additions & 7 deletions src/Paramore.Brighter.ServiceActivator/ConsumerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace Paramore.Brighter.ServiceActivator
{
internal class ConsumerFactory<TRequest> : IConsumerFactory where TRequest : class, IRequest
{
private readonly IAmACommandProcessorProvider _commandProcessorProvider;
private readonly IAmACommandProcessor _commandProcessor;
private readonly IAmAMessageMapperRegistry? _messageMapperRegistry;
private readonly Subscription _subscription;
private readonly IAmAMessageTransformerFactory? _messageTransformerFactory;
Expand All @@ -41,15 +41,15 @@ internal class ConsumerFactory<TRequest> : IConsumerFactory where TRequest : cla
private readonly IAmAMessageTransformerFactoryAsync? _messageTransformerFactoryAsync;

public ConsumerFactory(
IAmACommandProcessorProvider commandProcessorProvider,
IAmACommandProcessor commandProcessor,
Subscription subscription,
IAmAMessageMapperRegistry messageMapperRegistry,
IAmAMessageTransformerFactory? messageTransformerFactory,
IAmARequestContextFactory requestContextFactory,
IAmABrighterTracer tracer,
InstrumentationOptions instrumentationOptions = InstrumentationOptions.All)
{
_commandProcessorProvider = commandProcessorProvider;
_commandProcessor = commandProcessor;
_messageMapperRegistry = messageMapperRegistry;
_subscription = subscription;
_messageTransformerFactory = messageTransformerFactory ?? new EmptyMessageTransformerFactory();
Expand All @@ -60,15 +60,15 @@ public ConsumerFactory(
}

public ConsumerFactory(
IAmACommandProcessorProvider commandProcessorProvider,
IAmACommandProcessor commandProcessor,
Subscription subscription,
IAmAMessageMapperRegistryAsync messageMapperRegistryAsync,
IAmAMessageTransformerFactoryAsync? messageTransformerFactoryAsync,
IAmARequestContextFactory requestContextFactory,
IAmABrighterTracer tracer,
InstrumentationOptions instrumentationOptions = InstrumentationOptions.All)
{
_commandProcessorProvider = commandProcessorProvider;
_commandProcessor = commandProcessor;
_messageMapperRegistryAsync = messageMapperRegistryAsync;
_subscription = subscription;
_messageTransformerFactoryAsync = messageTransformerFactoryAsync ?? new EmptyMessageTransformerFactoryAsync();
Expand All @@ -95,7 +95,7 @@ private Consumer CreateReactor()
throw new ArgumentException("Subscription must have a Channel Factory in order to create a consumer.");

var channel = _subscription.ChannelFactory.CreateSyncChannel(_subscription);
var messagePump = new Reactor<TRequest>(_commandProcessorProvider, _messageMapperRegistry,
var messagePump = new Reactor<TRequest>(_commandProcessor, _messageMapperRegistry,
_messageTransformerFactory, _requestContextFactory, channel, _tracer, _instrumentationOptions)
{
Channel = channel,
Expand All @@ -117,7 +117,7 @@ private Consumer CreateProactor()
throw new ArgumentException("Subscription must have a Channel Factory in order to create a consumer.");

var channel = _subscription.ChannelFactory.CreateAsyncChannel(_subscription);
var messagePump = new Proactor<TRequest>(_commandProcessorProvider, _messageMapperRegistryAsync,
var messagePump = new Proactor<TRequest>(_commandProcessor, _messageMapperRegistryAsync,
_messageTransformerFactoryAsync, _requestContextFactory, channel, _tracer, _instrumentationOptions)
{
Channel = channel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,7 @@ public Dispatcher Build(string hostName)
if (_channelFactory is null) throw new ArgumentException("Channel Factory must not be null");

return DispatchBuilder.StartNew()
.CommandProcessorFactory(() =>
new CommandProcessorProvider(commandProcessor), new InMemoryRequestContextFactory()
.CommandProcessor(commandProcessor, new InMemoryRequestContextFactory()
preardon marked this conversation as resolved.
Show resolved Hide resolved
)
.MessageMappers(incomingMessageMapperRegistry, null, null, null)
.ChannelFactory(_channelFactory)
Expand Down
26 changes: 13 additions & 13 deletions src/Paramore.Brighter.ServiceActivator/DispatchBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ namespace Paramore.Brighter.ServiceActivator
/// progressive interfaces to manage the requirements for a complete Dispatcher via Intellisense in the IDE. The intent is to make it easier to
/// recognize those dependencies that you need to configure
/// </summary>
public class DispatchBuilder : INeedACommandProcessorFactory, INeedAChannelFactory, INeedAMessageMapper, INeedAListOfSubcriptions, INeedObservability, IAmADispatchBuilder
public class DispatchBuilder : INeedACommandProcessor, INeedAChannelFactory, INeedAMessageMapper, INeedAListOfSubcriptions, INeedObservability, IAmADispatchBuilder
{
private Func<IAmACommandProcessorProvider>? _commandProcessorFactory;
private IAmACommandProcessor? _commandProcessor;
private IAmAMessageMapperRegistry? _messageMapperRegistry;
private IAmAMessageMapperRegistryAsync? _messageMapperRegistryAsync;
private IAmAChannelFactory? _defaultChannelFactory;
Expand All @@ -54,23 +54,23 @@ private DispatchBuilder() { }
/// Begins the fluent interface
/// </summary>
/// <returns>INeedALogger.</returns>
public static INeedACommandProcessorFactory StartNew()
public static INeedACommandProcessor StartNew()
{
return new DispatchBuilder();
}

/// <summary>
/// The command processor used to send and publish messages to handlers by the service activator.
/// </summary>
/// <param name="commandProcessorFactory">The command processor Factory.</param>
/// <param name="commandProcessor">The command processor.</param>
/// <param name="requestContextFactory">The factory used to create a request synchronizationHelper for a pipeline</param>
/// <returns>INeedAMessageMapper.</returns>
public INeedAMessageMapper CommandProcessorFactory(
Func<IAmACommandProcessorProvider> commandProcessorFactory,
public INeedAMessageMapper CommandProcessor(
IAmACommandProcessor commandProcessor,
IAmARequestContextFactory requestContextFactory
)
{
_commandProcessorFactory = commandProcessorFactory;
_commandProcessor = commandProcessor;
_requestContextFactory = requestContextFactory;
return this;
}
Expand Down Expand Up @@ -157,10 +157,10 @@ public INeedObservability Subscriptions(IEnumerable<Subscription> subscriptions)
/// <returns>Dispatcher.</returns>
public Dispatcher Build()
{
if (_commandProcessorFactory is null || _subscriptions is null)
if (_commandProcessor is null || _subscriptions is null)
throw new ArgumentException("Command Processor Factory and Subscription are required.");

return new Dispatcher(_commandProcessorFactory, _subscriptions, _messageMapperRegistry,
return new Dispatcher(_commandProcessor, _subscriptions, _messageMapperRegistry,
_messageMapperRegistryAsync, _messageTransformerFactory, _messageTransformerFactoryAsync,
_requestContextFactory, _tracer, _instrumentationOptions
);
Expand All @@ -174,16 +174,16 @@ public Dispatcher Build()
/// <summary>
/// Interface INeedACommandProcessor
/// </summary>
public interface INeedACommandProcessorFactory
public interface INeedACommandProcessor
{
/// <summary>
/// The command processor used to send and publish messages to handlers by the service activator.
/// </summary>
/// <param name="commandProcessorFactory">The command processor provider Factory.</param>
/// <param name="commandProcessor">The command processor.</param>
/// <param name="requestContextFactory">The factory used to create a request synchronizationHelper for a pipeline</param>
/// <returns>INeedAMessageMapper.</returns>
INeedAMessageMapper CommandProcessorFactory(
Func<IAmACommandProcessorProvider> commandProcessorFactory,
INeedAMessageMapper CommandProcessor(
IAmACommandProcessor commandProcessor,
IAmARequestContextFactory requestContextFactory
);
}
Expand Down
Loading
Loading