feat(fundamentals): add KeyedLockPool for concurrent scraping synchronization and update MQTT RPC handlers

This commit is contained in:
2026-08-24 21:35:43 +02:00
parent 8112598602
commit 600ccf299e
6 changed files with 136 additions and 69 deletions
@@ -35,15 +35,10 @@ public class FundamentalsMqttClient : ManagedMqttClient, IHostedService
/// <inheritdoc />
public async Task StartAsync(CancellationToken cancellationToken)
{
var config = new MqttConfiguration
{
Host = _configuration["MQTT:Host"] ?? _configuration["MQTT__Host"] ?? "localhost",
Port = Convert.ToInt32(_configuration["MQTT:Port"] ?? _configuration["MQTT__Port"] ?? "1883"),
ClientId = _configuration["MQTT:ClientId"] ?? "finlytic_fundamentals_" + Guid.NewGuid().ToString("N")
};
var config = MqttConfiguration.FromConfiguration(_configuration, "FinlyticFundamentals");
_logger.LogInformation("[{Channel}] [MQTT_Client] Starting Fundamentals MQTT client. Host: {Host}, ClientId: {ClientId}", "MqttChannel", config.Host, config.ClientId);
await ConnectAsync(config);
}
@@ -58,18 +53,19 @@ public class FundamentalsMqttClient : ManagedMqttClient, IHostedService
protected override async Task OnConnectedAsync()
{
_logger.LogInformation("[{Channel}] [MQTT_Client] Connected. Subscribing to RPC request topics...", "MqttChannel");
await SubscribeAsync("services/request/fundamentals_Get/#");
await SubscribeAsync("services/request/events_GetAll/#");
await SubscribeAsync("services/request/events_GetByMonth/#");
await SubscribeAsync("services/request/fundamentals_settings_GetAll/#");
await SubscribeAsync("services/request/fundamentals_settings_Update/#");
await SubscribeAsync("services/request/health_Ping/#");
await SubscribeAsync(MqttTopics.ResponseWildcard);
await SubscribeAsync(MqttTopics.RequestFilter(MqttTopics.Channels.FundamentalsGet));
await SubscribeAsync(MqttTopics.RequestFilter(MqttTopics.Channels.EventsGetAll));
await SubscribeAsync(MqttTopics.RequestFilter(MqttTopics.Channels.EventsGetByMonth));
await SubscribeAsync(MqttTopics.RequestFilter(MqttTopics.Channels.FundamentalsSettingsGetAll));
await SubscribeAsync(MqttTopics.RequestFilter(MqttTopics.Channels.FundamentalsSettingsUpdate));
await SubscribeAsync(MqttTopics.RequestFilter(MqttTopics.Channels.HealthPing));
FinlyticLogBroadcaster.OnLogPublished = async (logDto) =>
{
if (IsConnected && string.Equals(logDto.ServiceName, "FinlyticFundamentals", StringComparison.OrdinalIgnoreCase))
{
await PublishAsync("finlytic/logs/FinlyticFundamentals", logDto);
await PublishAsync(MqttTopics.Logs("FinlyticFundamentals"), logDto);
}
};
}
@@ -84,27 +80,27 @@ public class FundamentalsMqttClient : ManagedMqttClient, IHostedService
var correlationId = topic.Substring(lastSlash + 1);
if (topic.StartsWith("services/request/fundamentals_Get", StringComparison.OrdinalIgnoreCase))
if (topic.StartsWith(MqttTopics.RequestPrefix + MqttTopics.Channels.FundamentalsGet, StringComparison.OrdinalIgnoreCase))
{
await OnFundamentalsGetAsync(payload, correlationId);
}
else if (topic.StartsWith("services/request/events_GetAll", StringComparison.OrdinalIgnoreCase))
else if (topic.StartsWith(MqttTopics.RequestPrefix + MqttTopics.Channels.EventsGetAll, StringComparison.OrdinalIgnoreCase))
{
await OnEventsGetAllAsync(correlationId);
}
else if (topic.StartsWith("services/request/events_GetByMonth", StringComparison.OrdinalIgnoreCase))
else if (topic.StartsWith(MqttTopics.RequestPrefix + MqttTopics.Channels.EventsGetByMonth, StringComparison.OrdinalIgnoreCase))
{
await OnEventsGetByMonthAsync(payload, correlationId);
}
else if (topic.StartsWith("services/request/fundamentals_settings_GetAll", StringComparison.OrdinalIgnoreCase))
else if (topic.StartsWith(MqttTopics.RequestPrefix + MqttTopics.Channels.FundamentalsSettingsGetAll, StringComparison.OrdinalIgnoreCase))
{
await OnSettingsGetAllAsync(correlationId);
}
else if (topic.StartsWith("services/request/fundamentals_settings_Update", StringComparison.OrdinalIgnoreCase))
else if (topic.StartsWith(MqttTopics.RequestPrefix + MqttTopics.Channels.FundamentalsSettingsUpdate, StringComparison.OrdinalIgnoreCase))
{
await OnSettingsUpdateAsync(payload, correlationId);
}
else if (topic.StartsWith("services/request/health_Ping", StringComparison.OrdinalIgnoreCase))
else if (topic.StartsWith(MqttTopics.RequestPrefix + MqttTopics.Channels.HealthPing, StringComparison.OrdinalIgnoreCase))
{
await OnHealthPingAsync(topic, correlationId);
}
@@ -135,7 +131,7 @@ public class FundamentalsMqttClient : ManagedMqttClient, IHostedService
request.Isin, request.ForceRefresh.ToString(), correlationId);
var fundamentals = await dbService.GetFundamentalsAsync(request.Isin, request.Ticker, request.ForceRefresh);
var responseTopic = $"services/response/fundamentals_Get/{correlationId}";
var responseTopic = MqttTopics.ResponseTopic(MqttTopics.Channels.FundamentalsGet, correlationId);
await finlyticLogger.LogInfoAsync(SettingKeys.MqttChannel, "[FinlyticFundamentals] [MQTT_Client] Publishing RPC fundamentals response to '{ResponseTopic}'", responseTopic);
await PublishAsync(responseTopic, fundamentals);
@@ -156,7 +152,7 @@ public class FundamentalsMqttClient : ManagedMqttClient, IHostedService
try
{
var events = await dbService.GetAllEventsAsync();
var responseTopic = $"services/response/events_GetAll/{correlationId}";
var responseTopic = MqttTopics.ResponseTopic(MqttTopics.Channels.EventsGetAll, correlationId);
await finlyticLogger.LogInfoAsync(SettingKeys.MqttChannel, "[FinlyticFundamentals] [MQTT_Client] Publishing events RPC response to '{ResponseTopic}'", responseTopic);
await PublishAsync(responseTopic, events);
@@ -183,7 +179,7 @@ public class FundamentalsMqttClient : ManagedMqttClient, IHostedService
await finlyticLogger.LogInfoAsync(SettingKeys.MqttChannel, "[FinlyticFundamentals] [MQTT_Client] Processing RPC events_GetByMonth request for {Year}/{Month} [CorrelationId: {CorrelationId}]", request.Year, request.Month, correlationId);
var events = await dbService.GetEventsByMonthAsync(request.Year, request.Month);
var responseTopic = $"services/response/events_GetByMonth/{correlationId}";
var responseTopic = MqttTopics.ResponseTopic(MqttTopics.Channels.EventsGetByMonth, correlationId);
await finlyticLogger.LogInfoAsync(SettingKeys.MqttChannel, "[FinlyticFundamentals] [MQTT_Client] Publishing monthly events RPC response to '{ResponseTopic}'", responseTopic);
await PublishAsync(responseTopic, events);
@@ -204,7 +200,7 @@ public class FundamentalsMqttClient : ManagedMqttClient, IHostedService
try
{
var settings = await settingsService.GetAllRegisteredSettingsAsync(new[] { typeof(SettingKeys) });
var responseTopic = $"services/response/fundamentals_settings_GetAll/{correlationId}";
var responseTopic = MqttTopics.ResponseTopic(MqttTopics.Channels.FundamentalsSettingsGetAll, correlationId);
await PublishAsync(responseTopic, settings);
await finlyticLogger.LogInfoAsync(SettingKeys.MqttChannel, "[FinlyticFundamentals] [Settings_GetAll] Published {Count} settings to '{ResponseTopic}'", settings.Count, responseTopic);
@@ -251,7 +247,7 @@ public class FundamentalsMqttClient : ManagedMqttClient, IHostedService
}
var currentSettings = await settingsService.GetAllRegisteredSettingsAsync(new[] { typeof(SettingKeys) });
var responseTopic = $"services/response/fundamentals_settings_Update/{correlationId}";
var responseTopic = MqttTopics.ResponseTopic(MqttTopics.Channels.FundamentalsSettingsUpdate, correlationId);
await PublishAsync(responseTopic, currentSettings);
}
catch (Exception ex)
@@ -267,7 +263,7 @@ public class FundamentalsMqttClient : ManagedMqttClient, IHostedService
await using var scope = _scopeFactory.CreateAsyncScope();
var finlyticLogger = scope.ServiceProvider.GetRequiredService<IFinlyticLogger<FundamentalsMqttClient>>();
var respTopic = $"services/response/health_Ping/{correlationId}";
var respTopic = MqttTopics.ResponseTopic(MqttTopics.Channels.HealthPing, correlationId);
await PublishAsync(respTopic, new ServiceHealthResponse("FinlyticFundamentals", "Online", DateTime.UtcNow, "Connected"));
await finlyticLogger.LogInfoAsync(SettingKeys.HealthPingChannel, "[FinlyticFundamentals] [Health_Ping] Responded to live health_Ping RPC request [CorrelationId: {CorrelationId}].", correlationId);
}