diff --git a/src/VirtoCommerce.XCart.Data/Middlewares/LoadCartToEvalContextMiddleware.cs b/src/VirtoCommerce.XCart.Data/Middlewares/LoadCartToEvalContextMiddleware.cs index 1a13d14..a86bea8 100644 --- a/src/VirtoCommerce.XCart.Data/Middlewares/LoadCartToEvalContextMiddleware.cs +++ b/src/VirtoCommerce.XCart.Data/Middlewares/LoadCartToEvalContextMiddleware.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using AutoMapper; using PipelineNet.Middleware; +using VirtoCommerce.CartModule.Core.Model; using VirtoCommerce.CartModule.Core.Model.Search; using VirtoCommerce.CoreModule.Core.Common; using VirtoCommerce.MarketingModule.Core.Model.Promotions; @@ -81,6 +82,7 @@ protected virtual ShoppingCartSearchCriteria GetCartSearchCriteria(PromotionEval cartSearchCriteria.CustomerId = context.CustomerId; cartSearchCriteria.OrganizationId = context.OrganizaitonId; cartSearchCriteria.Currency = context.Currency; + cartSearchCriteria.ResponseGroup = CartResponseGroup.Full.ToString(); return cartSearchCriteria; } @@ -94,6 +96,7 @@ protected virtual ShoppingCartSearchCriteria GetCartSearchCriteria(PriceEvaluati cartSearchCriteria.CustomerId = context.CustomerId; cartSearchCriteria.OrganizationId = context.OrganizationId; cartSearchCriteria.Currency = context.Currency; + cartSearchCriteria.ResponseGroup = CartResponseGroup.Full.ToString(); return cartSearchCriteria; } @@ -107,6 +110,7 @@ protected virtual ShoppingCartSearchCriteria GetCartSearchCriteria(TaxEvaluation cartSearchCriteria.CustomerId = context.CustomerId; cartSearchCriteria.OrganizationId = context.OrganizationId; cartSearchCriteria.Currency = context.Currency; + cartSearchCriteria.ResponseGroup = CartResponseGroup.Full.ToString(); return cartSearchCriteria; } diff --git a/src/VirtoCommerce.XCart.Data/Services/CartAggregateRepository.cs b/src/VirtoCommerce.XCart.Data/Services/CartAggregateRepository.cs index c62efd6..9bd81db 100644 --- a/src/VirtoCommerce.XCart.Data/Services/CartAggregateRepository.cs +++ b/src/VirtoCommerce.XCart.Data/Services/CartAggregateRepository.cs @@ -2,12 +2,15 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; using VirtoCommerce.CartModule.Core.Model; using VirtoCommerce.CartModule.Core.Model.Search; using VirtoCommerce.CartModule.Core.Services; using VirtoCommerce.CoreModule.Core.Common; using VirtoCommerce.CoreModule.Core.Currency; using VirtoCommerce.CustomerModule.Core.Services; +using VirtoCommerce.Platform.Caching; +using VirtoCommerce.Platform.Core.Caching; using VirtoCommerce.Platform.Core.Common; using VirtoCommerce.StoreModule.Core.Services; using VirtoCommerce.Xapi.Core.Extensions; @@ -30,6 +33,8 @@ public class CartAggregateRepository : ICartAggregateRepository private readonly IMemberResolver _memberResolver; private readonly IStoreService _storeService; + private readonly IPlatformMemoryCache _platformMemoryCache; + public CartAggregateRepository( Func cartAggregateFactory, IShoppingCartSearchService shoppingCartSearchService, @@ -37,7 +42,9 @@ public CartAggregateRepository( ICurrencyService currencyService, IMemberResolver memberResolver, IStoreService storeService, - ICartProductService cartProductsService) + ICartProductService cartProductsService, + IPlatformMemoryCache platformMemoryCache + ) { _cartAggregateFactory = cartAggregateFactory; _shoppingCartSearchService = shoppingCartSearchService; @@ -46,6 +53,7 @@ public CartAggregateRepository( _memberResolver = memberResolver; _storeService = storeService; _cartProductsService = cartProductsService; + _platformMemoryCache = platformMemoryCache; } public virtual async Task SaveAsync(CartAggregate cartAggregate) @@ -54,6 +62,9 @@ public virtual async Task SaveAsync(CartAggregate cartAggregate) cartAggregate.Cart.ModifiedDate = DateTime.UtcNow; await _shoppingCartService.SaveChangesAsync(new List { cartAggregate.Cart }); + + // Clear cache + GenericCachingRegion.ExpireTokenForKey(cartAggregate.Id); } public async Task GetCartByIdAsync(string cartId, string cultureName = null) @@ -76,7 +87,7 @@ public async Task GetCartByIdAsync(string cartId, string response var cart = await _shoppingCartService.GetByIdAsync(cartId, responseGroup); if (cart != null) { - return await InnerGetCartAggregateFromCartAsync(cart, cultureName ?? Language.InvariantLanguage.CultureName, productsIncludeFields); + return await InnerGetCartAggregateFromCartAsync(cart, cultureName ?? Language.InvariantLanguage.CultureName, productsIncludeFields, CartResponseGroup.Full.ToString()); } return null; } @@ -88,7 +99,7 @@ public Task GetCartForShoppingCartAsync(ShoppingCart cart, string return Task.FromResult(cartAggregate); } - return InnerGetCartAggregateFromCartAsync(cart, cultureName ?? Language.InvariantLanguage.CultureName); + return InnerGetCartAggregateFromCartAsync(cart, cultureName ?? Language.InvariantLanguage.CultureName, CartResponseGroup.Full.ToString()); } public async Task GetCartAsync(ICartRequest cartRequest, string responseGroup = null) @@ -116,7 +127,7 @@ public async Task GetCartAsync(ICartRequest cartRequest, string r var cart = cartSearchResult.Results.FirstOrDefault(x => cartRequest.CartType != null || x.Type == null); if (cart != null) { - return await InnerGetCartAggregateFromCartAsync(cart.Clone() as ShoppingCart, cartRequest.CultureName); + return await InnerGetCartAggregateFromCartAsync(cart.Clone() as ShoppingCart, cartRequest.CultureName, criteria.ResponseGroup); } return null; @@ -138,7 +149,7 @@ public async Task GetCartAsync(ShoppingCartSearchCriteria criteri var cart = cartSearchResult.Results.FirstOrDefault(x => criteria.Type != null || x.Type == null); if (cart != null) { - return await InnerGetCartAggregateFromCartAsync(cart.Clone() as ShoppingCart, cultureName ?? Language.InvariantLanguage.CultureName); + return await InnerGetCartAggregateFromCartAsync(cart.Clone() as ShoppingCart, cultureName ?? Language.InvariantLanguage.CultureName, criteria.ResponseGroup); } return null; @@ -154,19 +165,58 @@ public async Task SearchCartAsync(ShoppingCartSearchCriteria ArgumentNullException.ThrowIfNull(criteria); var searchResult = await _shoppingCartSearchService.SearchAsync(criteria); - var cartAggregates = await GetCartsForShoppingCartsAsync(searchResult.Results, productsIncludeFields); + var cartAggregates = await GetCartsForShoppingCartsAsync(criteria, searchResult.Results, productsIncludeFields); return new SearchCartResponse { Results = cartAggregates, TotalCount = searchResult.TotalCount }; } - public virtual Task RemoveCartAsync(string cartId) => _shoppingCartService.DeleteAsync(new[] { cartId }, softDelete: true); + public virtual async Task RemoveCartAsync(string cartId) + { + await _shoppingCartService.DeleteAsync(new[] { cartId }, softDelete: true); + GenericCachingRegion.ExpireTokenForKey(cartId); + } - protected virtual async Task InnerGetCartAggregateFromCartAsync(ShoppingCart cart, string language, CartAggregateResponseGroup responseGroup = CartAggregateResponseGroup.Full) + protected virtual async Task> GetCartsForShoppingCartsAsync(ShoppingCartSearchCriteria criteria, IList carts, IList productsIncludeFields, string cultureName = null) + { + var result = new List(); + + foreach (var shoppingCart in carts) + { + result.Add(await InnerGetCartAggregateFromCartAsync(shoppingCart, cultureName ?? Language.InvariantLanguage.CultureName, productsIncludeFields, criteria.ResponseGroup)); + } + + return result; + } + + protected virtual async Task InnerGetCartAggregateFromCartAsync(ShoppingCart cart, string language, string responseGroup) { return await InnerGetCartAggregateFromCartAsync(cart, language, null, responseGroup); } - protected virtual async Task InnerGetCartAggregateFromCartAsync(ShoppingCart cart, string language, IList productsIncludeFields, CartAggregateResponseGroup responseGroup = CartAggregateResponseGroup.Full) + protected virtual async Task InnerGetCartAggregateFromCartAsync(ShoppingCart cart, string language, IList productsIncludeFields, string responseGroup) + { + if (string.IsNullOrEmpty(cart.Id)) + { + return await InnerGetCartAggregateFromCartNoCacheAsync(cart, language, productsIncludeFields); + } + + var cacheKey = CacheKey.With(GetType(), + nameof(InnerGetCartAggregateFromCartAsync), + cart.Id, + language, + responseGroup ?? string.Empty, + !productsIncludeFields.IsNullOrEmpty() ? string.Join(',', productsIncludeFields) : string.Empty); + + var result = await _platformMemoryCache.GetOrCreateExclusiveAsync(cacheKey, async cacheOptions => + { + cacheOptions.AddExpirationToken(GenericCachingRegion.CreateChangeTokenForKey(cart.Id)); + return await InnerGetCartAggregateFromCartNoCacheAsync(cart, language, productsIncludeFields); + }); + + return result; + } + + private async Task InnerGetCartAggregateFromCartNoCacheAsync(ShoppingCart cart, string language, IList productsIncludeFields) { ArgumentNullException.ThrowIfNull(cart); @@ -248,17 +298,5 @@ protected virtual async Task InnerGetCartAggregateFromCartAsync(S return aggregate; } } - - protected virtual async Task> GetCartsForShoppingCartsAsync(IList carts, IList productsIncludeFields, string cultureName = null) - { - var result = new List(); - - foreach (var shoppingCart in carts) - { - result.Add(await InnerGetCartAggregateFromCartAsync(shoppingCart, cultureName ?? Language.InvariantLanguage.CultureName, productsIncludeFields)); - } - - return result; - } } } diff --git a/tests/VirtoCommerce.XCart.Tests/Repositories/CartAggregateRepositoryTests.cs b/tests/VirtoCommerce.XCart.Tests/Repositories/CartAggregateRepositoryTests.cs index a7c23ac..09b6f7a 100644 --- a/tests/VirtoCommerce.XCart.Tests/Repositories/CartAggregateRepositoryTests.cs +++ b/tests/VirtoCommerce.XCart.Tests/Repositories/CartAggregateRepositoryTests.cs @@ -3,6 +3,9 @@ using System.Threading.Tasks; using AutoFixture; using FluentAssertions; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Moq; using VirtoCommerce.CartModule.Core.Model; using VirtoCommerce.CartModule.Core.Model.Search; @@ -10,6 +13,7 @@ using VirtoCommerce.CoreModule.Core.Currency; using VirtoCommerce.CustomerModule.Core.Model; using VirtoCommerce.CustomerModule.Core.Services; +using VirtoCommerce.Platform.Caching; using VirtoCommerce.PricingModule.Core.Model; using VirtoCommerce.StoreModule.Core.Model; using VirtoCommerce.StoreModule.Core.Services; @@ -29,6 +33,7 @@ public class CartAggregateRepositoryTests : XCartMoqHelper private readonly Mock _currencyService; private readonly Mock _storeService; private readonly Mock _memberResolver; + private readonly PlatformMemoryCache _platformMemoryCache; private readonly CartAggregateRepository repository; @@ -40,6 +45,11 @@ public CartAggregateRepositoryTests() _storeService = new Mock(); _memberResolver = new Mock(); + _platformMemoryCache = new PlatformMemoryCache( + new MemoryCache(Options.Create(new MemoryCacheOptions())), + Options.Create(new CachingOptions()), + new Mock>().Object); + repository = new CartAggregateRepository( () => _fixture.Create(), _shoppingCartSearchService.Object, @@ -47,6 +57,7 @@ public CartAggregateRepositoryTests() _currencyService.Object, _memberResolver.Object, _storeService.Object, + null, null ); } @@ -152,7 +163,8 @@ public async Task GetCartForShoppingCartAsync_CartFound_AggregateReturnedCorrect _currencyService.Object, _memberResolver.Object, _storeService.Object, - _cartProductServiceMock.Object + _cartProductServiceMock.Object, + _platformMemoryCache ); var storeId = "Store"; @@ -201,7 +213,8 @@ public async Task GetCartForShoppingCartAsync_ProductPriceChanged_ShouldContainW _currencyService.Object, _memberResolver.Object, _storeService.Object, - _cartProductServiceMock.Object); + _cartProductServiceMock.Object, + _platformMemoryCache); var storeId = "Store"; var store = _fixture.Create();