From 900e7ed6b19d3b6cb79d70a250980889a85811d7 Mon Sep 17 00:00:00 2001 From: Niek Jannink Date: Tue, 22 May 2018 12:00:46 -0500 Subject: [PATCH 1/4] Add background cache update functionality. --- .../AssetsServiceWithCache.cs | 46 +++++++--- .../Cache/DictionaryCache.cs | 61 +++++++++++-- .../Cache/IDictionaryCache.cs | 21 +++++ .../IAssetsServiceWithCache.cs | 34 ++++++- .../Lykke.Service.Assets.Client.csproj | 6 +- .../ServiceCollectionExtensions.cs | 19 ++-- .../Cache/DictionaryCacheTest.cs | 88 +++++++++++++++++++ .../Lykke.Service.Assets.Tests.csproj | 1 + 8 files changed, 246 insertions(+), 30 deletions(-) create mode 100644 tests/Lykke.Service.Assets.Tests/Cache/DictionaryCacheTest.cs diff --git a/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs b/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs index 9f214de5..8d3554e6 100644 --- a/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs +++ b/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs @@ -1,26 +1,32 @@ -using System.Collections.Generic; +using Lykke.Service.Assets.Client.Cache; +using Lykke.Service.Assets.Client.Models; +using System; +using System.Collections.Generic; +using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; -using Lykke.Service.Assets.Client.Cache; -using Lykke.Service.Assets.Client.Models; +using Common.Log; namespace Lykke.Service.Assets.Client { + /// public class AssetsServiceWithCache : IAssetsServiceWithCache { - private readonly IAssetsService _assetsService; - private readonly IDictionaryCache _assetsCache; + private readonly IAssetsService _assetsService; + private readonly IDictionaryCache _assetsCache; private readonly IDictionaryCache _assetPairsCache; + private readonly ILog _log; - - public AssetsServiceWithCache(IAssetsService assetsService, IDictionaryCache assetsCache, IDictionaryCache assetPairsCache) + /// + public AssetsServiceWithCache(IAssetsService assetsService, IDictionaryCache assetsCache, IDictionaryCache assetPairsCache, ILog log) { - _assetsService = assetsService; - _assetsCache = assetsCache; + _assetsService = assetsService; + _assetsCache = assetsCache; _assetPairsCache = assetPairsCache; + _log = log; } - + /// public async Task> GetAllAssetPairsAsync(CancellationToken cancellationToken = new CancellationToken()) { await _assetPairsCache.EnsureCacheIsUpdatedAsync(() => GetUncachedAssetPairsAsync(cancellationToken)); @@ -28,13 +34,18 @@ public AssetsServiceWithCache(IAssetsService assetsService, IDictionaryCache> GetAllAssetsAsync(CancellationToken cancellationToken = new CancellationToken()) + async Task> IAssetsServiceWithCache.GetAllAssetsAsync(CancellationToken cancellationToken) + => await GetAllAssetsAsync(false, cancellationToken); + + /// + public async Task> GetAllAssetsAsync(bool includeNonTradable, CancellationToken cancellationToken = new CancellationToken()) { await _assetsCache.EnsureCacheIsUpdatedAsync(() => GetUncachedAssetsAsync(cancellationToken)); return _assetsCache.GetAll(); } + /// public async Task TryGetAssetAsync(string assetId, CancellationToken cancellationToken = new CancellationToken()) { await _assetsCache.EnsureCacheIsUpdatedAsync(() => GetUncachedAssetsAsync(cancellationToken)); @@ -42,6 +53,7 @@ public AssetsServiceWithCache(IAssetsService assetsService, IDictionaryCache public async Task TryGetAssetPairAsync(string assetPairId, CancellationToken cancellationToken = new CancellationToken()) { await _assetPairsCache.EnsureCacheIsUpdatedAsync(() => GetUncachedAssetPairsAsync(cancellationToken)); @@ -49,16 +61,28 @@ public AssetsServiceWithCache(IAssetsService assetsService, IDictionaryCache public async Task UpdateAssetPairsCacheAsync(CancellationToken cancellationToken = new CancellationToken()) { _assetPairsCache.Update(await GetUncachedAssetPairsAsync(cancellationToken)); } + /// public async Task UpdateAssetsCacheAsync(CancellationToken cancellationToken = new CancellationToken()) { _assetsCache.Update(await GetUncachedAssetsAsync(cancellationToken)); } + /// + public IDisposable StartAutoCacheUpdate() + { + return new CompositeDisposable + { + _assetPairsCache.StartAutoUpdate(nameof(AssetsServiceWithCache), _log, () => GetUncachedAssetPairsAsync(new CancellationToken())), + _assetsCache.StartAutoUpdate(nameof(AssetsServiceWithCache), _log, () => GetUncachedAssetsAsync(new CancellationToken())) + }; + } + private async Task> GetUncachedAssetsAsync(CancellationToken cancellationToken) { return await _assetsService.AssetGetAllAsync(false, cancellationToken); diff --git a/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs b/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs index 0ffbcce2..1e47c44b 100644 --- a/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs +++ b/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs @@ -1,38 +1,79 @@ -using System; +using Common; +using Common.Log; +using System; using System.Collections.Generic; using System.Linq; +using System.Reactive.Disposables; +using System.Threading; using System.Threading.Tasks; namespace Lykke.Service.Assets.Client.Cache { + /// public class DictionaryCache : IDictionaryCache where T : ICacheItem { private readonly IDateTimeProvider _dateTimeProvider; - private readonly TimeSpan _cacheExpirationPeriod; + private readonly TimeSpan _cacheExpirationPeriod; private Dictionary _items; - private DateTime _cacheExpirationMoment; - + private DateTime _cacheExpirationMoment; + private bool _inAutoUpdate; + /// public DictionaryCache(IDateTimeProvider dateTimeProvider, TimeSpan cacheExpirationPeriod) { - _items = new Dictionary(); + _items = new Dictionary(); _cacheExpirationMoment = DateTime.MinValue; - _dateTimeProvider = dateTimeProvider; + _dateTimeProvider = dateTimeProvider; _cacheExpirationPeriod = cacheExpirationPeriod; } + /// + public IDisposable StartAutoUpdate(string componentName, ILog log, Func>> getAllAsync) + { + if (_inAutoUpdate) + { + throw new InvalidOperationException("Dictionary is already in auto update mode."); + } + + _inAutoUpdate = true; + async Task UpdateCache(ITimerTrigger trigger, TimerTriggeredHandlerArgs args, CancellationToken token) + { + await Update(getAllAsync); + } + + var timer = new TimerTrigger(componentName, _cacheExpirationPeriod, log, UpdateCache); + timer.Start(); + + return Disposable.Create(() => + { + _inAutoUpdate = false; + timer.Dispose(); + }); + } + + /// public async Task EnsureCacheIsUpdatedAsync(Func>> getAllItemsAsync) { - if (_cacheExpirationMoment < _dateTimeProvider.UtcNow) + if (_inAutoUpdate) { - var items = await getAllItemsAsync(); + return; + } - Update(items); + if (_cacheExpirationMoment < _dateTimeProvider.UtcNow) + { + await Update(getAllItemsAsync); } } + private async Task Update(Func>> getAllItemsAsync) + { + var items = await getAllItemsAsync(); + Update(items); + } + + /// public void Update(IEnumerable items) { _items = items.ToDictionary(p => p.Id, p => p); @@ -40,6 +81,7 @@ public void Update(IEnumerable items) _cacheExpirationMoment = _dateTimeProvider.UtcNow + _cacheExpirationPeriod; } + /// public T TryGet(string id) { _items.TryGetValue(id, out var pair); @@ -47,6 +89,7 @@ public T TryGet(string id) return pair; } + /// public IReadOnlyCollection GetAll() { return _items.Values; diff --git a/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs b/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs index f81be666..a12d9a3a 100644 --- a/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs +++ b/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs @@ -1,18 +1,39 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Common.Log; namespace Lykke.Service.Assets.Client.Cache { + /// + /// Simple in-memory client side cache. + /// public interface IDictionaryCache where T : ICacheItem { + /// + /// Starts an automatic updater that keeps the cache updated on a background thread. + /// + IDisposable StartAutoUpdate(string componentName, ILog log, Func>> getAllAsync); + + /// + /// Update the cache when cache has expired. + /// Task EnsureCacheIsUpdatedAsync(Func>> getAllAsync); + /// + /// Update the cache with given data. + /// void Update(IEnumerable items); + /// + /// Try to get cached item with given id. + /// T TryGet(string id); + /// + /// Get all cached items. + /// IReadOnlyCollection GetAll(); } } diff --git a/client/Lykke.Service.Assets.Client/IAssetsServiceWithCache.cs b/client/Lykke.Service.Assets.Client/IAssetsServiceWithCache.cs index e19efbcc..02d7cff2 100644 --- a/client/Lykke.Service.Assets.Client/IAssetsServiceWithCache.cs +++ b/client/Lykke.Service.Assets.Client/IAssetsServiceWithCache.cs @@ -1,22 +1,44 @@ -using System.Collections.Generic; +using Lykke.Service.Assets.Client.Models; +using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Lykke.Service.Assets.Client.Models; namespace Lykke.Service.Assets.Client { + /// + /// Client side in-memory cached version of the . + /// public interface IAssetsServiceWithCache { + /// + /// Get all asset-pairs. + /// Task> GetAllAssetPairsAsync(CancellationToken cancellationToken = new CancellationToken()); + /// + /// Get all assets + /// + [Obsolete("Use GetAllAssetsAsync(bool) instead")] Task> GetAllAssetsAsync(CancellationToken cancellationToken = new CancellationToken()); + /// + /// Get all assets + /// + Task> GetAllAssetsAsync(bool includeNonTradable, CancellationToken cancellationToken = new CancellationToken()); + + /// + /// Try to find an asset with given id. + /// Task TryGetAssetAsync(string assetId, CancellationToken cancellationToken = new CancellationToken()); + /// + /// Try to find an asset-pair with given id. + /// Task TryGetAssetPairAsync(string assetPairId, CancellationToken cancellationToken = new CancellationToken()); /// - /// Forcibly updates client-side asset pairs cache + /// Forcibly updates client-side asset-pairs cache /// Task UpdateAssetPairsCacheAsync(CancellationToken cancellationToken = new CancellationToken()); @@ -24,5 +46,11 @@ public interface IAssetsServiceWithCache /// Forcibly updates client-side assets cache /// Task UpdateAssetsCacheAsync(CancellationToken cancellationToken = new CancellationToken()); + + /// + /// Starts an automatic update process that will keep the caches updated the background. + /// + /// the update process, when disposed the auto update will stop + IDisposable StartAutoCacheUpdate(); } } diff --git a/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj b/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj index 95ac412a..63ff8fb8 100644 --- a/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj +++ b/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj @@ -25,9 +25,11 @@ + - - + + + \ No newline at end of file diff --git a/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs b/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs index ede6a7c4..d987d6c4 100644 --- a/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs +++ b/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ -using System.Net.Http; +using Common.Log; +using System.Net.Http; using Lykke.Service.Assets.Client.Cache; using Lykke.Service.Assets.Client.Models; using Microsoft.Extensions.DependencyInjection; @@ -7,19 +8,27 @@ namespace Lykke.Service.Assets.Client { public static class ServiceCollectionExtensions { - public static void RegisterAssetsClient(this IServiceCollection services, AssetServiceSettings settings) + public static void RegisterAssetsClient(this IServiceCollection services, AssetServiceSettings settings, + ILog log) { services .AddSingleton(x => new AssetsService(settings.BaseUri, new HttpClient())); services - .AddTransient(); + .AddSingleton>(x => + new DictionaryCache(new DateTimeProvider(), settings.AssetsCacheExpirationPeriod)); services - .AddSingleton>(x => new DictionaryCache(new DateTimeProvider(), settings.AssetsCacheExpirationPeriod)); + .AddSingleton>(x => + new DictionaryCache(new DateTimeProvider(), settings.AssetPairsCacheExpirationPeriod)); services - .AddSingleton>(x => new DictionaryCache(new DateTimeProvider(), settings.AssetPairsCacheExpirationPeriod)); + .AddSingleton(x => new AssetsServiceWithCache( + x.GetService(), + x.GetService>(), + x.GetService>(), + log + )); } } } diff --git a/tests/Lykke.Service.Assets.Tests/Cache/DictionaryCacheTest.cs b/tests/Lykke.Service.Assets.Tests/Cache/DictionaryCacheTest.cs new file mode 100644 index 00000000..a55326f8 --- /dev/null +++ b/tests/Lykke.Service.Assets.Tests/Cache/DictionaryCacheTest.cs @@ -0,0 +1,88 @@ +using Lykke.Service.Assets.Client.Cache; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Common.Log; + +namespace Lykke.Service.Assets.Tests.Cache +{ + [TestClass] + public class DictionaryCacheTest + { + private int _getItemsCallCount; + + [TestInitialize] + public void Setup() + { + _getItemsCallCount = 0; + } + + [TestMethod] + public async Task TestExpiringCache() + { + var date = DateTime.UtcNow; + var provider = new Mock(); + provider.SetupGet(x => x.UtcNow).Returns(date); + + var sot = new DictionaryCache(provider.Object, TimeSpan.FromSeconds(30)); + + await sot.EnsureCacheIsUpdatedAsync(GetItems); + + Assert.IsTrue(sot.GetAll().Count > 0); + Assert.AreEqual(1, _getItemsCallCount); + + provider.SetupGet(x => x.UtcNow).Returns(date.AddMinutes(1)); + await sot.EnsureCacheIsUpdatedAsync(GetItems); + Assert.AreEqual(2, _getItemsCallCount); + + provider.SetupGet(x => x.UtcNow).Returns(date.AddSeconds(29)); + + await sot.EnsureCacheIsUpdatedAsync(GetItems); + Assert.AreEqual(2, _getItemsCallCount); + } + + [TestMethod] + public async Task TestRefreshingCache() + { + var date = DateTime.UtcNow; + var provider = new Mock(); + provider.SetupGet(x => x.UtcNow).Returns(date); + + var sot = new DictionaryCache(provider.Object, TimeSpan.FromMilliseconds(100)); + using (sot.StartAutoUpdate("TestRefreshingCache", Mock.Of(), GetItems)) + { + Assert.IsTrue(sot.GetAll().Count > 0, "Dictionary should be filled after update"); + Assert.AreEqual(1, _getItemsCallCount, "Initial update should be called at start"); + + await Task.Delay(150); + + Assert.AreEqual(2, _getItemsCallCount, "Update should have been called after timer tick"); + } + + await Task.Delay(150); + + Assert.AreEqual(2, _getItemsCallCount, "Auto update should have stopped after dispose"); + } + + private Task> GetItems() + { + _getItemsCallCount++; + + IEnumerable result = new List + { + new CacheItem {Id = "1"}, + new CacheItem {Id = "2"} + }; + + return Task.FromResult(result); + } + + private class CacheItem : ICacheItem + { + public string Id { get; set; } + } + } +} diff --git a/tests/Lykke.Service.Assets.Tests/Lykke.Service.Assets.Tests.csproj b/tests/Lykke.Service.Assets.Tests/Lykke.Service.Assets.Tests.csproj index 1438c861..d67232fd 100644 --- a/tests/Lykke.Service.Assets.Tests/Lykke.Service.Assets.Tests.csproj +++ b/tests/Lykke.Service.Assets.Tests/Lykke.Service.Assets.Tests.csproj @@ -12,6 +12,7 @@ + From c3faa832b9b662357b50bb06502a62c3b0662cba Mon Sep 17 00:00:00 2001 From: Niek Jannink Date: Wed, 30 May 2018 09:57:38 -0500 Subject: [PATCH 2/4] Update nuget packages --- .../Lykke.Service.Assets.Core.csproj | 2 +- src/Lykke.Service.Assets/Lykke.Service.Assets.csproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Lykke.Service.Assets.Core/Lykke.Service.Assets.Core.csproj b/src/Lykke.Service.Assets.Core/Lykke.Service.Assets.Core.csproj index 4a508e26..cdeec0c5 100644 --- a/src/Lykke.Service.Assets.Core/Lykke.Service.Assets.Core.csproj +++ b/src/Lykke.Service.Assets.Core/Lykke.Service.Assets.Core.csproj @@ -6,7 +6,7 @@ 1.0.0 - + \ No newline at end of file diff --git a/src/Lykke.Service.Assets/Lykke.Service.Assets.csproj b/src/Lykke.Service.Assets/Lykke.Service.Assets.csproj index d6c48471..33b5b51e 100644 --- a/src/Lykke.Service.Assets/Lykke.Service.Assets.csproj +++ b/src/Lykke.Service.Assets/Lykke.Service.Assets.csproj @@ -17,14 +17,14 @@ - + - + From 7a6801d0aa01206fa6320c938365f4b76287409d Mon Sep 17 00:00:00 2001 From: Niek Jannink Date: Thu, 31 May 2018 12:19:43 -0500 Subject: [PATCH 3/4] Refactored caching mechanism to use Lykke.Common and split responsibilities and leaking inner workings. --- .../AssetsServiceWithCache.cs | 79 ++++------------ .../Cache/DateTimeProvider.cs | 9 -- .../Cache/DictionaryCache.cs | 94 ++++++------------- .../Cache/ExpiringDictionaryCache.cs | 14 +++ .../Cache/IDateTimeProvider .cs | 9 -- .../Cache/IDictionaryCache.cs | 23 ++--- .../Cache/RefreshingDictionaryCache.cs | 34 +++++++ .../IAssetsServiceWithCache.cs | 6 -- .../Lykke.Service.Assets.Client.csproj | 1 - .../ServiceCollectionExtensions.cs | 47 +++++++--- .../Updaters/AssetPairsUpdater.cs | 22 +++++ .../Updaters/AssetsUpdater.cs | 22 +++++ .../Updaters/IUpdater.cs | 20 ++++ .../Cache/DictionaryCacheTest.cs | 88 ----------------- .../Cache/ExpiringDictionaryCacheTest.cs | 55 +++++++++++ .../Cache/RefreshingDictionaryCacheTest.cs | 60 ++++++++++++ 16 files changed, 313 insertions(+), 270 deletions(-) delete mode 100644 client/Lykke.Service.Assets.Client/Cache/DateTimeProvider.cs create mode 100644 client/Lykke.Service.Assets.Client/Cache/ExpiringDictionaryCache.cs delete mode 100644 client/Lykke.Service.Assets.Client/Cache/IDateTimeProvider .cs create mode 100644 client/Lykke.Service.Assets.Client/Cache/RefreshingDictionaryCache.cs create mode 100644 client/Lykke.Service.Assets.Client/Updaters/AssetPairsUpdater.cs create mode 100644 client/Lykke.Service.Assets.Client/Updaters/AssetsUpdater.cs create mode 100644 client/Lykke.Service.Assets.Client/Updaters/IUpdater.cs delete mode 100644 tests/Lykke.Service.Assets.Tests/Cache/DictionaryCacheTest.cs create mode 100644 tests/Lykke.Service.Assets.Tests/Cache/ExpiringDictionaryCacheTest.cs create mode 100644 tests/Lykke.Service.Assets.Tests/Cache/RefreshingDictionaryCacheTest.cs diff --git a/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs b/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs index 8d3554e6..4c8fcaab 100644 --- a/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs +++ b/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs @@ -1,96 +1,49 @@ -using Lykke.Service.Assets.Client.Cache; -using Lykke.Service.Assets.Client.Models; -using System; -using System.Collections.Generic; -using System.Reactive.Disposables; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Common.Log; +using Lykke.Service.Assets.Client.Cache; +using Lykke.Service.Assets.Client.Models; namespace Lykke.Service.Assets.Client { /// public class AssetsServiceWithCache : IAssetsServiceWithCache { - private readonly IAssetsService _assetsService; private readonly IDictionaryCache _assetsCache; private readonly IDictionaryCache _assetPairsCache; - private readonly ILog _log; /// - public AssetsServiceWithCache(IAssetsService assetsService, IDictionaryCache assetsCache, IDictionaryCache assetPairsCache, ILog log) + public AssetsServiceWithCache(IDictionaryCache assetsCache, IDictionaryCache assetPairsCache) { - _assetsService = assetsService; _assetsCache = assetsCache; _assetPairsCache = assetPairsCache; - _log = log; } /// - public async Task> GetAllAssetPairsAsync(CancellationToken cancellationToken = new CancellationToken()) - { - await _assetPairsCache.EnsureCacheIsUpdatedAsync(() => GetUncachedAssetPairsAsync(cancellationToken)); - - return _assetPairsCache.GetAll(); - } + public async Task> GetAllAssetPairsAsync(CancellationToken cancellationToken = new CancellationToken()) + => await _assetPairsCache.GetAll(cancellationToken); async Task> IAssetsServiceWithCache.GetAllAssetsAsync(CancellationToken cancellationToken) => await GetAllAssetsAsync(false, cancellationToken); /// - public async Task> GetAllAssetsAsync(bool includeNonTradable, CancellationToken cancellationToken = new CancellationToken()) - { - await _assetsCache.EnsureCacheIsUpdatedAsync(() => GetUncachedAssetsAsync(cancellationToken)); - - return _assetsCache.GetAll(); - } - - /// - public async Task TryGetAssetAsync(string assetId, CancellationToken cancellationToken = new CancellationToken()) - { - await _assetsCache.EnsureCacheIsUpdatedAsync(() => GetUncachedAssetsAsync(cancellationToken)); - - return _assetsCache.TryGet(assetId); - } + public async Task> GetAllAssetsAsync(bool includeNonTradable, CancellationToken cancellationToken = new CancellationToken()) + => await _assetsCache.GetAll(cancellationToken); /// - public async Task TryGetAssetPairAsync(string assetPairId, CancellationToken cancellationToken = new CancellationToken()) - { - await _assetPairsCache.EnsureCacheIsUpdatedAsync(() => GetUncachedAssetPairsAsync(cancellationToken)); - - return _assetPairsCache.TryGet(assetPairId); - } + public async Task TryGetAssetAsync(string assetId, CancellationToken cancellationToken = new CancellationToken()) + => await _assetsCache.TryGet(assetId, cancellationToken); /// - public async Task UpdateAssetPairsCacheAsync(CancellationToken cancellationToken = new CancellationToken()) - { - _assetPairsCache.Update(await GetUncachedAssetPairsAsync(cancellationToken)); - } + public async Task TryGetAssetPairAsync(string assetPairId, CancellationToken cancellationToken = new CancellationToken()) + => await _assetPairsCache.TryGet(assetPairId, cancellationToken); /// - public async Task UpdateAssetsCacheAsync(CancellationToken cancellationToken = new CancellationToken()) - { - _assetsCache.Update(await GetUncachedAssetsAsync(cancellationToken)); - } + public async Task UpdateAssetPairsCacheAsync(CancellationToken cancellationToken = new CancellationToken()) + => await _assetPairsCache.Reset(cancellationToken); /// - public IDisposable StartAutoCacheUpdate() - { - return new CompositeDisposable - { - _assetPairsCache.StartAutoUpdate(nameof(AssetsServiceWithCache), _log, () => GetUncachedAssetPairsAsync(new CancellationToken())), - _assetsCache.StartAutoUpdate(nameof(AssetsServiceWithCache), _log, () => GetUncachedAssetsAsync(new CancellationToken())) - }; - } - - private async Task> GetUncachedAssetsAsync(CancellationToken cancellationToken) - { - return await _assetsService.AssetGetAllAsync(false, cancellationToken); - } - - private async Task> GetUncachedAssetPairsAsync(CancellationToken cancellationToken) - { - return await _assetsService.AssetPairGetAllAsync(cancellationToken); - } + public async Task UpdateAssetsCacheAsync(CancellationToken cancellationToken = new CancellationToken()) + => await _assetsCache.Reset(cancellationToken); } } diff --git a/client/Lykke.Service.Assets.Client/Cache/DateTimeProvider.cs b/client/Lykke.Service.Assets.Client/Cache/DateTimeProvider.cs deleted file mode 100644 index 0310a63d..00000000 --- a/client/Lykke.Service.Assets.Client/Cache/DateTimeProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Lykke.Service.Assets.Client.Cache -{ - public class DateTimeProvider : IDateTimeProvider - { - public DateTime UtcNow => DateTime.UtcNow; - } -} diff --git a/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs b/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs index 1e47c44b..18d18cfe 100644 --- a/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs +++ b/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs @@ -1,98 +1,60 @@ -using Common; -using Common.Log; -using System; +using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; +using Lykke.Common.Cache; +using Lykke.Service.Assets.Client.Updaters; namespace Lykke.Service.Assets.Client.Cache { - /// public class DictionaryCache : IDictionaryCache where T : ICacheItem { - private readonly IDateTimeProvider _dateTimeProvider; - private readonly TimeSpan _cacheExpirationPeriod; + protected const string AllItems = @"AllItems"; - private Dictionary _items; - private DateTime _cacheExpirationMoment; - private bool _inAutoUpdate; + private readonly IUpdater _updater; + private readonly TimeSpan _expirationTime; - /// - public DictionaryCache(IDateTimeProvider dateTimeProvider, TimeSpan cacheExpirationPeriod) + protected DictionaryCache(IUpdater updater, TimeSpan expirationTime) { - _items = new Dictionary(); - _cacheExpirationMoment = DateTime.MinValue; - _dateTimeProvider = dateTimeProvider; - _cacheExpirationPeriod = cacheExpirationPeriod; + InnerCache = new OnDemandDataCache>(); + _updater = updater; + _expirationTime = expirationTime; } - /// - public IDisposable StartAutoUpdate(string componentName, ILog log, Func>> getAllAsync) - { - if (_inAutoUpdate) - { - throw new InvalidOperationException("Dictionary is already in auto update mode."); - } - - _inAutoUpdate = true; - async Task UpdateCache(ITimerTrigger trigger, TimerTriggeredHandlerArgs args, CancellationToken token) - { - await Update(getAllAsync); - } - - var timer = new TimerTrigger(componentName, _cacheExpirationPeriod, log, UpdateCache); - timer.Start(); - - return Disposable.Create(() => - { - _inAutoUpdate = false; - timer.Dispose(); - }); - } + protected OnDemandDataCache> InnerCache { get; } - /// - public async Task EnsureCacheIsUpdatedAsync(Func>> getAllItemsAsync) + public async Task Reset(CancellationToken token) { - if (_inAutoUpdate) - { - return; - } - - if (_cacheExpirationMoment < _dateTimeProvider.UtcNow) - { - await Update(getAllItemsAsync); - } + InnerCache.Remove(AllItems); + await GetItems(token); } - private async Task Update(Func>> getAllItemsAsync) + public async Task TryGet(string id, CancellationToken token) { - var items = await getAllItemsAsync(); - Update(items); + var items = await GetItems(token); + items.TryGetValue(id, out var item); + return item; } - /// - public void Update(IEnumerable items) + public async Task> GetAll(CancellationToken token) { - _items = items.ToDictionary(p => p.Id, p => p); - - _cacheExpirationMoment = _dateTimeProvider.UtcNow + _cacheExpirationPeriod; + var items = await GetItems(token); + return items.Values; } - /// - public T TryGet(string id) + private async Task> GetItems(CancellationToken token) { - _items.TryGetValue(id, out var pair); - - return pair; + return await InnerCache.GetOrAddAsync(AllItems, + async _ => await RetrieveFromUpdater(token) + , _expirationTime); } - /// - public IReadOnlyCollection GetAll() + protected async Task> RetrieveFromUpdater(CancellationToken token) { - return _items.Values; + var items = await _updater.GetItemsAsync(token); + return items.ToDictionary(x => x.Id); } } } diff --git a/client/Lykke.Service.Assets.Client/Cache/ExpiringDictionaryCache.cs b/client/Lykke.Service.Assets.Client/Cache/ExpiringDictionaryCache.cs new file mode 100644 index 00000000..10dd6498 --- /dev/null +++ b/client/Lykke.Service.Assets.Client/Cache/ExpiringDictionaryCache.cs @@ -0,0 +1,14 @@ +using System; +using Lykke.Service.Assets.Client.Updaters; + +namespace Lykke.Service.Assets.Client.Cache +{ + public class ExpiringDictionaryCache : DictionaryCache + where T : ICacheItem + { + public ExpiringDictionaryCache(TimeSpan expirationTime, IUpdater updater) + : base(updater, expirationTime) + { + } + } +} diff --git a/client/Lykke.Service.Assets.Client/Cache/IDateTimeProvider .cs b/client/Lykke.Service.Assets.Client/Cache/IDateTimeProvider .cs deleted file mode 100644 index f84bf8f2..00000000 --- a/client/Lykke.Service.Assets.Client/Cache/IDateTimeProvider .cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Lykke.Service.Assets.Client.Cache -{ - public interface IDateTimeProvider - { - DateTime UtcNow { get; } - } -} diff --git a/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs b/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs index a12d9a3a..77a55d68 100644 --- a/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs +++ b/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs @@ -1,7 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; -using Common.Log; namespace Lykke.Service.Assets.Client.Cache { @@ -12,28 +11,18 @@ public interface IDictionaryCache where T : ICacheItem { /// - /// Starts an automatic updater that keeps the cache updated on a background thread. + /// Resets the cache. /// - IDisposable StartAutoUpdate(string componentName, ILog log, Func>> getAllAsync); - - /// - /// Update the cache when cache has expired. - /// - Task EnsureCacheIsUpdatedAsync(Func>> getAllAsync); - - /// - /// Update the cache with given data. - /// - void Update(IEnumerable items); + Task Reset(CancellationToken token); /// /// Try to get cached item with given id. /// - T TryGet(string id); + Task TryGet(string id, CancellationToken token); /// /// Get all cached items. /// - IReadOnlyCollection GetAll(); + Task> GetAll(CancellationToken token); } } diff --git a/client/Lykke.Service.Assets.Client/Cache/RefreshingDictionaryCache.cs b/client/Lykke.Service.Assets.Client/Cache/RefreshingDictionaryCache.cs new file mode 100644 index 00000000..9772d52a --- /dev/null +++ b/client/Lykke.Service.Assets.Client/Cache/RefreshingDictionaryCache.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Common; +using Common.Log; +using Lykke.Service.Assets.Client.Updaters; + +namespace Lykke.Service.Assets.Client.Cache +{ + public class RefreshingDictionaryCache : DictionaryCache, IDisposable + where T : ICacheItem + { + private readonly TimerTrigger _trigger; + + public RefreshingDictionaryCache(TimeSpan expirationTime, IUpdater updater, ILog log) + : base(updater, expirationTime.Add(expirationTime)) + { + _trigger = new TimerTrigger(nameof(AssetsService), expirationTime, log, + async (x, y, token) => await UpdateCache(token)); + _trigger.Start(); + } + + private async Task UpdateCache(CancellationToken token) + { + var items = await RetrieveFromUpdater(token); + InnerCache.Set(AllItems, items); + } + + public void Dispose() + { + _trigger?.Dispose(); + } + } +} diff --git a/client/Lykke.Service.Assets.Client/IAssetsServiceWithCache.cs b/client/Lykke.Service.Assets.Client/IAssetsServiceWithCache.cs index 02d7cff2..17ce6729 100644 --- a/client/Lykke.Service.Assets.Client/IAssetsServiceWithCache.cs +++ b/client/Lykke.Service.Assets.Client/IAssetsServiceWithCache.cs @@ -46,11 +46,5 @@ public interface IAssetsServiceWithCache /// Forcibly updates client-side assets cache /// Task UpdateAssetsCacheAsync(CancellationToken cancellationToken = new CancellationToken()); - - /// - /// Starts an automatic update process that will keep the caches updated the background. - /// - /// the update process, when disposed the auto update will stop - IDisposable StartAutoCacheUpdate(); } } diff --git a/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj b/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj index 63ff8fb8..779a88c7 100644 --- a/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj +++ b/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj @@ -29,7 +29,6 @@ - \ No newline at end of file diff --git a/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs b/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs index d987d6c4..699f560d 100644 --- a/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs +++ b/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs @@ -1,34 +1,59 @@ +using System; using Common.Log; using System.Net.Http; using Lykke.Service.Assets.Client.Cache; using Lykke.Service.Assets.Client.Models; +using Lykke.Service.Assets.Client.Updaters; using Microsoft.Extensions.DependencyInjection; namespace Lykke.Service.Assets.Client { + /// + /// Service registration for client asset services. + /// public static class ServiceCollectionExtensions { - public static void RegisterAssetsClient(this IServiceCollection services, AssetServiceSettings settings, - ILog log) + /// + /// Register the asset services. + /// + /// the dependency injection service collection + /// the asset settings + /// the lykke log + /// use expiring caches or use a self refreshing cache for the assets and asset-pairs + public static void RegisterAssetsClient(this IServiceCollection services, AssetServiceSettings settings, ILog log, bool autoRefresh = false) { services .AddSingleton(x => new AssetsService(settings.BaseUri, new HttpClient())); - services - .AddSingleton>(x => - new DictionaryCache(new DateTimeProvider(), settings.AssetsCacheExpirationPeriod)); + services.AddTransient>(x => new AssetsUpdater(x.GetService())); + services.AddTransient(x => CreateDictionaryCache(x, settings.AssetsCacheExpirationPeriod, log, autoRefresh)); - services - .AddSingleton>(x => - new DictionaryCache(new DateTimeProvider(), settings.AssetPairsCacheExpirationPeriod)); + services.AddTransient>(x => new AssetPairsUpdater(x.GetService())); + services.AddTransient(x => CreateDictionaryCache(x, settings.AssetPairsCacheExpirationPeriod, log, autoRefresh)); services .AddSingleton(x => new AssetsServiceWithCache( - x.GetService(), x.GetService>(), - x.GetService>(), + x.GetService>() + )); + } + + private static IDictionaryCache CreateDictionaryCache(IServiceProvider provider, TimeSpan period, ILog log, bool refreshing) + where T : ICacheItem + { + if (refreshing) + { + return new RefreshingDictionaryCache( + period, + provider.GetService>(), log - )); + ); + } + + return new ExpiringDictionaryCache( + period, + provider.GetService>() + ); } } } diff --git a/client/Lykke.Service.Assets.Client/Updaters/AssetPairsUpdater.cs b/client/Lykke.Service.Assets.Client/Updaters/AssetPairsUpdater.cs new file mode 100644 index 00000000..bed42a09 --- /dev/null +++ b/client/Lykke.Service.Assets.Client/Updaters/AssetPairsUpdater.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Lykke.Service.Assets.Client.Models; + +namespace Lykke.Service.Assets.Client.Updaters +{ + internal class AssetPairsUpdater : IUpdater + { + private readonly IAssetsService _service; + + public AssetPairsUpdater(IAssetsService service) + { + _service = service; + } + + public async Task> GetItemsAsync(CancellationToken token) + { + return await _service.AssetPairGetAllAsync(token); + } + } +} diff --git a/client/Lykke.Service.Assets.Client/Updaters/AssetsUpdater.cs b/client/Lykke.Service.Assets.Client/Updaters/AssetsUpdater.cs new file mode 100644 index 00000000..d62052d6 --- /dev/null +++ b/client/Lykke.Service.Assets.Client/Updaters/AssetsUpdater.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Lykke.Service.Assets.Client.Models; + +namespace Lykke.Service.Assets.Client.Updaters +{ + internal class AssetsUpdater : IUpdater + { + private readonly IAssetsService _service; + + public AssetsUpdater(IAssetsService service) + { + _service = service; + } + + public async Task> GetItemsAsync(CancellationToken token) + { + return await _service.AssetGetAllAsync(false, token); + } + } +} diff --git a/client/Lykke.Service.Assets.Client/Updaters/IUpdater.cs b/client/Lykke.Service.Assets.Client/Updaters/IUpdater.cs new file mode 100644 index 00000000..b8ef06e4 --- /dev/null +++ b/client/Lykke.Service.Assets.Client/Updaters/IUpdater.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Lykke.Service.Assets.Client.Cache; + +namespace Lykke.Service.Assets.Client.Updaters +{ + /// + /// Updater of for retrieving the contents of a dictionary cache. + /// + /// the type of cache entry to retrieve + public interface IUpdater + where T : ICacheItem + { + /// + /// Retrieves the cache items. + /// + Task> GetItemsAsync(CancellationToken token); + } +} diff --git a/tests/Lykke.Service.Assets.Tests/Cache/DictionaryCacheTest.cs b/tests/Lykke.Service.Assets.Tests/Cache/DictionaryCacheTest.cs deleted file mode 100644 index a55326f8..00000000 --- a/tests/Lykke.Service.Assets.Tests/Cache/DictionaryCacheTest.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Lykke.Service.Assets.Client.Cache; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Common.Log; - -namespace Lykke.Service.Assets.Tests.Cache -{ - [TestClass] - public class DictionaryCacheTest - { - private int _getItemsCallCount; - - [TestInitialize] - public void Setup() - { - _getItemsCallCount = 0; - } - - [TestMethod] - public async Task TestExpiringCache() - { - var date = DateTime.UtcNow; - var provider = new Mock(); - provider.SetupGet(x => x.UtcNow).Returns(date); - - var sot = new DictionaryCache(provider.Object, TimeSpan.FromSeconds(30)); - - await sot.EnsureCacheIsUpdatedAsync(GetItems); - - Assert.IsTrue(sot.GetAll().Count > 0); - Assert.AreEqual(1, _getItemsCallCount); - - provider.SetupGet(x => x.UtcNow).Returns(date.AddMinutes(1)); - await sot.EnsureCacheIsUpdatedAsync(GetItems); - Assert.AreEqual(2, _getItemsCallCount); - - provider.SetupGet(x => x.UtcNow).Returns(date.AddSeconds(29)); - - await sot.EnsureCacheIsUpdatedAsync(GetItems); - Assert.AreEqual(2, _getItemsCallCount); - } - - [TestMethod] - public async Task TestRefreshingCache() - { - var date = DateTime.UtcNow; - var provider = new Mock(); - provider.SetupGet(x => x.UtcNow).Returns(date); - - var sot = new DictionaryCache(provider.Object, TimeSpan.FromMilliseconds(100)); - using (sot.StartAutoUpdate("TestRefreshingCache", Mock.Of(), GetItems)) - { - Assert.IsTrue(sot.GetAll().Count > 0, "Dictionary should be filled after update"); - Assert.AreEqual(1, _getItemsCallCount, "Initial update should be called at start"); - - await Task.Delay(150); - - Assert.AreEqual(2, _getItemsCallCount, "Update should have been called after timer tick"); - } - - await Task.Delay(150); - - Assert.AreEqual(2, _getItemsCallCount, "Auto update should have stopped after dispose"); - } - - private Task> GetItems() - { - _getItemsCallCount++; - - IEnumerable result = new List - { - new CacheItem {Id = "1"}, - new CacheItem {Id = "2"} - }; - - return Task.FromResult(result); - } - - private class CacheItem : ICacheItem - { - public string Id { get; set; } - } - } -} diff --git a/tests/Lykke.Service.Assets.Tests/Cache/ExpiringDictionaryCacheTest.cs b/tests/Lykke.Service.Assets.Tests/Cache/ExpiringDictionaryCacheTest.cs new file mode 100644 index 00000000..4eeaaef3 --- /dev/null +++ b/tests/Lykke.Service.Assets.Tests/Cache/ExpiringDictionaryCacheTest.cs @@ -0,0 +1,55 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Lykke.Service.Assets.Client.Cache; +using Lykke.Service.Assets.Client.Updaters; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace Lykke.Service.Assets.Tests.Cache +{ + [TestClass] + public class ExpiringDictionaryCacheTest + { + [TestMethod] + public async Task TestExpiringCache() + { + var items = Enumerable.Range(1, 5).Select(x => new CacheItem { Id = x.ToString() }); + + var updater = new Mock>(); + updater.Setup(x => x.GetItemsAsync(It.IsAny())).Returns(Task.FromResult(items)); + + var sot = new ExpiringDictionaryCache(TimeSpan.FromMilliseconds(100), updater.Object); + + var result1 = await sot.GetAll(new CancellationToken()); + Assert.IsNotNull(result1); + Assert.AreEqual(5, result1.Count); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Once); + + var result2 = await sot.GetAll(new CancellationToken()); + Assert.AreSame(result1, result2); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Once); + + await Task.Delay(150); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Once, @"Cache should expire not auto update"); + + var result3 = await sot.GetAll(new CancellationToken()); + Assert.IsNotNull(result3); + Assert.AreEqual(5, result1.Count); + Assert.AreNotSame(result1, result3); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Exactly(2)); + } + + public class CacheItem : ICacheItem + { + public string Id { get; set; } + } + } +} diff --git a/tests/Lykke.Service.Assets.Tests/Cache/RefreshingDictionaryCacheTest.cs b/tests/Lykke.Service.Assets.Tests/Cache/RefreshingDictionaryCacheTest.cs new file mode 100644 index 00000000..f57fa503 --- /dev/null +++ b/tests/Lykke.Service.Assets.Tests/Cache/RefreshingDictionaryCacheTest.cs @@ -0,0 +1,60 @@ +using Lykke.Service.Assets.Client.Cache; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Common.Log; +using Lykke.Service.Assets.Client.Updaters; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; + +namespace Lykke.Service.Assets.Tests.Cache +{ + [TestClass] + public class RefreshingDictionaryCacheTest + { + [TestMethod] + public async Task TestRefreshingCache() + { + var items = Enumerable.Range(1, 5).Select(x => new CacheItem { Id = x.ToString() }); + + var updater = new Mock>(); + updater.Setup(x => x.GetItemsAsync(It.IsAny())).Returns(Task.FromResult(items)); + + var sot = new RefreshingDictionaryCache(TimeSpan.FromMilliseconds(100), updater.Object, Mock.Of()); + + // Give system some time to update in background + await Task.Delay(10); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Once, @"Cache should auto initialize"); + + var result1 = await sot.GetAll(new CancellationToken()); + Assert.IsNotNull(result1); + Assert.AreEqual(5, result1.Count); + + var result2 = await sot.GetAll(new CancellationToken()); + Assert.AreSame(result1, result2); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Once, @"Cache should not update on GetItems"); + + await Task.Delay(125); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Exactly(2), @"Cache should update in background"); + + var result3 = await sot.GetAll(new CancellationToken()); + Assert.IsNotNull(result3); + Assert.AreEqual(5, result1.Count); + Assert.AreNotSame(result1, result3); + + updater.Verify(x => x.GetItemsAsync(It.IsAny()), Times.Exactly(2)); + } + + public class CacheItem : ICacheItem + { + public string Id { get; set; } + } + } +} From 9484c15760160f945efde7f08e56d34beabda42b Mon Sep 17 00:00:00 2001 From: Niek Jannink Date: Thu, 31 May 2018 14:25:16 -0500 Subject: [PATCH 4/4] review comments and nuget references cleanup --- .../AssetsServiceWithCache.cs | 30 ++++++++-------- .../Cache/DictionaryCache.cs | 35 +++++++++++-------- .../Cache/ExpiringDictionaryCache.cs | 11 +++++- .../Cache/ICacheItem.cs | 8 ++++- .../Cache/IDictionaryCache.cs | 2 +- .../Cache/RefreshingDictionaryCache.cs | 29 ++++++++------- .../InternalsVisibleTo.cs | 4 +++ .../Lykke.Service.Assets.Client.csproj | 2 -- .../ServiceCollectionExtensions.cs | 5 ++- .../Updaters/IUpdater.cs | 2 +- .../Lykke.Service.Assets.Repositories.csproj | 1 - .../Lykke.Service.Assets.Services.csproj | 1 - .../Lykke.Service.Assets.csproj | 11 ------ 13 files changed, 76 insertions(+), 65 deletions(-) create mode 100644 client/Lykke.Service.Assets.Client/InternalsVisibleTo.cs diff --git a/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs b/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs index 4c8fcaab..0ebf197f 100644 --- a/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs +++ b/client/Lykke.Service.Assets.Client/AssetsServiceWithCache.cs @@ -7,7 +7,7 @@ namespace Lykke.Service.Assets.Client { /// - public class AssetsServiceWithCache : IAssetsServiceWithCache + internal class AssetsServiceWithCache : IAssetsServiceWithCache { private readonly IDictionaryCache _assetsCache; private readonly IDictionaryCache _assetPairsCache; @@ -20,30 +20,30 @@ public AssetsServiceWithCache(IDictionaryCache assetsCache, IDictionaryCa } /// - public async Task> GetAllAssetPairsAsync(CancellationToken cancellationToken = new CancellationToken()) - => await _assetPairsCache.GetAll(cancellationToken); + public Task> GetAllAssetPairsAsync(CancellationToken cancellationToken = new CancellationToken()) + => _assetPairsCache.GetAll(cancellationToken); - async Task> IAssetsServiceWithCache.GetAllAssetsAsync(CancellationToken cancellationToken) - => await GetAllAssetsAsync(false, cancellationToken); + Task> IAssetsServiceWithCache.GetAllAssetsAsync(CancellationToken cancellationToken = new CancellationToken()) + => GetAllAssetsAsync(false, cancellationToken); /// - public async Task> GetAllAssetsAsync(bool includeNonTradable, CancellationToken cancellationToken = new CancellationToken()) - => await _assetsCache.GetAll(cancellationToken); + public Task> GetAllAssetsAsync(bool includeNonTradable, CancellationToken cancellationToken = new CancellationToken()) + => _assetsCache.GetAll(cancellationToken); /// - public async Task TryGetAssetAsync(string assetId, CancellationToken cancellationToken = new CancellationToken()) - => await _assetsCache.TryGet(assetId, cancellationToken); + public Task TryGetAssetAsync(string assetId, CancellationToken cancellationToken = new CancellationToken()) + => _assetsCache.TryGet(assetId, cancellationToken); /// - public async Task TryGetAssetPairAsync(string assetPairId, CancellationToken cancellationToken = new CancellationToken()) - => await _assetPairsCache.TryGet(assetPairId, cancellationToken); + public Task TryGetAssetPairAsync(string assetPairId, CancellationToken cancellationToken = new CancellationToken()) + => _assetPairsCache.TryGet(assetPairId, cancellationToken); /// - public async Task UpdateAssetPairsCacheAsync(CancellationToken cancellationToken = new CancellationToken()) - => await _assetPairsCache.Reset(cancellationToken); + public Task UpdateAssetPairsCacheAsync(CancellationToken cancellationToken = new CancellationToken()) + => _assetPairsCache.Reset(cancellationToken); /// - public async Task UpdateAssetsCacheAsync(CancellationToken cancellationToken = new CancellationToken()) - => await _assetsCache.Reset(cancellationToken); + public Task UpdateAssetsCacheAsync(CancellationToken cancellationToken = new CancellationToken()) + => _assetsCache.Reset(cancellationToken); } } diff --git a/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs b/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs index 18d18cfe..5c81cf40 100644 --- a/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs +++ b/client/Lykke.Service.Assets.Client/Cache/DictionaryCache.cs @@ -8,29 +8,35 @@ namespace Lykke.Service.Assets.Client.Cache { - public class DictionaryCache : IDictionaryCache + /// + /// Base class for a dictionary cache. + /// + internal class DictionaryCache : IDictionaryCache where T : ICacheItem { - protected const string AllItems = @"AllItems"; - + private const string AllItems = @"AllItems"; + private readonly OnDemandDataCache> _innerCache; private readonly IUpdater _updater; private readonly TimeSpan _expirationTime; + /// + /// Create new dictionary cache. + /// protected DictionaryCache(IUpdater updater, TimeSpan expirationTime) { - InnerCache = new OnDemandDataCache>(); + _innerCache = new OnDemandDataCache>(); _updater = updater; _expirationTime = expirationTime; } - protected OnDemandDataCache> InnerCache { get; } - + /// public async Task Reset(CancellationToken token) { - InnerCache.Remove(AllItems); + _innerCache.Remove(AllItems); await GetItems(token); } + /// public async Task TryGet(string id, CancellationToken token) { var items = await GetItems(token); @@ -38,6 +44,7 @@ public async Task TryGet(string id, CancellationToken token) return item; } + /// public async Task> GetAll(CancellationToken token) { var items = await GetItems(token); @@ -46,15 +53,13 @@ public async Task> GetAll(CancellationToken token) private async Task> GetItems(CancellationToken token) { - return await InnerCache.GetOrAddAsync(AllItems, - async _ => await RetrieveFromUpdater(token) - , _expirationTime); - } + async Task> Refresh() + { + var items = await _updater.GetItemsAsync(token); + return items.ToDictionary(x => x.Id); + } - protected async Task> RetrieveFromUpdater(CancellationToken token) - { - var items = await _updater.GetItemsAsync(token); - return items.ToDictionary(x => x.Id); + return await _innerCache.GetOrAddAsync(AllItems, _ => Refresh(), _expirationTime); } } } diff --git a/client/Lykke.Service.Assets.Client/Cache/ExpiringDictionaryCache.cs b/client/Lykke.Service.Assets.Client/Cache/ExpiringDictionaryCache.cs index 10dd6498..2696f305 100644 --- a/client/Lykke.Service.Assets.Client/Cache/ExpiringDictionaryCache.cs +++ b/client/Lykke.Service.Assets.Client/Cache/ExpiringDictionaryCache.cs @@ -3,9 +3,18 @@ namespace Lykke.Service.Assets.Client.Cache { - public class ExpiringDictionaryCache : DictionaryCache + /// + /// Expiring dictionary cache where the cache entry expires after given time. + /// + /// the type of cached item + internal sealed class ExpiringDictionaryCache : DictionaryCache where T : ICacheItem { + /// + /// Create a new expiring dictionary cache. + /// + /// expiration time + /// item updater public ExpiringDictionaryCache(TimeSpan expirationTime, IUpdater updater) : base(updater, expirationTime) { diff --git a/client/Lykke.Service.Assets.Client/Cache/ICacheItem.cs b/client/Lykke.Service.Assets.Client/Cache/ICacheItem.cs index d071486d..4f7c944a 100644 --- a/client/Lykke.Service.Assets.Client/Cache/ICacheItem.cs +++ b/client/Lykke.Service.Assets.Client/Cache/ICacheItem.cs @@ -1,7 +1,13 @@ namespace Lykke.Service.Assets.Client.Cache { - public interface ICacheItem + /// + /// Cache item {T} in a . + /// + internal interface ICacheItem { + /// + /// The id of the entry + /// string Id { get; } } } diff --git a/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs b/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs index 77a55d68..8d5e564f 100644 --- a/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs +++ b/client/Lykke.Service.Assets.Client/Cache/IDictionaryCache.cs @@ -7,7 +7,7 @@ namespace Lykke.Service.Assets.Client.Cache /// /// Simple in-memory client side cache. /// - public interface IDictionaryCache + internal interface IDictionaryCache where T : ICacheItem { /// diff --git a/client/Lykke.Service.Assets.Client/Cache/RefreshingDictionaryCache.cs b/client/Lykke.Service.Assets.Client/Cache/RefreshingDictionaryCache.cs index 9772d52a..8548d67f 100644 --- a/client/Lykke.Service.Assets.Client/Cache/RefreshingDictionaryCache.cs +++ b/client/Lykke.Service.Assets.Client/Cache/RefreshingDictionaryCache.cs @@ -1,31 +1,34 @@ using System; -using System.Threading; -using System.Threading.Tasks; using Common; using Common.Log; using Lykke.Service.Assets.Client.Updaters; namespace Lykke.Service.Assets.Client.Cache { - public class RefreshingDictionaryCache : DictionaryCache, IDisposable + /// + /// A dictionary cache that refreshes/synchronizes in the background. + /// + /// the type of cached item + internal sealed class RefreshingDictionaryCache : DictionaryCache, IDisposable where T : ICacheItem { private readonly TimerTrigger _trigger; - public RefreshingDictionaryCache(TimeSpan expirationTime, IUpdater updater, ILog log) - : base(updater, expirationTime.Add(expirationTime)) + /// + /// Creates a new refreshing dictionary cache. + /// + /// the refresh time + /// the item updater + /// the lykke log + public RefreshingDictionaryCache(TimeSpan refreshTime, IUpdater updater, ILog log) + : base(updater, refreshTime.Add(refreshTime)) { - _trigger = new TimerTrigger(nameof(AssetsService), expirationTime, log, - async (x, y, token) => await UpdateCache(token)); + _trigger = new TimerTrigger(nameof(AssetsService), refreshTime, log, + async (x, y, token) => await Reset(token)); _trigger.Start(); } - private async Task UpdateCache(CancellationToken token) - { - var items = await RetrieveFromUpdater(token); - InnerCache.Set(AllItems, items); - } - + /// public void Dispose() { _trigger?.Dispose(); diff --git a/client/Lykke.Service.Assets.Client/InternalsVisibleTo.cs b/client/Lykke.Service.Assets.Client/InternalsVisibleTo.cs new file mode 100644 index 00000000..5d280308 --- /dev/null +++ b/client/Lykke.Service.Assets.Client/InternalsVisibleTo.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Lykke.Service.Assets.Tests")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj b/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj index 779a88c7..4e588a27 100644 --- a/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj +++ b/client/Lykke.Service.Assets.Client/Lykke.Service.Assets.Client.csproj @@ -26,9 +26,7 @@ - - \ No newline at end of file diff --git a/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs b/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs index 699f560d..0a5eecfb 100644 --- a/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs +++ b/client/Lykke.Service.Assets.Client/ServiceCollectionExtensions.cs @@ -20,7 +20,7 @@ public static class ServiceCollectionExtensions /// the asset settings /// the lykke log /// use expiring caches or use a self refreshing cache for the assets and asset-pairs - public static void RegisterAssetsClient(this IServiceCollection services, AssetServiceSettings settings, ILog log, bool autoRefresh = false) + public static void RegisterAssetsClient(this IServiceCollection services, AssetServiceSettings settings, ILog log, bool autoRefresh = true) { services .AddSingleton(x => new AssetsService(settings.BaseUri, new HttpClient())); @@ -31,8 +31,7 @@ public static void RegisterAssetsClient(this IServiceCollection services, AssetS services.AddTransient>(x => new AssetPairsUpdater(x.GetService())); services.AddTransient(x => CreateDictionaryCache(x, settings.AssetPairsCacheExpirationPeriod, log, autoRefresh)); - services - .AddSingleton(x => new AssetsServiceWithCache( + services.AddSingleton(x => new AssetsServiceWithCache( x.GetService>(), x.GetService>() )); diff --git a/client/Lykke.Service.Assets.Client/Updaters/IUpdater.cs b/client/Lykke.Service.Assets.Client/Updaters/IUpdater.cs index b8ef06e4..1b831f20 100644 --- a/client/Lykke.Service.Assets.Client/Updaters/IUpdater.cs +++ b/client/Lykke.Service.Assets.Client/Updaters/IUpdater.cs @@ -9,7 +9,7 @@ namespace Lykke.Service.Assets.Client.Updaters /// Updater of for retrieving the contents of a dictionary cache. /// /// the type of cache entry to retrieve - public interface IUpdater + internal interface IUpdater where T : ICacheItem { /// diff --git a/src/Lykke.Service.Assets.Repositories/Lykke.Service.Assets.Repositories.csproj b/src/Lykke.Service.Assets.Repositories/Lykke.Service.Assets.Repositories.csproj index 634b1ac1..a6110243 100644 --- a/src/Lykke.Service.Assets.Repositories/Lykke.Service.Assets.Repositories.csproj +++ b/src/Lykke.Service.Assets.Repositories/Lykke.Service.Assets.Repositories.csproj @@ -5,7 +5,6 @@ - diff --git a/src/Lykke.Service.Assets.Services/Lykke.Service.Assets.Services.csproj b/src/Lykke.Service.Assets.Services/Lykke.Service.Assets.Services.csproj index 3c231b77..afa74d51 100644 --- a/src/Lykke.Service.Assets.Services/Lykke.Service.Assets.Services.csproj +++ b/src/Lykke.Service.Assets.Services/Lykke.Service.Assets.Services.csproj @@ -5,7 +5,6 @@ - diff --git a/src/Lykke.Service.Assets/Lykke.Service.Assets.csproj b/src/Lykke.Service.Assets/Lykke.Service.Assets.csproj index 33b5b51e..4659f6b0 100644 --- a/src/Lykke.Service.Assets/Lykke.Service.Assets.csproj +++ b/src/Lykke.Service.Assets/Lykke.Service.Assets.csproj @@ -13,11 +13,6 @@ 1701;1702;1705;1591 - - - - - @@ -25,14 +20,8 @@ - - - - - -