mirror of
https://github.com/bitwarden/server
synced 2026-01-07 11:03:37 +00:00
[PM-23845] Update cache service to handle concurrency (#6170)
This commit is contained in:
152
src/Core/Services/Implementations/FeatureRoutedCacheService.cs
Normal file
152
src/Core/Services/Implementations/FeatureRoutedCacheService.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
using Bit.Core.AdminConsole.AbilitiesCache;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Models.Data.Provider;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
|
||||
namespace Bit.Core.Services.Implementations;
|
||||
|
||||
/// <summary>
|
||||
/// A feature-flagged routing service for application caching that bridges the gap between
|
||||
/// scoped dependency injection (IFeatureService) and singleton services (cache implementations).
|
||||
/// This service allows dynamic routing between IVCurrentInMemoryApplicationCacheService and
|
||||
/// IVNextInMemoryApplicationCacheService based on the PM23845_VNextApplicationCache feature flag.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This service is necessary because:
|
||||
/// - IFeatureService is registered as Scoped in the DI container
|
||||
/// - IVNextInMemoryApplicationCacheService and IVCurrentInMemoryApplicationCacheService are registered as Singleton
|
||||
/// - We need to evaluate feature flags at request time while maintaining singleton cache behavior
|
||||
///
|
||||
/// The service acts as a scoped proxy that can access the scoped IFeatureService while
|
||||
/// delegating actual cache operations to the appropriate singleton implementation.
|
||||
/// </remarks>
|
||||
public class FeatureRoutedCacheService(
|
||||
IFeatureService featureService,
|
||||
IVNextInMemoryApplicationCacheService vNextInMemoryApplicationCacheService,
|
||||
IVCurrentInMemoryApplicationCacheService inMemoryApplicationCacheService,
|
||||
IApplicationCacheServiceBusMessaging serviceBusMessaging)
|
||||
: IApplicationCacheService
|
||||
{
|
||||
public async Task<IDictionary<Guid, OrganizationAbility>> GetOrganizationAbilitiesAsync()
|
||||
{
|
||||
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
|
||||
{
|
||||
return await vNextInMemoryApplicationCacheService.GetOrganizationAbilitiesAsync();
|
||||
}
|
||||
|
||||
return await inMemoryApplicationCacheService.GetOrganizationAbilitiesAsync();
|
||||
}
|
||||
|
||||
public async Task<OrganizationAbility?> GetOrganizationAbilityAsync(Guid orgId)
|
||||
{
|
||||
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
|
||||
{
|
||||
return await vNextInMemoryApplicationCacheService.GetOrganizationAbilityAsync(orgId);
|
||||
}
|
||||
return await inMemoryApplicationCacheService.GetOrganizationAbilityAsync(orgId);
|
||||
}
|
||||
|
||||
public async Task<IDictionary<Guid, ProviderAbility>> GetProviderAbilitiesAsync()
|
||||
{
|
||||
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
|
||||
{
|
||||
return await vNextInMemoryApplicationCacheService.GetProviderAbilitiesAsync();
|
||||
}
|
||||
return await inMemoryApplicationCacheService.GetProviderAbilitiesAsync();
|
||||
}
|
||||
|
||||
public async Task UpsertOrganizationAbilityAsync(Organization organization)
|
||||
{
|
||||
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
|
||||
{
|
||||
await vNextInMemoryApplicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
||||
await serviceBusMessaging.NotifyOrganizationAbilityUpsertedAsync(organization);
|
||||
}
|
||||
else
|
||||
{
|
||||
await inMemoryApplicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpsertProviderAbilityAsync(Provider provider)
|
||||
{
|
||||
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
|
||||
{
|
||||
await vNextInMemoryApplicationCacheService.UpsertProviderAbilityAsync(provider);
|
||||
}
|
||||
else
|
||||
{
|
||||
await inMemoryApplicationCacheService.UpsertProviderAbilityAsync(provider);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteOrganizationAbilityAsync(Guid organizationId)
|
||||
{
|
||||
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
|
||||
{
|
||||
await vNextInMemoryApplicationCacheService.DeleteOrganizationAbilityAsync(organizationId);
|
||||
await serviceBusMessaging.NotifyOrganizationAbilityDeletedAsync(organizationId);
|
||||
}
|
||||
else
|
||||
{
|
||||
await inMemoryApplicationCacheService.DeleteOrganizationAbilityAsync(organizationId);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteProviderAbilityAsync(Guid providerId)
|
||||
{
|
||||
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
|
||||
{
|
||||
await vNextInMemoryApplicationCacheService.DeleteProviderAbilityAsync(providerId);
|
||||
await serviceBusMessaging.NotifyProviderAbilityDeletedAsync(providerId);
|
||||
}
|
||||
else
|
||||
{
|
||||
await inMemoryApplicationCacheService.DeleteProviderAbilityAsync(providerId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async Task BaseUpsertOrganizationAbilityAsync(Organization organization)
|
||||
{
|
||||
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
|
||||
{
|
||||
await vNextInMemoryApplicationCacheService.UpsertOrganizationAbilityAsync(organization);
|
||||
}
|
||||
else
|
||||
{
|
||||
// NOTE: This is a temporary workaround InMemoryServiceBusApplicationCacheService legacy implementation.
|
||||
// Avoid using this approach in new code.
|
||||
if (inMemoryApplicationCacheService is InMemoryServiceBusApplicationCacheService serviceBusCache)
|
||||
{
|
||||
await serviceBusCache.BaseUpsertOrganizationAbilityAsync(organization);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Expected {nameof(inMemoryApplicationCacheService)} to be of type {nameof(InMemoryServiceBusApplicationCacheService)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task BaseDeleteOrganizationAbilityAsync(Guid organizationId)
|
||||
{
|
||||
if (featureService.IsEnabled(FeatureFlagKeys.PM23845_VNextApplicationCache))
|
||||
{
|
||||
await vNextInMemoryApplicationCacheService.DeleteOrganizationAbilityAsync(organizationId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// NOTE: This is a temporary workaround InMemoryServiceBusApplicationCacheService legacy implementation.
|
||||
// Avoid using this approach in new code.
|
||||
if (inMemoryApplicationCacheService is InMemoryServiceBusApplicationCacheService serviceBusCache)
|
||||
{
|
||||
await serviceBusCache.BaseDeleteOrganizationAbilityAsync(organizationId);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"Expected {nameof(inMemoryApplicationCacheService)} to be of type {nameof(InMemoryServiceBusApplicationCacheService)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
using Bit.Core.AdminConsole.AbilitiesCache;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Models.Data.Provider;
|
||||
@@ -10,7 +11,7 @@ using Bit.Core.Repositories;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public class InMemoryApplicationCacheService : IApplicationCacheService
|
||||
public class InMemoryApplicationCacheService : IVCurrentInMemoryApplicationCacheService
|
||||
{
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
|
||||
@@ -8,9 +8,8 @@ using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Core.Services;
|
||||
|
||||
public class InMemoryServiceBusApplicationCacheService : InMemoryApplicationCacheService, IApplicationCacheService
|
||||
public class InMemoryServiceBusApplicationCacheService : InMemoryApplicationCacheService
|
||||
{
|
||||
private readonly ServiceBusClient _serviceBusClient;
|
||||
private readonly ServiceBusSender _topicMessageSender;
|
||||
private readonly string _subName;
|
||||
|
||||
@@ -21,7 +20,7 @@ public class InMemoryServiceBusApplicationCacheService : InMemoryApplicationCach
|
||||
: base(organizationRepository, providerRepository)
|
||||
{
|
||||
_subName = CoreHelpers.GetApplicationCacheServiceBusSubscriptionName(globalSettings);
|
||||
_serviceBusClient = new ServiceBusClient(globalSettings.ServiceBus.ConnectionString);
|
||||
|
||||
_topicMessageSender = new ServiceBusClient(globalSettings.ServiceBus.ConnectionString).CreateSender(globalSettings.ServiceBus.ApplicationCacheTopicName);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user