Skip to content

Commit

Permalink
chore: block concurrent requests in decorators
Browse files Browse the repository at this point in the history
  • Loading branch information
alsami committed Oct 23, 2020
1 parent 267c2d5 commit a9aaa12
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ namespace Covid19Api.Services.Abstractions.Loader
{
public interface ICountryMetaDataLoader
{
Task<CountryMetaData[]> LoadCountryMetaDataByCountryAsync();
Task<CountryMetaData[]> LoadCountryMetaDataAsync();
}
}
15 changes: 3 additions & 12 deletions src/Covid19Api.Services.Abstractions/Models/CountryMetaData.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
// ReSharper disable MemberCanBePrivate.Global
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
// ReSharper disable UnusedMember.Local
// ReSharper disable ClassNeverInstantiated.Global

namespace Covid19Api.Services.Abstractions.Models
{
public class CountryMetaData
Expand All @@ -14,14 +9,10 @@ public CountryMetaData(string name, string alpha2Code, string[] altSpellings)
this.AltSpellings = altSpellings;
}

private CountryMetaData()
{
}

public string Name { get; set; } = null!;
public string Name { get; }

public string Alpha2Code { get; set; } = null!;
public string Alpha2Code { get; }

public string[] AltSpellings { get; set; } = null!;
public string[] AltSpellings { get; }
}
}
3 changes: 2 additions & 1 deletion src/Covid19Api.Services/Covid19Api.Services.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<NoWarn>S3881</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0-rc.2.20475.5" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0-rc.2.20475.5" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0-rc.2.20475.5" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="System.Text.Json" Version="5.0.0-rc.2.20475.5" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Covid19Api.Services.Abstractions.Compression;
using Covid19Api.Services.Abstractions.Loader;
Expand All @@ -10,10 +11,12 @@

namespace Covid19Api.Services.Decorator
{
public class CountryMetaDataLoaderDecorator : ICountryMetaDataLoader
public class CountryMetaDataLoaderDecorator : ICountryMetaDataLoader, IDisposable
{
private const string CacheKey = "CountryMetaData";

private static readonly SemaphoreSlim Mutex = new SemaphoreSlim(1);

private readonly IDistributedCache distributedCache;
private readonly ICountryMetaDataLoader countryMetaDataLoader;
private readonly ICompressionService compressionService;
Expand All @@ -25,8 +28,28 @@ public CountryMetaDataLoaderDecorator(IDistributedCache distributedCache,
this.countryMetaDataLoader = countryMetaDataLoader;
this.compressionService = compressionService;
}


public void Dispose()
{
Mutex.Dispose();
GC.SuppressFinalize(this);
}

public async Task<CountryMetaData[]> LoadCountryMetaDataAsync()
{
try
{
await Mutex.WaitAsync();
return await this.LoadCountryMetaDataInternalAsync();
}
finally
{
Mutex.Release(1);
}
}

public async Task<CountryMetaData[]> LoadCountryMetaDataByCountryAsync()
private async Task<CountryMetaData[]> LoadCountryMetaDataInternalAsync()
{
var cached = await this.distributedCache.GetAsync(CacheKey);

Expand All @@ -36,17 +59,21 @@ public async Task<CountryMetaData[]> LoadCountryMetaDataByCountryAsync()
return JsonSerializer.Deserialize<CountryMetaData[]>(decompressed) ?? Array.Empty<CountryMetaData>();
}

var fetchedCountryMetaData = await this.countryMetaDataLoader.LoadCountryMetaDataByCountryAsync();
var fetchedCountryMetaData = await this.countryMetaDataLoader.LoadCountryMetaDataAsync();
await this.CacheAsync(fetchedCountryMetaData);

return fetchedCountryMetaData;
}

private async ValueTask CacheAsync(CountryMetaData[] fetchedCountryMetaData)
{
var serialized = JsonSerializer.Serialize(fetchedCountryMetaData);
var compressed = await this.compressionService.CompressAsync(Encoding.UTF8.GetBytes(serialized));

await this.distributedCache.SetAsync(CacheKey, compressed, new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTime.UtcNow.AddDays(10)
});

return fetchedCountryMetaData;
}
}
}
24 changes: 23 additions & 1 deletion src/Covid19Api.Services/Decorator/HtmlDocumentLoaderDecorator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Covid19Api.Services.Abstractions.Compression;
using Covid19Api.Services.Abstractions.Loader;
Expand All @@ -9,10 +10,12 @@

namespace Covid19Api.Services.Decorator
{
public class HtmlDocumentLoaderDecorator : IHtmlDocumentLoader
public class HtmlDocumentLoaderDecorator : IHtmlDocumentLoader, IDisposable
{
private const string Key = "HTMLDOCCACHE";

private static readonly SemaphoreSlim Mutex = new SemaphoreSlim(1);

private readonly IDistributedCache distributedCache;
private readonly IHtmlDocumentLoader htmlDocumentLoader;
private readonly ICompressionService compressionService;
Expand All @@ -25,7 +28,26 @@ public HtmlDocumentLoaderDecorator(IDistributedCache distributedCache, IHtmlDocu
this.compressionService = compressionService;
}

public void Dispose()
{
Mutex.Dispose();
GC.SuppressFinalize(this);
}

public async Task<HtmlDocument> LoadAsync()
{
try
{
await Mutex.WaitAsync();
return await this.LoadInternalAsync();
}
finally
{
Mutex.Release(1);
}
}

private async Task<HtmlDocument> LoadInternalAsync()
{
HtmlDocument document;
var compressed = await this.distributedCache.GetAsync(Key);
Expand Down
16 changes: 14 additions & 2 deletions src/Covid19Api.Services/Loader/CountryMetaDataLoader.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using Covid19Api.Services.Abstractions.Loader;
using Covid19Api.Services.Abstractions.Models;
Expand All @@ -13,6 +14,11 @@ public class CountryMetaDataLoader : ICountryMetaDataLoader
private const string ApiUrl = "https://restcountries.eu/rest/v2/all";
#pragma warning restore

private static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

private readonly ILogger<CountryMetaDataLoader> logger;
private readonly IHttpClientFactory httpClientFactory;

Expand All @@ -22,13 +28,19 @@ public CountryMetaDataLoader(ILogger<CountryMetaDataLoader> logger, IHttpClientF
this.httpClientFactory = httpClientFactory;
}

public async Task<CountryMetaData[]> LoadCountryMetaDataByCountryAsync()
public async Task<CountryMetaData[]> LoadCountryMetaDataAsync()
{
var client = this.httpClientFactory.CreateClient();

var response = await client.GetAsync(ApiUrl);

if (response.IsSuccessStatusCode) return await response.Content.ReadAsAsync<CountryMetaData[]>();
if (response.IsSuccessStatusCode)
{
var deserialized = await JsonSerializer.DeserializeAsync<CountryMetaData[]>(
await response.Content.ReadAsStreamAsync(), SerializerOptions);

return deserialized ?? Array.Empty<CountryMetaData>();
}

var error = await response.Content.ReadAsStringAsync();
this.logger.LogError("Failed load country meta-data from {url}! Status-Code: {statusCode} Error:\n{error}",
Expand Down
2 changes: 1 addition & 1 deletion src/Covid19Api.Services/Loader/CountryStatisticsLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public CountryStatisticsLoader(ICountryMetaDataLoader countryMetaDataLoader,
{
var document = await this.htmlDocumentLoader.LoadAsync();

var countryMetaData = await this.countryMetaDataLoader.LoadCountryMetaDataByCountryAsync();
var countryMetaData = await this.countryMetaDataLoader.LoadCountryMetaDataAsync();

var countryStatistics = GetTableRows(document)
.Select(tableRow => Parse(countryMetaData, tableRow, fetchedAt))
Expand Down

0 comments on commit a9aaa12

Please sign in to comment.