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: 18 additions & 0 deletions FlowMediator.Console/Entities/TestEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using FlowMediator.Console.Events;
using FlowMediator.Contracts;

namespace FlowMediator.Console.Entities
{
public class TestEntity : BaseEntity
{
public string Name { get; private set; }

Check warning on line 8 in FlowMediator.Console/Entities/TestEntity.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 8 in FlowMediator.Console/Entities/TestEntity.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

public static TestEntity Create(string name)
{
var entity = new TestEntity { Name = name };
entity.AddDomainEvent(new TestEvent($"Entity {name} created"));
return entity;
}
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace FlowMediator.Console
namespace FlowMediator.Console.Entities
{
public class User
{
Expand Down
15 changes: 15 additions & 0 deletions FlowMediator.Console/EventHandlers/TestEventHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using FlowMediator.Console.Events;
using FlowMediator.Contracts;

namespace FlowMediator.Console.EventHandlers
{
public class TestEventHandler : IRequestHandler<TestEvent, Unit>
{
public Task<Unit> Handle(TestEvent request, CancellationToken cancellationToken)
{
System.Console.WriteLine($"[Handler] Event triggered with message: {request.Message}");
return Task.FromResult(Unit.Value);
}
}

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

namespace FlowMediator.Console.Events
{
public class TestEvent : IDomainEvent
{
public string Message { get; }
public DateTime OccurredOn { get; } = DateTime.UtcNow;

public TestEvent(string message) => Message = message;
}

}
36 changes: 21 additions & 15 deletions FlowMediator.Console/Program.cs
Original file line number Diff line number Diff line change
@@ -1,58 +1,64 @@
using FlowMediator.Console;
using FlowMediator.Console.Behaviors;
using FlowMediator.Console.Entities;
using FlowMediator.Console.Queries;
using FlowMediator.Console.Repositories;
using FlowMediator.Contracts;
using FlowMediator.Extensions;
using FluentValidation;
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>();


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

services.AddFlowMediator(Assembly.GetExecutingAssembly());

services.AddSingleton<IUserRepository, UserRepository>();

services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());


services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));

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



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

System.Console.WriteLine($"User Id: {user.Id}, Name: {user.Name}");

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

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


foreach (var domainEvent in testEntity.DomainEvents)
{
await mediator.SendAsync(domainEvent);
}
testEntity.ClearDomainEvents();
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using FlowMediator.Contracts;
using FlowMediator.Console.Entities;
using FlowMediator.Console.Repositories;
using FlowMediator.Contracts;

namespace FlowMediator.Console
namespace FlowMediator.Console.Queries
{
public class GetUserByIdHandler : IRequestHandler<GetUserByIdQuery, User>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using FlowMediator.Contracts;
using FlowMediator.Console.Entities;
using FlowMediator.Contracts;

namespace FlowMediator.Console
namespace FlowMediator.Console.Queries
{
public class GetUserByIdQuery : IRequest<User>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace FlowMediator.Console
using FlowMediator.Console.Entities;

namespace FlowMediator.Console.Repositories
{
public interface IUserRepository
{
Expand Down
3 changes: 2 additions & 1 deletion FlowMediator.Console/Validators/GetUserByIdQueryValidator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FluentValidation;
using FlowMediator.Console.Queries;
using FluentValidation;

namespace FlowMediator.Console.Validators
{
Expand Down
14 changes: 14 additions & 0 deletions FlowMediator/Contracts/BaseEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace FlowMediator.Contracts
{
/// <summary>
/// Base class for domain entities that can raise domain events.
/// </summary>
public abstract class BaseEntity
{
private readonly List<IDomainEvent> _domainEvents = new();
public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();

protected void AddDomainEvent(IDomainEvent domainEvent) => _domainEvents.Add(domainEvent);
public void ClearDomainEvents() => _domainEvents.Clear();
}
}
12 changes: 12 additions & 0 deletions FlowMediator/Contracts/IDomainEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace FlowMediator.Contracts
{
/// <summary>
/// Marker interface for domain events.
/// Domain events are treated as IRequest<Unit> so they flow through mediator like commands.
/// </summary>
public interface IDomainEvent : IRequest<Unit>
{
DateTime OccurredOn { get; }
}
}

11 changes: 11 additions & 0 deletions FlowMediator/Contracts/Unit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace FlowMediator.Contracts
{
/// <summary>
/// Represents a void result (like MediatR.Unit).
/// </summary>
public struct Unit
{
public static readonly Unit Value = new Unit();
}
}

31 changes: 31 additions & 0 deletions tests/FlowMediator.Tests/DomainEventTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using FlowMediator.Contracts;
using FlowMediator.Extensions;
using FlowMediator.Tests.DomainEvents;
using Microsoft.Extensions.DependencyInjection;

namespace FlowMediator.Tests
{
public class DomainEventTests
{
[Fact]
public async Task Should_Handle_DomainEvent_When_EntityCreated()
{
// Arrange
var services = new ServiceCollection();
services.AddFlowMediator(typeof(DomainEventTests).Assembly);
var provider = services.BuildServiceProvider();
var mediator = provider.GetRequiredService<IMediator>();

var entity = TestEntity.Create("UnitTest");

// Act
foreach (var domainEvent in entity.DomainEvents)
{
await mediator.SendAsync(domainEvent);
}

Assert.Single(entity.DomainEvents);
}
}

}
17 changes: 17 additions & 0 deletions tests/FlowMediator.Tests/DomainEvents/TestEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using FlowMediator.Contracts;

namespace FlowMediator.Tests.DomainEvents
{
public class TestEntity : BaseEntity
{
public string Name { get; private set; }

Check warning on line 7 in tests/FlowMediator.Tests/DomainEvents/TestEntity.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

Check warning on line 7 in tests/FlowMediator.Tests/DomainEvents/TestEntity.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Name' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

public static TestEntity Create(string name)
{
var entity = new TestEntity { Name = name };
entity.AddDomainEvent(new TestEvent($"Entity {name} created"));
return entity;
}
}

}
13 changes: 13 additions & 0 deletions tests/FlowMediator.Tests/DomainEvents/TestEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using FlowMediator.Contracts;

namespace FlowMediator.Tests.DomainEvents
{
public class TestEvent : IDomainEvent
{
public string Message { get; }
public DateTime OccurredOn { get; } = DateTime.UtcNow;

public TestEvent(string message) => Message = message;
}

}
14 changes: 14 additions & 0 deletions tests/FlowMediator.Tests/DomainEvents/TestEventHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using FlowMediator.Contracts;

namespace FlowMediator.Tests.DomainEvents
{
public class TestEventHandler : IRequestHandler<TestEvent, Unit>
{
public Task<Unit> Handle(TestEvent request, CancellationToken cancellationToken)
{
System.Console.WriteLine($"[Handler] Event triggered with message: {request.Message}");
return Task.FromResult(Unit.Value);
}
}

}