Skip to content

Commit

Permalink
Cache tenant in TenantStore.
Browse files Browse the repository at this point in the history
Resolve #6970
  • Loading branch information
maliming committed Jan 22, 2021
1 parent 0abcd7a commit f0f5e2c
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Data\Volo.Abp.Data.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Ddd.Domain\Volo.Abp.Ddd.Domain.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.MultiTenancy\Volo.Abp.MultiTenancy.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Caching\Volo.Abp.Caching.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AutoMapper;
using Volo.Abp.Caching;
using Volo.Abp.Data;
using Volo.Abp.Domain;
using Volo.Abp.Domain.Entities.Events.Distributed;
Expand All @@ -16,6 +17,7 @@ namespace Volo.Abp.TenantManagement
[DependsOn(typeof(AbpDataModule))]
[DependsOn(typeof(AbpDddDomainModule))]
[DependsOn(typeof(AbpAutoMapperModule))]
[DependsOn(typeof(AbpCachingModule))]
public class AbpTenantManagementDomainModule : AbpModule
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using Volo.Abp.MultiTenancy;

namespace Volo.Abp.TenantManagement
{
[Serializable]
[IgnoreMultiTenancy]
public class TenantCacheItem
{
private const string CacheKeyFormat = "i:{0},n:{1}";

public TenantConfiguration Value { get; set; }

public TenantCacheItem()
{

}

public TenantCacheItem(TenantConfiguration value)
{
Value = value;
}

public static string CalculateCacheKey(Guid? id, string name)
{
if (id == null && name.IsNullOrWhiteSpace())
{
throw new AbpException("Both id and name can't be invalid.");
}

return string.Format(CacheKeyFormat,
id?.ToString() ?? "null",
(name.IsNullOrWhiteSpace() ? "null" : name));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Threading.Tasks;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.EventBus;

namespace Volo.Abp.TenantManagement
{
public class TenantCacheItemInvalidator : ILocalEventHandler<EntityChangedEventData<Tenant>>, ITransientDependency
{
protected IDistributedCache<TenantCacheItem> Cache { get; }

public TenantCacheItemInvalidator(IDistributedCache<TenantCacheItem> cache)
{
Cache = cache;
}

public virtual async Task HandleEventAsync(EntityChangedEventData<Tenant> eventData)
{
await Cache.RemoveAsync(TenantCacheItem.CalculateCacheKey(eventData.Entity.Id, null));
await Cache.RemoveAsync(TenantCacheItem.CalculateCacheKey(null, eventData.Entity.Name));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectMapping;
Expand All @@ -13,73 +15,125 @@ public class TenantStore : ITenantStore, ITransientDependency
protected ITenantRepository TenantRepository { get; }
protected IObjectMapper<AbpTenantManagementDomainModule> ObjectMapper { get; }
protected ICurrentTenant CurrentTenant { get; }
protected IDistributedCache<TenantCacheItem> Cache { get; }

public TenantStore(
ITenantRepository tenantRepository,
IObjectMapper<AbpTenantManagementDomainModule> objectMapper,
ICurrentTenant currentTenant)
ICurrentTenant currentTenant,
IDistributedCache<TenantCacheItem> cache)
{
TenantRepository = tenantRepository;
ObjectMapper = objectMapper;
CurrentTenant = currentTenant;
Cache = cache;
}

public virtual async Task<TenantConfiguration> FindAsync(string name)
{
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
{
var tenant = await TenantRepository.FindByNameAsync(name);
if (tenant == null)
{
return null;
}

return ObjectMapper.Map<Tenant, TenantConfiguration>(tenant);
}
return (await GetCacheItemAsync(null, name)).Value;
}

public virtual async Task<TenantConfiguration> FindAsync(Guid id)
{
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
{
var tenant = await TenantRepository.FindAsync(id);
if (tenant == null)
{
return null;
}

return ObjectMapper.Map<Tenant, TenantConfiguration>(tenant);
}
return (await GetCacheItemAsync(id, null)).Value;
}

[Obsolete("Use FindAsync method.")]
public virtual TenantConfiguration Find(string name)
{
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
return (GetCacheItem(null, name)).Value;
}

[Obsolete("Use FindAsync method.")]
public virtual TenantConfiguration Find(Guid id)
{
return (GetCacheItem(id, null)).Value;
}

protected virtual async Task<TenantCacheItem> GetCacheItemAsync(Guid? id, string name)
{
var cacheKey = CalculateCacheKey(id, name);

var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true);
if (cacheItem != null)
{
return cacheItem;
}

if (id.HasValue)
{
var tenant = TenantRepository.FindByName(name);
if (tenant == null)
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
{
return null;
var tenant = await TenantRepository.FindAsync(id.Value);
return await SetCacheAsync(cacheKey, tenant);
}
}

return ObjectMapper.Map<Tenant, TenantConfiguration>(tenant);
if (!name.IsNullOrWhiteSpace())
{
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
{
var tenant = await TenantRepository.FindByNameAsync(name);
return await SetCacheAsync(cacheKey, tenant);
}
}

throw new AbpException("Both id and name can't be invalid.");
}

[Obsolete("Use FindAsync method.")]
public virtual TenantConfiguration Find(Guid id)
protected virtual async Task<TenantCacheItem> SetCacheAsync(string cacheKey, [CanBeNull]Tenant tenant)
{
var tenantConfiguration = tenant != null ? ObjectMapper.Map<Tenant, TenantConfiguration>(tenant) : null;
var cacheItem = new TenantCacheItem(tenantConfiguration);
await Cache.SetAsync(cacheKey, cacheItem, considerUow: true);
return cacheItem;
}

[Obsolete("Use GetCacheItemAsync method.")]
protected virtual TenantCacheItem GetCacheItem(Guid? id, string name)
{
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
var cacheKey = CalculateCacheKey(id, name);

var cacheItem = Cache.Get(cacheKey, considerUow: true);
if (cacheItem != null)
{
return cacheItem;
}

if (id.HasValue)
{
var tenant = TenantRepository.FindById(id);
if (tenant == null)
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
{
return null;
var tenant = TenantRepository.FindById(id.Value);
return SetCache(cacheKey, tenant);
}
}

return ObjectMapper.Map<Tenant, TenantConfiguration>(tenant);
if (!name.IsNullOrWhiteSpace())
{
using (CurrentTenant.Change(null)) //TODO: No need this if we can implement to define host side (or tenant-independent) entities!
{
var tenant = TenantRepository.FindByName(name);
return SetCache(cacheKey, tenant);
}
}

throw new AbpException("Both id and name can't be invalid.");
}

[Obsolete("Use SetCacheAsync method.")]
protected virtual TenantCacheItem SetCache(string cacheKey, [CanBeNull]Tenant tenant)
{
var tenantConfiguration = tenant != null ? ObjectMapper.Map<Tenant, TenantConfiguration>(tenant) : null;
var cacheItem = new TenantCacheItem(tenantConfiguration);
Cache.Set(cacheKey, cacheItem, considerUow: true);
return cacheItem;
}

protected virtual string CalculateCacheKey(Guid? id, string name)
{
return TenantCacheItem.CalculateCacheKey(id, name);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System.Threading.Tasks;
using Shouldly;
using Volo.Abp.Caching;
using Volo.Abp.MultiTenancy;
using Xunit;

namespace Volo.Abp.TenantManagement
{
public class TenantCacheItemInvalidator_Tests : AbpTenantManagementDomainTestBase
{
private readonly IDistributedCache<TenantCacheItem> _cache;
private readonly ITenantStore _tenantStore;
private readonly ITenantRepository _tenantRepository;

public TenantCacheItemInvalidator_Tests()
{
_cache = GetRequiredService<IDistributedCache<TenantCacheItem>>();
_tenantStore = GetRequiredService<ITenantStore>();
_tenantRepository = GetRequiredService<ITenantRepository>();
}

[Fact]
public async Task Get_Tenant_Should_Cached()
{
var acme = await _tenantRepository.FindByNameAsync("acme");
acme.ShouldNotBeNull();

(await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(acme.Id, null))).ShouldBeNull();
(await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(null, acme.Name))).ShouldBeNull();

await _tenantStore.FindAsync(acme.Id);
(await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(acme.Id, null))).ShouldNotBeNull();

await _tenantStore.FindAsync(acme.Name);
(await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(null, acme.Name))).ShouldNotBeNull();


var volosoft = _tenantRepository.FindByName("volosoft");
volosoft.ShouldNotBeNull();

(_cache.Get(TenantCacheItem.CalculateCacheKey(volosoft.Id, null))).ShouldBeNull();
(_cache.Get(TenantCacheItem.CalculateCacheKey(null, volosoft.Name))).ShouldBeNull();

_tenantStore.Find(volosoft.Id);
(_cache.Get(TenantCacheItem.CalculateCacheKey(volosoft.Id, null))).ShouldNotBeNull();

_tenantStore.Find(volosoft.Name);
(_cache.Get(TenantCacheItem.CalculateCacheKey(null, volosoft.Name))).ShouldNotBeNull();
}

[Fact]
public async Task Cache_Should_Invalidator_When_Tenant_Changed()
{
var acme = await _tenantRepository.FindByNameAsync("acme");
acme.ShouldNotBeNull();

// FindAsync will cache tenant.
await _tenantStore.FindAsync(acme.Id);
await _tenantStore.FindAsync(acme.Name);

(await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(acme.Id, null))).ShouldNotBeNull();
(await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(null, acme.Name))).ShouldNotBeNull();

await _tenantRepository.DeleteAsync(acme);

(await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(acme.Id, null))).ShouldBeNull();
(await _cache.GetAsync(TenantCacheItem.CalculateCacheKey(null, acme.Name))).ShouldBeNull();


var volosoft = await _tenantRepository.FindByNameAsync("volosoft");
volosoft.ShouldNotBeNull();

// Find will cache tenant.
_tenantStore.Find(volosoft.Id);
_tenantStore.Find(volosoft.Name);

(_cache.Get(TenantCacheItem.CalculateCacheKey(volosoft.Id, null))).ShouldNotBeNull();
(_cache.Get(TenantCacheItem.CalculateCacheKey(null, volosoft.Name))).ShouldNotBeNull();

await _tenantRepository.DeleteAsync(volosoft);

(_cache.Get(TenantCacheItem.CalculateCacheKey(volosoft.Id, null))).ShouldBeNull();
(_cache.Get(TenantCacheItem.CalculateCacheKey(null, volosoft.Name))).ShouldBeNull();
}
}
}

0 comments on commit f0f5e2c

Please sign in to comment.