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
18 changes: 0 additions & 18 deletions FlowMediator.Console/Entities/TestEntity.cs

This file was deleted.

15 changes: 0 additions & 15 deletions FlowMediator.Console/EventHandlers/TestEventHandler.cs

This file was deleted.

19 changes: 19 additions & 0 deletions FlowMediator.Console/EventHandlers/UserCreatedEventHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using FlowMediator.Contracts;
using FlowMediator.Console.Events;

namespace FlowMediator.Console.EventHandlers
{
public class UserCreatedEventHandler
: IEventHandler<UserCreatedEvent>
{
public Task Handle(
UserCreatedEvent @event,
CancellationToken cancellationToken)
{
System.Console.WriteLine(
$"[EVENT] User created: {@event.UserName}");

return Task.CompletedTask;
}
}
}
13 changes: 0 additions & 13 deletions FlowMediator.Console/Events/TestEvent.cs

This file was deleted.

18 changes: 18 additions & 0 deletions FlowMediator.Console/Events/UserCreatedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using FlowMediator.Contracts;

namespace FlowMediator.Console.Events
{
public class UserCreatedEvent : IEvent
{
public Guid EventId { get; } = Guid.NewGuid();
public DateTime OccurredOn { get; } = DateTime.UtcNow;

public string UserName { get; }

public UserCreatedEvent(string userName)
{
UserName = userName
?? throw new ArgumentNullException(nameof(userName));
}
}
}
59 changes: 31 additions & 28 deletions FlowMediator.Console/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using FlowMediator.Console;
using FlowMediator.Console.Behaviors;
using FlowMediator.Console.Entities;
using FlowMediator.Console.Behaviors;
using FlowMediator.Console.Events;
using FlowMediator.Console.EventHandlers;
using FlowMediator.Console.Queries;
using FlowMediator.Console.Repositories;
using FlowMediator.Contracts;
Expand All @@ -9,56 +9,59 @@
using Microsoft.Extensions.DependencyInjection;
using System.Reflection;

// handlers + behavior pipelines are automatic
//var services = new ServiceCollection();
//services.AddFlowMediatorWithBehaviors(Assembly.GetExecutingAssembly());
//services.AddSingleton<IUserRepository, UserRepository>();
//services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
//var provider = services.BuildServiceProvider();
//var mediator = provider.GetRequiredService<IMediator>();
// ---------------------------------
// Service registration
// ---------------------------------

// handlers are automatic, behavior pipelines manual
var services = new ServiceCollection();

// Register FlowMediator (handlers auto, pipeline manual)
services.AddFlowMediator(Assembly.GetExecutingAssembly());

// App services
services.AddSingleton<IUserRepository, UserRepository>();

// FluentValidation validators
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());

// Pipeline behaviors (explicit order)
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));

var provider = services.BuildServiceProvider();
var mediator = provider.GetRequiredService<IMediator>();

// ------------------------
// Query test
// ------------------------
// ---------------------------------
// Query test (SendAsync)
// ---------------------------------

Console.WriteLine("---- QUERY TEST ----");

var user = await mediator.SendAsync(new GetUserByIdQuery(1));
System.Console.WriteLine($"User Id: {user.Id}, Name: {user.Name}");
Console.WriteLine($"User Id: {user.Id}, Name: {user.Name}");

try
{
var invalidUser = await mediator.SendAsync(new GetUserByIdQuery(0));
System.Console.WriteLine($"User Id: {invalidUser.Id}, Name: {invalidUser.Name}");
await mediator.SendAsync(new GetUserByIdQuery(0));
}
catch (ValidationException ex)
{
System.Console.WriteLine("Validation failed:");
Console.WriteLine("Validation failed:");
foreach (var error in ex.Errors)
{
System.Console.WriteLine($" - {error.PropertyName}: {error.ErrorMessage}");
Console.WriteLine($" - {error.PropertyName}: {error.ErrorMessage}");
}
}

// ------------------------
// Domain Event test
// ------------------------
var testEntity = TestEntity.Create("SampleEntity");
// ---------------------------------
// Event test (PublishAsync)
// ---------------------------------

Console.WriteLine();
Console.WriteLine("---- EVENT TEST ----");

foreach (var domainEvent in testEntity.DomainEvents)
{
await mediator.SendAsync(domainEvent);
}
testEntity.ClearDomainEvents();
await mediator.PublishAsync(
new UserCreatedEvent(user.Name));

Check warning on line 64 in FlowMediator.Console/Program.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'userName' in 'UserCreatedEvent.UserCreatedEvent(string userName)'.

Check warning on line 64 in FlowMediator.Console/Program.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'userName' in 'UserCreatedEvent.UserCreatedEvent(string userName)'.

Console.WriteLine();
Console.WriteLine("Demo finished successfully.");
6 changes: 2 additions & 4 deletions FlowMediator/Contracts/IDomainEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
{
/// <summary>
/// Marker interface for domain events.
/// Domain events are treated as IRequest<Unit> so they flow through mediator like commands.
/// Domain events represent something that already happened in the domain.
/// </summary>
public interface IDomainEvent : IRequest<Unit>
public interface IDomainEvent : IEvent
{
DateTime OccurredOn { get; }
}
}

8 changes: 8 additions & 0 deletions FlowMediator/Contracts/IEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace FlowMediator.Contracts
{
public interface IEvent
{
Guid EventId { get; }
DateTime OccurredOn { get; }
}
}
8 changes: 8 additions & 0 deletions FlowMediator/Contracts/IEventHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace FlowMediator.Contracts
{
public interface IEventHandler<in TEvent>
where TEvent : IEvent
{
Task Handle(TEvent @event, CancellationToken cancellationToken);
}
}
13 changes: 8 additions & 5 deletions FlowMediator/Contracts/IMediator.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
namespace FlowMediator.Contracts
{
/// <summary>
/// The mediator that receives a request and dispatches it to the appropriate handler.
/// </summary>

public interface IMediator
{
Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default);
Task<TResponse> SendAsync<TResponse>(
IRequest<TResponse> request,
CancellationToken cancellationToken = default);

Task PublishAsync<TEvent>(
TEvent @event,
CancellationToken cancellationToken = default)
where TEvent : IEvent;
}
}
11 changes: 0 additions & 11 deletions FlowMediator/Contracts/Unit.cs

This file was deleted.

40 changes: 34 additions & 6 deletions FlowMediator/Core/Mediator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FlowMediator.Contracts;
using Microsoft.Extensions.DependencyInjection;

namespace FlowMediator.Core
{
Expand All @@ -17,31 +18,58 @@ public async Task<TResponse> SendAsync<TResponse>(
IRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
if (request == null)
if (request is null)
throw new ArgumentNullException(nameof(request));

// Resolve handler type
var handlerType = typeof(IRequestHandler<,>)
.MakeGenericType(request.GetType(), typeof(TResponse));

var handler = _serviceProvider.GetService(handlerType);
if (handler == null)
throw new InvalidOperationException($"No handler found for {request.GetType().Name}");
if (handler is null)
throw new InvalidOperationException(
$"No handler found for request type {request.GetType().Name}");

var method = handlerType.GetMethod("Handle");
if (method == null)
throw new InvalidOperationException($"Handler {handler.GetType().Name} does not contain Handle method");
if (method is null)
throw new InvalidOperationException(
$"Handler {handler.GetType().Name} does not contain Handle method");

RequestHandlerDelegate<TResponse> handlerDelegate = async () =>
{
var result = method.Invoke(handler, new object[] { request, cancellationToken });

if (result is Task<TResponse> task)
return await task;

throw new InvalidOperationException(
$"Handler {handler.GetType().Name} did not return Task<{typeof(TResponse).Name}>");
};

return await _pipelineExecutor.Execute((dynamic)request, cancellationToken, handlerDelegate);
// Pipeline only applies to Send (Command / Query)
return await _pipelineExecutor.Execute(
(dynamic)request,
cancellationToken,
handlerDelegate);
}


public async Task PublishAsync<TEvent>(
TEvent @event,
CancellationToken cancellationToken = default)
where TEvent : IEvent
{
if (@event is null)
throw new ArgumentNullException(nameof(@event));

var handlers = _serviceProvider
.GetServices<IEventHandler<TEvent>>()
.ToList();

foreach (var handler in handlers)
{
await handler.Handle(@event, cancellationToken);
}
}
}
}
20 changes: 19 additions & 1 deletion FlowMediator/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public static class ServiceCollectionExtensions
public static IServiceCollection AddFlowMediator(this IServiceCollection services, Assembly assembly)
{
services.RegisterHandlers(assembly);
services.RegisterEventHandlers(assembly);
services.AddSingleton<IMediator, Mediator>();
return services;
}
Expand All @@ -27,6 +28,7 @@ public static IServiceCollection AddFlowMediator(this IServiceCollection service
public static IServiceCollection AddFlowMediatorWithBehaviors(this IServiceCollection services, Assembly assembly)
{
services.RegisterHandlers(assembly);
services.RegisterEventHandlers(assembly);
services.RegisterBehaviors(assembly);
services.AddSingleton<IMediator, Mediator>();
return services;
Expand Down Expand Up @@ -64,6 +66,22 @@ private static IServiceCollection RegisterBehaviors(this IServiceCollection serv

return services;
}
}

private static IServiceCollection RegisterEventHandlers(
this IServiceCollection services,
Assembly assembly)
{
var handlerTypes = assembly.GetTypes()
.Where(t => !t.IsAbstract && !t.IsInterface)
.SelectMany(t => t.GetInterfaces()
.Where(i => i.IsGenericType &&
i.GetGenericTypeDefinition() == typeof(IEventHandler<>))
.Select(i => new { Handler = t, Interface = i }));

foreach (var h in handlerTypes)
services.AddTransient(h.Interface, h.Handler);

return services;
}
}
}
Loading