Skip to content

Commit

Permalink
feat: add country-statistics aggregates
Browse files Browse the repository at this point in the history
  • Loading branch information
alsami committed Oct 21, 2020
1 parent 84598d7 commit 18b961c
Show file tree
Hide file tree
Showing 23 changed files with 555 additions and 13 deletions.
18 changes: 18 additions & 0 deletions src/Covid19Api.AutoMapper/CountryStatisticsAggregateProfile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using AutoMapper;
using Covid19Api.Domain;
using Covid19Api.Presentation.Response;

namespace Covid19Api.AutoMapper
{
public class CountryStatisticsAggregateProfile : Profile
{
public CountryStatisticsAggregateProfile()
{
this.CreateMap<CountryStatisticsAggregate, CountryStatisticsAggregateDto>()
.ConvertUsing(source => new CountryStatisticsAggregateDto(source.Id, source.Country, source.CountryCode,
source.Total, source.New, source.Deaths, source.NewDeaths, source.Recovered, source.Active,
source.Month,
source.Year));
}
}
}
8 changes: 8 additions & 0 deletions src/Covid19Api.IoC/Extensions/ContainerBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public static ContainerBuilder RegisterRepositories(this ContainerBuilder builde
.As<ICountryStatisticsRepository>()
.InstancePerLifetimeScope();

builder.RegisterType<CountryStatisticsAggregatesRepository>()
.As<ICountryStatisticsAggregatesRepository>()
.InstancePerLifetimeScope();

builder.RegisterModule(new Covid19ApiDbContextModule(hostEnvironment, configuration));

return builder;
Expand Down Expand Up @@ -81,6 +85,10 @@ public static ContainerBuilder RegisterWorker(this ContainerBuilder builder)
builder.RegisterType<GlobalStatisticsAggregationWorker>()
.As<IHostedService>()
.InstancePerDependency();

builder.RegisterType<CountryStatisticsAggregateWorker>()
.As<IHostedService>()
.InstancePerDependency();

return builder;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Covid19Api.Mongo.Migrator.Configuration
{
public class CountryAggregatesStartConfiguration
{
public int Month { get; set; } = 9;

public int Year { get; set; } = 2020;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

namespace Covid19Api.Mongo.Migrator.Migrations
{
// ReSharper disable once UnusedType.Global
public class GlobalAggregatesMigration : DatabaseMigration
{
private readonly GlobalAggregatesStartConfiguration options;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Covid19Api.Mongo.Migrator.Abstractions;
using Covid19Api.Mongo.Migrator.Configuration;
using Covid19Api.UseCases.Abstractions.Commands;
using Covid19Api.UseCases.Abstractions.Queries;
using MediatR;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Covid19Api.Mongo.Migrator.Migrations
{
// ReSharper disable once UnusedType.Global
public class CountryAggregatesMigration : DatabaseMigration
{
private readonly CountryAggregatesStartConfiguration options;
private readonly IMediator mediator;

// ReSharper disable once SuggestBaseTypeForParameter
public CountryAggregatesMigration(ILogger<CountryAggregatesMigration> logger,
IOptions<CountryAggregatesStartConfiguration> options, IMediator mediator) : base(logger)
{
this.mediator = mediator;
this.options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}

public override int Number => 1;
protected override string Name => nameof(CountryAggregatesMigration);

protected override async Task ExecuteAsync()
{
var next = new DateTime(this.options.Year, this.options.Month, 1, 0, 0, 0, DateTimeKind.Utc);
var end = DateTime.UtcNow.Date;

while (true)
{
if (next.Month > end.Month && next.Year >= end.Year)
break;

var loadCountriesStatisticsQuery = new LoadLatestCountriesStatisticsQuery();
var countries = (await this.mediator.Send(loadCountriesStatisticsQuery))
.Select(country => country.Country).ToList();

foreach (var country in countries.ToList())
{
var query = new LoadCountryStatisticsAggregate(country, next.Month, next.Year);
var aggregate = await this.mediator.Send(query);

await Task.Delay(50);
if (aggregate is null) continue;

countries.Remove(country);
}

if (!countries.Any())
{
next = next.AddMonths(1);
await Task.Delay(100);
continue;
}

var command = new AggregateCountryStatisticsCommand(countries.ToArray(), next.Month, next.Year);
await this.mediator.Send(command);

next = next.AddMonths(1);
await Task.Delay(100);
}
}
}
}
8 changes: 7 additions & 1 deletion src/Covid19Api.Mongo.Migrator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public static async Task Main(string[] args)

foreach (var databaseMigration in migrations.OrderBy(migration => migration.Number))
await databaseMigration.ExecuteUpdateAsync();

await host.StopAsync();
}

Expand All @@ -45,8 +45,14 @@ private static IHost CreateHost(string[] args)
private static void ConfigureServices(HostBuilderContext hostBuilderContext, IServiceCollection services)
{
services.AddOptions();

services.AddHttpClient();

services.Configure<GlobalAggregatesStartConfiguration>(options =>
hostBuilderContext.Configuration.GetSection(nameof(GlobalAggregatesStartConfiguration)).Bind(options));

services.Configure<CountryAggregatesStartConfiguration>(options =>
hostBuilderContext.Configuration.GetSection(nameof(CountryAggregatesStartConfiguration)).Bind(options));
}

private static void ConfigureLogger(HostBuilderContext context, LoggerConfiguration loggerConfiguration)
Expand Down
6 changes: 5 additions & 1 deletion src/Covid19Api.Mongo.Migrator/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"Serilog": {
"MinimumLevel": "Information",
"MinimumLevel": "Debug",
"WriteTo": [
{
"Name": "Console",
Expand All @@ -17,5 +17,9 @@
"GlobalAggregatesStartConfiguration": {
"Month": 9,
"Year": 2020
},
"CountryAggregatesStartConfiguration": {
"Month": 9,
"Year": 2020
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ public GlobalStatisticAggregateUpdateDefinition(ILogger<GlobalStatisticAggregate
protected override async Task ExecuteAsync()
{
await this.databaseContext.Database.CreateCollectionIfNotExistsAsync(CollectionNames
.GlobalStatisticsAggregate);
.GlobalStatisticsAggregates);

var monthIndex = Builders<GlobalStatisticsAggregate>
.IndexKeys
.Descending(statistics => statistics.Month);

var monthIndexModel = new CreateIndexModel<GlobalStatisticsAggregate>(monthIndex, new CreateIndexOptions
{
Name = $"{CollectionNames.GlobalStatisticsAggregate}_month_descending"
Name = $"{CollectionNames.GlobalStatisticsAggregates}_month_descending"
});

var yearIndex = Builders<GlobalStatisticsAggregate>
Expand All @@ -41,7 +41,7 @@ await this.databaseContext.Database.CreateCollectionIfNotExistsAsync(CollectionN

var yearIndexModel = new CreateIndexModel<GlobalStatisticsAggregate>(yearIndex, new CreateIndexOptions
{
Name = $"{CollectionNames.GlobalStatisticsAggregate}_year_descending"
Name = $"{CollectionNames.GlobalStatisticsAggregates}_year_descending"
});

var yearMonthIndex = Builders<GlobalStatisticsAggregate>
Expand All @@ -51,13 +51,13 @@ await this.databaseContext.Database.CreateCollectionIfNotExistsAsync(CollectionN
var yearMonthIndexModel = new CreateIndexModel<GlobalStatisticsAggregate>(yearMonthIndex,
new CreateIndexOptions
{
Name = $"{CollectionNames.GlobalStatisticsAggregate}_year_month",
Name = $"{CollectionNames.GlobalStatisticsAggregates}_year_month",
Unique = true
});

var collection =
this.databaseContext.Database.GetCollection<GlobalStatisticsAggregate>(CollectionNames
.GlobalStatisticsAggregate);
.GlobalStatisticsAggregates);

await collection.Indexes.CreateManyAsync(new[]
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System.Threading.Tasks;
using Covid19Api.Domain;
using Covid19Api.Mongo.Scaffolder.Abstractions;
using Covid19Api.Mongo.Scaffolder.Extensions;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;

namespace Covid19Api.Mongo.Scaffolder.Updates
{
// ReSharper disable once UnusedType.Global
public class CountryStatisticAggregateUpdateDefinition : DatabaseUpdateDefinition
{
private readonly Covid19ApiDbContext databaseContext;

// ReSharper disable once SuggestBaseTypeForParameter
public CountryStatisticAggregateUpdateDefinition(ILogger<CountryStatisticAggregateUpdateDefinition> logger,
Covid19ApiDbContext databaseContext) : base(logger)
{
this.databaseContext = databaseContext;
}

public override int Version => 3;

protected override async Task ExecuteAsync()
{
await this.databaseContext.Database.CreateCollectionIfNotExistsAsync(CollectionNames
.CountryStatisticsAggregates);

var collection =
this.databaseContext.Database.GetCollection<CountryStatisticsAggregate>(CollectionNames
.CountryStatisticsAggregates);

var countryIndex = Builders<CountryStatisticsAggregate>
.IndexKeys
.Ascending(statistics => statistics.Country);

var countryIndexModel = new CreateIndexModel<CountryStatisticsAggregate>(countryIndex, new CreateIndexOptions
{
Name = $"{CollectionNames.CountryStatisticsAggregates}_country"
});

await collection.Indexes.CreateOneAsync(countryIndexModel);

var monthIndex = Builders<CountryStatisticsAggregate>
.IndexKeys
.Descending(statistics => statistics.Month);

var monthIndexModel = new CreateIndexModel<CountryStatisticsAggregate>(monthIndex, new CreateIndexOptions
{
Name = $"{CollectionNames.CountryStatisticsAggregates}_month_descending"
});

await collection.Indexes.CreateOneAsync(monthIndexModel);

var yearIndex = Builders<CountryStatisticsAggregate>
.IndexKeys
.Descending(statistics => statistics.Year);

var yearIndexModel = new CreateIndexModel<CountryStatisticsAggregate>(yearIndex, new CreateIndexOptions
{
Name = $"{CollectionNames.CountryStatisticsAggregates}_year_descending"
});

await collection.Indexes.CreateOneAsync(yearIndexModel);

var yearMonthIndex = Builders<CountryStatisticsAggregate>
.IndexKeys
.Combine(yearIndex, monthIndex);

var yearMonthIndexModel = new CreateIndexModel<CountryStatisticsAggregate>(yearMonthIndex,
new CreateIndexOptions
{
Name = $"{CollectionNames.CountryStatisticsAggregates}_year_month",
});

await collection.Indexes.CreateOneAsync(yearMonthIndexModel);

var countryYearMonthIndex = Builders<CountryStatisticsAggregate>
.IndexKeys
.Combine(countryIndex, yearIndex, monthIndex);

var countryYearMonthIndexModel = new CreateIndexModel<CountryStatisticsAggregate>(countryYearMonthIndex,
new CreateIndexOptions
{
Name = $"{CollectionNames.CountryStatisticsAggregates}_country_year_month",
Unique = true
});

await collection.Indexes.CreateOneAsync(countryYearMonthIndexModel);
}
}
}
2 changes: 1 addition & 1 deletion src/Covid19Api.Mongo.Scaffolder/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"Serilog": {
"MinimumLevel": "Information",
"MinimumLevel": "Debug",
"WriteTo": [
{
"Name": "Console",
Expand Down
3 changes: 2 additions & 1 deletion src/Covid19Api.Mongo/CollectionNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ namespace Covid19Api.Mongo
public static class CollectionNames
{
public const string GlobalStatistics = "globalStatistics";
public const string GlobalStatisticsAggregate = "globalStatisticsAggregate";
public const string GlobalStatisticsAggregates = "globalStatisticsAggregates";

public const string CountryStatistics = "countryStatistics";
public const string CountryStatisticsAggregates = "countryStatisticsAggregates";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;

namespace Covid19Api.Presentation.Response
{
public class CountryStatisticsAggregateDto
{
public CountryStatisticsAggregateDto(Guid id, string country, string? countryCode, int total, int @new,
int deaths,
int newDeaths,
int recovered, int active, int month, int year)
{
this.Id = id;
this.Country = country;
this.CountryCode = countryCode;
this.Total = total;
this.New = @new;
this.Deaths = deaths;
this.NewDeaths = newDeaths;
this.Recovered = recovered;
this.Active = active;
this.Month = month;
this.Year = year;
}

public Guid Id { get; set; }
public string Country { get; set; }
public string? CountryCode { get; set; }
public int Total { get; set; }
public int New { get; set; }
public int Deaths { get; set; }
public int NewDeaths { get; set; }
public int Recovered { get; set; }
public int Active { get; set; }
public int Month { get; set; }
public int Year { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Threading.Tasks;
using Covid19Api.Domain;

namespace Covid19Api.Repositories.Abstractions
{
public interface ICountryStatisticsAggregatesRepository
{
Task StoreAsync(CountryStatisticsAggregate countryStatisticsAggregate);

Task<CountryStatisticsAggregate?> FindAsync(string country, int month, int year);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public interface ICountryStatisticsRepository
Task<IEnumerable<CountryStatistics>> HistoricalAsync(DateTime minFetchedAt);
Task<IEnumerable<CountryStatistics>> HistoricalAsync(DateTime minFetchedAt, string country);
Task<IEnumerable<CountryStatistics>> HistoricalForDayAsync(DateTime minFetchedAt, string country);
Task<IList<CountryStatistics>> HistoricalInRangeAsync(string country, DateTime inclusiveStart,
DateTime exclusiveEnd);
Task StoreManyAsync(IEnumerable<CountryStatistics> countryStats);
}
}
Loading

0 comments on commit 18b961c

Please sign in to comment.