diff --git a/README.md b/README.md index 2758a3f..6fa92f3 100644 --- a/README.md +++ b/README.md @@ -125,3 +125,7 @@ public class UserController(IMemoryMessagingManager memoryMessagingManager) : Co ### Can we create a multiple message handlers for the same message/event type? Yes, we can. The library is designed to work with multiple a message handlers for the message type, even if there are multiple message types with the same name, we support them. So, when a message received, all handlers of a message will be executed. + +### What Dependency Injection scope is used for the message handlers? +The library registers the `IMemoryMessagingManager` interface as a `Scoped` service. This means that all message handlers are created within the same scope as the request. It means that the scope of your service that is used to publish a message is the same as the scope of the message handler. + diff --git a/src/Extensions/MemoryMessagingExtensions.cs b/src/Extensions/MemoryMessagingExtensions.cs index e837e51..c958532 100644 --- a/src/Extensions/MemoryMessagingExtensions.cs +++ b/src/Extensions/MemoryMessagingExtensions.cs @@ -18,45 +18,44 @@ public static void AddInMemoryMessaging(this IServiceCollection services, Assembly[] assemblies, EventHandler executingReceivedMessage = null) { - services.AddSingleton(serviceProvider => - { - var publisherManager = new MemoryMessagingManager(serviceProvider); - RegisterAllMessageHandlers(publisherManager, assemblies); - - return publisherManager; - }); + services.AddScoped(); - RegisterAllSubscriberReceiversToDependencyInjection(services, assemblies); + RegisterAllMessageHandlersToDependencyInjectionAndMessagingManager(services, assemblies); if (executingReceivedMessage is not null) MemoryMessagingManager.ExecutingMessageHandlers += executingReceivedMessage; } - #region Message Handlers + #region Message Handlers Registration - private static void RegisterAllMessageHandlers(MemoryMessagingManager subscriberManager, + internal static void RegisterAllMessageHandlersToDependencyInjectionAndMessagingManager(IServiceCollection services, Assembly[] assemblies) { var allMessagesIncludingHandlers = GetAllMessageTypesIncludingHandlers(assemblies); - foreach (var (messageType, messageHandlerTypes) in allMessagesIncludingHandlers) - subscriberManager.AddHandlers(messageType, messageHandlerTypes.ToArray()); - } + RegisterAllSubscriberReceiversToDependencyInjection(); + RegisterAllSubscriberReceiversToMemoryMessagingManager(); + + return; - internal static void RegisterAllSubscriberReceiversToDependencyInjection(IServiceCollection services, - Assembly[] assemblies) - { - var allMessagesIncludingHandlers = GetAllMessageTypesIncludingHandlers(assemblies); + void RegisterAllSubscriberReceiversToDependencyInjection() + { + foreach (var (_, messageHandlerTypes) in allMessagesIncludingHandlers) + { + foreach (var messageHandlerType in messageHandlerTypes) + services.AddTransient(messageHandlerType); + } + } - foreach (var (_, messageHandlerTypes) in allMessagesIncludingHandlers) + void RegisterAllSubscriberReceiversToMemoryMessagingManager() { - foreach (var messageHandlerType in messageHandlerTypes) - services.AddTransient(messageHandlerType); + foreach (var (messageType, messageHandlerTypes) in allMessagesIncludingHandlers) + MemoryMessagingManager.AddHandlers(messageType, messageHandlerTypes.ToArray()); } } - static readonly Type MessageHandlerType = typeof(IMessageHandler<>); - static readonly Type IMassageType = typeof(IMessage); + private static readonly Type MessageHandlerType = typeof(IMessageHandler<>); + private static readonly Type IMassageType = typeof(IMessage); /// /// Get all message types from the assemblies including the message handler types. diff --git a/src/Managers/MemoryMessagingManager.cs b/src/Managers/MemoryMessagingManager.cs index 5e23a2f..3a8bd1f 100644 --- a/src/Managers/MemoryMessagingManager.cs +++ b/src/Managers/MemoryMessagingManager.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using System.Reflection; using InMemoryMessaging.EventArgs; using InMemoryMessaging.Exceptions; using InMemoryMessaging.Instrumentation.Trace; @@ -10,7 +9,7 @@ namespace InMemoryMessaging.Managers; internal class MemoryMessagingManager(IServiceProvider serviceProvider) : IMemoryMessagingManager { - private readonly Dictionary _allHandlers = new(); + private static readonly Dictionary AllHandlers = new(); /// /// The event to be executed before executing the handlers of the message. @@ -22,7 +21,7 @@ internal class MemoryMessagingManager(IServiceProvider serviceProvider) : IMemor /// /// The type of the message. /// The types of the handler. - internal void AddHandlers(Type typeOfMessage, Type[] typesOfHandler) + internal static void AddHandlers(Type typeOfMessage, Type[] typesOfHandler) { const string handleMethodName = nameof(IMessageHandler.HandleAsync); @@ -31,17 +30,21 @@ internal void AddHandlers(Type typeOfMessage, Type[] typesOfHandler) var handleMethod = handlerType.GetMethod(handleMethodName); if (handleMethod is null) throw new InMemoryMessagingException($"The handler '{handlerType.Name}' must implement the '{handleMethodName}' method."); - - return (handlerType, handleMethod); + + return new MessageHandlerInformation + { + MessageHandlerType = handlerType, + HandleMethod = handleMethod + }; }).ToArray(); - _allHandlers[typeOfMessage.Name] = handlersWithMethod; + AllHandlers[typeOfMessage.Name] = handlersWithMethod; } public async Task PublishAsync(TMessage message) where TMessage : class, IMessage { var messageName = message.GetType().Name; - if (!_allHandlers.TryGetValue(messageName, out var messageHandlers) || messageHandlers.Length == 0) + if (!AllHandlers.TryGetValue(messageName, out var messageHandlers) || messageHandlers.Length == 0) return; try @@ -49,14 +52,12 @@ public async Task PublishAsync(TMessage message) where TMessage : clas var traceParentId = Activity.Current?.Id; using var activity = InMemoryMessagingTraceInstrumentation.StartActivity($"Executing handlers of the '{messageName}' memory message.", ActivityKind.Producer, traceParentId); - //Create a new scope to execute the handler service as a scoped service for each a message. - using var serviceScope = serviceProvider.CreateScope(); - OnExecutingReceivedMessage(message, serviceScope.ServiceProvider); + OnExecutingReceivedMessage(message); - foreach (var (messageHandler, handleMethod) in messageHandlers) + foreach (var handlerInfo in messageHandlers) { - var eventReceiver = serviceScope.ServiceProvider.GetRequiredService(messageHandler); - await ((Task)handleMethod.Invoke(eventReceiver, [message]))!; + var eventReceiver = serviceProvider.GetRequiredService(handlerInfo.MessageHandlerType); + await ((Task)handlerInfo.HandleMethod.Invoke(eventReceiver, [message]))!; } } catch (Exception ex) @@ -71,13 +72,12 @@ public async Task PublishAsync(TMessage message) where TMessage : clas /// Invokes the ExecutingMessageHandlers event to be able to execute another an action before the handler. /// /// Executing a message - /// The IServiceProvider used to resolve dependencies from the scope. - private void OnExecutingReceivedMessage(IMessage message, IServiceProvider provider) + private void OnExecutingReceivedMessage(IMessage message) { if (ExecutingMessageHandlers is null) return; - var eventArgs = new ReceivedMessageArgs(message, provider); + var eventArgs = new ReceivedMessageArgs(message, serviceProvider); ExecutingMessageHandlers.Invoke(this, eventArgs); } diff --git a/src/Models/MessageHandlerInformation.cs b/src/Models/MessageHandlerInformation.cs new file mode 100644 index 0000000..3efe8d6 --- /dev/null +++ b/src/Models/MessageHandlerInformation.cs @@ -0,0 +1,16 @@ +using System.Reflection; + +namespace InMemoryMessaging.Models; + +public record MessageHandlerInformation +{ + /// + /// The type of the message handler. + /// + public required Type MessageHandlerType { get; init; } + + /// + /// The handle method of the message handler. + /// + public required MethodInfo HandleMethod { get; init; } +} \ No newline at end of file diff --git a/tests/InMemoryMessaging.Tests/UnitTests/MemoryMessagingManagerTests.cs b/tests/InMemoryMessaging.Tests/UnitTests/MemoryMessagingManagerTests.cs index 64bdca6..fa73cde 100644 --- a/tests/InMemoryMessaging.Tests/UnitTests/MemoryMessagingManagerTests.cs +++ b/tests/InMemoryMessaging.Tests/UnitTests/MemoryMessagingManagerTests.cs @@ -1,6 +1,7 @@ using System.Reflection; using InMemoryMessaging.Extensions; using InMemoryMessaging.Managers; +using InMemoryMessaging.Models; using InMemoryMessaging.Tests.Domain; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; @@ -10,89 +11,61 @@ namespace InMemoryMessaging.Tests.UnitTests; public class MemoryMessagingManagerTests : BaseTestEntity { private readonly ServiceProvider _serviceProvider; - private MemoryMessagingManager _memoryMessagingManager; #region SutUp public MemoryMessagingManagerTests() { ServiceCollection serviceCollection = new(); - MemoryMessagingExtensions.RegisterAllSubscriberReceiversToDependencyInjection - (serviceCollection, [typeof(MemoryMessagingExtensionsTests).Assembly]); + Assembly[] assemblies = [typeof(MemoryMessagingManagerTests).Assembly]; + MemoryMessagingExtensions.RegisterAllMessageHandlersToDependencyInjectionAndMessagingManager + (serviceCollection, assemblies); _serviceProvider = serviceCollection.BuildServiceProvider(); } - [SetUp] - public void Setup() - { - _memoryMessagingManager = new MemoryMessagingManager(_serviceProvider); - } - #endregion #region AddHandlers [Test] - public void AddHandlers_JustCreatedManager_ShouldReturnEmpty() - { - var handlersInfo = GetAllHandlersInfo(); - Assert.That(handlersInfo, Is.Empty); - } - - [Test] - public void AddHandlers_AddedOneHandler_ShouldReturnOneMessageTypeWithOneHandlerInfo() + public void AddHandlers_RegisteringHandlersWhileSettingUpTest_ShouldReturnOneMessageTypeWithTwoHandlerInfo() { var messageType = typeof(UserCreated); var messageHandlerType1 = typeof(Domain.Module1.UserCreatedHandler); - _memoryMessagingManager.AddHandlers(messageType, [messageHandlerType1]); - - var handlersInfo = GetAllHandlersInfo(); - Assert.That(handlersInfo.ContainsKey(messageType.Name), Is.True); - - var handlerTypes = handlersInfo[messageType.Name]; - Assert.That(handlerTypes, Has.Length.EqualTo(1)); - Assert.That(handlerTypes[0].handlerType, Is.EqualTo(messageHandlerType1)); - - var handleMethod = messageHandlerType1.GetMethod(nameof(Domain.Module1.UserCreatedHandler.HandleAsync)); - Assert.That(handlerTypes[0].handleMethod, Is.EqualTo(handleMethod)); - } - - [Test] - public void AddHandlers_AddedOneHandlerTwice_ShouldReturnOneMessageTypeWithOneHandlerInfo() - { - var messageType = typeof(UserCreated); - var messageHandlerType1 = typeof(Domain.Module1.UserCreatedHandler); - _memoryMessagingManager.AddHandlers(messageType, [messageHandlerType1]); - _memoryMessagingManager.AddHandlers(messageType, [messageHandlerType1]); - + var messageHandlerType2 = typeof(Domain.Module2.UserCreatedHandler); + var handlersInfo = GetAllHandlersInfo(); Assert.That(handlersInfo.ContainsKey(messageType.Name), Is.True); var handlerTypes = handlersInfo[messageType.Name]; - Assert.That(handlerTypes, Has.Length.EqualTo(1)); + Assert.That(handlerTypes, Has.Length.EqualTo(2)); + Assert.Multiple(() => + { + Assert.That(handlerTypes.Any(h => h.MessageHandlerType == messageHandlerType1), Is.True); + Assert.That(handlerTypes.Any(h => h.MessageHandlerType == messageHandlerType2), Is.True); + + var firstHandler = handlerTypes.First(h => h.MessageHandlerType == messageHandlerType1); + var handleMethod = messageHandlerType1.GetMethod(nameof(Domain.Module1.UserCreatedHandler.HandleAsync)); + Assert.That(firstHandler.HandleMethod, Is.EqualTo(handleMethod)); + }); } [Test] - public void AddHandlers_AddedTwoHandlers_ShouldReturnOneMessageTypeWithTwoHandlersInfo() + public void AddHandlers_RegisteringMessageTypeWithHandlersTwice_MessageHandlersInformationShouldBeSameAsBefore() { var messageType = typeof(UserCreated); var messageHandlerType1 = typeof(Domain.Module1.UserCreatedHandler); var messageHandlerType2 = typeof(Domain.Module2.UserCreatedHandler); - _memoryMessagingManager.AddHandlers(messageType, [messageHandlerType1, messageHandlerType2]); + MemoryMessagingManager.AddHandlers(messageType, [messageHandlerType1, messageHandlerType2]); var handlersInfo = GetAllHandlersInfo(); Assert.That(handlersInfo.ContainsKey(messageType.Name), Is.True); var handlerTypes = handlersInfo[messageType.Name]; Assert.That(handlerTypes, Has.Length.EqualTo(2)); - Assert.Multiple(() => - { - Assert.That(handlerTypes.Any(h => h.handlerType == messageHandlerType1), Is.True); - Assert.That(handlerTypes.Any(h => h.handlerType == messageHandlerType2), Is.True); - }); } - + #endregion #region PublishAsync @@ -101,8 +74,10 @@ public void AddHandlers_AddedTwoHandlers_ShouldReturnOneMessageTypeWithTwoHandle public async Task PublishAsync_PublishingMessageWhichDoesNotHaveHandler_ShouldNotBeExecuted() { + var memoryMessagingManager = new MemoryMessagingManager(_serviceProvider); var message = new UserUpdated(); - await _memoryMessagingManager.PublishAsync(message); + + await memoryMessagingManager.PublishAsync(message); Assert.That(message.Counter, Is.EqualTo(0)); } @@ -111,13 +86,10 @@ public async Task public async Task PublishAsync_PublishingMessageWhichHasTwoHandlers_ShouldBeExecutedTwice() { - var messageType = typeof(UserCreated); - var messageHandlerType1 = typeof(Domain.Module1.UserCreatedHandler); - var messageHandlerType2 = typeof(Domain.Module2.UserCreatedHandler); - _memoryMessagingManager.AddHandlers(messageType, [messageHandlerType1, messageHandlerType2]); - + var memoryMessagingManager = new MemoryMessagingManager(_serviceProvider); var message = new UserCreated(); - await _memoryMessagingManager.PublishAsync(message); + + await memoryMessagingManager.PublishAsync(message); Assert.That(message.Counter, Is.EqualTo(2)); } @@ -139,15 +111,15 @@ public void OneTimeTearDown() /// /// Get the all handlers information from the memory messaging manager /// - private Dictionary GetAllHandlersInfo() + private Dictionary GetAllHandlersInfo() { - const string handlersFieldName = "_allHandlers"; - var field = _memoryMessagingManager.GetType().GetField(handlersFieldName, - BindingFlags.NonPublic | BindingFlags.Instance); + const string handlersFieldName = "AllHandlers"; + var field = typeof(MemoryMessagingManager).GetField(handlersFieldName, + BindingFlags.NonPublic | BindingFlags.Static); Assert.That(handlersFieldName, Is.Not.Null); var handlers = - (Dictionary)field!.GetValue(_memoryMessagingManager); + (Dictionary)field!.GetValue(null); return handlers; }