Skip to content

Commit

Permalink
Introduce tag-based handler filtering through HandlerTag and `Handl…
Browse files Browse the repository at this point in the history
…erTags` attributes
  • Loading branch information
litenova committed Dec 23, 2023
1 parent 61fc28c commit 91f8a42
Show file tree
Hide file tree
Showing 58 changed files with 947 additions and 111 deletions.
5 changes: 5 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## v.0.22.0
- Introduce tag-based handler filtering through `HandlerTag` and `HandlerTags` attributes.
- Add `CommandMediationSettings` to `ICommandMediator` to allow configuring command mediation.
- Add `QueryMediationSettings` to `IQueryMediator` to allow configuring query mediation.

## v.0.21.0
- Fixed Query, Event, and Command error handlers returning `object` instead of `Task`.

Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<LangVersion>latest</LangVersion>
<Authors>A. Shafie</Authors>
<PackageTags>mediator;cqrs</PackageTags>
<VersionPrefix>0.21.0</VersionPrefix>
<VersionPrefix>0.22.0</VersionPrefix>
<PackageIcon>icon.png</PackageIcon>
<Description>LiteBus is an easy-to-use and ambitious in-process mediator providing the foundation to implement Command Query Separation (CQS). It is implemented with minimal reflection and instead utilizes covariance and contravariance to provide its core functionality.</Description>
<PackageProjectUrl>https://github.com/litenova/LiteBus</PackageProjectUrl>
Expand Down
27 changes: 27 additions & 0 deletions src/LiteBus.Commands.Abstractions/CommandMediationSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#nullable enable

using System.Collections.Generic;

namespace LiteBus.Commands.Abstractions;

/// <summary>
/// Represents the settings used during command mediation.
/// </summary>
public sealed class CommandMediationSettings
{
/// <summary>
/// Gets the filters to be applied during command mediation.
/// </summary>
public CommandMediationFilters Filters { get; } = new();

/// <summary>
/// Represents the filters to be applied during command mediation.
/// </summary>
public sealed class CommandMediationFilters
{
/// <summary>
/// Gets or sets the collection of tags used to filter command handlers (i.e., pre, main and post) during mediation.
/// </summary>
public IEnumerable<string> Tags { get; set; } = new List<string>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace LiteBus.Commands.Abstractions.Exceptions;

public sealed class MultipleCommandHandlerFoundException : Exception
{
public MultipleCommandHandlerFoundException(Type commandType, IEnumerable<Type> handlerTypes)
: base($"Multiple command handler found for command type {commandType.FullName}. " +
$"Handler types: {string.Join(", ", handlerTypes.Select(x => x.FullName))}")
{
}
}
33 changes: 21 additions & 12 deletions src/LiteBus.Commands.Abstractions/ICommandMediator.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
using System.Threading;
#nullable enable

using System.Threading;
using System.Threading.Tasks;

namespace LiteBus.Commands.Abstractions;

/// <summary>
/// Sends commands to their corresponding handlers
/// Represents the mediator interface for sending commands within the application.
/// </summary>
public interface ICommandMediator
{
/// <summary>
/// Sends a command without result to its corresponding handler
/// Asynchronously sends a command for mediation.
/// </summary>
/// <param name="command">the command to send</param>
/// <param name="cancellationToken">cancellation token</param>
/// <returns></returns>
Task SendAsync(ICommand command, CancellationToken cancellationToken = default);
/// <param name="command">The command to be sent.</param>
/// <param name="commandMediationSettings">Optional settings for command mediation.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task SendAsync(ICommand command,
CommandMediationSettings? commandMediationSettings = null,
CancellationToken cancellationToken = default);

/// <summary>
/// Sends a command to its corresponding handler and returns the command result
/// Asynchronously sends a command for mediation and returns a result.
/// </summary>
/// <param name="command">the command to send</param>
/// <param name="cancellationToken">cancellation token</param>
/// <returns>The command result</returns>
Task<TCommandResult> SendAsync<TCommandResult>(ICommand<TCommandResult> command, CancellationToken cancellationToken = default);
/// <typeparam name="TCommandResult">The type of the result returned by the command.</typeparam>
/// <param name="command">The command to be sent.</param>
/// <param name="commandMediationSettings">Optional settings for command mediation.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous operation with a result of type <typeparamref name="TCommandResult"/>.</returns>
Task<TCommandResult> SendAsync<TCommandResult>(ICommand<TCommandResult> command,
CommandMediationSettings? commandMediationSettings = null,
CancellationToken cancellationToken = default);
}
19 changes: 14 additions & 5 deletions src/LiteBus.Commands/CommandMediator.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using System.Threading;
#nullable enable

using System.Threading;
using System.Threading.Tasks;
using LiteBus.Commands.Abstractions;
using LiteBus.Messaging.Abstractions;

namespace LiteBus.Commands;

/// <inheritdoc cref="ICommandMediator" />
public class CommandMediator : ICommandMediator
public sealed class CommandMediator : ICommandMediator
{
private readonly IMessageMediator _messageMediator;

Expand All @@ -15,8 +17,11 @@ public CommandMediator(IMessageMediator messageMediator)
_messageMediator = messageMediator;
}

public Task SendAsync(ICommand command, CancellationToken cancellationToken = default)
public Task SendAsync(ICommand command,
CommandMediationSettings? commandMediationSettings = default,
CancellationToken cancellationToken = default)
{
commandMediationSettings ??= new CommandMediationSettings();
var mediationStrategy = new SingleAsyncHandlerMediationStrategy<ICommand>();

var findStrategy = new ActualTypeOrFirstAssignableTypeMessageResolveStrategy();
Expand All @@ -25,23 +30,27 @@ public Task SendAsync(ICommand command, CancellationToken cancellationToken = de
{
MessageMediationStrategy = mediationStrategy,
MessageResolveStrategy = findStrategy,
CancellationToken = cancellationToken
CancellationToken = cancellationToken,
Tags = commandMediationSettings.Filters.Tags
};

return _messageMediator.Mediate(command, options);
}

public Task<TCommandResult> SendAsync<TCommandResult>(ICommand<TCommandResult> command,
CommandMediationSettings? commandMediationSettings = null,
CancellationToken cancellationToken = default)
{
commandMediationSettings ??= new CommandMediationSettings();
var mediationStrategy = new SingleAsyncHandlerMediationStrategy<ICommand<TCommandResult>, TCommandResult>();
var findStrategy = new ActualTypeOrFirstAssignableTypeMessageResolveStrategy();

var options = new MediateOptions<ICommand<TCommandResult>, Task<TCommandResult>>
{
MessageResolveStrategy = findStrategy,
MessageMediationStrategy = mediationStrategy,
CancellationToken = cancellationToken
CancellationToken = cancellationToken,
Tags = commandMediationSettings.Filters.Tags
};

return _messageMediator.Mediate(command, options);
Expand Down
67 changes: 43 additions & 24 deletions src/LiteBus.Events.Abstractions/EventMediationSettings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#nullable enable

using System;
using System.Collections.Generic;

namespace LiteBus.Events.Abstractions;

Expand All @@ -7,30 +10,6 @@ namespace LiteBus.Events.Abstractions;
/// </summary>
public sealed class EventMediationSettings
{
/// <summary>
/// Gets or initializes the function used to filter event handlers.
/// </summary>
/// <remarks>
/// This filter function is used to determine whether a given event handler
/// should be invoked based on the event type. The default behavior is to
/// allow all event handlers (i.e., the function returns true for all event types).
/// </remarks>
/// <example>
/// This example shows how to set a filter to only allow handlers for event types
/// that have the 'LocalExecution' attribute:
/// <code>
/// var settings = new EventMediationSettings
/// {
/// FilterHandler = type => Attribute.IsDefined(type, typeof(LocalExecutionAttribute))
/// };
/// </code>
/// </example>
/// <value>
/// A function that takes a <see cref="Type"/> representing the event handler type
/// and returns a boolean indicating whether the handler should be invoked.
/// </value>
public Func<Type, bool> HandlerFilter { get; init; } = _ => true;

/// <summary>
/// Gets or sets a value indicating whether to throw an exception when no handler is found for an event.
/// </summary>
Expand All @@ -43,4 +22,44 @@ public sealed class EventMediationSettings
/// True to throw an exception when no handler is found; false to ignore and proceed silently.
/// </value>
public bool ThrowIfNoHandlerFound { get; init; } = false;

/// <summary>
/// Gets the filters to be applied during event mediation.
/// </summary>
public EventMediationFilters Filters { get; } = new();

/// <summary>
/// Represents the filters to be applied during event mediation.
/// </summary>
public sealed class EventMediationFilters
{
/// <summary>
/// Gets or sets the collection of tags used to filter event handlers (i.e., pre, main and post) during mediation.
/// </summary>
public IEnumerable<string> Tags { get; set; } = new List<string>();

/// <summary>
/// Gets or initializes the function used to filter event handlers.
/// </summary>
/// <remarks>
/// This filter function is used to determine whether a given event handler
/// should be invoked based on the event type. The default behavior is to
/// allow all event handlers (i.e., the function returns true for all event types).
/// </remarks>
/// <example>
/// This example shows how to set a filter to only allow handlers for event types
/// that have the 'LocalExecution' attribute:
/// <code>
/// var settings = new EventMediationSettings
/// {
/// FilterHandler = type => Attribute.IsDefined(type, typeof(LocalExecutionAttribute))
/// };
/// </code>
/// </example>
/// <value>
/// A function that takes a <see cref="Type"/> representing the event handler type
/// and returns a boolean indicating whether the handler should be invoked.
/// </value>
public Func<Type, bool> HandlerPredicate { get; set; } = _ => true;
}
}
32 changes: 17 additions & 15 deletions src/LiteBus.Events.Abstractions/IEventMediator.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
using System.Threading;
#nullable enable

using System.Threading;
using System.Threading.Tasks;

namespace LiteBus.Events.Abstractions;

/// <summary>
/// Defines the interface for an event mediator that facilitates the publishing of events.
/// Represents the mediator interface for publishing events within the application.
/// </summary>
public interface IEventMediator
{
/// <summary>
/// Publishes an event asynchronously.
/// Asynchronously publishes an event.
/// </summary>
/// <param name="event">The event to publish.</param>
/// <param name="settings">Optional settings for event mediation. If null, default settings are used.</param>
/// <param name="cancellationToken">A token for cancelling the publish operation.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task PublishAsync(IEvent @event, EventMediationSettings settings = null, CancellationToken cancellationToken = default);
/// <param name="event">The event to be published.</param>
/// <param name="eventMediationSettings">Optional settings for event mediation.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous event publication operation.</returns>
Task PublishAsync(IEvent @event, EventMediationSettings? eventMediationSettings = null, CancellationToken cancellationToken = default);

/// <summary>
/// Publishes an event asynchronously.
/// Asynchronously publishes an event with a specific type.
/// </summary>
/// <typeparam name="TEvent">The type of the event to publish.</typeparam>
/// <param name="event">The event to publish.</param>
/// <param name="settings">Optional settings for event mediation. If null, default settings are used.</param>
/// <param name="cancellationToken">A token for cancelling the publish operation.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task PublishAsync<TEvent>(TEvent @event, EventMediationSettings settings = null, CancellationToken cancellationToken = default);
/// <typeparam name="TEvent">The type of the event to be published.</typeparam>
/// <param name="event">The event to be published.</param>
/// <param name="eventMediationSettings">Optional settings for event mediation.</param>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A task representing the asynchronous event publication operation.</returns>
Task PublishAsync<TEvent>(TEvent @event, EventMediationSettings? eventMediationSettings = null, CancellationToken cancellationToken = default) where TEvent : notnull;
}
26 changes: 13 additions & 13 deletions src/LiteBus.Events/EventMediator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Threading;
#nullable enable

using System.Threading;
using System.Threading.Tasks;
using LiteBus.Events.Abstractions;
using LiteBus.Events.MediationStrategies;
Expand All @@ -16,37 +18,35 @@ public EventMediator(IMessageMediator messageMediator)
_messageMediator = messageMediator;
}

public Task PublishAsync(IEvent @event, EventMediationSettings settings = null, CancellationToken cancellationToken = default)
public Task PublishAsync(IEvent @event, EventMediationSettings? eventMediationSettings = null, CancellationToken cancellationToken = default)
{
settings ??= new EventMediationSettings();

var mediationStrategy = new AsyncBroadcastMediationStrategy<IEvent>(settings);

eventMediationSettings ??= new EventMediationSettings();
var mediationStrategy = new AsyncBroadcastMediationStrategy<IEvent>(eventMediationSettings);
var resolveStrategy = new ActualTypeOrFirstAssignableTypeMessageResolveStrategy();

return _messageMediator.Mediate(@event,
new MediateOptions<IEvent, Task>
{
MessageMediationStrategy = mediationStrategy,
MessageResolveStrategy = resolveStrategy,
CancellationToken = cancellationToken
CancellationToken = cancellationToken,
Tags = eventMediationSettings.Filters.Tags
});
}

public Task PublishAsync<TEvent>(TEvent @event, EventMediationSettings settings = null, CancellationToken cancellationToken = default)
public Task PublishAsync<TEvent>(TEvent @event, EventMediationSettings? eventMediationSettings = null, CancellationToken cancellationToken = default) where TEvent : notnull
{
settings ??= new EventMediationSettings();

var mediationStrategy = new AsyncBroadcastMediationStrategy<TEvent>(settings);

eventMediationSettings ??= new EventMediationSettings();
var mediationStrategy = new AsyncBroadcastMediationStrategy<TEvent>(eventMediationSettings);
var resolveStrategy = new ActualTypeOrFirstAssignableTypeMessageResolveStrategy();

return _messageMediator.Mediate(@event,
new MediateOptions<TEvent, Task>
{
MessageMediationStrategy = mediationStrategy,
MessageResolveStrategy = resolveStrategy,
CancellationToken = cancellationToken
CancellationToken = cancellationToken,
Tags = eventMediationSettings.Filters.Tags
});
}
}
Loading

0 comments on commit 91f8a42

Please sign in to comment.