refactor: Extract ProfileWorker into modular components
- Create ProfileWorker namespace with 4 separate components - ProfileWorker.cs: BackgroundService orchestrator with IOptions support - ProfileWorkerOptions.cs: Configuration model with validation - ProfileCache.cs: Thread-safe cache using composition pattern - DependencyInjection.cs: Service registration with IValidateOptions Benefits: - Separation of concerns (orchestration, config, state, DI) - IOptions pattern for appsettings.json configuration - Startup validation for configuration errors - Better testability and maintainability
This commit is contained in:
42
ECMJobRunner.WebCron/ProfileWorker/DependencyInjection.cs
Normal file
42
ECMJobRunner.WebCron/ProfileWorker/DependencyInjection.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ECMJobRunner.WebCron.ProfileWorker;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static IServiceCollection AddProfileWorker(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
// Configure ProfileWorker options from appsettings.json
|
||||
services.Configure<ProfileWorkerOptions>(
|
||||
configuration.GetSection(ProfileWorkerOptions.SectionName));
|
||||
|
||||
// Validate options at startup
|
||||
services.AddSingleton<IValidateOptions<ProfileWorkerOptions>, ProfileWorkerOptionsValidator>();
|
||||
|
||||
services.AddHostedService<ProfileWorker>();
|
||||
services.AddSingleton<ProfileCache>();
|
||||
services.AddScoped<ProfileWork>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates ProfileWorkerOptions configuration at startup.
|
||||
/// </summary>
|
||||
internal class ProfileWorkerOptionsValidator : IValidateOptions<ProfileWorkerOptions>
|
||||
{
|
||||
public ValidateOptionsResult Validate(string? name, ProfileWorkerOptions options)
|
||||
{
|
||||
if (options.IntervalMS <= 0)
|
||||
{
|
||||
return ValidateOptionsResult.Fail("ProfileWorker:IntervalMs must be greater than 0");
|
||||
}
|
||||
|
||||
if (options.IntervalMS < 100)
|
||||
{
|
||||
return ValidateOptionsResult.Fail("ProfileWorker:IntervalMs should be at least 100ms to avoid excessive CPU usage");
|
||||
}
|
||||
|
||||
return ValidateOptionsResult.Success;
|
||||
}
|
||||
}
|
||||
23
ECMJobRunner.WebCron/ProfileWorker/ProfileCache.cs
Normal file
23
ECMJobRunner.WebCron/ProfileWorker/ProfileCache.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using ECMJobRunner.Application.Common.Dtos;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace ECMJobRunner.WebCron.ProfileWorker;
|
||||
|
||||
/// <summary>
|
||||
/// Thread-safe cache for storing active profile configurations.
|
||||
/// Used to track profile state and detect changes in schedule or removal.
|
||||
/// </summary>
|
||||
public class ProfileCache
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, CfgProfileDto> _cache = new();
|
||||
|
||||
public CfgProfileDto? Get(string jobId) => _cache.TryGetValue(jobId, out var profile) ? profile : null;
|
||||
|
||||
public void AddOrUpdate(string jobId, CfgProfileDto profile) => _cache[jobId] = profile;
|
||||
|
||||
public bool TryRemove(string jobId, out CfgProfileDto? profile) => _cache.TryRemove(jobId, out profile);
|
||||
|
||||
public IEnumerable<string> GetAllJobIds() => _cache.Keys;
|
||||
|
||||
public bool TryGetValue(string jobId, out CfgProfileDto? profile) => _cache.TryGetValue(jobId, out profile);
|
||||
}
|
||||
39
ECMJobRunner.WebCron/ProfileWorker/ProfileWorker.cs
Normal file
39
ECMJobRunner.WebCron/ProfileWorker/ProfileWorker.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ECMJobRunner.WebCron.ProfileWorker;
|
||||
|
||||
public class ProfileWorker(
|
||||
ILogger<ProfileWorker> Logger,
|
||||
IServiceScopeFactory ScopeFactory,
|
||||
IOptions<ProfileWorkerOptions> Options) : BackgroundService
|
||||
{
|
||||
private readonly ProfileWorkerOptions _options = Options.Value;
|
||||
|
||||
private int _workCount = 0;
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
_workCount++;
|
||||
if (Logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
Logger.LogInformation("Worker running {workCount} at: {time}", _workCount, DateTimeOffset.Now);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Create a scope to resolve scoped services (ISQLExecutor used by MediatR pipeline)
|
||||
using var scope = ScopeFactory.CreateScope();
|
||||
var work = scope.ServiceProvider.GetRequiredService<ProfileWork>();
|
||||
await work.ExecuteAsync(stoppingToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "An unexpected error occurred in ProfileWorker. Work count: {workCount}", _workCount);
|
||||
}
|
||||
|
||||
await Task.Delay(_options.IntervalMS, stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
ECMJobRunner.WebCron/ProfileWorker/ProfileWorkerOptions.cs
Normal file
15
ECMJobRunner.WebCron/ProfileWorker/ProfileWorkerOptions.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace ECMJobRunner.WebCron.ProfileWorker;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration options for ProfileWorker background service.
|
||||
/// </summary>
|
||||
public class ProfileWorkerOptions
|
||||
{
|
||||
public const string SectionName = "ProfileWorker";
|
||||
|
||||
/// <summary>
|
||||
/// Interval in milliseconds between profile synchronization checks.
|
||||
/// Default: 1000ms (1 second)
|
||||
/// </summary>
|
||||
public int IntervalMS { get; set; } = 1000;
|
||||
}
|
||||
Reference in New Issue
Block a user