Adds Decorator, wip

This commit is contained in:
Janus C. H. Knudsen 2025-03-11 00:28:06 +01:00
parent a86a2d7ade
commit 49f9b99ee1
9 changed files with 112 additions and 45 deletions

View file

@ -1,4 +1,5 @@
using System.Data; using System.Data;
using System.Diagnostics;
using Microsoft.ApplicationInsights.DataContracts; using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.Extensibility;
@ -6,19 +7,24 @@ namespace PlanTempus.Core.Database;
public class DatabaseScope : IDisposable public class DatabaseScope : IDisposable
{ {
private readonly IOperationHolder<DependencyTelemetry> _operation; internal readonly IOperationHolder<DependencyTelemetry> _operation;
private readonly Stopwatch _stopwatch;
public DatabaseScope(IDbConnection connection, IOperationHolder<DependencyTelemetry> operation) public DatabaseScope(IDbConnection connection, IOperationHolder<DependencyTelemetry> operation)
{ {
Connection = connection; Connection = connection;
_operation = operation; _operation = operation;
_operation.Telemetry.Success = true; _operation.Telemetry.Success = true;
_stopwatch = Stopwatch.StartNew();
} }
public IDbConnection Connection { get; } public IDbConnection Connection { get; }
public void Dispose() public void Dispose()
{ {
_stopwatch.Stop();
_operation.Telemetry.Duration = _stopwatch.Elapsed;
_operation.Dispose(); _operation.Dispose();
Connection.Dispose(); Connection.Dispose();
} }

View file

@ -0,0 +1,14 @@
using Autofac;
using PlanTempus.Core.SeqLogging;
namespace PlanTempus.Core.ModuleRegistry
{
public class SecurityModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<SecureTokenizer>()
.As<ISecureTokenizer>();
}
}
}

View file

@ -5,9 +5,9 @@ namespace PlanTempus.Components;
public class CommandHandler(IComponentContext context) : ICommandHandler public class CommandHandler(IComponentContext context) : ICommandHandler
{ {
public Task<TCommandResult> Handle<TCommand, TCommandResult>(TCommand command) public async Task<TCommandResult> Handle<TCommand, TCommandResult>(TCommand command)
{ {
var handler = context.Resolve<ICommandHandler<TCommand, TCommandResult>>(); var handler = context.Resolve<ICommandHandler<TCommand, TCommandResult>>();
return handler.Handle(command); return await handler.Handle(command);
} }
} }

View file

@ -0,0 +1,15 @@
namespace PlanTempus.Components;
public interface ICommandHandler
{
Task<TCommandResult> Handle<TCommand, TCommandResult>(TCommand command);
}
public interface ICommandHandler<in T, TResult>
{
Task<TResult> Handle(T input);
}
public interface ICommandHandlerDecorator
{
}

View file

@ -1,4 +1,5 @@
using Autofac; using Autofac;
using PlanTempus.Components.Users.Create;
using PlanTempus.Core.SeqLogging; using PlanTempus.Core.SeqLogging;
namespace PlanTempus.Components.ModuleRegistry namespace PlanTempus.Components.ModuleRegistry
@ -9,15 +10,34 @@ namespace PlanTempus.Components.ModuleRegistry
protected override void Load(ContainerBuilder builder) protected override void Load(ContainerBuilder builder)
{ {
// Registrer alle handlers
builder.RegisterAssemblyTypes() builder.RegisterType<PlanTempus.Components.CommandHandler>()
.AsClosedTypesOf(typeof(ICommandHandler<>)) .As<PlanTempus.Components.ICommandHandler>()
.InstancePerLifetimeScope(); .InstancePerLifetimeScope();
// Registrer en decorator for alle ICommandHandler<TCommand> builder.RegisterAssemblyTypes(ThisAssembly)
builder.RegisterGenericDecorator( .Where(t => !typeof(ICommandHandlerDecorator).IsAssignableFrom(t))
typeof(CreateUserHandlerDecorator<>), // Din decorator-klasse .AsClosedTypesOf(typeof(ICommandHandler<,>))
typeof(ICommandHandler<>)); // Interface, der skal dekoreres .InstancePerLifetimeScope();
// Registrer alle handlers
// builder.RegisterAssemblyTypes(ThisAssembly)
// .AsClosedTypesOf(typeof(ICommandHandler<,>))
// .InstancePerLifetimeScope();
builder.RegisterAssemblyTypes(ThisAssembly)
.As<ICommandHandlerDecorator>();
builder.RegisterDecorator(
typeof(CreateUserHandlerDecorator),
typeof(ICommandHandler<CreateUserCommand, CreateUserResult>));
//
// Registrer en decorator for alle ICommandHandler<TCommand>
// builder.RegisterGenericDecorator(
// typeof(CommandHandlerDecorator<,>),
// typeof(ICommandHandler<,>));
} }
} }

View file

@ -8,16 +8,6 @@ using PlanTempus.Core.Telemetry;
namespace PlanTempus.Components.Users.Create namespace PlanTempus.Components.Users.Create
{ {
public interface ICommandHandler
{
Task<TCommandResult> Handle<TCommand, TCommandResult>(TCommand command );
}
public interface ICommandHandler<in T, TResult>
{
Task<TResult> Handle(T input);
}
public class CreateUserHandler( public class CreateUserHandler(
TelemetryClient telemetryClient, TelemetryClient telemetryClient,
IDatabaseOperations databaseOperations, IDatabaseOperations databaseOperations,
@ -50,7 +40,6 @@ namespace PlanTempus.Components.Users.Create
var result = data.First(); var result = data.First();
telemetryClient.TrackTrace(GetType().Name, result.Format());
return result; return result;
} }

View file

@ -1,25 +1,41 @@
using Microsoft.ApplicationInsights; using System.Diagnostics;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;
namespace PlanTempus.Components.Users.Create; namespace PlanTempus.Components.Users.Create;
public class CreateUserHandlerDecorator : ICommandHandler<CreateUserCommand, CreateUserResult> public class CreateUserHandlerDecorator(
{
private readonly ICommandHandler<CreateUserCommand, CreateUserResult> _decoratedHandler;
private readonly TelemetryClient _telemetryClient;
public CreateUserHandlerDecorator(
ICommandHandler<CreateUserCommand, CreateUserResult> decoratedHandler, ICommandHandler<CreateUserCommand, CreateUserResult> decoratedHandler,
TelemetryClient telemetryClient) TelemetryClient telemetryClient)
{ : ICommandHandler<CreateUserCommand, CreateUserResult>, ICommandHandlerDecorator
_decoratedHandler = decoratedHandler; {
_telemetryClient = telemetryClient;
}
public async Task<CreateUserResult> Handle(CreateUserCommand command) public async Task<CreateUserResult> Handle(CreateUserCommand command)
{ {
_telemetryClient.TrackTrace($"Before handling {nameof(CreateUserCommand)}"); // var correlationId = Activity.Current?.RootId ?? command.CorrelationId;
var result = await _decoratedHandler.Handle(command);
_telemetryClient.TrackTrace($"After handling {nameof(CreateUserCommand)}"); using (var operation =
telemetryClient.StartOperation<RequestTelemetry>($"Handle {nameof(CreateUserCommand)}",
command.CorrelationId.ToString()))
{
try
{
operation.Telemetry.Properties["CorrelationId"] = command.CorrelationId.ToString();
var result = await decoratedHandler.Handle(command);
operation.Telemetry.Success = true;
return result; return result;
} }
catch (Exception ex)
{
operation.Telemetry.Success = false;
telemetryClient.TrackException(ex, new Dictionary<string, string>
{
["CommandType"] = nameof(CreateUserCommand)
});
throw;
}
}
}
} }

View file

@ -4,6 +4,7 @@ using PlanTempus.Components;
using PlanTempus.Components.Users.Create; using PlanTempus.Components.Users.Create;
using PlanTempus.Core.Database; using PlanTempus.Core.Database;
using PlanTempus.Core.Database.ConnectionFactory; using PlanTempus.Core.Database.ConnectionFactory;
using Shouldly;
namespace PlanTempus.X.TDD.CommandQueryHandlerTests; namespace PlanTempus.X.TDD.CommandQueryHandlerTests;
@ -16,18 +17,21 @@ public class HandlerTest : TestFixture
} }
[TestMethod] [TestMethod]
public void TestDefaultConnection() public async Task ShouldResolveCommandHandlerAndDispatchToGenericCommandHandler()
{ {
var commandHandler = Container.Resolve<CommandHandler>(); var commandHandler = Container.Resolve<ICommandHandler>();
commandHandler.ShouldBeOfType<CommandHandler>();
var command = new CreateUserCommand var command = new CreateUserCommand
{ {
Email = "lloyd@dumbanddumber.com", // Lloyd Christmas Email = "lloyd@dumbanddumber.com1", // Lloyd Christmas
Password = "1234AceVentura#LOL", // Ace Ventura Password = "1234AceVentura#LOL", // Ace Ventura
IsActive = true, IsActive = true,
CorrelationId = Guid.NewGuid() CorrelationId = Guid.NewGuid()
}; };
var result = commandHandler.Handle<CreateUserCommand, CreateUserResult>(command); var result = await commandHandler.Handle<CreateUserCommand, CreateUserResult>(command);
result.ShouldNotBeNull();
} }
} }

View file

@ -2,6 +2,7 @@
using Autofac; using Autofac;
using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using PlanTempus.Components.ModuleRegistry;
using PlanTempus.Core.Configurations; using PlanTempus.Core.Configurations;
using PlanTempus.Core.Configurations.JsonConfigProvider; using PlanTempus.Core.Configurations.JsonConfigProvider;
using PlanTempus.Core.ModuleRegistry; using PlanTempus.Core.ModuleRegistry;
@ -67,6 +68,8 @@ public abstract class TestFixture
SeqConfiguration = configuration.GetSection("SeqConfiguration").ToObject<SeqConfiguration>() SeqConfiguration = configuration.GetSection("SeqConfiguration").ToObject<SeqConfiguration>()
}); });
builder.RegisterModule<CommandModule>();
builder.RegisterModule<SecurityModule>();
ContainerBuilder = builder; ContainerBuilder = builder;
} }