feat(Worker): Implementa servicio de notificación para alertas de fallos críticos - Se remueve .env y se utilizan appsettings.Development.json y User Secrets

This commit is contained in:
2025-07-03 15:55:48 -03:00
parent 4cc9d239cf
commit 20b6babc37
12 changed files with 292 additions and 212 deletions

View File

@@ -6,90 +6,62 @@ using Mercados.Infrastructure.Persistence.Repositories;
using Mercados.Worker;
using Polly;
using Polly.Extensions.Http;
using Mercados.Infrastructure.Services;
using DotNetEnv;
using DotNetEnv.Configuration;
// Carga las variables de entorno desde el archivo .env en la raíz de la solución.
DotNetEnv.Env.Load();
var envFilePath = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../../../.env"));
// Cargamos el archivo .env desde la ruta explícita.
// Si no lo encuentra, Load retornará false.
if (!Env.Load(envFilePath).Any())
{
Console.WriteLine($"ADVERTENCIA: No se pudo encontrar el archivo .env en la ruta: {envFilePath}");
}
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
// --- Configuración del Host ---
// Esto prepara el host del servicio, permitiendo la inyección de dependencias,
// la configuración desde appsettings.json y el logging.
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
// Obtenemos la configuración desde el host builder para usarla aquí.
// La línea 'config.AddDotNetEnv(optional: true);' ha sido eliminada.
IConfiguration configuration = hostContext.Configuration;
// --- 1. Registro de Servicios de Infraestructura ---
// Registramos la fábrica de conexiones a la BD. Es un Singleton porque
// solo necesita ser creada una vez para leer la cadena de conexión.
services.AddSingleton<IDbConnectionFactory, SqlConnectionFactory>();
// Registramos los repositorios. Se crean "por petición" (Scoped).
// En un worker, "Scoped" significa que se creará una instancia por cada
// ejecución del servicio, lo cual es seguro y eficiente.
services.AddScoped<ICotizacionGanadoRepository, CotizacionGanadoRepository>();
services.AddScoped<ICotizacionGranoRepository, CotizacionGranoRepository>();
services.AddScoped<ICotizacionBolsaRepository, CotizacionBolsaRepository>();
services.AddScoped<IFuenteDatoRepository, FuenteDatoRepository>();
//services.AddScoped<INotificationService, ConsoleNotificationService>();
services.AddScoped<INotificationService, EmailNotificationService>();
// --- 2. Registro de los Data Fetchers ---
// Registramos CADA uno de nuestros fetchers. El contenedor de DI sabrá
// que todos implementan la interfaz IDataFetcher.
// Descomentados para la versión final y funcional.
services.AddScoped<IDataFetcher, MercadoAgroFetcher>();
services.AddScoped<IDataFetcher, BcrDataFetcher>();
services.AddScoped<IDataFetcher, FinnhubDataFetcher>();
services.AddScoped<IDataFetcher, YahooFinanceDataFetcher>();
// El cliente HTTP es fundamental para hacer llamadas a APIs externas.
// Le damos un nombre al cliente de Finnhub para cumplir con los requisitos de su constructor.
//services.AddHttpClient("Finnhub");
// Configuramos CADA cliente HTTP que nuestros fetchers usan.
// IHttpClientFactory nos permite nombrar y configurar clientes de forma independiente.
// Cliente para el scraper del MercadoAgro, con una política de reintentos
services.AddHttpClient("MercadoAgroFetcher")
.AddPolicyHandler(GetRetryPolicy());
// --- 3. Configuración de Clientes HTTP con Polly ---
services.AddHttpClient("MercadoAgroFetcher").AddPolicyHandler(GetRetryPolicy());
services.AddHttpClient("BcrDataFetcher").AddPolicyHandler(GetRetryPolicy());
services.AddHttpClient("FinnhubDataFetcher").AddPolicyHandler(GetRetryPolicy());
// Cliente para la API de BCR, con la misma política de reintentos
services.AddHttpClient("BcrDataFetcher")
.AddPolicyHandler(GetRetryPolicy());
// Cliente para Finnhub, con la misma política de reintentos
services.AddHttpClient("FinnhubDataFetcher")
.AddPolicyHandler(GetRetryPolicy());
// Cliente para YahooFinance (aunque es menos probable que falle, es buena práctica incluirlo)
// La librería YahooFinanceApi usa su propio HttpClient, así que esta configuración
// no le afectará directamente. La resiliencia para YahooFinance la manejaremos de otra forma si es necesario.
// Por ahora, lo dejamos así y nos enfocamos en los que usan IHttpClientFactory.
// --- 3. Registro del Worker Principal ---
// Finalmente, registramos nuestro servicio de fondo (el worker en sí).
// --- 4. Registro del Worker Principal ---
services.AddHostedService<DataFetchingService>();
})
.Build();
// Esta función define nuestra política de reintentos.
static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy()
{
// Polly.Extensions.Http nos da este método conveniente.
return HttpPolicyExtensions
// Maneja errores de red transitorios O códigos de estado de servidor que indican un problema temporal.
.HandleTransientHttpError()
// También maneja el error 408 Request Timeout
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.RequestTimeout)
// Política de reintento con espera exponencial: 3 reintentos, esperando 2^intento segundos.
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
.HandleTransientHttpError()
.OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.RequestTimeout)
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (outcome, timespan, retryAttempt, context) =>
{
// Registramos un log cada vez que se realiza un reintento.
// Esta es una forma de hacerlo sin tener acceso directo al ILogger aquí.
Console.WriteLine($"[Polly] Reintentando petición... Intento {retryAttempt}. Esperando {timespan.TotalSeconds}s. Causa: {outcome.Exception?.Message ?? outcome.Result.ReasonPhrase}");
});
}