diff --git a/samples/EasyWay.Samples/Commands/ErrorCommand.cs b/samples/EasyWay.Samples/Commands/ErrorCommand.cs index 60a9b93..66f6d13 100644 --- a/samples/EasyWay.Samples/Commands/ErrorCommand.cs +++ b/samples/EasyWay.Samples/Commands/ErrorCommand.cs @@ -1,6 +1,6 @@ namespace EasyWay.Samples.Commands { - public class ErrorCommand : Command + public class ErrorCommand : Command { } diff --git a/samples/EasyWay.Samples/Commands/ErrorCommandHandler.cs b/samples/EasyWay.Samples/Commands/ErrorCommandHandler.cs index 34cf102..d216c52 100644 --- a/samples/EasyWay.Samples/Commands/ErrorCommandHandler.cs +++ b/samples/EasyWay.Samples/Commands/ErrorCommandHandler.cs @@ -2,7 +2,7 @@ namespace EasyWay.Samples.Commands { - public class ErrorCommandHandler : ICommandHandler + public class ErrorCommandHandler : ICommandHandler { public Task Handle(ErrorCommand command) { diff --git a/samples/EasyWay.Samples/Commands/SampleCommand.cs b/samples/EasyWay.Samples/Commands/SampleCommand.cs index 8ab3a90..401de8f 100644 --- a/samples/EasyWay.Samples/Commands/SampleCommand.cs +++ b/samples/EasyWay.Samples/Commands/SampleCommand.cs @@ -1,6 +1,6 @@ namespace EasyWay.Samples.Commands { - public class SampleCommand : Command, IWithConcurrencyToken + public class SampleCommand : Command, IWithConcurrencyToken { public Guid Id { get; init; } diff --git a/samples/EasyWay.Samples/Commands/SampleCommandHandler.cs b/samples/EasyWay.Samples/Commands/SampleCommandHandler.cs index 853f412..c0037fa 100644 --- a/samples/EasyWay.Samples/Commands/SampleCommandHandler.cs +++ b/samples/EasyWay.Samples/Commands/SampleCommandHandler.cs @@ -3,7 +3,7 @@ namespace EasyWay.Samples.Commands { - internal sealed class SampleCommandHandler : ICommandHandler + internal sealed class SampleCommandHandler : ICommandHandler { private readonly ICancellationContext _cancellationContext; diff --git a/samples/EasyWay.Samples/Commands/WithResult/SampleCommandWithResult.cs b/samples/EasyWay.Samples/Commands/WithResult/SampleCommandWithResult.cs index 40e741f..b58e018 100644 --- a/samples/EasyWay.Samples/Commands/WithResult/SampleCommandWithResult.cs +++ b/samples/EasyWay.Samples/Commands/WithResult/SampleCommandWithResult.cs @@ -1,7 +1,9 @@ namespace EasyWay.Samples.Commands.WithResult { - public class SampleCommandWithResult : Command + public class SampleCommandWithResult : Command, IWithConcurrencyToken { public string Name { get; init; } + + public short ConcurrencyToken { get; init; } } } diff --git a/samples/EasyWay.Samples/Commands/WithResult/SampleCommandWithResultHandler.cs b/samples/EasyWay.Samples/Commands/WithResult/SampleCommandWithResultHandler.cs index 9e55531..daf653e 100644 --- a/samples/EasyWay.Samples/Commands/WithResult/SampleCommandWithResultHandler.cs +++ b/samples/EasyWay.Samples/Commands/WithResult/SampleCommandWithResultHandler.cs @@ -1,7 +1,7 @@  namespace EasyWay.Samples.Commands.WithResult { - public sealed class SampleCommandWithResultHandler : ICommandHandler + public sealed class SampleCommandWithResultHandler : ICommandHandler { public Task> Handle(SampleCommandWithResult command) { diff --git a/samples/EasyWay.Samples/Program.cs b/samples/EasyWay.Samples/Program.cs index c5b8816..568cd7c 100644 --- a/samples/EasyWay.Samples/Program.cs +++ b/samples/EasyWay.Samples/Program.cs @@ -1,4 +1,6 @@ using EasyWay.Samples; +using EasyWay.Samples.Commands; +using EasyWay.Samples.Commands.WithResult; using EasyWay.Samples.Queries; using Microsoft.AspNetCore.Mvc; @@ -14,6 +16,7 @@ await kernel builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +builder.Services.AddEasyWayWebApi(); var app = builder.Build(); @@ -25,12 +28,27 @@ await kernel app.UseHttpsRedirection(); +//app.UseEasyWay(); -app.MapPost("/query", async ([FromBody] SampleQuery query, IModuleExecutor executor) => + +app.MapPost("/query", async ([FromBody] SampleQuery query, IModuleExecutor executor, IWebApiResultMapper mapper) => +{ + var x = await executor.Query(query); + + return mapper.Map(x); +}); + +app.MapPost("/command", async ([FromBody] SampleCommand command, IModuleExecutor executor, IWebApiResultMapper mapper) => { - return await executor.Execute(query); + return await executor.Command(command); }); +app.MapPost("/commandwithresult", async ([FromBody] SampleCommandWithResult command, IModuleExecutor executor, IWebApiResultMapper mapper) => +{ + var x = await executor.Command(command); + + return mapper.Map(x); +}); app.Run(); diff --git a/samples/EasyWay.Samples/Queries/SampleQuery.cs b/samples/EasyWay.Samples/Queries/SampleQuery.cs index c0a676e..bac5e71 100644 --- a/samples/EasyWay.Samples/Queries/SampleQuery.cs +++ b/samples/EasyWay.Samples/Queries/SampleQuery.cs @@ -1,6 +1,6 @@ namespace EasyWay.Samples.Queries { - public class SampleQuery : Query + public class SampleQuery : Query { public required string Name { get; init; } diff --git a/samples/EasyWay.Samples/Queries/SampleQueryHandler.cs b/samples/EasyWay.Samples/Queries/SampleQueryHandler.cs index 9f2822f..51a0fe1 100644 --- a/samples/EasyWay.Samples/Queries/SampleQueryHandler.cs +++ b/samples/EasyWay.Samples/Queries/SampleQueryHandler.cs @@ -1,6 +1,6 @@ namespace EasyWay.Samples.Queries { - public class SampleQueryHandler : IQueryHandler + public class SampleQueryHandler : IQueryHandler { public Task> Handle(SampleQuery query) { diff --git a/samples/EasyWay.Samples/SampleModule.cs b/samples/EasyWay.Samples/SampleModule.cs index 66f0f5e..5e4a575 100644 --- a/samples/EasyWay.Samples/SampleModule.cs +++ b/samples/EasyWay.Samples/SampleModule.cs @@ -1,4 +1,5 @@ -using EasyWay.Samples.Databases; +using EasyWay.Samples.Commands; +using EasyWay.Samples.Databases; using Microsoft.EntityFrameworkCore; using System.Reflection; @@ -8,7 +9,8 @@ public sealed class SampleModule : EasyWayModule { protected override IEnumerable Assemblies => new List { - typeof(SampleModule).Assembly + typeof(SampleModule).Assembly, + typeof(SampleCommand).Assembly }; protected override void ConfigureDependencies(IServiceCollection services, IConfiguration configuration) diff --git a/source/Directory.Packages.props b/source/Directory.Packages.props index 3b0bd91..a795e54 100644 --- a/source/Directory.Packages.props +++ b/source/Directory.Packages.props @@ -7,15 +7,15 @@ - - - - - - + + + + + + - + - + \ No newline at end of file diff --git a/source/EasyWay.WebApi/Internals/WebApiResultMapper.cs b/source/EasyWay.WebApi/Internals/WebApiResultMapper.cs index de35d49..7948921 100644 --- a/source/EasyWay.WebApi/Internals/WebApiResultMapper.cs +++ b/source/EasyWay.WebApi/Internals/WebApiResultMapper.cs @@ -20,7 +20,7 @@ public IResult Map(CommandResult commandResu { return commandResult.Error switch { - CommandErrorEnum.None => Results.Ok(), + CommandErrorEnum.None => Results.Ok(commandResult.OperationResult), CommandErrorEnum.Validation => Results.BadRequest(commandResult.ValidationErrors), _ => Results.StatusCode(500), }; diff --git a/source/EasyWay/Command.cs b/source/EasyWay/Command.cs index 08eb75f..7ce2cb5 100644 --- a/source/EasyWay/Command.cs +++ b/source/EasyWay/Command.cs @@ -3,10 +3,8 @@ /// /// Represents a command /// - public abstract class Command - where TModule : EasyWayModule; + public abstract class Command; - public abstract class Command - where TModule : EasyWayModule + public abstract class Command where TCommandResult : OperationResult; } diff --git a/source/EasyWay/ICommandHandler.cs b/source/EasyWay/ICommandHandler.cs index 4e87b85..28a345f 100644 --- a/source/EasyWay/ICommandHandler.cs +++ b/source/EasyWay/ICommandHandler.cs @@ -4,9 +4,8 @@ /// Defines a handler for a command /// /// The type of command being handled - public interface ICommandHandler - where TModule : EasyWayModule - where TCommand : Command + public interface ICommandHandler + where TCommand : Command { /// /// Handles a command @@ -15,9 +14,8 @@ public interface ICommandHandler Task Handle(TCommand command); } - public interface ICommandHandler - where TModule : EasyWayModule - where TCommand : Command + public interface ICommandHandler + where TCommand : Command where TOperationResult : OperationResult { Task> Handle(TCommand command); diff --git a/source/EasyWay/IConcurrencyConflictValidator.cs b/source/EasyWay/IConcurrencyConflictValidator.cs index ff24438..4f12e41 100644 --- a/source/EasyWay/IConcurrencyConflictValidator.cs +++ b/source/EasyWay/IConcurrencyConflictValidator.cs @@ -2,7 +2,7 @@ { public interface IConcurrencyConflictValidator { - void Validate(TAggregateRoot aggregateRoot, IWithConcurrencyToken command) - where TAggregateRoot : AggregateRoot; + void Validate(AggregateRoot aggregateRoot, TCommand command) + where TCommand : Command, IWithConcurrencyToken; } } diff --git a/source/EasyWay/IModuleExecutor.cs b/source/EasyWay/IModuleExecutor.cs index cdeb201..609093a 100644 --- a/source/EasyWay/IModuleExecutor.cs +++ b/source/EasyWay/IModuleExecutor.cs @@ -3,13 +3,15 @@ public interface IModuleExecutor where TModule : EasyWayModule { - Task Execute(TCommand command, CancellationToken cancellationToken = default) - where TCommand : Command; + Task Command(TCommand command, CancellationToken cancellationToken = default) + where TCommand : Command; - Task> Execute(Command command, CancellationToken cancellationToken = default) + Task> Command(TCommand command, CancellationToken cancellationToken = default) + where TCommand : Command where TOperationResult : OperationResult; - Task> Execute(Query query, CancellationToken cancellationToken = default) + Task> Query(TQuery query, CancellationToken cancellationToken = default) + where TQuery : Query where TReadModel : ReadModel; } } diff --git a/source/EasyWay/IQueryHandler.cs b/source/EasyWay/IQueryHandler.cs index 01295f1..0098732 100644 --- a/source/EasyWay/IQueryHandler.cs +++ b/source/EasyWay/IQueryHandler.cs @@ -6,9 +6,8 @@ namespace EasyWay /// Defines a handler for a query /// /// The type of query being handled - public interface IQueryHandler - where TModule : EasyWayModule - where TQuery : Query + public interface IQueryHandler + where TQuery : Query where TReadModel : ReadModel { /// diff --git a/source/EasyWay/Internals/Commands/CommandExecutor.cs b/source/EasyWay/Internals/Commands/CommandExecutor.cs index 77f4520..f73be7f 100644 --- a/source/EasyWay/Internals/Commands/CommandExecutor.cs +++ b/source/EasyWay/Internals/Commands/CommandExecutor.cs @@ -24,7 +24,7 @@ public CommandExecutor( } public async Task Execute(TCommand command, CancellationToken cancellationToken) - where TCommand : Command + where TCommand : Command { _cancellationContextConstructor.Set(cancellationToken); @@ -41,7 +41,7 @@ public async Task Execute(TCommand command, Cancellatio } var commandResult = await _serviceProvider - .GetRequiredService>() + .GetRequiredService>() .Handle(command); await _unitOfWorkCommandHandler.Handle(); diff --git a/source/EasyWay/Internals/Commands/CommandWithOperationResultExecutor.cs b/source/EasyWay/Internals/Commands/CommandWithOperationResultExecutor.cs index 703e971..1213a45 100644 --- a/source/EasyWay/Internals/Commands/CommandWithOperationResultExecutor.cs +++ b/source/EasyWay/Internals/Commands/CommandWithOperationResultExecutor.cs @@ -23,22 +23,17 @@ public CommandWithOperationResultExecutor( _unitOfWorkCommandHandler = unitOfWorkCommandHandler; } - public async Task> Execute(Command command, CancellationToken cancellationToken) + public async Task> Command(TCommand command, CancellationToken cancellationToken = default) + where TCommand : Command where TOperationResult : OperationResult { _cancellationContextConstructor.Set(cancellationToken); - var commandType = command.GetType(); - - var validatorType = typeof(IEasyWayValidator<>).MakeGenericType(commandType); - - var validator = _serviceProvider.GetService(validatorType); + var validator = _serviceProvider.GetService>(); if (validator is not null) { - var errors = (IDictionary)validatorType - .GetMethod("Validate") - ?.Invoke(validator, [command]); + var errors = validator.Validate(command); if (errors.Any()) { @@ -46,11 +41,9 @@ public async Task> Execute(Com } } - var commandHandlerType = typeof(ICommandHandler<,,>).MakeGenericType(typeof(TModule), commandType, typeof(TOperationResult)); - - var commandHandler = _serviceProvider.GetRequiredService(commandHandlerType); + var commandHandler = _serviceProvider.GetRequiredService>(); - var commandResult = await (Task>)commandHandlerType.GetMethod("Handle").Invoke(commandHandler, [command]); + var commandResult = await commandHandler.Handle(command); await _unitOfWorkCommandHandler.Handle(); diff --git a/source/EasyWay/Internals/Commands/ConcurrencyConflictValidator.cs b/source/EasyWay/Internals/Commands/ConcurrencyConflictValidator.cs index 672fe6f..1c3eb6c 100644 --- a/source/EasyWay/Internals/Commands/ConcurrencyConflictValidator.cs +++ b/source/EasyWay/Internals/Commands/ConcurrencyConflictValidator.cs @@ -2,10 +2,8 @@ { internal sealed class ConcurrencyConflictValidator : IConcurrencyConflictValidator { - public void Validate( - TAggregateRoot aggregateRoot, - IWithConcurrencyToken command) - where TAggregateRoot : AggregateRoot + public void Validate(AggregateRoot aggregateRoot, TCommand command) + where TCommand : Command, IWithConcurrencyToken { if (aggregateRoot.ConcurrencyToken == command.ConcurrencyToken) { diff --git a/source/EasyWay/Internals/Commands/Extensions.cs b/source/EasyWay/Internals/Commands/Extensions.cs index 3544d8b..eda3c67 100644 --- a/source/EasyWay/Internals/Commands/Extensions.cs +++ b/source/EasyWay/Internals/Commands/Extensions.cs @@ -14,12 +14,12 @@ internal static IServiceCollection AddCommands( services.AddScoped(typeof(ICommandWithOperationResultExecutor<>).MakeGenericType(moduleType), typeof(CommandWithOperationResultExecutor<>).MakeGenericType(moduleType)); services.Scan(s => s.FromAssemblies(assemblies) - .AddClasses(c => c.AssignableTo(typeof(ICommandHandler<,>))) + .AddClasses(c => c.AssignableTo(typeof(ICommandHandler<>))) .AsImplementedInterfaces() .WithScopedLifetime()); services.Scan(s => s.FromAssemblies(assemblies) - .AddClasses(c => c.AssignableTo(typeof(ICommandHandler<,,>))) + .AddClasses(c => c.AssignableTo(typeof(ICommandHandler<,>))) .AsImplementedInterfaces() .WithScopedLifetime()); diff --git a/source/EasyWay/Internals/Commands/ICommandExecutor.cs b/source/EasyWay/Internals/Commands/ICommandExecutor.cs index 49502ae..c6f64b5 100644 --- a/source/EasyWay/Internals/Commands/ICommandExecutor.cs +++ b/source/EasyWay/Internals/Commands/ICommandExecutor.cs @@ -4,6 +4,6 @@ internal interface ICommandExecutor where TModule : EasyWayModule { Task Execute(TCommand command, CancellationToken cancellationToken) - where TCommand : Command; + where TCommand : Command; } } diff --git a/source/EasyWay/Internals/Commands/ICommandWithOperationResultExecutor.cs b/source/EasyWay/Internals/Commands/ICommandWithOperationResultExecutor.cs index de7193d..6388fb2 100644 --- a/source/EasyWay/Internals/Commands/ICommandWithOperationResultExecutor.cs +++ b/source/EasyWay/Internals/Commands/ICommandWithOperationResultExecutor.cs @@ -3,7 +3,8 @@ internal interface ICommandWithOperationResultExecutor where TModule : EasyWayModule { - Task> Execute(Command command, CancellationToken cancellationToken) + Task> Command(TCommand command, CancellationToken cancellationToken = default) + where TCommand : Command where TOperationResult : OperationResult; } } diff --git a/source/EasyWay/Internals/Modules/ModuleExecutor.cs b/source/EasyWay/Internals/Modules/ModuleExecutor.cs index d1d88cc..de4af31 100644 --- a/source/EasyWay/Internals/Modules/ModuleExecutor.cs +++ b/source/EasyWay/Internals/Modules/ModuleExecutor.cs @@ -14,8 +14,8 @@ public ModuleExecutor(IServiceProvider serviceProvider) _serviceProvider = serviceProvider; } - public async Task Execute(TCommand command, CancellationToken cancellationToken = default) - where TCommand : Command + public async Task Command(TCommand command, CancellationToken cancellationToken = default) + where TCommand : Command { CommandResult commandResult; @@ -31,7 +31,8 @@ public async Task Execute(TCommand command, Cancellatio return commandResult; } - public async Task> Execute(Command command, CancellationToken cancellationToken = default) + public async Task> Command(TCommand command, CancellationToken cancellationToken = default) + where TCommand : Command where TOperationResult : OperationResult { CommandResult commandResult; @@ -42,13 +43,14 @@ public async Task> Execute(Com commandResult = await sp .GetRequiredService>() - .Execute(command, cancellationToken); + .Command(command, cancellationToken); } return commandResult; } - public async Task> Execute(Query query, CancellationToken cancellationToken = default) + public async Task> Query(TQuery query, CancellationToken cancellationToken = default) + where TQuery : Query where TReadModel : ReadModel { QueryResult result; @@ -59,7 +61,7 @@ public async Task> Execute(Query>() - .Execute(query, cancellationToken); + .Execute(query, cancellationToken); } return result; diff --git a/source/EasyWay/Internals/Queries/Extensions.cs b/source/EasyWay/Internals/Queries/Extensions.cs index 64cd50a..0a1715a 100644 --- a/source/EasyWay/Internals/Queries/Extensions.cs +++ b/source/EasyWay/Internals/Queries/Extensions.cs @@ -13,7 +13,7 @@ internal static IServiceCollection AddQueries( services.AddScoped(typeof(IQueryExecutor<>).MakeGenericType(moduleType), typeof(QueryExecutor<>).MakeGenericType(moduleType)); services.Scan(s => s.FromAssemblies(assemblies) - .AddClasses(c => c.AssignableTo(typeof(IQueryHandler<,,>))) + .AddClasses(c => c.AssignableTo(typeof(IQueryHandler<,>))) .AsImplementedInterfaces() .WithScopedLifetime()); diff --git a/source/EasyWay/Internals/Queries/IQueryExecutor.cs b/source/EasyWay/Internals/Queries/IQueryExecutor.cs index f874c59..1cbf27c 100644 --- a/source/EasyWay/Internals/Queries/IQueryExecutor.cs +++ b/source/EasyWay/Internals/Queries/IQueryExecutor.cs @@ -3,7 +3,8 @@ internal interface IQueryExecutor where TModule : EasyWayModule { - Task> Execute(Query query, CancellationToken cancellationToken) + Task> Execute(TQuery query, CancellationToken cancellationToken = default) + where TQuery : Query where TReadModel : ReadModel; } } diff --git a/source/EasyWay/Internals/Queries/QueryExecutor.cs b/source/EasyWay/Internals/Queries/QueryExecutor.cs index c0cabd9..86f295e 100644 --- a/source/EasyWay/Internals/Queries/QueryExecutor.cs +++ b/source/EasyWay/Internals/Queries/QueryExecutor.cs @@ -19,7 +19,8 @@ public QueryExecutor( _cancellationContextConstructor = cancellationContextConstructor; } - public async Task> Execute(Query query, CancellationToken cancellationToken) + public async Task> Execute(TQuery query, CancellationToken cancellationToken = default) + where TQuery : Query where TReadModel : ReadModel { _cancellationContextConstructor.Set(cancellationToken); @@ -28,13 +29,11 @@ public async Task> Execute(Query).MakeGenericType(queryType); - var validator = _serviceProvider.GetService(validatorType); + var validator = _serviceProvider.GetService>(); if (validator is not null) { - var errors = (IDictionary)validatorType - .GetMethod("Validate") - ?.Invoke(validator, [query]); + var errors = validator.Validate(query); if (errors.Any()) { @@ -42,11 +41,9 @@ public async Task> Execute(Query).MakeGenericType(typeof(TModule), queryType, typeof(TReadModel)); + var queryHandler = _serviceProvider.GetRequiredService>(); - var queryHandler = _serviceProvider.GetRequiredService(queryHandlerType); - - var queryResult = await (Task>) queryHandlerType.GetMethod("Handle").Invoke(queryHandler, [query]); + var queryResult = await queryHandler.Handle(query); return queryResult; } diff --git a/source/EasyWay/Query.cs b/source/EasyWay/Query.cs index 54f1b6c..8508ce0 100644 --- a/source/EasyWay/Query.cs +++ b/source/EasyWay/Query.cs @@ -4,7 +4,6 @@ /// Represents a query /// /// Read model type - public abstract class Query - where TModule : EasyWayModule + public abstract class Query where TReadModel : ReadModel; } diff --git a/tests/EasyWay.Tests/Internals/Commands/ConcurrencyConflictValidatorTests.cs b/tests/EasyWay.Tests/Internals/Commands/ConcurrencyConflictValidatorTests.cs index 1887c54..9a48d45 100644 --- a/tests/EasyWay.Tests/Internals/Commands/ConcurrencyConflictValidatorTests.cs +++ b/tests/EasyWay.Tests/Internals/Commands/ConcurrencyConflictValidatorTests.cs @@ -10,7 +10,15 @@ public sealed class ConcurrencyConflictValidatorTests internal sealed class TestAggragate : AggregateRoot; - internal sealed record TestClass(short ConcurrencyToken) : IWithConcurrencyToken; + internal sealed class TestClass : Command, IWithConcurrencyToken + { + public short ConcurrencyToken { get; } + + public TestClass(short concurrencyToken) + { + ConcurrencyToken = concurrencyToken; + } + } [Fact(DisplayName = $"When concurrency tokens are different validator should throw {nameof(ConcurrencyException)}")] public void WhenConcurrencyTokensAreDifferent()