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: 0 additions & 4 deletions src/Cortex.Mediator/Abstractions/ICommand.cs

This file was deleted.

9 changes: 0 additions & 9 deletions src/Cortex.Mediator/Abstractions/IHandler.cs

This file was deleted.

9 changes: 0 additions & 9 deletions src/Cortex.Mediator/Abstractions/IMediator.cs

This file was deleted.

42 changes: 42 additions & 0 deletions src/Cortex.Mediator/Behaviors/LoggingCommandBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Cortex.Mediator.Commands;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Cortex.Mediator.Behaviors
{
/// <summary>
/// Pipeline behavior for logging command/query execution.
/// </summary>
public class LoggingCommandBehavior<TCommand> : ICommandPipelineBehavior<TCommand>
where TCommand : ICommand
{
private readonly ILogger<LoggingCommandBehavior<TCommand>> _logger;

public LoggingCommandBehavior(ILogger<LoggingCommandBehavior<TCommand>> logger)
{
_logger = logger;
}

public async Task Handle(
TCommand command,
CommandHandlerDelegate next,
CancellationToken cancellationToken)
{
var commandName = typeof(TCommand).Name;
_logger.LogInformation("Executing command {CommandName}", commandName);

try
{
await next();
_logger.LogInformation("Command {CommandName} executed successfully", commandName);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error executing command {CommandName}", commandName);
throw;
}
}
}
}
39 changes: 39 additions & 0 deletions src/Cortex.Mediator/Behaviors/TransactionCommandBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Cortex.Mediator.Commands;
using Cortex.Mediator.Infrastructure;
using System.Threading;
using System.Threading.Tasks;

namespace Cortex.Mediator.Behaviors
{
/// <summary>
/// Pipeline behavior for wrapping command execution in a transaction.
/// </summary>
public class TransactionCommandBehavior<TCommand> : ICommandPipelineBehavior<TCommand>
where TCommand : ICommand
{
private readonly IUnitOfWork _unitOfWork;

public TransactionCommandBehavior(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}

public async Task Handle(
TCommand command,
CommandHandlerDelegate next,
CancellationToken cancellationToken)
{
await using var transaction = await _unitOfWork.BeginTransactionAsync();
try
{
await next();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
}
}
50 changes: 50 additions & 0 deletions src/Cortex.Mediator/Behaviors/ValidationCommandBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Cortex.Mediator.Commands;
using FluentValidation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Cortex.Mediator.Behaviors
{
/// <summary>
/// Pipeline behavior for validating commands and queries before execution.
/// </summary>
public class ValidationCommandBehavior<TCommand> : ICommandPipelineBehavior<TCommand>
where TCommand : ICommand
{
private readonly IEnumerable<IValidator<TCommand>> _validators;

public ValidationCommandBehavior(IEnumerable<IValidator<TCommand>> validators)
{
_validators = validators;
}

public async Task Handle(
TCommand command,
CommandHandlerDelegate next,
CancellationToken cancellationToken)
{
var context = new ValidationContext<TCommand>(command);
var failures = _validators
.Select(v => v.Validate(context))
.SelectMany(r => r.Errors)
.Where(f => f != null)
.ToList();

if (failures.Count() > 0)
{
var errors = failures
.GroupBy(f => f.PropertyName)
.ToDictionary(
g => g.Key,
g => g.Select(f => f.ErrorMessage).ToArray());

throw new Exceptions.ValidationException(errors);
}

await next();
}
}
}
10 changes: 10 additions & 0 deletions src/Cortex.Mediator/Commands/ICommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Cortex.Mediator.Commands
{
/// <summary>
/// Represents a command in the CQRS pattern.
/// Commands are used to change the system state and do not return a value.
/// </summary>
public interface ICommand
{
}
}
20 changes: 20 additions & 0 deletions src/Cortex.Mediator/Commands/ICommandHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Threading;
using System.Threading.Tasks;

namespace Cortex.Mediator.Commands
{
/// <summary>
/// Defines a handler for a command.
/// </summary>
/// <typeparam name="TCommand">The type of command being handled.</typeparam>
public interface ICommandHandler<in TCommand>
where TCommand : ICommand
{
/// <summary>
/// Handles the specified command.
/// </summary>
/// <param name="command">The command to handle.</param>
/// <param name="cancellationToken">The cancellation token.</param>
Task Handle(TCommand command, CancellationToken cancellationToken);
}
}
26 changes: 26 additions & 0 deletions src/Cortex.Mediator/Commands/ICommandPipelineBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Threading;
using System.Threading.Tasks;

namespace Cortex.Mediator.Commands
{
/// <summary>
/// Defines a pipeline behavior for wrapping command handlers.
/// </summary>
/// <typeparam name="TCommand">The type of command being handled.</typeparam>
public interface ICommandPipelineBehavior<in TCommand>
where TCommand : ICommand
{
/// <summary>
/// Handles the command and invokes the next behavior in the pipeline.
/// </summary>
Task Handle(
TCommand command,
CommandHandlerDelegate next,
CancellationToken cancellationToken);
}

/// <summary>
/// Represents a delegate that wraps the command handler execution.
/// </summary>
public delegate Task CommandHandlerDelegate();
}
10 changes: 8 additions & 2 deletions src/Cortex.Mediator/Cortex.Mediator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<FileVersion>1.0.0</FileVersion>
<Product>Buildersoft Cortex Framework</Product>
<Company>Buildersoft</Company>
<LangVersion>12</LangVersion>
<Authors>Buildersoft,EnesHoxha</Authors>
<Copyright>Copyright © Buildersoft 2025</Copyright>

Expand All @@ -16,7 +17,7 @@


<RepositoryUrl>https://github.com/buildersoftio/cortex</RepositoryUrl>
<PackageTags>cortex vortex mediator eda streaming distributed</PackageTags>
<PackageTags>cortex vortex mediator eda cqrs streaming</PackageTags>

<Version>1.0.0</Version>
<PackageLicenseFile>license.md</PackageLicenseFile>
Expand All @@ -26,7 +27,7 @@
<IsPublishable>True</IsPublishable>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<RepositoryType></RepositoryType>
<PackageReleaseNotes>Added Mediator logic for Merlin.AI</PackageReleaseNotes>
<PackageReleaseNotes>Just as the Cortex in our brains handles complex processing efficiently, Cortex Data Framework brings brainpower to your data management! </PackageReleaseNotes>
<PackageProjectUrl>https://buildersoft.io/</PackageProjectUrl>

</PropertyGroup>
Expand All @@ -46,6 +47,11 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.11.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.11.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
<PackageReference Include="Scrutor" Version="6.0.1" />
</ItemGroup>
</Project>
57 changes: 57 additions & 0 deletions src/Cortex.Mediator/DependencyInjection/MediatorOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Cortex.Mediator.Commands;
using Cortex.Mediator.Queries;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Cortex.Mediator.DependencyInjection
{
public class MediatorOptions
{
internal List<Type> CommandBehaviors { get; } = new();
internal List<Type> QueryBehaviors { get; } = new();

public MediatorOptions AddCommandPipelineBehavior<TBehavior>()
where TBehavior : ICommandPipelineBehavior<ICommand> // Add constraint
{
var behaviorType = typeof(TBehavior);
if (behaviorType.IsGenericTypeDefinition)
{
throw new ArgumentException("Open generic types must be registered using AddOpenCommandPipelineBehavior");
}
CommandBehaviors.Add(behaviorType);
return this;
}

public MediatorOptions AddOpenCommandPipelineBehavior(Type openGenericBehaviorType)
{
if (!openGenericBehaviorType.IsGenericTypeDefinition)
{
throw new ArgumentException("Type must be an open generic type definition");
}

CommandBehaviors.Add(openGenericBehaviorType);
return this;
}

public MediatorOptions AddOpenQueryPipelineBehavior(Type openGenericBehaviorType)
{
if (!openGenericBehaviorType.IsGenericTypeDefinition)
{
throw new ArgumentException("Type must be an open generic type definition");
}

var queryBehaviorInterface = openGenericBehaviorType.GetInterfaces()
.FirstOrDefault(i => i.IsGenericType &&
i.GetGenericTypeDefinition() == typeof(IQueryPipelineBehavior<,>));

if (queryBehaviorInterface == null)
{
throw new ArgumentException("Type must implement IQueryPipelineBehavior<,>");
}

QueryBehaviors.Add(openGenericBehaviorType);
return this;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Cortex.Mediator.Behaviors;
using Cortex.Mediator.Commands;

namespace Cortex.Mediator.DependencyInjection
{
public static class MediatorOptionsExtensions
{
public static MediatorOptions AddDefaultBehaviors(this MediatorOptions options)
{
return options
.AddCommandPipelineBehavior<ValidationCommandBehavior<ICommand>>()
.AddCommandPipelineBehavior<LoggingCommandBehavior<ICommand>>()
.AddCommandPipelineBehavior<TransactionCommandBehavior<ICommand>>();
}
}
}
Loading
Loading