Skip to content

Commit

Permalink
Merge pull request #196 from code4romania/features/memory-cache
Browse files Browse the repository at this point in the history
Brought back memory cache for elections older than 2024
  • Loading branch information
gheorghelupu17 authored Jun 10, 2024
2 parents 79e3132 + 6068a73 commit 7c02792
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 34 deletions.
25 changes: 20 additions & 5 deletions src/ElectionResults.API/Controllers/BallotsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using ElectionResults.Core.Endpoints.Response;
using ElectionResults.Core.Infrastructure;
using ElectionResults.Core.Repositories;
using LazyCache;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

Expand All @@ -18,22 +19,27 @@ namespace ElectionResults.API.Controllers
public class BallotsController : ControllerBase
{
private readonly IResultsAggregator _resultsAggregator;
private readonly IAppCache _appCache;
private readonly ITerritoryRepository _territoryRepository;
private readonly MemoryCacheSettings _cacheSettings;

public BallotsController(IResultsAggregator resultsAggregator,
IAppCache appCache,
ITerritoryRepository territoryRepository,
IOptions<MemoryCacheSettings> cacheSettings)
{
_resultsAggregator = resultsAggregator;
_appCache = appCache;
_territoryRepository = territoryRepository;
_cacheSettings = cacheSettings.Value;
}

[HttpGet("ballots")]
public async Task<ActionResult<List<ElectionMeta>>> GetBallots()
{
var result = await _resultsAggregator.GetAllBallots();
var result = await _appCache.GetOrAddAsync(
"ballots", () => _resultsAggregator.GetAllBallots(),
DateTimeOffset.Now.AddMinutes(120));

if (result.IsSuccess)
{
Expand Down Expand Up @@ -65,12 +71,15 @@ public async Task<ActionResult<List<PartyList>>> GetCandidatesForBallot([FromQue
query.Round = null;
}

var result = await _resultsAggregator.GetBallotCandidates(query);
var result = await _appCache.GetOrAddAsync(
query.GetCacheKey(), () => _resultsAggregator.GetBallotCandidates(query),
DateTimeOffset.Now.AddMinutes(query.GetCacheDurationInMinutes()));

return result.Value;
}
catch (Exception e)
{
_appCache.Remove(query.GetCacheKey());
Log.LogError(e, "Exception encountered while retrieving voter turnout stats");
return StatusCode(500, e.StackTrace);
}
Expand All @@ -96,7 +105,11 @@ public async Task<ActionResult<ElectionResponse>> GetBallot([FromQuery] Election
query.Round = null;
}

var result = await _resultsAggregator.GetBallotResults(query);
var expiration = GetExpirationDate(query);

var result = await _appCache.GetOrAddAsync(
query.GetCacheKey(), () => _resultsAggregator.GetBallotResults(query),
expiration);

var newsFeed = await _resultsAggregator.GetNewsFeed(query, result.Value.Meta.ElectionId);
result.Value.ElectionNews = newsFeed;
Expand All @@ -105,6 +118,7 @@ public async Task<ActionResult<ElectionResponse>> GetBallot([FromQuery] Election
}
catch (Exception e)
{
_appCache.Remove(query.GetCacheKey());
Log.LogError(e, "Exception encountered while retrieving voter turnout stats");
return StatusCode(500, e.StackTrace);
}
Expand Down Expand Up @@ -182,11 +196,12 @@ public async Task<ActionResult<List<LocationData>>> GetCountries([FromQuery] int

private DateTimeOffset GetExpirationDate(ElectionResultsQuery electionResultsQuery)
{
if (electionResultsQuery.BallotId <= 110) // ballot older than parliament elections in 2020
if (electionResultsQuery.BallotId <= 113) // ballot older than parliament elections in 2020
{
return DateTimeOffset.Now.AddDays(1);
}
return DateTimeOffset.Now.AddMinutes(_cacheSettings.ResultsCacheInMinutes);

return DateTimeOffset.Now.AddMinutes(1);
}
}
}
7 changes: 1 addition & 6 deletions src/ElectionResults.API/ElectionResults.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,7 @@
<None Include="wwwroot\*" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Import\**" />
<Content Remove="Import\**" />
<EmbeddedResource Remove="Import\**" />
<None Remove="Import\**" />
</ItemGroup>
<ItemGroup>
<Folder Include="Import\" />
<Folder Include="Options\" />
<Folder Include="wwwroot\lib\" />
<Folder Include="wwwroot\upload\" />
Expand Down
2 changes: 2 additions & 0 deletions src/ElectionResults.API/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public void ConfigureServices(IServiceCollection services)
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(new SnakeCaseNamingPolicy()));
});
services.AddLazyCache();
RegisterDependencies(services, Configuration);
services.AddSwaggerGen(c =>
{
Expand All @@ -64,6 +65,7 @@ public void ConfigureServices(IServiceCollection services)
options.UseMySQL(connectionString);
});

services.AddLazyCache();
services.AddCors(options =>
{
options.AddPolicy("origins",
Expand Down
2 changes: 2 additions & 0 deletions src/ElectionResults.Core/ElectionResults.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<PackageReference Include="CSharpFunctionalExtensions" Version="2.42.0" />
<PackageReference Include="CsvHelper" Version="32.0.3" />
<PackageReference Include="Diacritics" Version="3.3.29" />
<PackageReference Include="LazyCache" Version="2.4.0" />
<PackageReference Include="LazyCache.AspNetCore" Version="2.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
<PackageReference Include="Humanizer" Version="2.14.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.6" />
Expand Down
18 changes: 15 additions & 3 deletions src/ElectionResults.Core/Elections/WinnersAggregator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using ElectionResults.Core.Extensions;
using ElectionResults.Core.Infrastructure;
using ElectionResults.Core.Repositories;
using LazyCache;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -17,16 +18,19 @@ namespace ElectionResults.Core.Elections
public class WinnersAggregator : IWinnersAggregator
{
private readonly ApplicationDbContext _dbContext;
private readonly IAppCache _appCache;
private readonly IServiceProvider _serviceProvider;
private readonly IPartiesRepository _partiesRepository;
private readonly ITerritoryRepository _territoryRepository;

public WinnersAggregator(ApplicationDbContext dbContext,
IAppCache appCache,
IServiceProvider serviceProvider,
IPartiesRepository partiesRepository,
ITerritoryRepository territoryRepository)
{
_dbContext = dbContext;
_appCache = appCache;
_serviceProvider = serviceProvider;
_partiesRepository = partiesRepository;
_territoryRepository = territoryRepository;
Expand All @@ -41,6 +45,8 @@ public async Task<Result<List<Winner>>> GetLocalityCityHallWinnersByCounty(int b
return dbWinners;
}

_appCache.Remove(MemoryCache.CreateWinnersKey(ballotId, countyId, ElectionDivision.Locality));

var localities = await _dbContext
.Localities
.Where(l => l.CountyId == countyId).ToListAsync();
Expand Down Expand Up @@ -91,12 +97,17 @@ public async Task<Result<List<Winner>>> GetLocalityCityHallWinnersByCounty(int b

private async Task<List<Winner>> GetWinners(int ballotId, int? countyId, ElectionDivision division)
{
var winners = await CreateWinnersQuery()
var query = CreateWinnersQuery()
.Where(w => w.BallotId == ballotId
&& w.Division == division
&& w.CountyId == countyId)
.ToListAsync();

var winnersKey = MemoryCache.CreateWinnersKey(ballotId, countyId, division);

var winners = await _appCache
.GetOrAddAsync(winnersKey, () => query, DateTimeOffset.Now.AddMinutes(10));

return winners;
}

Expand Down Expand Up @@ -149,7 +160,7 @@ public async Task<Result<List<ElectionMapWinner>>> GetCountryWinners(int ballotI

if (dbWinners.Count > 0)
return dbWinners.Select(winner => WinnerToElectionMapWinner(winner, parties.ToList())).ToList();

_appCache.Remove(MemoryCache.CreateWinnersKey(ballotId, null, ElectionDivision.Diaspora_Country));
var winners = new List<ElectionMapWinner>();
var countries = await _territoryRepository.GetCountries(null);
if (countries.IsFailure)
Expand Down Expand Up @@ -245,7 +256,7 @@ public async Task<Result<List<ElectionMapWinner>>> GetCountyWinners(int ballotId
var dbWinners = await GetWinners(ballotId, null, ElectionDivision.County);
if (dbWinners.Count > 0)
return dbWinners.Select(winner => WinnerToElectionMapWinner(winner, parties)).ToList();

_appCache.Remove(MemoryCache.CreateWinnersKey(ballotId, null, ElectionDivision.Diaspora_Country));
var winners = await AggregateCountyWinners(ballotId, parties);
var ids = winners.Select(w => w.Id).ToList();
winners = await _dbContext.Winners
Expand Down Expand Up @@ -307,6 +318,7 @@ public async Task<Result<List<CandidateResult>>> GetWinningCandidatesByCounty(in
var dbWinners = await GetWinners(ballotId, null, ElectionDivision.County);
if (dbWinners.Count > 0)
return dbWinners.Select(w => w.Candidate).ToList();
_appCache.Remove(MemoryCache.CreateWinnersKey(ballotId, null, ElectionDivision.Diaspora_Country));
var winners = await AggregateCountyWinners(ballotId, parties);
return winners.Select(w => w.Candidate).ToList();
}
Expand Down
26 changes: 20 additions & 6 deletions src/ElectionResults.Core/Repositories/BallotsRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,44 @@
using System.Linq;
using System.Threading.Tasks;
using ElectionResults.Core.Entities;
using LazyCache;
using Microsoft.EntityFrameworkCore;

namespace ElectionResults.Core.Repositories
{
public class BallotsRepository : IBallotsRepository
{
private readonly ApplicationDbContext _dbContext;
private readonly IAppCache _appCache;
private readonly CacheSettings _cacheSettings;

public BallotsRepository(ApplicationDbContext dbContext)
public BallotsRepository(ApplicationDbContext dbContext, IAppCache appCache)
{
_dbContext = dbContext;
_appCache = appCache;
_cacheSettings = MemoryCache.Ballots;
}

public async Task<IEnumerable<Ballot>> GetAllBallots(bool includeElection = false)
{
return await CreateQueryable(includeElection).ToListAsync();
return await _appCache.GetOrAddAsync(
_cacheSettings.Key, () =>
{
var query = CreateQueryable(includeElection);
return query.ToListAsync();
},
DateTimeOffset.Now.AddMinutes(_cacheSettings.Minutes));
}

public async Task<Ballot> GetBallotById(int ballotId, bool includeElection = false)
{

var query = CreateQueryable(includeElection);
var ballot = await query.FirstOrDefaultAsync(b => b.BallotId == ballotId);

var ballot = await _appCache.GetOrAddAsync(
_cacheSettings.Key, () =>
{
var query = CreateQueryable(includeElection);
return query.FirstOrDefaultAsync(b => b.BallotId == ballotId);
},
DateTimeOffset.Now.AddMinutes(_cacheSettings.Minutes));
return ballot;
}

Expand Down
12 changes: 9 additions & 3 deletions src/ElectionResults.Core/Repositories/ElectionsRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,29 @@
using CSharpFunctionalExtensions;
using ElectionResults.Core.Endpoints.Response;
using ElectionResults.Core.Entities;
using LazyCache;
using Microsoft.EntityFrameworkCore;

namespace ElectionResults.Core.Repositories
{
public class ElectionsRepository : IElectionsRepository
{
private readonly ApplicationDbContext _dbContext;
private readonly IAppCache _appCache;
private readonly CacheSettings _cacheSettings;

public ElectionsRepository(ApplicationDbContext dbContext)
public ElectionsRepository(ApplicationDbContext dbContext, IAppCache appCache)
{
_dbContext = dbContext;
_appCache = appCache;
_cacheSettings = MemoryCache.Elections;
}

public async Task<Result<List<Election>>> GetAllElections(bool includeBallots = false)
{
var elections = await CreateQueryable(includeBallots).ToListAsync();

var elections = await _appCache.GetOrAddAsync(
_cacheSettings.Key, () => CreateQueryable(includeBallots).ToListAsync(),
DateTimeOffset.Now.AddMinutes(_cacheSettings.Minutes));
return Result.Success(elections);
}

Expand Down
44 changes: 44 additions & 0 deletions src/ElectionResults.Core/Repositories/MemoryCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using ElectionResults.Core.Entities;

namespace ElectionResults.Core.Repositories
{
public class MemoryCache
{
public static readonly CacheSettings Parties = new CacheSettings("all_parties", 60*24);

public static readonly CacheSettings Ballots = new CacheSettings("all_ballots", 60*24);

public static readonly CacheSettings Elections = new CacheSettings("all_elections", 60*24);

public static readonly CacheSettings Counties = new CacheSettings("all_counties", 60*24);

public static readonly CacheSettings Localities = new CacheSettings("all_localities", 60*24);

public static readonly CacheSettings Locality = new CacheSettings("locality", 60*24);

public static readonly CacheSettings County = new CacheSettings("county", 60*24);

public static readonly CacheSettings Countries = new CacheSettings("all_countries", 60*24);

public const string NationalTurnout = "national-total";
public const string DiasporaTurnout = "diaspora-total";

public static string CreateWinnersKey(int ballotId, int? countyId, ElectionDivision division)
{
return $"winner-{ballotId}-{countyId}-{division}";
}
}

public class CacheSettings
{
public CacheSettings(string key, int durationInMinutes)
{
Key = key;
Minutes = durationInMinutes;
}

public string Key { get; set; }

public int Minutes { get; set; }
}
}
11 changes: 9 additions & 2 deletions src/ElectionResults.Core/Repositories/PartiesRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,29 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using ElectionResults.Core.Entities;
using LazyCache;
using Microsoft.EntityFrameworkCore;

namespace ElectionResults.Core.Repositories
{
public class PartiesRepository : IPartiesRepository
{
private readonly ApplicationDbContext _dbContext;
private readonly IAppCache _appCache;
private readonly CacheSettings _cacheSettings;

public PartiesRepository(ApplicationDbContext dbContext)
public PartiesRepository(ApplicationDbContext dbContext, IAppCache appCache)
{
_dbContext = dbContext;
_appCache = appCache;
_cacheSettings = MemoryCache.Parties;
}

public async Task<IEnumerable<Party>> GetAllParties()
{
return await _dbContext.Parties.ToListAsync();
return await _appCache.GetOrAddAsync(
_cacheSettings.Key, () => _dbContext.Parties.ToListAsync(),
DateTimeOffset.Now.AddMinutes(_cacheSettings.Minutes));
}
}
}
Loading

0 comments on commit 7c02792

Please sign in to comment.