-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAdminAnnouncementProjectionRecoveryWorker.cs
More file actions
103 lines (91 loc) · 4.14 KB
/
Copy pathAdminAnnouncementProjectionRecoveryWorker.cs
File metadata and controls
103 lines (91 loc) · 4.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
using Admin.Application.Authorization;
using Admin.Application.Consumers;
using Admin.Application.ProcessManagers;
using Admin.Infrastructure.Configuration;
using BuildingBlocks.Application.Dispatching;
using BuildingBlocks.Infrastructure.Modules;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NodaTime;
using DomainClock = BuildingBlocks.Domain.Time.IClock;
namespace Admin.Infrastructure.Workers;
internal sealed class AdminAnnouncementProjectionRecoveryWorker : ModulePollingBackgroundService
{
private readonly DomainClock _clock;
private readonly ILogger<AdminAnnouncementProjectionRecoveryWorker> _logger;
private readonly IServiceScopeFactory _scopeFactory;
private readonly IAdminAnnouncementProjectionProcessManagerStore _store;
private readonly int _batchSize;
private readonly Duration _staleAfter;
public AdminAnnouncementProjectionRecoveryWorker(
DomainClock clock,
IServiceScopeFactory scopeFactory,
BuildingBlocks.Application.Modules.IModuleExecutionGate moduleExecutionGate,
IAdminAnnouncementProjectionProcessManagerStore store,
IOptions<AdminInfrastructureOptions> options,
ILogger<AdminAnnouncementProjectionRecoveryWorker> logger)
: base(moduleExecutionGate, ResolvePollInterval(options).ToTimeSpan(), logger)
{
ArgumentNullException.ThrowIfNull(options);
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory));
_store = store ?? throw new ArgumentNullException(nameof(store));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
var worker = options.Value.ProjectionRecoveryWorker;
_batchSize = worker.BatchSize;
_staleAfter = Duration.FromMilliseconds(worker.StaleAfterMilliseconds);
}
protected override string ModuleKey => AdminModuleInfo.ModuleKey;
protected override async Task ExecuteEnabledIterationAsync(CancellationToken stoppingToken)
{
var updatedBefore = _clock.GetCurrentInstant() - _staleAfter;
var checkpoints = await _store.ListRecoverableAsync(updatedBefore, _batchSize, stoppingToken);
foreach (var checkpoint in checkpoints)
{
try
{
using var scope = _scopeFactory.CreateScope();
var dispatcher = scope.ServiceProvider.GetRequiredService<IDispatcher>();
var result = await dispatcher.Send(
new RecoverAdminAnnouncementProjectionCommand(ToProjection(checkpoint.State)),
stoppingToken);
if (result.IsFailure)
{
_logger.LogWarning(
"Admin announcement recovery failed for announcement {AnnouncementId}: {ErrorCode} {ErrorMessage}",
checkpoint.State.AnnouncementId,
result.Error.Code,
result.Error.Message);
}
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
throw;
}
catch (Exception exception)
{
_logger.LogWarning(
exception,
"Admin announcement recovery failed for announcement {AnnouncementId}.",
checkpoint.State.AnnouncementId);
}
}
}
private static Duration ResolvePollInterval(IOptions<AdminInfrastructureOptions> options)
{
ArgumentNullException.ThrowIfNull(options);
return Duration.FromMilliseconds(options.Value.ProjectionRecoveryWorker.PollIntervalMilliseconds);
}
private static AdminAnnouncementProjection ToProjection(AdminAnnouncementProjectionProcessState state)
{
return new AdminAnnouncementProjection(
state.AnnouncementId,
state.Title,
state.Body,
state.PublishedUtc,
state.PublishedByActorId,
state.SourceModuleKey,
state.SourceReference);
}
}