diff --git a/src/Bss.Platform.Events.Abstractions/Bss.Platform.Events.Abstractions.csproj b/src/Bss.Platform.Events.Abstractions/Bss.Platform.Events.Abstractions.csproj index b0a2923..f4a5d20 100644 --- a/src/Bss.Platform.Events.Abstractions/Bss.Platform.Events.Abstractions.csproj +++ b/src/Bss.Platform.Events.Abstractions/Bss.Platform.Events.Abstractions.csproj @@ -3,6 +3,6 @@ Luxoft.Bss.Platform.Events.Abstractions - + diff --git a/src/Bss.Platform.Events.Abstractions/IDomainEvent.cs b/src/Bss.Platform.Events.Abstractions/IDomainEvent.cs index 82e2c5d..2f68705 100644 --- a/src/Bss.Platform.Events.Abstractions/IDomainEvent.cs +++ b/src/Bss.Platform.Events.Abstractions/IDomainEvent.cs @@ -1,4 +1,4 @@ -using MediatR; +using Bss.Platform.Mediation.Abstractions; namespace Bss.Platform.Events.Abstractions; diff --git a/src/Bss.Platform.Events.Abstractions/IIntegrationEvent.cs b/src/Bss.Platform.Events.Abstractions/IIntegrationEvent.cs index 256cbdc..12cc497 100644 --- a/src/Bss.Platform.Events.Abstractions/IIntegrationEvent.cs +++ b/src/Bss.Platform.Events.Abstractions/IIntegrationEvent.cs @@ -1,4 +1,4 @@ -using MediatR; +using Bss.Platform.Mediation.Abstractions; namespace Bss.Platform.Events.Abstractions; diff --git a/src/Bss.Platform.Events/Bss.Platform.Events.csproj b/src/Bss.Platform.Events/Bss.Platform.Events.csproj index 0ad9700..7a22725 100644 --- a/src/Bss.Platform.Events/Bss.Platform.Events.csproj +++ b/src/Bss.Platform.Events/Bss.Platform.Events.csproj @@ -3,12 +3,13 @@ Luxoft.Bss.Platform.Events - + + - - - - + + + + diff --git a/src/Bss.Platform.Events/Publishers/DomainEventPublisher.cs b/src/Bss.Platform.Events/Publishers/DomainEventPublisher.cs index a3b9072..511359b 100644 --- a/src/Bss.Platform.Events/Publishers/DomainEventPublisher.cs +++ b/src/Bss.Platform.Events/Publishers/DomainEventPublisher.cs @@ -1,6 +1,5 @@ using Bss.Platform.Events.Abstractions; - -using MediatR; +using Bss.Platform.Mediation.Abstractions; namespace Bss.Platform.Events.Publishers; diff --git a/src/Bss.Platform.Mediation.Abstractions/IMediator.cs b/src/Bss.Platform.Mediation.Abstractions/IMediator.cs index e7029b1..f5b58f2 100644 --- a/src/Bss.Platform.Mediation.Abstractions/IMediator.cs +++ b/src/Bss.Platform.Mediation.Abstractions/IMediator.cs @@ -7,4 +7,7 @@ Task Send(TRequest request, CancellationToken cancel Task Send(TRequest request, CancellationToken cancellationToken = default) where TRequest : IRequest; + + Task Publish(TNotification notification, CancellationToken cancellationToken = default) + where TNotification : INotification; } diff --git a/src/Bss.Platform.Mediation.Abstractions/INotification.cs b/src/Bss.Platform.Mediation.Abstractions/INotification.cs new file mode 100644 index 0000000..fbec3b2 --- /dev/null +++ b/src/Bss.Platform.Mediation.Abstractions/INotification.cs @@ -0,0 +1,3 @@ +namespace Bss.Platform.Mediation.Abstractions; + +public interface INotification; diff --git a/src/Bss.Platform.Mediation.Abstractions/INotificationHandler.cs b/src/Bss.Platform.Mediation.Abstractions/INotificationHandler.cs new file mode 100644 index 0000000..295f21c --- /dev/null +++ b/src/Bss.Platform.Mediation.Abstractions/INotificationHandler.cs @@ -0,0 +1,6 @@ +namespace Bss.Platform.Mediation.Abstractions; + +public interface INotificationHandler where TNotification : INotification +{ + Task Handle(TNotification notification, CancellationToken cancellationToken); +} diff --git a/src/Bss.Platform.Mediation/Bss.Platform.Mediation.csproj b/src/Bss.Platform.Mediation/Bss.Platform.Mediation.csproj index 53c0bbe..c131612 100644 --- a/src/Bss.Platform.Mediation/Bss.Platform.Mediation.csproj +++ b/src/Bss.Platform.Mediation/Bss.Platform.Mediation.csproj @@ -7,12 +7,13 @@ - + + - - + + diff --git a/src/Bss.Platform.Mediation/Mediator.cs b/src/Bss.Platform.Mediation/Mediator.cs index 3af73e7..f10d2c7 100644 --- a/src/Bss.Platform.Mediation/Mediator.cs +++ b/src/Bss.Platform.Mediation/Mediator.cs @@ -39,6 +39,17 @@ public Task Send(TRequest request, CancellationToken cancellationToken return next(request, cancellationToken); } + public async Task Publish(TNotification notification, CancellationToken cancellationToken = default) + where TNotification : INotification + { + var handlers = this.ServiceProvider.GetServices>(); + + foreach (var handler in handlers) + { + await handler.Handle(notification, cancellationToken); + } + } + private TInterface[] GetBehaviors() => this.ServiceProvider .GetServices() diff --git a/src/Tests.Unit/Platform/Mediation/NotificationTests.cs b/src/Tests.Unit/Platform/Mediation/NotificationTests.cs new file mode 100644 index 0000000..f054e79 --- /dev/null +++ b/src/Tests.Unit/Platform/Mediation/NotificationTests.cs @@ -0,0 +1,63 @@ +using Bss.Platform.Mediation; +using Bss.Platform.Mediation.Abstractions; + +using FluentAssertions; + +using Microsoft.Extensions.DependencyInjection; + +using Xunit; + +namespace Tests.Unit.Platform.Mediation; + +public class NotificationTests +{ + private readonly ServiceCollection services = []; + + private readonly List executionLog = []; + + public NotificationTests() + { + this.services.AddSingleton(this.executionLog); + this.services.AddTransient(); + } + + public record AlertNotification : INotification; + + public class AlertHandler1(List log) : INotificationHandler + { + public Task Handle(AlertNotification notification, CancellationToken cancellationToken) + { + log.Add("Handler1"); + return Task.CompletedTask; + } + } + + public class AlertHandler2(List log) : INotificationHandler + { + public Task Handle(AlertNotification notification, CancellationToken cancellationToken) + { + log.Add("Handler2"); + return Task.CompletedTask; + } + } + + [Fact] + public async Task Publish_Notification_ExecutesAllHandlers() + { + // Arrange + this.services.AddTransient, AlertHandler1>(); + this.services.AddTransient, AlertHandler2>(); + + var provider = this.services.BuildServiceProvider(); + var mediator = provider.GetRequiredService(); + + // Act + await mediator.Publish(new AlertNotification()); + + // Assert + this.executionLog.Should().Contain("Handler1"); + this.executionLog.Should().Contain("Handler2"); + this.executionLog.Should().HaveCount(2); + } +} + diff --git a/src/Tests.Unit/Platform/Mediation/RequestTests.cs b/src/Tests.Unit/Platform/Mediation/RequestTests.cs new file mode 100644 index 0000000..5597df0 --- /dev/null +++ b/src/Tests.Unit/Platform/Mediation/RequestTests.cs @@ -0,0 +1,111 @@ +using Bss.Platform.Mediation; +using Bss.Platform.Mediation.Abstractions; + +using FluentAssertions; + +using Microsoft.Extensions.DependencyInjection; + +using Xunit; + +namespace Tests.Unit.Platform.Mediation; + +public class RequestTests +{ + private readonly ServiceCollection services = []; + + private readonly List executionLog = []; + + public RequestTests() + { + this.services.AddSingleton(this.executionLog); + this.services.AddTransient(); + } + + public record PingRequest(string Name) : IRequest; + + public class PingHandler(List log) : IRequestHandler + { + public Task Handle(PingRequest request, CancellationToken cancellationToken) + { + log.Add("Handler"); + return Task.FromResult($"Hello {request.Name}"); + } + } + + public class LoggingBehavior(List log) : IPipelineBehavior + { + public async Task Handle(TRequest request, CancellationToken ct, Func> next) + { + log.Add("Behavior Pre"); + var result = await next(request, ct); + log.Add("Behavior Post"); + return result; + } + } + + public record VoidRequest : IRequest; + + public class VoidHandler(List log) : IRequestHandler + { + public Task Handle(VoidRequest request, CancellationToken cancellationToken) + { + log.Add("VoidHandler"); + return Task.CompletedTask; + } + } + + public class LoggingVoidBehavior(List log) : IPipelineBehavior + { + public async Task Handle(TRequest request, CancellationToken ct, Func next) + { + log.Add("VoidBehavior Pre"); + await next(request, ct); + log.Add("VoidBehavior Post"); + } + } + + [Fact] + public async Task Send_RequestWithResult_ExecutesBehaviorsAndHandler() + { + // Arrange + this.services.AddTransient, LoggingBehavior>(); + this.services.AddTransient, PingHandler>(); + + var provider = this.services.BuildServiceProvider(); + var mediator = provider.GetRequiredService(); + + // Act + var result = await mediator.Send(new PingRequest("World")); + + // Assert + result.Should().Be("Hello World"); + this.executionLog.Should() + .ContainInOrder( + "Behavior Pre", + "Handler", + "Behavior Post" + ); + } + + [Fact] + public async Task Send_VoidRequest_ExecutesBehaviorsAndHandler() + { + // Arrange + this.services.AddTransient, LoggingVoidBehavior>(); + this.services.AddTransient, VoidHandler>(); + + var provider = this.services.BuildServiceProvider(); + var mediator = provider.GetRequiredService(); + + // Act + await mediator.Send(new VoidRequest()); + + // Assert + this.executionLog.Should() + .ContainInOrder( + "VoidBehavior Pre", + "VoidHandler", + "VoidBehavior Post" + ); + } +} diff --git a/src/Tests.Unit/Tests.Unit.csproj b/src/Tests.Unit/Tests.Unit.csproj index 77cfd7e..f82a352 100644 --- a/src/Tests.Unit/Tests.Unit.csproj +++ b/src/Tests.Unit/Tests.Unit.csproj @@ -5,12 +5,16 @@ + + + +