From c42de9561244d0bf82b97ae1647220fc77fb6196 Mon Sep 17 00:00:00 2001 From: Piotr Gankiewicz Date: Fri, 30 Sep 2016 14:30:45 +0200 Subject: [PATCH] Filters and generic storage client for API #19 --- src/Coolector.Api/Modules/Base/ModuleBase.cs | 10 ++- src/Coolector.Api/Modules/UserModule.cs | 3 +- .../Extensions/PaginationExtensions.cs | 7 +- src/Coolector.Common/Types/IFilter.cs | 4 +- src/Coolector.Common/Types/IPagedQuery.cs | 8 +++ src/Coolector.Common/Types/PagedQueryBase.cs | 2 +- .../Filters/BrowseUsersFilter.cs | 9 +-- src/Coolector.Core/Filters/EmptyFilter.cs | 14 ++++ src/Coolector.Core/Filters/FilterResolver.cs | 22 ++++++ src/Coolector.Core/Filters/IFilterResolver.cs | 9 +++ .../IoC/Modules/FilterModule.cs | 2 + src/Coolector.Core/Storages/IStorageClient.cs | 7 +- src/Coolector.Core/Storages/IUserStorage.cs | 3 +- src/Coolector.Core/Storages/StorageClient.cs | 68 ++++++++++++++++--- src/Coolector.Core/Storages/UserStorage.cs | 13 ++-- .../Framework/Bootstrapper.cs | 2 +- .../Providers/IProviderClient.cs | 2 +- .../{ => Modules}/Providers/IServiceClient.cs | 2 +- .../{ => Modules}/Providers/IUserProvider.cs | 2 +- .../{ => Modules}/Providers/ProviderClient.cs | 2 +- .../{ => Modules}/Providers/ServiceClient.cs | 2 +- .../{ => Modules}/Providers/UserProvider.cs | 2 +- .../Modules/UserModule.cs | 2 +- 23 files changed, 155 insertions(+), 42 deletions(-) create mode 100644 src/Coolector.Common/Types/IPagedQuery.cs create mode 100644 src/Coolector.Core/Filters/EmptyFilter.cs create mode 100644 src/Coolector.Core/Filters/FilterResolver.cs create mode 100644 src/Coolector.Core/Filters/IFilterResolver.cs rename src/Services/Coolector.Services.Storage/{ => Modules}/Providers/IProviderClient.cs (89%) rename src/Services/Coolector.Services.Storage/{ => Modules}/Providers/IServiceClient.cs (82%) rename src/Services/Coolector.Services.Storage/{ => Modules}/Providers/IUserProvider.cs (78%) rename src/Services/Coolector.Services.Storage/{ => Modules}/Providers/ProviderClient.cs (95%) rename src/Services/Coolector.Services.Storage/{ => Modules}/Providers/ServiceClient.cs (95%) rename src/Services/Coolector.Services.Storage/{ => Modules}/Providers/UserProvider.cs (96%) diff --git a/src/Coolector.Api/Modules/Base/ModuleBase.cs b/src/Coolector.Api/Modules/Base/ModuleBase.cs index 079d542..18514c3 100644 --- a/src/Coolector.Api/Modules/Base/ModuleBase.cs +++ b/src/Coolector.Api/Modules/Base/ModuleBase.cs @@ -1,4 +1,6 @@ -using Coolector.Core.Commands; +using System.Collections.Generic; +using Coolector.Common.Types; +using Coolector.Core.Commands; using Nancy; namespace Coolector.Api.Modules.Base @@ -12,5 +14,11 @@ public ModuleBase(ICommandDispatcher commandDispatcher, string modulePath = "") { CommandDispatcher = commandDispatcher; } + + //TODO: Add headers etc. + protected IEnumerable FromPagedResult(Maybe> result) + { + return result.HasValue ? result.Value.Items : new List(); + } } } \ No newline at end of file diff --git a/src/Coolector.Api/Modules/UserModule.cs b/src/Coolector.Api/Modules/UserModule.cs index e2a21f2..f4234d1 100644 --- a/src/Coolector.Api/Modules/UserModule.cs +++ b/src/Coolector.Api/Modules/UserModule.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using Coolector.Api.Modules.Base; -using Coolector.Common.DTO.Users; using Coolector.Core.Commands; using Coolector.Core.Filters; using Coolector.Core.Storages; @@ -18,7 +17,7 @@ public UserModule(ICommandDispatcher commandDispatcher, IUserStorage userStorage var query = this.Bind(); var users = await userStorage.BrowseAsync(query); - return users.HasValue ? users.Value.Items : new List(); + return FromPagedResult(users); }); } } diff --git a/src/Coolector.Common/Extensions/PaginationExtensions.cs b/src/Coolector.Common/Extensions/PaginationExtensions.cs index 7f3613b..63c5c2f 100644 --- a/src/Coolector.Common/Extensions/PaginationExtensions.cs +++ b/src/Coolector.Common/Extensions/PaginationExtensions.cs @@ -7,7 +7,10 @@ namespace Coolector.Common.Extensions { public static class PaginationExtensions { - public static PagedResult Paginate(this IEnumerable values, PagedQueryBase query) + public static PagedResult PaginateWithoutLimit(this IEnumerable values) + => values.Paginate(1, int.MaxValue); + + public static PagedResult Paginate(this IEnumerable values, IPagedQuery query) => values.Paginate(query.Page, query.Results); public static PagedResult Paginate(this IEnumerable values, @@ -30,7 +33,7 @@ public static PagedResult Paginate(this IEnumerable values, return PagedResult.Create(data, page, resultsPerPage, totalPages, totalResults); } - public static IEnumerable Limit(this IEnumerable collection, PagedQueryBase query) + public static IEnumerable Limit(this IEnumerable collection, IPagedQuery query) => collection.Limit(query.Page, query.Results); public static IEnumerable Limit(this IEnumerable collection, diff --git a/src/Coolector.Common/Types/IFilter.cs b/src/Coolector.Common/Types/IFilter.cs index 323e815..a9e03b7 100644 --- a/src/Coolector.Common/Types/IFilter.cs +++ b/src/Coolector.Common/Types/IFilter.cs @@ -2,8 +2,8 @@ namespace Coolector.Common.Types { - public interface IFilter where TQuery : PagedQueryBase + public interface IFilter where TQuery : IQuery { - Maybe> Filter(Maybe> values, TQuery query); + Maybe> Filter(Maybe> values, TQuery query); } } \ No newline at end of file diff --git a/src/Coolector.Common/Types/IPagedQuery.cs b/src/Coolector.Common/Types/IPagedQuery.cs new file mode 100644 index 0000000..e4ccf1f --- /dev/null +++ b/src/Coolector.Common/Types/IPagedQuery.cs @@ -0,0 +1,8 @@ +namespace Coolector.Common.Types +{ + public interface IPagedQuery : IQuery + { + int Page { get; } + int Results { get; } + } +} \ No newline at end of file diff --git a/src/Coolector.Common/Types/PagedQueryBase.cs b/src/Coolector.Common/Types/PagedQueryBase.cs index 43b68f7..a946833 100644 --- a/src/Coolector.Common/Types/PagedQueryBase.cs +++ b/src/Coolector.Common/Types/PagedQueryBase.cs @@ -1,6 +1,6 @@ namespace Coolector.Common.Types { - public abstract class PagedQueryBase : IQuery + public abstract class PagedQueryBase : IPagedQuery { public int Page { get; set; } public int Results { get; set; } diff --git a/src/Coolector.Core/Filters/BrowseUsersFilter.cs b/src/Coolector.Core/Filters/BrowseUsersFilter.cs index 5ffb7a4..86ace49 100644 --- a/src/Coolector.Core/Filters/BrowseUsersFilter.cs +++ b/src/Coolector.Core/Filters/BrowseUsersFilter.cs @@ -1,18 +1,19 @@ using System.Collections.Generic; using Coolector.Common.DTO.Users; -using Coolector.Common.Extensions; using Coolector.Common.Types; namespace Coolector.Core.Filters { public class BrowseUsersFilter : IFilter { - public Maybe> Filter(Maybe> values, BrowseUsers query) + public Maybe> Filter(Maybe> values, BrowseUsers query) { if(values.HasNoValue) - return new Maybe>(); + return new Maybe>(); + if (query == null) + return values; - return values.Value.Paginate(query); + return values; } } } \ No newline at end of file diff --git a/src/Coolector.Core/Filters/EmptyFilter.cs b/src/Coolector.Core/Filters/EmptyFilter.cs new file mode 100644 index 0000000..053b5d9 --- /dev/null +++ b/src/Coolector.Core/Filters/EmptyFilter.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using Coolector.Common.Types; + +namespace Coolector.Core.Filters +{ + public class EmptyFilter : IFilter where TQuery : IQuery + { + public Maybe> Filter(Maybe> values, TQuery query) + { + return values; + } + } +} \ No newline at end of file diff --git a/src/Coolector.Core/Filters/FilterResolver.cs b/src/Coolector.Core/Filters/FilterResolver.cs new file mode 100644 index 0000000..8c7c9a6 --- /dev/null +++ b/src/Coolector.Core/Filters/FilterResolver.cs @@ -0,0 +1,22 @@ +using Autofac; +using Coolector.Common.Types; + +namespace Coolector.Core.Filters +{ + public class FilterResolver : IFilterResolver + { + private readonly IComponentContext _context; + + public FilterResolver(IComponentContext context) + { + _context = context; + } + + public IFilter Resolve() where TQuery : IQuery + { + IFilter filter; + + return _context.TryResolve(out filter) ? filter : new EmptyFilter(); + } + } +} \ No newline at end of file diff --git a/src/Coolector.Core/Filters/IFilterResolver.cs b/src/Coolector.Core/Filters/IFilterResolver.cs new file mode 100644 index 0000000..0948d3c --- /dev/null +++ b/src/Coolector.Core/Filters/IFilterResolver.cs @@ -0,0 +1,9 @@ +using Coolector.Common.Types; + +namespace Coolector.Core.Filters +{ + public interface IFilterResolver + { + IFilter Resolve() where TQuery : IQuery; + } +} \ No newline at end of file diff --git a/src/Coolector.Core/IoC/Modules/FilterModule.cs b/src/Coolector.Core/IoC/Modules/FilterModule.cs index c779db1..6fe6bfd 100644 --- a/src/Coolector.Core/IoC/Modules/FilterModule.cs +++ b/src/Coolector.Core/IoC/Modules/FilterModule.cs @@ -1,6 +1,7 @@ using System.Reflection; using Autofac; using Coolector.Common.Types; +using Coolector.Core.Filters; using Module = Autofac.Module; namespace Coolector.Core.IoC.Modules @@ -9,6 +10,7 @@ public class FilterModule : Module { protected override void Load(ContainerBuilder builder) { + builder.RegisterType().As(); var coreAssembly = Assembly.Load(new AssemblyName("Coolector.Core")); builder.RegisterAssemblyTypes(coreAssembly).AsClosedTypesOf(typeof(IFilter<,>)); } diff --git a/src/Coolector.Core/Storages/IStorageClient.cs b/src/Coolector.Core/Storages/IStorageClient.cs index 50e2183..d71ed65 100644 --- a/src/Coolector.Core/Storages/IStorageClient.cs +++ b/src/Coolector.Core/Storages/IStorageClient.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading.Tasks; using Coolector.Common.Types; @@ -12,7 +11,11 @@ public interface IStorageClient Task> GetUsingCacheAsync(string endpoint, string cacheKey = null, TimeSpan? expiry = null) where T : class; - Task>> GetCollectionUsingCacheAsync(string endpoint, string cacheKey = null, + Task>> GetCollectionUsingCacheAsync(string endpoint, string cacheKey = null, TimeSpan? expiry = null) where T : class; + + Task>> GetFilteredCollectionUsingCacheAsync(TQuery query, + string endpoint, string cacheKey = null, TimeSpan? expiry = null) + where TResult : class where TQuery : class, IPagedQuery; } } \ No newline at end of file diff --git a/src/Coolector.Core/Storages/IUserStorage.cs b/src/Coolector.Core/Storages/IUserStorage.cs index 4070c7a..0d6294b 100644 --- a/src/Coolector.Core/Storages/IUserStorage.cs +++ b/src/Coolector.Core/Storages/IUserStorage.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; using Coolector.Common.DTO.Users; using Coolector.Common.Types; using Coolector.Core.Filters; diff --git a/src/Coolector.Core/Storages/StorageClient.cs b/src/Coolector.Core/Storages/StorageClient.cs index 9529dd2..7a331c1 100644 --- a/src/Coolector.Core/Storages/StorageClient.cs +++ b/src/Coolector.Core/Storages/StorageClient.cs @@ -6,21 +6,26 @@ using Coolector.Common.Extensions; using Newtonsoft.Json; using System.Linq; +using System.Reflection; +using System.Text; +using Coolector.Core.Filters; namespace Coolector.Core.Storages { public class StorageClient : IStorageClient { private readonly ICache _cache; + private readonly IFilterResolver _filterResolver; private readonly StorageSettings _settings; private readonly HttpClient _httpClient; private string BaseAddress => _settings.Url.EndsWith("/", StringComparison.CurrentCulture) ? _settings.Url : $"{_settings.Url}/"; - public StorageClient(ICache cache, StorageSettings settings) + public StorageClient(ICache cache, IFilterResolver filterResolver, StorageSettings settings) { _cache = cache; + _filterResolver = filterResolver; _settings = settings; _httpClient = new HttpClient {BaseAddress = new Uri(BaseAddress)}; _httpClient.DefaultRequestHeaders.Remove("Accept"); @@ -67,20 +72,63 @@ public async Task> GetUsingCacheAsync(string endpoint, string cacheK return result; } - public async Task>> GetCollectionUsingCacheAsync(string endpoint, string cacheKey = null, + public async Task>> GetCollectionUsingCacheAsync(string endpoint, string cacheKey = null, TimeSpan? expiry = null) where T : class { - var result = await GetFromCacheAsync>(endpoint, cacheKey); - if (result.HasValue && result.Value.Any()) - return result; + var results = await GetFromCacheAsync>(endpoint, cacheKey); + if (results.HasValue && results.Value.Any()) + return results.Value.PaginateWithoutLimit(); - result = await GetAsync>(endpoint); - if (result.HasNoValue || !result.Value.Any()) - return new Maybe>(); + results = await GetAsync>(endpoint); + if (results.HasNoValue || !results.Value.Any()) + return new Maybe>(); - await StoreInCacheAsync(result, endpoint, cacheKey, expiry); + await StoreInCacheAsync(results, endpoint, cacheKey, expiry); - return result; + return results.Value.PaginateWithoutLimit(); + } + + public async Task>> GetFilteredCollectionUsingCacheAsync( + TQuery query, string endpoint, string cacheKey = null, TimeSpan? expiry = null) where TResult : class + where TQuery : class, IPagedQuery + { + var filter = _filterResolver.Resolve(); + var results = await GetFromCacheAsync>(endpoint, cacheKey); + if (results.HasValue && results.Value.Any()) + return FilterAndPaginateResults(filter, results, query); + + results = await GetAsync>(GetEndpointWithQuery(endpoint, query)); + if (results.HasNoValue || !results.Value.Any()) + return PagedResult.Empty; + + await StoreInCacheAsync(results, endpoint, cacheKey, expiry); + + return FilterAndPaginateResults(filter, results, query); + } + + private static Maybe> FilterAndPaginateResults( + IFilter filter, + Maybe> results, TQuery query) where TQuery : class, IPagedQuery + { + var filteredValues = filter.Filter(results, query); + + return filteredValues.HasValue ? filteredValues.Value.Paginate(query) : PagedResult.Empty; + } + + private static string GetEndpointWithQuery(string endpoint, T query) where T : class, IQuery + { + if (query == null) + return endpoint; + + var values = new List(); + foreach (var property in query.GetType().GetProperties()) + { + var value = property.GetValue(query, null); + values.Add($"{property.Name.ToLowerInvariant()}={value}"); + } + + var endpointQuery = string.Join("&", values); + return $"{endpoint}?{endpointQuery}"; } private async Task> GetFromCacheAsync(string endpoint, string cacheKey = null) where T : class diff --git a/src/Coolector.Core/Storages/UserStorage.cs b/src/Coolector.Core/Storages/UserStorage.cs index 2c903a7..f75019b 100644 --- a/src/Coolector.Core/Storages/UserStorage.cs +++ b/src/Coolector.Core/Storages/UserStorage.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; using Coolector.Common.DTO.Users; using Coolector.Common.Types; using Coolector.Core.Filters; @@ -8,22 +9,16 @@ namespace Coolector.Core.Storages public class UserStorage : IUserStorage { private readonly IStorageClient _storageClient; - private readonly IFilter _usersFilter; - public UserStorage(IStorageClient storageClient, IFilter usersFilter) + public UserStorage(IStorageClient storageClient) { _storageClient = storageClient; - _usersFilter = usersFilter; } public async Task> GetAsync(string id) => await _storageClient.GetUsingCacheAsync($"users/{id}"); public async Task>> BrowseAsync(BrowseUsers query) - { - var users = await _storageClient.GetCollectionUsingCacheAsync("users"); - - return _usersFilter.Filter(users, query); - } + => await _storageClient.GetFilteredCollectionUsingCacheAsync(query, "users"); } } \ No newline at end of file diff --git a/src/Services/Coolector.Services.Storage/Framework/Bootstrapper.cs b/src/Services/Coolector.Services.Storage/Framework/Bootstrapper.cs index 7f82e0d..005399f 100644 --- a/src/Services/Coolector.Services.Storage/Framework/Bootstrapper.cs +++ b/src/Services/Coolector.Services.Storage/Framework/Bootstrapper.cs @@ -3,7 +3,7 @@ using Coolector.Services.Mongo; using Coolector.Services.Nancy; using Coolector.Services.Storage.Framework.IoC; -using Coolector.Services.Storage.Providers; +using Coolector.Services.Storage.Modules.Providers; using Coolector.Services.Storage.Repositories; using Coolector.Services.Storage.Settings; using Nancy.Bootstrapper; diff --git a/src/Services/Coolector.Services.Storage/Providers/IProviderClient.cs b/src/Services/Coolector.Services.Storage/Modules/Providers/IProviderClient.cs similarity index 89% rename from src/Services/Coolector.Services.Storage/Providers/IProviderClient.cs rename to src/Services/Coolector.Services.Storage/Modules/Providers/IProviderClient.cs index 3a19f39..845a1dc 100644 --- a/src/Services/Coolector.Services.Storage/Providers/IProviderClient.cs +++ b/src/Services/Coolector.Services.Storage/Modules/Providers/IProviderClient.cs @@ -3,7 +3,7 @@ using Coolector.Common.Types; using Coolector.Services.Storage.Mappers; -namespace Coolector.Services.Storage.Providers +namespace Coolector.Services.Storage.Modules.Providers { public interface IProviderClient { diff --git a/src/Services/Coolector.Services.Storage/Providers/IServiceClient.cs b/src/Services/Coolector.Services.Storage/Modules/Providers/IServiceClient.cs similarity index 82% rename from src/Services/Coolector.Services.Storage/Providers/IServiceClient.cs rename to src/Services/Coolector.Services.Storage/Modules/Providers/IServiceClient.cs index f0b7411..6671296 100644 --- a/src/Services/Coolector.Services.Storage/Providers/IServiceClient.cs +++ b/src/Services/Coolector.Services.Storage/Modules/Providers/IServiceClient.cs @@ -2,7 +2,7 @@ using Coolector.Common.Types; using Coolector.Services.Storage.Mappers; -namespace Coolector.Services.Storage.Providers +namespace Coolector.Services.Storage.Modules.Providers { public interface IServiceClient { diff --git a/src/Services/Coolector.Services.Storage/Providers/IUserProvider.cs b/src/Services/Coolector.Services.Storage/Modules/Providers/IUserProvider.cs similarity index 78% rename from src/Services/Coolector.Services.Storage/Providers/IUserProvider.cs rename to src/Services/Coolector.Services.Storage/Modules/Providers/IUserProvider.cs index 66a4cf5..585c5a9 100644 --- a/src/Services/Coolector.Services.Storage/Providers/IUserProvider.cs +++ b/src/Services/Coolector.Services.Storage/Modules/Providers/IUserProvider.cs @@ -2,7 +2,7 @@ using Coolector.Common.DTO.Users; using Coolector.Common.Types; -namespace Coolector.Services.Storage.Providers +namespace Coolector.Services.Storage.Modules.Providers { public interface IUserProvider { diff --git a/src/Services/Coolector.Services.Storage/Providers/ProviderClient.cs b/src/Services/Coolector.Services.Storage/Modules/Providers/ProviderClient.cs similarity index 95% rename from src/Services/Coolector.Services.Storage/Providers/ProviderClient.cs rename to src/Services/Coolector.Services.Storage/Modules/Providers/ProviderClient.cs index ca5a54b..fbe701f 100644 --- a/src/Services/Coolector.Services.Storage/Providers/ProviderClient.cs +++ b/src/Services/Coolector.Services.Storage/Modules/Providers/ProviderClient.cs @@ -3,7 +3,7 @@ using Coolector.Common.Types; using Coolector.Services.Storage.Mappers; -namespace Coolector.Services.Storage.Providers +namespace Coolector.Services.Storage.Modules.Providers { public class ProviderClient : IProviderClient { diff --git a/src/Services/Coolector.Services.Storage/Providers/ServiceClient.cs b/src/Services/Coolector.Services.Storage/Modules/Providers/ServiceClient.cs similarity index 95% rename from src/Services/Coolector.Services.Storage/Providers/ServiceClient.cs rename to src/Services/Coolector.Services.Storage/Modules/Providers/ServiceClient.cs index 8422ee8..8b9f5aa 100644 --- a/src/Services/Coolector.Services.Storage/Providers/ServiceClient.cs +++ b/src/Services/Coolector.Services.Storage/Modules/Providers/ServiceClient.cs @@ -5,7 +5,7 @@ using Coolector.Services.Storage.Mappers; using Newtonsoft.Json; -namespace Coolector.Services.Storage.Providers +namespace Coolector.Services.Storage.Modules.Providers { public class ServiceClient : IServiceClient { diff --git a/src/Services/Coolector.Services.Storage/Providers/UserProvider.cs b/src/Services/Coolector.Services.Storage/Modules/Providers/UserProvider.cs similarity index 96% rename from src/Services/Coolector.Services.Storage/Providers/UserProvider.cs rename to src/Services/Coolector.Services.Storage/Modules/Providers/UserProvider.cs index c138420..01059fc 100644 --- a/src/Services/Coolector.Services.Storage/Providers/UserProvider.cs +++ b/src/Services/Coolector.Services.Storage/Modules/Providers/UserProvider.cs @@ -5,7 +5,7 @@ using Coolector.Services.Storage.Repositories; using Coolector.Services.Storage.Settings; -namespace Coolector.Services.Storage.Providers +namespace Coolector.Services.Storage.Modules.Providers { public class UserProvider : IUserProvider { diff --git a/src/Services/Coolector.Services.Storage/Modules/UserModule.cs b/src/Services/Coolector.Services.Storage/Modules/UserModule.cs index e13377f..60387e7 100644 --- a/src/Services/Coolector.Services.Storage/Modules/UserModule.cs +++ b/src/Services/Coolector.Services.Storage/Modules/UserModule.cs @@ -1,4 +1,4 @@ -using Coolector.Services.Storage.Providers; +using Coolector.Services.Storage.Modules.Providers; using Nancy; namespace Coolector.Services.Storage.Modules