From d30da9246b4b649ce11820090f92ceb6891b3a92 Mon Sep 17 00:00:00 2001 From: adimiko Date: Wed, 12 Mar 2025 18:45:35 +0100 Subject: [PATCH] Added decorators and logger for query executor --- samples/EasyWay.Samples/Program.cs | 2 +- source/Directory.Packages.props | 10 ++-- source/EasyWay/EasyWay.csproj | 3 ++ source/EasyWay/Internals/Extensions.cs | 4 +- .../Internals/Loggers/EasyWayLogger.cs | 26 +++++++++++ .../EasyWay/Internals/Loggers/Extensions.cs | 20 ++++++++ .../Internals/Modules/ModuleExecutor.cs | 4 +- ...eryExecutorCancellationContextDecorator.cs | 29 ++++++++++++ .../QueryExecutorLoggerDecorator.cs | 46 +++++++++++++++++++ .../QueryExecutorValidatorDecorator.cs | 42 +++++++++++++++++ .../EasyWay/Internals/Queries/Extensions.cs | 21 +++++++-- .../Internals/Queries/IQueryExecutor.cs | 6 +-- .../Internals/Queries/QueryExecutor.cs | 41 +++-------------- 13 files changed, 203 insertions(+), 51 deletions(-) create mode 100644 source/EasyWay/Internals/Loggers/EasyWayLogger.cs create mode 100644 source/EasyWay/Internals/Loggers/Extensions.cs create mode 100644 source/EasyWay/Internals/Queries/Decorators/QueryExecutorCancellationContextDecorator.cs create mode 100644 source/EasyWay/Internals/Queries/Decorators/QueryExecutorLoggerDecorator.cs create mode 100644 source/EasyWay/Internals/Queries/Decorators/QueryExecutorValidatorDecorator.cs diff --git a/samples/EasyWay.Samples/Program.cs b/samples/EasyWay.Samples/Program.cs index fb08d2e..659a42f 100644 --- a/samples/EasyWay.Samples/Program.cs +++ b/samples/EasyWay.Samples/Program.cs @@ -14,7 +14,7 @@ await kernel .AddModule() .BuildAsync(builder.Services); - +builder.Services.AddLogging(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddEasyWayWebApi(); diff --git a/source/Directory.Packages.props b/source/Directory.Packages.props index a795e54..a11f311 100644 --- a/source/Directory.Packages.props +++ b/source/Directory.Packages.props @@ -7,13 +7,15 @@ - - + + - + - + + + diff --git a/source/EasyWay/EasyWay.csproj b/source/EasyWay/EasyWay.csproj index be2d914..b5e04d0 100644 --- a/source/EasyWay/EasyWay.csproj +++ b/source/EasyWay/EasyWay.csproj @@ -7,6 +7,9 @@ + + + diff --git a/source/EasyWay/Internals/Extensions.cs b/source/EasyWay/Internals/Extensions.cs index 5508031..34ad898 100644 --- a/source/EasyWay/Internals/Extensions.cs +++ b/source/EasyWay/Internals/Extensions.cs @@ -6,6 +6,7 @@ using EasyWay.Internals.DomainServices; using EasyWay.Internals.Factories; using EasyWay.Internals.Initializers; +using EasyWay.Internals.Loggers; using EasyWay.Internals.Policies; using EasyWay.Internals.Queries; using EasyWay.Internals.Repositories; @@ -24,9 +25,10 @@ internal static void AddEasyWay( services .AddClocks() .AddContexts() + .AddLoggers(moduleType) .AddAggregateRoots() .AddCommands(moduleType, assemblies) - .AddQueries(moduleType, assemblies) + .AddQueries(assemblies) .AddDomainEvents(assemblies) .AddRepositories(assemblies) .AddPolicies(assemblies) diff --git a/source/EasyWay/Internals/Loggers/EasyWayLogger.cs b/source/EasyWay/Internals/Loggers/EasyWayLogger.cs new file mode 100644 index 0000000..b0d8019 --- /dev/null +++ b/source/EasyWay/Internals/Loggers/EasyWayLogger.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.Logging; + +namespace EasyWay.Internals.Queries.Loggers +{ + internal sealed partial class EasyWayLogger + where TModule : EasyWayModule + { + private readonly ILogger _logger; + + public EasyWayLogger(ILogger logger) => _logger = logger; + + //TODO scope logger CorrelationId (ScopeId) + + [LoggerMessage(0, LogLevel.Information, "Executing {@component}", SkipEnabledCheck = true)] + public partial void Executing(object component); + + [LoggerMessage(1, LogLevel.Information, "Executed", SkipEnabledCheck = true)] + public partial void Executed(); + + [LoggerMessage(2, LogLevel.Information, "Failed", SkipEnabledCheck = true)] + public partial void Failed(); + + [LoggerMessage(3, LogLevel.Error, "Unexpected exception", SkipEnabledCheck = true)] + public partial void UnexpectedException(Exception exception); + } +} diff --git a/source/EasyWay/Internals/Loggers/Extensions.cs b/source/EasyWay/Internals/Loggers/Extensions.cs new file mode 100644 index 0000000..b974566 --- /dev/null +++ b/source/EasyWay/Internals/Loggers/Extensions.cs @@ -0,0 +1,20 @@ +using EasyWay.Internals.Queries.Loggers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace EasyWay.Internals.Loggers +{ + internal static class Extensions + { + internal static IServiceCollection AddLoggers(this IServiceCollection services, Type moduleType) + { + services.AddLogging(x => x.AddJsonConsole()); + + var loggerType = typeof(EasyWayLogger<>).MakeGenericType(moduleType); + + services.AddSingleton(loggerType); + + return services; + } + } +} diff --git a/source/EasyWay/Internals/Modules/ModuleExecutor.cs b/source/EasyWay/Internals/Modules/ModuleExecutor.cs index f8ed950..373cea2 100644 --- a/source/EasyWay/Internals/Modules/ModuleExecutor.cs +++ b/source/EasyWay/Internals/Modules/ModuleExecutor.cs @@ -60,8 +60,8 @@ public async Task> Query(TQuery quer var sp = scope.ServiceProvider; result = await sp - .GetRequiredService>() - .Execute(query, cancellationToken); + .GetRequiredService() + .Execute(query, cancellationToken); } return result; diff --git a/source/EasyWay/Internals/Queries/Decorators/QueryExecutorCancellationContextDecorator.cs b/source/EasyWay/Internals/Queries/Decorators/QueryExecutorCancellationContextDecorator.cs new file mode 100644 index 0000000..e69cd3e --- /dev/null +++ b/source/EasyWay/Internals/Queries/Decorators/QueryExecutorCancellationContextDecorator.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace EasyWay.Internals.Queries.Decorators +{ + internal sealed class QueryExecutorCancellationContextDecorator : IQueryExecutor + { + private readonly IQueryExecutor _decoratedQueryExecutor; + + private readonly IServiceProvider _serviceProvider; + + public QueryExecutorCancellationContextDecorator( + IQueryExecutor decoratedQueryExecutor, + IServiceProvider serviceProvider) + { + _decoratedQueryExecutor = decoratedQueryExecutor; + _serviceProvider = serviceProvider; + } + + public Task> Execute(TQuery query, CancellationToken cancellationToken = default) + where TModule : EasyWayModule + where TQuery : Query + where TReadModel : ReadModel + { + _serviceProvider.GetRequiredService().Set(cancellationToken); + + return _decoratedQueryExecutor.Execute(query, cancellationToken); + } + } +} diff --git a/source/EasyWay/Internals/Queries/Decorators/QueryExecutorLoggerDecorator.cs b/source/EasyWay/Internals/Queries/Decorators/QueryExecutorLoggerDecorator.cs new file mode 100644 index 0000000..e5be751 --- /dev/null +++ b/source/EasyWay/Internals/Queries/Decorators/QueryExecutorLoggerDecorator.cs @@ -0,0 +1,46 @@ +using EasyWay.Internals.Queries.Loggers; +using Microsoft.Extensions.DependencyInjection; + +namespace EasyWay.Internals.Queries.Decorators +{ + internal sealed class QueryExecutorLoggerDecorator : IQueryExecutor + { + private readonly IQueryExecutor _decoratedQueryExecutor; + + private readonly IServiceProvider _serviceProvider; + + public QueryExecutorLoggerDecorator( + IQueryExecutor decoratedQueryExecutor, + IServiceProvider serviceProvider) + { + _decoratedQueryExecutor = decoratedQueryExecutor; + _serviceProvider = serviceProvider; + } + + public Task> Execute(TQuery query, CancellationToken cancellationToken = default) + where TModule : EasyWayModule + where TQuery : Query + where TReadModel : ReadModel + { + var logger = _serviceProvider.GetRequiredService>(); + + //TODO begin scope (correlation Id) + + logger.Executing(query); + + try + { + var result = _decoratedQueryExecutor.Execute(query, cancellationToken); + + logger.Executed(); + + return result; + } + catch (Exception ex) + { + logger.UnexpectedException(ex); + throw; + } + } + } +} diff --git a/source/EasyWay/Internals/Queries/Decorators/QueryExecutorValidatorDecorator.cs b/source/EasyWay/Internals/Queries/Decorators/QueryExecutorValidatorDecorator.cs new file mode 100644 index 0000000..867fa93 --- /dev/null +++ b/source/EasyWay/Internals/Queries/Decorators/QueryExecutorValidatorDecorator.cs @@ -0,0 +1,42 @@ +using EasyWay.Internals.Validation; +using Microsoft.Extensions.DependencyInjection; + +namespace EasyWay.Internals.Queries.Decorators +{ + internal sealed class QueryExecutorValidatorDecorator : IQueryExecutor + { + private readonly IQueryExecutor _decoratedQueryExecutor; + + private readonly IServiceProvider _serviceProvider; + + public QueryExecutorValidatorDecorator( + IQueryExecutor decoratedQueryExecutor, + IServiceProvider serviceProvider) + { + _decoratedQueryExecutor = decoratedQueryExecutor; + _serviceProvider = serviceProvider; + } + + public Task> Execute(TQuery query, CancellationToken cancellationToken = default) + where TModule : EasyWayModule + where TQuery : Query + where TReadModel : ReadModel + { + var validatorType = typeof(IEasyWayValidator<>).MakeGenericType(query.GetType()); + + var validator = _serviceProvider.GetService>(); + + if (validator is not null) + { + var errors = validator.Validate(query); + + if (errors.Any()) + { + return Task.FromResult(QueryResult.Validation(errors)); + } + } + + return _decoratedQueryExecutor.Execute(query, cancellationToken); + } + } +} diff --git a/source/EasyWay/Internals/Queries/Extensions.cs b/source/EasyWay/Internals/Queries/Extensions.cs index 02ba17d..8b248dd 100644 --- a/source/EasyWay/Internals/Queries/Extensions.cs +++ b/source/EasyWay/Internals/Queries/Extensions.cs @@ -1,4 +1,6 @@ using System.Reflection; +using EasyWay.Internals.Queries.Decorators; +using EasyWay.Internals.Validation; using Microsoft.Extensions.DependencyInjection; namespace EasyWay.Internals.Queries @@ -7,17 +9,26 @@ internal static class Extensions { internal static IServiceCollection AddQueries( this IServiceCollection services, - Type moduleType, IEnumerable assemblies) { - services.AddScoped(typeof(IQueryExecutor<>).MakeGenericType(moduleType), typeof(QueryExecutor<>).MakeGenericType(moduleType)); + services.AddScoped(); - services.AddAsBasedType(typeof(QueryHandler<,>), ServiceLifetime.Scoped, assemblies); + services.AddScoped(p => + { + var executor = p.GetRequiredService(); - return services; - } + var cancellationContext = new QueryExecutorCancellationContextDecorator(executor, p); + + var validator = new QueryExecutorValidatorDecorator(cancellationContext, p); + var logger = new QueryExecutorLoggerDecorator(validator, p); + return logger; + }); + services.AddAsBasedType(typeof(QueryHandler<,>), ServiceLifetime.Scoped, assemblies); + + return services; + } } } diff --git a/source/EasyWay/Internals/Queries/IQueryExecutor.cs b/source/EasyWay/Internals/Queries/IQueryExecutor.cs index 1cbf27c..3cabdbf 100644 --- a/source/EasyWay/Internals/Queries/IQueryExecutor.cs +++ b/source/EasyWay/Internals/Queries/IQueryExecutor.cs @@ -1,9 +1,9 @@ namespace EasyWay.Internals.Queries { - internal interface IQueryExecutor - where TModule : EasyWayModule + internal interface IQueryExecutor { - Task> Execute(TQuery query, CancellationToken cancellationToken = default) + Task> Execute(TQuery query, CancellationToken cancellationToken = default) + where TModule : EasyWayModule where TQuery : Query where TReadModel : ReadModel; } diff --git a/source/EasyWay/Internals/Queries/QueryExecutor.cs b/source/EasyWay/Internals/Queries/QueryExecutor.cs index 9c01e3a..942892e 100644 --- a/source/EasyWay/Internals/Queries/QueryExecutor.cs +++ b/source/EasyWay/Internals/Queries/QueryExecutor.cs @@ -1,51 +1,22 @@ -using EasyWay.Internals.Contexts; -using EasyWay.Internals.Validation; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; namespace EasyWay.Internals.Queries { - internal sealed class QueryExecutor : IQueryExecutor - where TModule : EasyWayModule + internal sealed class QueryExecutor : IQueryExecutor { private readonly IServiceProvider _serviceProvider; - private readonly CancellationContext _cancellationContext; - - public QueryExecutor( - IServiceProvider serviceProvider, - CancellationContext cancellationContext) + public QueryExecutor(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; - _cancellationContext = cancellationContext; } - public async Task> Execute(TQuery query, CancellationToken cancellationToken = default) + public async Task> Execute(TQuery query, CancellationToken cancellationToken = default) + where TModule : EasyWayModule where TQuery : Query where TReadModel : ReadModel { - _cancellationContext.Set(cancellationToken); - - var queryType = query.GetType(); - - var validatorType = typeof(IEasyWayValidator<>).MakeGenericType(queryType); - - var validator = _serviceProvider.GetService>(); - - if (validator is not null) - { - var errors = validator.Validate(query); - - if (errors.Any()) - { - return QueryResult.Validation(errors); - } - } - - var queryHandler = _serviceProvider.GetRequiredService>(); - - var queryResult = await queryHandler.Handle(query); - - return queryResult; + return await _serviceProvider.GetRequiredService>().Handle(query); } } }