From ba819c7452df8ee5054290e583bb7187ed327cb7 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Tue, 30 Jan 2024 00:43:03 -0500 Subject: [PATCH] Refactored to services --- .../Localization/CachedLocalizationService.cs | 37 ++++++ .../IRemoteLocalizationService.cs | 10 ++ .../Localization/RemoteLocalizationService.cs | 92 ++++++++++++++ .../Aggregations/LocalizationAggregation.cs | 113 ++++-------------- .../ReverseProxyBuilderExtensions.cs | 15 +-- 5 files changed, 163 insertions(+), 104 deletions(-) create mode 100644 gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/CachedLocalizationService.cs create mode 100644 gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/IRemoteLocalizationService.cs create mode 100644 gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/RemoteLocalizationService.cs diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/CachedLocalizationService.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/CachedLocalizationService.cs new file mode 100644 index 00000000..33f4a27c --- /dev/null +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/CachedLocalizationService.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.WebGateway.Aggregations.Localization; + +public class CachedLocalizationService : ISingletonDependency +{ + protected ConcurrentDictionary LocalizationDictionary { get; } + + public CachedLocalizationService() + { + LocalizationDictionary = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + } + + //TODO:skip url -> Use ServiceName_Culture as cache key + public void AddOrUpdate(string serviceNameWithCulture, ApplicationLocalizationDto localizationInfo) + { + LocalizationDictionary.AddOrUpdate(serviceNameWithCulture, localizationInfo, (key, value) => localizationInfo); + } + + public ApplicationLocalizationDto Get(string serviceNameWithCulture) + { + return LocalizationDictionary.TryGetValue(serviceNameWithCulture, out var result) ? result : null; + } + + public IDictionary GetMultipleLocalizationsAsync(string[] serviceNamesWithCulture) + { + return LocalizationDictionary + .Where(kv => serviceNamesWithCulture.Contains(kv.Key)) + .ToImmutableDictionary(); + } +} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/IRemoteLocalizationService.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/IRemoteLocalizationService.cs new file mode 100644 index 00000000..6f2dca4b --- /dev/null +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/IRemoteLocalizationService.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; + +namespace EShopOnAbp.WebGateway.Aggregations.Localization; + +public interface IRemoteLocalizationService +{ + Task> GetMultipleLocalizationsAsync(Dictionary serviceNameWithUrlDictionary); +} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/RemoteLocalizationService.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/RemoteLocalizationService.cs new file mode 100644 index 00000000..869c601f --- /dev/null +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/Localization/RemoteLocalizationService.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; +using Volo.Abp.DependencyInjection; + +namespace EShopOnAbp.WebGateway.Aggregations.Localization; + +public class RemoteLocalizationService : IRemoteLocalizationService, ITransientDependency +{ + private readonly ILogger _logger; + + public RemoteLocalizationService(ILogger logger) + { + _logger = logger; + } + + public async Task> GetMultipleLocalizationsAsync(Dictionary serviceNameWithUrlDictionary) + { + Dictionary> completedTasks = new Dictionary>(); + Dictionary> runningTasks = new Dictionary>(); + Dictionary completedResult = new Dictionary(); + + using (HttpClient httpClient = new HttpClient()) + { + foreach (var service in serviceNameWithUrlDictionary) + { + Task requestTask = MakeRequestAsync(httpClient, service.Value); + runningTasks.Add(service.Key, requestTask); + } + + while (runningTasks.Count > 0) + { + KeyValuePair> completedTask = await WaitForAnyTaskAsync(runningTasks); + + runningTasks.Remove(completedTask.Key); + + try + { + ApplicationLocalizationDto result = await completedTask.Value; + + completedTasks.Add(completedTask.Key, completedTask.Value); + completedResult.Add(completedTask.Key, result); + + _logger.LogInformation($"Key: {completedTask.Key}, Value: {result}"); + } + catch (Exception ex) + { + _logger.LogInformation($"Error for the {completedTask.Key}: {ex.Message}"); + } + } + } + + return completedResult; + } + + private async Task>> WaitForAnyTaskAsync(Dictionary> tasks) + { + Task> completedTask = Task.WhenAny(tasks.Values); + Task result = await completedTask; + + KeyValuePair> completedTaskPair = tasks.First(kv => kv.Value == result); + + return completedTaskPair; + } + + private async Task MakeRequestAsync(HttpClient httpClient, string url) + { + try + { + HttpResponseMessage response = await httpClient.GetAsync(url); + response.EnsureSuccessStatusCode(); + + string content = await response.Content.ReadAsStringAsync(); + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }; + return JsonSerializer.Deserialize(content, options); + } + catch (Exception ex) + { + // Log or handle the exception, and return a default value or throw it again if needed + Console.WriteLine($"Error making request to {url}: {ex.Message}"); + throw; + } + } +} \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/LocalizationAggregation.cs b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/LocalizationAggregation.cs index 0530a3d3..f2a004e9 100644 --- a/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/LocalizationAggregation.cs +++ b/gateways/web/src/EShopOnAbp.WebGateway/Aggregations/LocalizationAggregation.cs @@ -4,9 +4,11 @@ using System.Linq; using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; +using EShopOnAbp.WebGateway.Aggregations.Localization; using Microsoft.Extensions.Logging; using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; using Volo.Abp.Caching; +using Volo.Abp.DependencyInjection; namespace EShopOnAbp.WebGateway.Aggregations; @@ -15,12 +17,14 @@ public class LocalizationAggregation : ILocalizationAggregation //Yarp localization route name public string LocalizationEndpoint { get; } = "EshopOnAbpLocalization"; private readonly ILogger _logger; - private readonly IDistributedCache _localizationResponseCache; + private readonly CachedLocalizationService _cachedLocalizationService; + private readonly RemoteLocalizationService _remoteLocalizationService; - public LocalizationAggregation(ILogger logger, IDistributedCache localizationResponseCache) + public LocalizationAggregation(ILogger logger, CachedLocalizationService cachedLocalizationService, RemoteLocalizationService remoteLocalizationService) { _logger = logger; - _localizationResponseCache = localizationResponseCache; + _cachedLocalizationService = cachedLocalizationService; + _remoteLocalizationService = remoteLocalizationService; } public async Task GetLocalizationAsync(LocalizationRequest input) @@ -28,23 +32,30 @@ public class LocalizationAggregation : ILocalizationAggregation //Temp list Dictionary services = new Dictionary() { - { "Administration", "https://localhost:44353/api/abp/application-localization?CultureName=en" }, - { "Catalog", "https://localhost:44354/api/abp/application-localization?CultureName=en" } + { "Administration_tr", "https://localhost:44353/api/abp/application-localization?cultureName=tr&onlyDynamics=false" }, + { "Catalog_tr", "https://localhost:44354/api/abp/application-localization?cultureName=tr&onlyDynamics=false" } }; // Check the cache + var cachedLocalization = _cachedLocalizationService.GetMultipleLocalizationsAsync(services.Keys.ToArray()); + // Compare cache with input service list - - Dictionary localizationResults = await GetCompletedLocalizationResultsAsync(services); - ApplicationLocalizationDto mergedResult = MergeLocalizations(localizationResults); - + // Make request to remote localization service + var remoteLocalizationResults = await _remoteLocalizationService.GetMultipleLocalizationsAsync(services); + + //merge result + ApplicationLocalizationDto mergedResult = MergeLocalizations(remoteLocalizationResults); + + //return result + return mergedResult; } private ApplicationLocalizationDto MergeLocalizations(Dictionary localizationResults) { var localizationDto = new ApplicationLocalizationDto(); + //TODO: fix foreach (var localizationResult in localizationResults) { localizationDto.Resources.AddIfNotContains(localizationResult.Value.Resources); @@ -52,80 +63,6 @@ public class LocalizationAggregation : ILocalizationAggregation return localizationDto; } - - private async Task> GetCompletedLocalizationResultsAsync(Dictionary services) - { - Dictionary> completedTasks = new Dictionary>(); - Dictionary> runningTasks = new Dictionary>(); - Dictionary completedResult = new Dictionary(); - - using (HttpClient httpClient = new HttpClient()) - { - foreach (var service in services) - { - Task requestTask = MakeRequestAsync(httpClient, service.Value); - runningTasks.Add(service.Key, requestTask); - } - - while (runningTasks.Count > 0) - { - KeyValuePair> completedTask = await WaitForAnyTaskAsync(runningTasks); - - runningTasks.Remove(completedTask.Key); - - try - { - ApplicationLocalizationDto result = await completedTask.Value; - - completedTasks.Add(completedTask.Key, completedTask.Value); - completedResult.Add(completedTask.Key, result); - - _logger.LogInformation($"Key: {completedTask.Key}, Value: {result}"); - } - catch (Exception ex) - { - _logger.LogInformation($"Error for the {completedTask.Key}: {ex.Message}"); - } - } - } - - return completedResult; - } - - private async Task>> WaitForAnyTaskAsync( - Dictionary> tasks) - { - Task> completedTask = Task.WhenAny(tasks.Values); - Task result = await completedTask; - - // Find the key corresponding to the completed task - KeyValuePair> - completedTaskPair = tasks.First(kv => kv.Value == result); - - return completedTaskPair; - } - - private async Task MakeRequestAsync(HttpClient httpClient, string url) - { - try - { - HttpResponseMessage response = await httpClient.GetAsync(url); - response.EnsureSuccessStatusCode(); - - string content = await response.Content.ReadAsStringAsync(); - var options = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }; - return JsonSerializer.Deserialize(content, options); - } - catch (Exception ex) - { - // Log or handle the exception, and return a default value or throw it again if needed - Console.WriteLine($"Error making request to {url}: {ex.Message}"); - throw; - } - } } public class LocalizationRequest @@ -133,20 +70,10 @@ public class LocalizationRequest // Localization clusterName - localization endpoint pair public Dictionary LocalizationEndpoints { get; } public string CultureName { get; set; } - public bool? OnlyDynamics { get; set; } public LocalizationRequest(string cultureName) { CultureName = cultureName; LocalizationEndpoints = new Dictionary(); } -} - -public class LocalizationResponse -{ - // Localization url - localization resource pair - public Dictionary Localization { get; } - public string CultureName { get; set; } - public bool? OnlyDynamics { get; set; } - public string ServiceName { get; set; } } \ No newline at end of file diff --git a/gateways/web/src/EShopOnAbp.WebGateway/ReverseProxyBuilderExtensions.cs b/gateways/web/src/EShopOnAbp.WebGateway/ReverseProxyBuilderExtensions.cs index d53f4a9f..7601ff6a 100644 --- a/gateways/web/src/EShopOnAbp.WebGateway/ReverseProxyBuilderExtensions.cs +++ b/gateways/web/src/EShopOnAbp.WebGateway/ReverseProxyBuilderExtensions.cs @@ -8,13 +8,14 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations; using Yarp.ReverseProxy.Configuration; namespace EShopOnAbp.WebGateway; public static class ReverseProxyBuilderExtensions { - public static string LocalizationEndpoint = "api/abp/application-localization"; + public static string LocalizationPath = "/api/abp/application-localization?cultureName=tr&onlyDynamics=false"; public static ReverseProxyConventionBuilder MapReverseProxyWithLocalization(this IEndpointRouteBuilder endpoints) { @@ -50,22 +51,14 @@ public static class ReverseProxyBuilderExtensions var proxyConfig = context.RequestServices.GetRequiredService(); context.Request.Query.TryGetValue("CultureName", out var cultureName); - context.Request.Query.TryGetValue("OnlyDynamics", out var onlyDynamics); var input = new LocalizationRequest(cultureName); - StringBuilder stringBuilder = new StringBuilder(LocalizationEndpoint + $"?CultureName={cultureName}"); - - if (!string.IsNullOrEmpty(onlyDynamics)) - { - input.OnlyDynamics = bool.Parse(onlyDynamics); - stringBuilder.Append($"&OnlyDynamics={onlyDynamics}"); - } var clusterList = GetClusters(proxyConfig); foreach (var cluster in clusterList) { - var hostUrl = new Uri(cluster.Value.Address) + $"{stringBuilder}"; - input.LocalizationEndpoints.Add(cluster.Key, hostUrl); + var hostUrl = new Uri(cluster.Value.Address) + $"{LocalizationPath}"; + input.LocalizationEndpoints.Add($"{cluster.Key}_{cultureName}", hostUrl); } return input;