diff --git a/src/CQRS/LeanCode.CQRS.AspNetCore/ObjectExecutorFactory.cs b/src/CQRS/LeanCode.CQRS.AspNetCore/ObjectExecutorFactory.cs index 8878e899..a26c29b5 100644 --- a/src/CQRS/LeanCode.CQRS.AspNetCore/ObjectExecutorFactory.cs +++ b/src/CQRS/LeanCode.CQRS.AspNetCore/ObjectExecutorFactory.cs @@ -33,14 +33,16 @@ public ObjectExecutor CreateExecutorFor(CQRSObjectMetadata @object) return @object.ObjectKind switch { CQRSObjectKind.Command - => ExecuteCommandMethod.MakeGenericMethod(@object.ObjectType).CreateDelegate(), + => ExecuteCommandMethod + .MakeGenericMethod(@object.ObjectType, @object.HandlerType) + .CreateDelegate(), CQRSObjectKind.Query => ExecuteQueryMethod - .MakeGenericMethod(@object.ObjectType, GetResultType(typeof(IQuery<>))) + .MakeGenericMethod(@object.ObjectType, GetResultType(typeof(IQuery<>)), @object.HandlerType) .CreateDelegate(), CQRSObjectKind.Operation => ExecuteOperationMethod - .MakeGenericMethod(@object.ObjectType, GetResultType(typeof(IOperation<>))) + .MakeGenericMethod(@object.ObjectType, GetResultType(typeof(IOperation<>)), @object.HandlerType) .CreateDelegate(), _ => throw new InvalidOperationException($"Unexpected object kind: {@object.ObjectKind}") }; @@ -54,44 +56,47 @@ Type GetResultType(Type interfaceType) => .First(); } - private static async Task ExecuteOperationAsync( + private static async Task ExecuteOperationAsync( HttpContext httpContext, CQRSRequestPayload payload ) where TOperation : IOperation + where THandler : IOperationHandler { var operation = (TOperation)payload.Payload; var handler = - httpContext.RequestServices.GetService>() + httpContext.RequestServices.GetService() ?? throw new OperationHandlerNotFoundException(typeof(TOperation)); return await handler.ExecuteAsync(httpContext, operation); } - private static async Task ExecuteQueryAsync( + private static async Task ExecuteQueryAsync( HttpContext httpContext, CQRSRequestPayload payload ) where TQuery : IQuery + where THandler : IQueryHandler { var query = (TQuery)payload.Payload; var handler = - httpContext.RequestServices.GetService>() + httpContext.RequestServices.GetService() ?? throw new QueryHandlerNotFoundException(typeof(TQuery)); return await handler.ExecuteAsync(httpContext, query); } - private static async Task ExecuteCommandAsync( + private static async Task ExecuteCommandAsync( HttpContext httpContext, CQRSRequestPayload payload ) where TCommand : ICommand + where THandler : ICommandHandler { var command = (TCommand)payload.Payload; var handler = - httpContext.RequestServices.GetService>() + httpContext.RequestServices.GetService() ?? throw new CommandHandlerNotFoundException(typeof(TCommand)); await handler.ExecuteAsync(httpContext, command); diff --git a/src/CQRS/LeanCode.CQRS.AspNetCore/Registration/CQRSObjectsRegistrationSource.cs b/src/CQRS/LeanCode.CQRS.AspNetCore/Registration/CQRSObjectsRegistrationSource.cs index 527781fd..5531db1f 100644 --- a/src/CQRS/LeanCode.CQRS.AspNetCore/Registration/CQRSObjectsRegistrationSource.cs +++ b/src/CQRS/LeanCode.CQRS.AspNetCore/Registration/CQRSObjectsRegistrationSource.cs @@ -60,12 +60,14 @@ public void AddCQRSObjects(TypesCatalog contractsCatalog, TypesCatalog handlersC public void AddCQRSObject(CQRSObjectMetadata metadata) { - var added = objects.Add(metadata); - - if (added) + if (!objects.Add(metadata)) { - services.AddCQRSHandler(metadata); + throw new InvalidOperationException( + $"CQRS Object({metadata.ObjectKind}) {metadata.ObjectType} is already registered." + ); } + + services.AddCQRSHandler(metadata); } private static bool ValidateContractType(TypeInfo type) diff --git a/src/CQRS/LeanCode.CQRS.AspNetCore/Registration/ServiceCollectionRegistrationExtensions.cs b/src/CQRS/LeanCode.CQRS.AspNetCore/Registration/ServiceCollectionRegistrationExtensions.cs index 7bae2192..141a999b 100644 --- a/src/CQRS/LeanCode.CQRS.AspNetCore/Registration/ServiceCollectionRegistrationExtensions.cs +++ b/src/CQRS/LeanCode.CQRS.AspNetCore/Registration/ServiceCollectionRegistrationExtensions.cs @@ -20,8 +20,9 @@ IEnumerable cqrsObjects public static void AddCQRSHandler(this IServiceCollection serviceCollection, CQRSObjectMetadata obj) { + serviceCollection.Add(new(obj.HandlerType, obj.HandlerType, ServiceLifetime.Scoped)); serviceCollection.Add( - new ServiceDescriptor(MakeHandlerInterfaceType(obj), obj.HandlerType, ServiceLifetime.Scoped) + new(MakeHandlerInterfaceType(obj), sp => sp.GetRequiredService(obj.HandlerType), ServiceLifetime.Scoped) ); Type MakeHandlerInterfaceType(CQRSObjectMetadata obj) diff --git a/test/CQRS/LeanCode.CQRS.AspNetCore.Tests/CQRSObjectsRegistrationSourceTests.cs b/test/CQRS/LeanCode.CQRS.AspNetCore.Tests/CQRSObjectsRegistrationSourceTests.cs index c0136e20..dad4cf6f 100644 --- a/test/CQRS/LeanCode.CQRS.AspNetCore.Tests/CQRSObjectsRegistrationSourceTests.cs +++ b/test/CQRS/LeanCode.CQRS.AspNetCore.Tests/CQRSObjectsRegistrationSourceTests.cs @@ -49,7 +49,7 @@ public void Objects_are_registered_in_di_container() } [Fact] - public void Same_objects_are_not_registered_multiple_times() + public void Duplicate_objects_cannot_be_registered() { var registrationSource = new CQRSObjectsRegistrationSource(services); @@ -59,13 +59,12 @@ public void Same_objects_are_not_registered_multiple_times() ); var firstCount = registrationSource.Objects.Count; - registrationSource.AddCQRSObjects( - TypesCatalog.Of(), - TypesCatalog.Of() - ); - var secondCount = registrationSource.Objects.Count; - - firstCount.Should().Be(secondCount); + var act = () => + registrationSource.AddCQRSObjects( + TypesCatalog.Of(), + TypesCatalog.Of() + ); + act.Should().Throw(); } private void AssertRegistered() diff --git a/test/CQRS/LeanCode.CQRS.AspNetCore.Tests/ObjectExecutorFactoryTests.cs b/test/CQRS/LeanCode.CQRS.AspNetCore.Tests/ObjectExecutorFactoryTests.cs index cb34a49f..cb7b6876 100644 --- a/test/CQRS/LeanCode.CQRS.AspNetCore.Tests/ObjectExecutorFactoryTests.cs +++ b/test/CQRS/LeanCode.CQRS.AspNetCore.Tests/ObjectExecutorFactoryTests.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using FluentAssertions; using LeanCode.Contracts; using LeanCode.CQRS.Execution; using Microsoft.AspNetCore.Http; @@ -10,32 +11,27 @@ namespace LeanCode.CQRS.AspNetCore.Tests; [SuppressMessage(category: "?", "CA1034", Justification = "Nesting public types for better tests separation")] public class ObjectExecutorFactoryTests { - private readonly ObjectExecutorFactory executorFactory; - private readonly IServiceProvider serviceProvider; - private readonly ICommandHandler commandHandler; - private readonly IQueryHandler queryHandler; - private readonly IOperationHandler operationHandler; + private readonly ObjectExecutorFactory executorFactory = new ObjectExecutorFactory(); + private readonly IServiceProvider serviceProvider = Substitute.For(); + + private readonly CommandHandler commandHandler = Substitute.For(); + private readonly QueryHandler queryHandler = Substitute.For(); + private readonly OperationHandler operationHandler = Substitute.For(); private readonly CQRSObjectMetadata queryMetadata = - new(CQRSObjectKind.Query, typeof(Query), typeof(QueryResult), typeof(IgnoreType)); + new(CQRSObjectKind.Query, typeof(Query), typeof(QueryResult), typeof(QueryHandler)); private readonly CQRSObjectMetadata commandMetadata = - new(CQRSObjectKind.Command, typeof(Command), typeof(CommandResult), typeof(IgnoreType)); + new(CQRSObjectKind.Command, typeof(Command), typeof(CommandResult), typeof(CommandHandler)); private readonly CQRSObjectMetadata operationMetadata = - new(CQRSObjectKind.Operation, typeof(Operation), typeof(OperationResult), typeof(IgnoreType)); + new(CQRSObjectKind.Operation, typeof(Operation), typeof(OperationResult), typeof(OperationHandler)); public ObjectExecutorFactoryTests() { - executorFactory = new ObjectExecutorFactory(); - serviceProvider = Substitute.For(); - commandHandler = Substitute.For>(); - queryHandler = Substitute.For>(); - operationHandler = Substitute.For>(); - - serviceProvider.GetService(typeof(ICommandHandler)).Returns(commandHandler); - serviceProvider.GetService(typeof(IQueryHandler)).Returns(queryHandler); - serviceProvider.GetService(typeof(IOperationHandler)).Returns(operationHandler); + serviceProvider.GetService(typeof(CommandHandler)).Returns(commandHandler); + serviceProvider.GetService(typeof(QueryHandler)).Returns(queryHandler); + serviceProvider.GetService(typeof(OperationHandler)).Returns(operationHandler); } [Fact] @@ -48,8 +44,23 @@ public async Task Creates_executor_for_command_which_passes_payload_to_command_h var result = await executorMethod(ctx, new CQRSRequestPayload(cmd)); await commandHandler.Received().ExecuteAsync(ctx, cmd); - var commandResult = Assert.IsType(result); - Assert.True(commandResult.WasSuccessful); + result.Should().BeOfType().Which.WasSuccessful.Should().BeTrue(); + } + + [Fact] + public async Task Resolves_the_exact_command_handler_from_DI() + { + var cmd = new Command(); + var ctx = MockHttpContext(); + + serviceProvider + .GetService(typeof(ICommandHandler)) + .Returns(Substitute.For>()); + + var executorMethod = executorFactory.CreateExecutorFor(commandMetadata); + await executorMethod(ctx, new CQRSRequestPayload(cmd)); + + await commandHandler.Received().ExecuteAsync(ctx, cmd); } [Fact] @@ -65,8 +76,23 @@ public async Task Creates_query_handler_which_passes_payload_to_query_handler() var result = await executorMethod(ctx, new CQRSRequestPayload(query)); await queryHandler.Received().ExecuteAsync(ctx, query); - var queryResult = Assert.IsType(result); - Assert.Same(returnedResult, queryResult); + result.Should().BeOfType().Which.Should().BeSameAs(returnedResult); + } + + [Fact] + public async Task Resolves_the_exact_query_handler_from_DI() + { + var query = new Query(); + var ctx = MockHttpContext(); + + serviceProvider + .GetService(typeof(IQueryHandler)) + .Returns(Substitute.For>()); + + var executorMethod = executorFactory.CreateExecutorFor(queryMetadata); + await executorMethod(ctx, new CQRSRequestPayload(query)); + + await queryHandler.Received().ExecuteAsync(ctx, Arg.Any()); } [Fact] @@ -81,8 +107,23 @@ public async Task Creates_operation_handler_which_passes_payload_to_operation_ha var result = await executorMethod(ctx, new CQRSRequestPayload(operation)); await operationHandler.Received().ExecuteAsync(ctx, operation); - var operationResult = Assert.IsType(result); - Assert.Same(returnedResult, operationResult); + result.Should().BeOfType().Which.Should().BeSameAs(returnedResult); + } + + [Fact] + public async Task Resolves_the_exact_operation_handler_from_DI() + { + var operation = new Operation(); + var ctx = MockHttpContext(); + + serviceProvider + .GetService(typeof(IOperationHandler)) + .Returns(Substitute.For>()); + + var executorMethod = executorFactory.CreateExecutorFor(operationMetadata); + await executorMethod(ctx, new CQRSRequestPayload(operation)); + + await operationHandler.Received().ExecuteAsync(ctx, Arg.Any()); } [Fact] @@ -91,12 +132,11 @@ public async Task Throws_command_handler_not_found_if_cannot_get_it_from_DI() var cmd = new Command(); var ctx = MockHttpContext(); - serviceProvider.GetService(typeof(ICommandHandler)).Returns(null); + serviceProvider.GetService(typeof(CommandHandler)).Returns(null); var executorMethod = executorFactory.CreateExecutorFor(commandMetadata); - await Assert.ThrowsAsync( - () => executorMethod(ctx, new CQRSRequestPayload(cmd)) - ); + var act = () => executorMethod(ctx, new CQRSRequestPayload(cmd)); + await act.Should().ThrowAsync(); } [Fact] @@ -105,12 +145,11 @@ public async Task Throws_query_handler_not_found_if_cannot_get_it_from_DI() var query = new Query(); var ctx = MockHttpContext(); - serviceProvider.GetService(typeof(IQueryHandler)).Returns(null); + serviceProvider.GetService(typeof(QueryHandler)).Returns(null); var executorMethod = executorFactory.CreateExecutorFor(queryMetadata); - await Assert.ThrowsAsync( - () => executorMethod(ctx, new CQRSRequestPayload(query)) - ); + var act = () => executorMethod(ctx, new CQRSRequestPayload(query)); + await act.Should().ThrowAsync(); } [Fact] @@ -119,12 +158,11 @@ public async Task Throws_operation_handler_not_found_if_cannot_get_it_from_DI() var operation = new Operation(); var ctx = MockHttpContext(); - serviceProvider.GetService(typeof(IOperationHandler)).Returns(null); + serviceProvider.GetService(typeof(OperationHandler)).Returns(null); var executorMethod = executorFactory.CreateExecutorFor(operationMetadata); - await Assert.ThrowsAsync( - () => executorMethod(ctx, new CQRSRequestPayload(operation)) - ); + var act = () => executorMethod(ctx, new CQRSRequestPayload(operation)); + await act.Should().ThrowAsync(); } private HttpContext MockHttpContext() @@ -136,13 +174,28 @@ private HttpContext MockHttpContext() public class Command : ICommand { } - public class Query : IQuery { } + public class CommandHandler : ICommandHandler + { + public Task ExecuteAsync(HttpContext context, Command command) => Task.CompletedTask; + } public class QueryResult { } + public class Query : IQuery { } + + public class QueryHandler : IQueryHandler + { + public virtual Task ExecuteAsync(HttpContext context, Query query) => + Task.FromResult(new QueryResult()); + } + public class Operation : IOperation { } public class OperationResult { } - public class IgnoreType { } + public class OperationHandler : IOperationHandler + { + public virtual Task ExecuteAsync(HttpContext context, Operation operation) => + Task.FromResult(new OperationResult()); + } }