Skip to content

Commit

Permalink
VCST-430: Simplify event handlers registration (#2769)
Browse files Browse the repository at this point in the history
  • Loading branch information
artem-dudarev authored Mar 13, 2024
1 parent d754d45 commit e8ba03a
Show file tree
Hide file tree
Showing 16 changed files with 199 additions and 61 deletions.
8 changes: 4 additions & 4 deletions src/VirtoCommerce.Platform.Caching/PlatformMemoryCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public PlatformMemoryCache(IMemoryCache memoryCache, IOptions<CachingOptions> op
SlidingExpiration = cachingOptions.CacheSlidingExpiration;
_log = log;
}

public virtual ICacheEntry CreateEntry(object key)
{
var result = _memoryCache.CreateEntry(key);
Expand Down Expand Up @@ -88,7 +88,7 @@ public void Dispose()
{
Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// Therefore, you should call GC.SuppressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
Expand All @@ -107,10 +107,10 @@ protected virtual void Dispose(bool disposing)
}
}


protected virtual void EvictionCallback(object key, object value, EvictionReason reason, object state)
{
_log.LogTrace($"EvictionCallback: Cache entry with key:{key} has been removed.");
}
}
}
}
8 changes: 5 additions & 3 deletions src/VirtoCommerce.Platform.Core/Bus/HandlerWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@

namespace VirtoCommerce.Platform.Core.Bus
{
[DebuggerDisplay("{HandlerModuleName} {EventType.Name}")]
public sealed class HandlerWrapper
{
public string EventName { get; set; }
public Type EventType { get; set; }

public string HandlerModuleName { get; set; }

Expand All @@ -26,8 +27,9 @@ public Task Handle(IEvent @event, CancellationToken cancellationToken)
Handler(@event, cancellationToken).GetAwaiter().GetResult();
stopWatch.Stop();
Logger.LogInformation($"event:{EventName} module:{HandlerModuleName} overall_elapsed:{stopWatch.ElapsedMilliseconds}");
});
Logger.LogInformation("event:{Event} module:{Handler} overall_elapsed:{Elapsed}",
@event.GetType().Name, HandlerModuleName, stopWatch.ElapsedMilliseconds);
}, cancellationToken);
}
}
}
3 changes: 2 additions & 1 deletion src/VirtoCommerce.Platform.Core/Bus/IHandlerRegistrar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace VirtoCommerce.Platform.Core.Bus
{
public interface IHandlerRegistrar
{
void RegisterHandler<T>(Func<T, CancellationToken,Task> handler) where T : class, IMessage;
[Obsolete("Use IApplicationBuilder.RegisterEventHandler<>()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")]
void RegisterHandler<T>(Func<T, CancellationToken, Task> handler) where T : IMessage;
}
}
60 changes: 47 additions & 13 deletions src/VirtoCommerce.Platform.Core/Bus/InProcessBus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,40 +9,74 @@

namespace VirtoCommerce.Platform.Core.Bus
{
public class InProcessBus : IEventPublisher, IHandlerRegistrar
public class InProcessBus : IEventHandlerRegistrar, IEventPublisher, IHandlerRegistrar
{
private readonly ILogger<InProcessBus> _logger;
private readonly Dictionary<Type, List<HandlerWrapper>> _handlersByType = new Dictionary<Type, List<HandlerWrapper>>();
private readonly List<HandlerWrapper> _handlers = [];

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

public void RegisterHandler<T>(Func<T, CancellationToken, Task> handler) where T : class, IMessage
public void RegisterEventHandler<T>(Func<T, Task> handler)
where T : IEvent
{
if (!_handlersByType.TryGetValue(typeof(T), out var handlers))
var eventType = typeof(T);

var handlerWrapper = new HandlerWrapper
{
handlers = new List<HandlerWrapper>();
_handlersByType.Add(typeof(T), handlers);
}
EventType = eventType,
HandlerModuleName = handler.Target?.GetType().Module.Assembly.GetName().Name,
Handler = (message, _) => handler((T)message),
Logger = _logger
};

_handlers.Add(handlerWrapper);
}

public void RegisterEventHandler<T>(Func<T, CancellationToken, Task> handler)
where T : IEvent
{
#pragma warning disable VC0008 // Type or member is obsolete
RegisterHandler(handler);
#pragma warning restore VC0008 // Type or member is obsolete
}

[Obsolete("Use IApplicationBuilder.RegisterEventHandler<>()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions")]
public void RegisterHandler<T>(Func<T, CancellationToken, Task> handler)
where T : IMessage
{
var eventType = typeof(T);

var handlerWrapper = new HandlerWrapper
{
EventName = typeof(T).Name,
HandlerModuleName = handler.Target.GetType().Module.Assembly.GetName().Name,
EventType = eventType,
HandlerModuleName = handler.Target?.GetType().Module.Assembly.GetName().Name,
Handler = (message, token) => handler((T)message, token),
Logger = _logger
};

handlers.Add(handlerWrapper);
_handlers.Add(handlerWrapper);
}

public async Task Publish<T>(T @event, CancellationToken cancellationToken = default(CancellationToken)) where T : class, IEvent
public async Task Publish<T>(T @event, CancellationToken cancellationToken = default)
where T : IEvent
{
if (!EventSuppressor.EventsSuppressed && _handlersByType.TryGetValue(@event.GetType(), out var handlers))
if (EventSuppressor.EventsSuppressed)
{
return;
}

var eventType = @event.GetType();

var handlers = _handlers
.Where(x => x.EventType.IsAssignableFrom(eventType))
.ToList();

if (handlers.Count > 0)
{
await Task.WhenAll(handlers.Select(handler => handler.Handle(@event, cancellationToken)));
await Task.WhenAll(handlers.Select(x => x.Handle(@event, cancellationToken)));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace VirtoCommerce.Platform.Core.Events;

public static class ApplicationBuilderExtensions
{
public static IApplicationBuilder RegisterEventHandler<TEvent, THandler>(this IApplicationBuilder applicationBuilder)
where TEvent : IEvent
where THandler : IEventHandler<TEvent>
{
var handler = applicationBuilder.ApplicationServices.GetRequiredService<THandler>();
return applicationBuilder.RegisterEventHandler<TEvent>(handler.Handle);
}

public static IApplicationBuilder RegisterEventHandler<TEvent>(this IApplicationBuilder applicationBuilder, Func<TEvent, Task> handler)
where TEvent : IEvent
{
var registrar = applicationBuilder.ApplicationServices.GetRequiredService<IEventHandlerRegistrar>();
registrar.RegisterEventHandler(handler);
return applicationBuilder;
}

public static IApplicationBuilder RegisterCancellableEventHandler<TEvent, THandler>(this IApplicationBuilder applicationBuilder)
where TEvent : IEvent
where THandler : ICancellableEventHandler<TEvent>
{
var handler = applicationBuilder.ApplicationServices.GetRequiredService<THandler>();
return applicationBuilder.RegisterCancellableEventHandler<TEvent>(handler.Handle);
}

public static IApplicationBuilder RegisterCancellableEventHandler<TEvent>(this IApplicationBuilder applicationBuilder, Func<TEvent, CancellationToken, Task> handler)
where TEvent : IEvent
{
var registrar = applicationBuilder.ApplicationServices.GetRequiredService<IEventHandlerRegistrar>();
registrar.RegisterEventHandler(handler);
return applicationBuilder;
}
}
13 changes: 8 additions & 5 deletions src/VirtoCommerce.Platform.Core/Events/EventSuppressor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,23 @@ private void Dispose(bool disposing)
}
}

private static readonly AsyncLocal<bool> EventsSuppressedStorage = new AsyncLocal<bool>();
private static readonly AsyncLocal<bool> _eventsSuppressedStorage = new();

/// <summary>
/// The flag indicates that events are suppressed for the current asynchronous control flow
/// </summary>
public static bool EventsSuppressed => EventsSuppressedStorage.Value;
public static bool EventsSuppressed => _eventsSuppressedStorage.Value;

[Obsolete("Use SuppressEvents()", DiagnosticId = "VC0008", UrlFormat = "https://docs.virtocommerce.org/products/products-virto3-versions/")]
public static IDisposable SupressEvents() => SuppressEvents();

/// <summary>
/// The flag indicates that events are suppressed for the current asynchronous control flow
/// </summary>
public static IDisposable SupressEvents()
public static IDisposable SuppressEvents()
{
EventsSuppressedStorage.Value = true;
return new DisposableActionGuard(() => { EventsSuppressedStorage.Value = false; });
_eventsSuppressedStorage.Value = true;
return new DisposableActionGuard(() => { _eventsSuppressedStorage.Value = false; });
}
}
}
11 changes: 11 additions & 0 deletions src/VirtoCommerce.Platform.Core/Events/IEventHandlerRegistrar.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace VirtoCommerce.Platform.Core.Events;

public interface IEventHandlerRegistrar
{
void RegisterEventHandler<T>(Func<T, Task> handler) where T : IEvent;
void RegisterEventHandler<T>(Func<T, CancellationToken, Task> handler) where T : IEvent;
}
2 changes: 1 addition & 1 deletion src/VirtoCommerce.Platform.Core/Events/IEventPublisher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ namespace VirtoCommerce.Platform.Core.Events
{
public interface IEventPublisher
{
Task Publish<T>(T @event, CancellationToken cancellationToken = default(CancellationToken)) where T : class, IEvent;
Task Publish<T>(T @event, CancellationToken cancellationToken = default) where T : IEvent;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public async Task ImportAsync(Stream inputStream, PlatformExportManifest importO
progressCallback(progressInfo);

using (var zipArchive = new ZipArchive(inputStream, ZipArchiveMode.Read, true))
using (EventSuppressor.SupressEvents())
using (EventSuppressor.SuppressEvents())
{
//Import selected platform entries
await ImportPlatformEntriesInternalAsync(zipArchive, importOptions, progressCallback, сancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public static IServiceCollection AddPlatformServices(this IServiceCollection ser
services.AddDynamicProperties();

services.AddSingleton<InProcessBus>();
services.AddSingleton<IEventHandlerRegistrar>(x => x.GetRequiredService<InProcessBus>());
services.AddSingleton<IHandlerRegistrar>(x => x.GetRequiredService<InProcessBus>());
services.AddSingleton<IEventPublisher>(x => x.GetRequiredService<InProcessBus>());
services.AddTransient<IChangeLogService, ChangeLogService>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using VirtoCommerce.Platform.Core.Bus;
using VirtoCommerce.Platform.Core.Events;
using VirtoCommerce.Platform.Core.Security;
using VirtoCommerce.Platform.Core.Settings;
using VirtoCommerce.Platform.Core.Settings.Events;
Expand Down Expand Up @@ -61,10 +61,9 @@ public static IApplicationBuilder UseHangfire(this IApplicationBuilder appBuilde
var mvcJsonOptions = appBuilder.ApplicationServices.GetService<IOptions<MvcNewtonsoftJsonOptions>>();
GlobalConfiguration.Configuration.UseSerializerSettings(mvcJsonOptions.Value.SerializerSettings);

var inProcessBus = appBuilder.ApplicationServices.GetService<IHandlerRegistrar>();
var recurringJobManager = appBuilder.ApplicationServices.GetService<IRecurringJobManager>();
var settingsManager = appBuilder.ApplicationServices.GetService<ISettingsManager>();
inProcessBus.RegisterHandler<ObjectSettingChangedEvent>(async (message, token) => await recurringJobManager.HandleSettingChangeAsync(settingsManager, message));
appBuilder.RegisterEventHandler<ObjectSettingChangedEvent>(message => recurringJobManager.HandleSettingChangeAsync(settingsManager, message));

// Add Hangfire filters/middlewares
var userNameResolver = appBuilder.ApplicationServices.CreateScope().ServiceProvider.GetRequiredService<IUserNameResolver>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using VirtoCommerce.Platform.Core;
using VirtoCommerce.Platform.Core.Bus;
using VirtoCommerce.Platform.Core.Common;
using VirtoCommerce.Platform.Core.Events;
using VirtoCommerce.Platform.Core.Security;
using VirtoCommerce.Platform.Core.Security.Events;
using VirtoCommerce.Platform.Core.Settings;
Expand All @@ -29,15 +29,14 @@ public static IApplicationBuilder UsePlatformPermissions(this IApplicationBuilde

public static IApplicationBuilder UseSecurityHandlers(this IApplicationBuilder appBuilder)
{
var inProcessBus = appBuilder.ApplicationServices.GetService<IHandlerRegistrar>();
inProcessBus.RegisterHandler<UserChangedEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<LogChangesUserChangedEventHandler>().Handle(message));
inProcessBus.RegisterHandler<UserChangedEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<UserApiKeyActualizeEventHandler>().Handle(message));
inProcessBus.RegisterHandler<UserPasswordChangedEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<LogChangesUserChangedEventHandler>().Handle(message));
inProcessBus.RegisterHandler<UserResetPasswordEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<LogChangesUserChangedEventHandler>().Handle(message));
inProcessBus.RegisterHandler<UserLoginEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<LogChangesUserChangedEventHandler>().Handle(message));
inProcessBus.RegisterHandler<UserLogoutEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<LogChangesUserChangedEventHandler>().Handle(message));
inProcessBus.RegisterHandler<UserRoleAddedEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<LogChangesUserChangedEventHandler>().Handle(message));
inProcessBus.RegisterHandler<UserRoleRemovedEvent>(async (message, token) => await appBuilder.ApplicationServices.GetService<LogChangesUserChangedEventHandler>().Handle(message));
appBuilder.RegisterEventHandler<UserChangedEvent, LogChangesUserChangedEventHandler>();
appBuilder.RegisterEventHandler<UserChangedEvent, UserApiKeyActualizeEventHandler>();
appBuilder.RegisterEventHandler<UserPasswordChangedEvent, LogChangesUserChangedEventHandler>();
appBuilder.RegisterEventHandler<UserResetPasswordEvent, LogChangesUserChangedEventHandler>();
appBuilder.RegisterEventHandler<UserLoginEvent, LogChangesUserChangedEventHandler>();
appBuilder.RegisterEventHandler<UserLogoutEvent, LogChangesUserChangedEventHandler>();
appBuilder.RegisterEventHandler<UserRoleAddedEvent, LogChangesUserChangedEventHandler>();
appBuilder.RegisterEventHandler<UserRoleRemovedEvent, LogChangesUserChangedEventHandler>();

return appBuilder;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using VirtoCommerce.Platform.Core.ChangeLog;
using VirtoCommerce.Platform.Core.Common;
using VirtoCommerce.Platform.Core.Security;
using VirtoCommerce.Platform.Core.Security.Search;
Expand All @@ -23,8 +22,8 @@ public static IServiceCollection AddSecurityServices(this IServiceCollection ser
services.AddTransient<ISecurityRepository, SecurityRepository>();
services.AddTransient<Func<ISecurityRepository>>(provider => () => provider.CreateScope().ServiceProvider.GetService<ISecurityRepository>());

services.AddScoped<IUserApiKeyService, UserApiKeyService>();
services.AddScoped<IUserApiKeySearchService, UserApiKeySearchService>();
services.AddSingleton<IUserApiKeyService, UserApiKeyService>();
services.AddSingleton<IUserApiKeySearchService, UserApiKeySearchService>();

services.AddScoped<IUserNameResolver, HttpContextUserResolver>();
services.AddSingleton<IPermissionsRegistrar, DefaultPermissionProvider>();
Expand Down Expand Up @@ -57,8 +56,8 @@ public static IServiceCollection AddSecurityServices(this IServiceCollection ser
services.Configure(setupAction);
}

services.AddSingleton(provider => new LogChangesUserChangedEventHandler(provider.CreateScope().ServiceProvider.GetService<IChangeLogService>()));
services.AddSingleton(provider => new UserApiKeyActualizeEventHandler(provider.CreateScope().ServiceProvider.GetService<IUserApiKeyService>()));
services.AddSingleton<LogChangesUserChangedEventHandler>();
services.AddSingleton<UserApiKeyActualizeEventHandler>();

services.AddTransient<IServerCertificateService, ServerCertificateService>();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Moq;
using Nager.Country;
using VirtoCommerce.Platform.Core.Common;
Expand All @@ -10,12 +11,12 @@ namespace VirtoCommerce.Platform.Tests.Common
public class CountriesServiceTests
{
[Fact]
public void CanGetCountries()
public async Task CanGetCountries()
{
var filesystemCountryService = new Mock<ICountriesService>();
var service = new CountriesService(filesystemCountryService.Object as FileSystemCountriesService);

var countries = service.GetCountriesAsync().GetAwaiter().GetResult();
var countries = await service.GetCountriesAsync();

Assert.Equal(249, countries.Count);
}
Expand Down
Loading

0 comments on commit e8ba03a

Please sign in to comment.