feat(Worker): Implementa política de reintentos con Polly para resiliencia en la obtención de datos
This commit is contained in:
		| @@ -69,7 +69,8 @@ namespace Mercados.Infrastructure.DataFetchers | |||||||
|             _logger.LogInformation("Iniciando fetch para {SourceName}.", SourceName); |             _logger.LogInformation("Iniciando fetch para {SourceName}.", SourceName); | ||||||
|             try |             try | ||||||
|             { |             { | ||||||
|                 var client = _httpClientFactory.CreateClient(); // Creamos el cliente una vez |                 // Pedimos el cliente con el nombre correcto | ||||||
|  |                 var client = _httpClientFactory.CreateClient("BcrDataFetcher"); | ||||||
|  |  | ||||||
|                 var token = await GetAuthTokenAsync(client); |                 var token = await GetAuthTokenAsync(client); | ||||||
|                 if (string.IsNullOrEmpty(token)) |                 if (string.IsNullOrEmpty(token)) | ||||||
|   | |||||||
| @@ -36,7 +36,8 @@ namespace Mercados.Infrastructure.DataFetchers | |||||||
|             { |             { | ||||||
|                 throw new InvalidOperationException("La clave de API de Finnhub no está configurada en appsettings.json (ApiKeys:Finnhub)"); |                 throw new InvalidOperationException("La clave de API de Finnhub no está configurada en appsettings.json (ApiKeys:Finnhub)"); | ||||||
|             } |             } | ||||||
|             _client = new FinnhubClient(httpClientFactory.CreateClient("Finnhub"), apiKey); |             // Le pasamos el cliente HTTP que ya está configurado con Polly en Program.cs | ||||||
|  |             _client = new FinnhubClient(httpClientFactory.CreateClient("FinnhubDataFetcher"), apiKey); | ||||||
|             _cotizacionRepository = cotizacionRepository; |             _cotizacionRepository = cotizacionRepository; | ||||||
|             _fuenteDatoRepository = fuenteDatoRepository; |             _fuenteDatoRepository = fuenteDatoRepository; | ||||||
|             _logger = logger; |             _logger = logger; | ||||||
|   | |||||||
| @@ -61,7 +61,8 @@ namespace Mercados.Infrastructure.DataFetchers | |||||||
|  |  | ||||||
|         private async Task<string> GetHtmlContentAsync() |         private async Task<string> GetHtmlContentAsync() | ||||||
|         { |         { | ||||||
|             var client = _httpClientFactory.CreateClient(); |             // Pedimos el cliente HTTP con el nombre específico que tiene la política de Polly | ||||||
|  |             var client = _httpClientFactory.CreateClient("MercadoAgroFetcher"); | ||||||
|             // Es importante simular un navegador para evitar bloqueos. |             // Es importante simular un navegador para evitar bloqueos. | ||||||
|             client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"); |             client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ | |||||||
|     <PackageReference Include="Dapper" Version="2.1.66" /> |     <PackageReference Include="Dapper" Version="2.1.66" /> | ||||||
|     <PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" /> |     <PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" /> | ||||||
|     <PackageReference Include="Microsoft.Extensions.Http" Version="9.0.6" /> |     <PackageReference Include="Microsoft.Extensions.Http" Version="9.0.6" /> | ||||||
|  |     <PackageReference Include="Microsoft.Extensions.Http.Polly" Version="9.0.6" /> | ||||||
|     <PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" /> |     <PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" /> | ||||||
|     <PackageReference Include="ThreeFourteen.Finnhub.Client" Version="1.2.0" /> |     <PackageReference Include="ThreeFourteen.Finnhub.Client" Version="1.2.0" /> | ||||||
|     <PackageReference Include="YahooFinanceApi" Version="2.3.3" /> |     <PackageReference Include="YahooFinanceApi" Version="2.3.3" /> | ||||||
|   | |||||||
| @@ -4,6 +4,8 @@ using Mercados.Infrastructure.DataFetchers; | |||||||
| using Mercados.Infrastructure.Persistence; | using Mercados.Infrastructure.Persistence; | ||||||
| using Mercados.Infrastructure.Persistence.Repositories; | using Mercados.Infrastructure.Persistence.Repositories; | ||||||
| using Mercados.Worker; | using Mercados.Worker; | ||||||
|  | using Polly; | ||||||
|  | using Polly.Extensions.Http; | ||||||
|  |  | ||||||
| // Carga las variables de entorno desde el archivo .env en la raíz de la solución. | // Carga las variables de entorno desde el archivo .env en la raíz de la solución. | ||||||
| DotNetEnv.Env.Load(); | DotNetEnv.Env.Load(); | ||||||
| @@ -43,7 +45,27 @@ IHost host = Host.CreateDefaultBuilder(args) | |||||||
|  |  | ||||||
|         // El cliente HTTP es fundamental para hacer llamadas a APIs externas. |         // 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. |         // Le damos un nombre al cliente de Finnhub para cumplir con los requisitos de su constructor. | ||||||
|         services.AddHttpClient("Finnhub"); |         //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()); | ||||||
|  |  | ||||||
|  |         // 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 --- |         // --- 3. Registro del Worker Principal --- | ||||||
| @@ -53,4 +75,23 @@ IHost host = Host.CreateDefaultBuilder(args) | |||||||
|     }) |     }) | ||||||
|     .Build(); |     .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)),  | ||||||
|  |             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}"); | ||||||
|  |             }); | ||||||
|  | } | ||||||
|  |  | ||||||
| await host.RunAsync(); | await host.RunAsync(); | ||||||
		Reference in New Issue
	
	Block a user