To do this task you first need to complete the Redis-task
Your client has a complex price model, so you need to replace Litiums price list logic with a custom solution that fetches prices from the custumers ERP.
As with all Litium interfaces and abstract classes you only need to add your own implementation to override the default (fallback) implementation.
Litium.Accelerator.Utilities.ErpPriceCalculatorImpl
that implementsLitium.Products.PriceCalculator.IPriceCalculator
GetPriceLists()
can be implemented so that it returns new List<ProductPriceList>();
GetListPrices()
-method:
new Dictionary<Guid, PriceCalculatorResult>()
that the method should returnitemArgs
add an item to the dictionary with price set to 100Only logged in B2B-customers should get their prices from ERP, add a check so Litium's standard price logic is still used for all anonymous users.
Rename ErpPriceCalculatorImpl
to ErpPriceCalculatorDecorator
and use the information on docs to convert it into a decorator.
Inject SecurityContextService
and use the code below to check if current user is logged in, if current user is logged in return your custom price, otherwise return Litium list price.
var userId = _securityContextService.GetIdentityUserSystemId();
var isAuthenticated = userId.HasValue && userId != SecurityContextService.Everyone.SystemId;
To get better performance when getting price from external sources such as ERP you always need to add data to cache. By using a distributed cache in Redis the same cache is used for all your web applications.
You will be using the DistributedMemoryCache
which stores data both in a short (2 minute) memory cache in the application and also in a distributed cache (Redis) for 24 hours.
Make your price fetch really slow by adding Thread.Sleep(500);
before returning price for a variant and then test the performance on a category page listing multiple products
Inject DistributedMemoryCacheService
in the constructor of ErpPriceCalculatorDecorator
First try getting the value from cache before your Thread.Sleep
:
var cacheKey = $"{nameof(ErpPriceCalculatorDecorator)}:{variantSystemId}";
if (_distributedMemoryCacheService.TryGet<PriceCalculatorResult>(cacheKey, out var price))
return price;
Then make sure you store the price in cache after getting it from ERP:
_distributedMemoryCacheService.Set(cacheKey, price);
Test by reloading your product listing again, now only the first page load should be slow and all consecutive page loads should be instant
Note that expiration is sliding so in any production sceario you would also need to add functionality to clear cached price on any price change event.
When multiple threads or applications might access the same data at the same time it is important to lock the resource to avoid conflicts.
The price calculator has to prevent multiple requests to ERP for the same variant when the price is no longer cached. You do this by placing a lock on the variant while you fetch the price to prevent other servers or threads from calling the API for the same data.
Inject DistributedLockService
in the constructor of ErpPriceCalculatorDecorator
Wrap the code where you get price for a variant in a lock:
using (_distributedLockService.AcquireLock(cacheKey, TimeSpan.FromSeconds(10)))
{
// Try getting value from cache again since it may have been added
// from another thread/app while you were waiting for the lock
if (_distributedMemoryCacheService.TryGet<PriceCalculatorResult>(cacheKey, out price))
return price;
// Rest of implementation code for getting variant price
...
}
A finished example with everything implemented can be found here
ErpPriceCalculatorDecorator
that checks the logic as both anonymous and logged in user, use SecurityContextService
to execute code with different user contextA finished example can be found here