Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

43 changes: 21 additions & 22 deletions src/Extensions/MemoryMessagingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,45 +18,44 @@ public static void AddInMemoryMessaging(this IServiceCollection services,
Assembly[] assemblies,
EventHandler<ReceivedMessageArgs> executingReceivedMessage = null)
{
services.AddSingleton<IMemoryMessagingManager>(serviceProvider =>
{
var publisherManager = new MemoryMessagingManager(serviceProvider);
RegisterAllMessageHandlers(publisherManager, assemblies);

return publisherManager;
});
services.AddScoped<IMemoryMessagingManager, MemoryMessagingManager>();

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

/// <summary>
/// Get all message types from the assemblies including the message handler types.
Expand Down
32 changes: 16 additions & 16 deletions src/Managers/MemoryMessagingManager.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Diagnostics;
using System.Reflection;
using InMemoryMessaging.EventArgs;
using InMemoryMessaging.Exceptions;
using InMemoryMessaging.Instrumentation.Trace;
Expand All @@ -10,7 +9,7 @@ namespace InMemoryMessaging.Managers;

internal class MemoryMessagingManager(IServiceProvider serviceProvider) : IMemoryMessagingManager
{
private readonly Dictionary<string, (Type handlerType, MethodInfo handleMethod)[]> _allHandlers = new();
private static readonly Dictionary<string, MessageHandlerInformation[]> AllHandlers = new();

/// <summary>
/// The event to be executed before executing the handlers of the message.
Expand All @@ -22,7 +21,7 @@ internal class MemoryMessagingManager(IServiceProvider serviceProvider) : IMemor
/// </summary>
/// <param name="typeOfMessage">The type of the message.</param>
/// <param name="typesOfHandler">The types of the handler.</param>
internal void AddHandlers(Type typeOfMessage, Type[] typesOfHandler)
internal static void AddHandlers(Type typeOfMessage, Type[] typesOfHandler)
{
const string handleMethodName = nameof(IMessageHandler<IMessage>.HandleAsync);

Expand All @@ -31,32 +30,34 @@ 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>(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
{
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)
Expand All @@ -71,13 +72,12 @@ public async Task PublishAsync<TMessage>(TMessage message) where TMessage : clas
/// Invokes the ExecutingMessageHandlers event to be able to execute another an action before the handler.
/// </summary>
/// <param name="message">Executing a message</param>
/// <param name="provider">The IServiceProvider used to resolve dependencies from the scope.</param>
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);
}

Expand Down
16 changes: 16 additions & 0 deletions src/Models/MessageHandlerInformation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Reflection;

namespace InMemoryMessaging.Models;

public record MessageHandlerInformation
{
/// <summary>
/// The type of the message handler.
/// </summary>
public required Type MessageHandlerType { get; init; }

/// <summary>
/// The handle method of the message handler.
/// </summary>
public required MethodInfo HandleMethod { get; init; }
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand All @@ -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));
}
Expand All @@ -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));
}
Expand All @@ -139,15 +111,15 @@ public void OneTimeTearDown()
/// <summary>
/// Get the all handlers information from the memory messaging manager
/// </summary>
private Dictionary<string, (Type handlerType, MethodInfo handleMethod)[]> GetAllHandlersInfo()
private Dictionary<string, MessageHandlerInformation[]> 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<string, (Type handlerType, MethodInfo handleMethod)[]>)field!.GetValue(_memoryMessagingManager);
(Dictionary<string, MessageHandlerInformation[]>)field!.GetValue(null);
return handlers;
}

Expand Down
Loading