Distributed Entity Cache
@realLiangshiwei can you please add unit tests.
Usage
Entity objects are directly cached
- Register the entity cache:
context.Services.AddEntityCache<Product, Guid>(). - Inject & use
IEntityCache<Product, Guid>service.
With this usage, the entity class should be serializable/deserializable to/from JSON.
Entities are mapped to a cache item
- Define a cache item class, like
ProductCacheItem. - Configure object mapper for
Product->ProductCacheItemmapping. - Register the entity cache:
context.Services.AddEntityCache<Product, ProductCacheItem, Guid>(). - Inject & use
IEntityCache<ProductCacheItem, Guid>service.
With contextualized object mapper
This is only needed for reusables modules build with the best practices guide. Application developers don't need it.
- Define a cache item class, like
ProductCacheItem. - Configure object mapper for
Product->ProductCacheItemmapping. - Register the entity cache:
context.Services.AddEntityCache<TMyModule, Product, ProductCacheItem, Guid>().* - Inject & use
IEntityCache<ProductCacheItem, Guid>service.
*TMyModule is Type of the module class we define the object mapper context.
The entity cache may have the danger of consistent problems.
https://github.com/abpframework/abp/blob/91fcbd8b3e7d222b83e0c18b245a3337f610c35e/framework/src/Volo.Abp.Ddd.Domain/Volo/Abp/Domain/Entities/Caching/EntityCacheBase.cs#L44-L52
What if another thread re-creates the cache before the current UOW is complete? The dirty cache item could exist for a long time (even eternal) since the default cache duration time is 20min and sliding.
I consider that the cache invalidator should also implement the IDistributedEventHandler:
- The
IDistributedEventHandlerremoves the target cache item. - The
ILocalEventHandlerremoves the target cache item on the current UOW completed.
I believe it could cover all these scenarios:
- Local distributed event bus.
- Real distributed event bus.
- Real distributed event bus with event boxes.
The cache duration time should be shorter if we don't use event boxes since the uow.OnCompleted method may not execute due to the app crashing, making a consistent problem. And I believe keeping the cache duration short is better for safety, even if we use event boxes
Entity key property is protected set, developer need to use JsonInclude if using Entity objects are directly cached.
For example:
public class Product: Entity<Guid>
{
[JsonInclude]
public override Guid Id { get; protected set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
We don't need the [JsonInclude] after https://github.com/abpframework/abp/pull/13357
@realLiangshiwei why we've added these lines:
services
.TryAddTransient<EntityCacheWithObjectMapper<TEntity, TEntityCacheItem, TKey>>();
It is not needed to resolve services with EntityCacheWithObjectMapper<TEntity, TEntityCacheItem, TKey>, we should resolve with IEntityCache.
@gdlcf88
- See the "Additional notes" I've added to the PR summary, for the shorter cache duration. It is 2 minutes now, and not sliding.
- For the consistency problem, I double remove the cache item. See this commit: https://github.com/abpframework/abp/pull/14055/commits/f96277192bd49fe02c319438eec7587984f126b0
I don't want to use distributed event bus. It has big cost, and also requires additional steps to publish distributed events.
@realLiangshiwei I am merging this. Please see my comments (https://github.com/abpframework/abp/pull/14055#issuecomment-1266419811) and remove these and send a PR if we don't have a good reason.
I found an article summarizing the cache consistency solutions: https://danielw.cn/cache-consistency-with-database.
The double-deletion solution does improve the problem but still has non-consistency dangers:

Another solution rockscache looks like a good way, but it depends on the Lua script feature of Redis, which is not acceptable with the ABP framework.
Actually, cache consistency is hard to implement by the ABP framework itself.
I think the current design is enough. When the accident occurs, stale data can only exist for up to 2min (by default). What the developers should do with the entity caches is face the non-consistency and don't directly use them in a writing operation.
Entity Cache is designed for read-only. We shouldn't make changes on the same entity when we use the entity cache. If we need to change the entity, we should always read it from database to ensure transactional consistency. I will write note about that in the documentation.
And a remote service query that returns entity data should also avoid using the entity cache if we use the data for a writing operation.
https://github.com/abpframework/abp/pull/14055#issuecomment-1266439997
it is necessary because we want to remove the entity cache when the entity is changed.
EntityCacheWithObjectMapper and EntityCacheWithObjectMapperContext is the Event Handler, we need to expose themselves to the dependency injection container

Thanks @realLiangshiwei for your explanation. Then it seems fine.