Skip to content

[API Proposal] Support keyed HybridCache with keyed DistributedCaches #117976

@kelly-yinn

Description

@kelly-yinn

Background and motivation

Microsoft.Extensions.Caching.Hybrid an API for adding a HybridCache to the application's service collection, and the AddHybridCache() method results in constructing a DefaultHybridCache singleton. DefaultHybridCache respects that an IDistributedCache service might be registered, and that cache will be used for an L2 cache provider.

Applications can use keyed services to register multiple IDistributedCache services for use throughout the application, but the DefaultHybridCache cannot consume these keyed services. Moreover, if DefaultHybridCache could respect a distributed cache service key, that would lead to the need for registering multiple HybridCache services into the collection using keyed services.

Because DefaultHybridCache is internal and can only be registered into the service collection via AddHybridCache(), there are no clear paths toward registering multiple instances that consume different keyed services for the distributed caches. This presents the need for keyed hybrid caches using keyed distributed caches.

API Proposal

  namespace Microsoft.Extensions.Caching.Hybrid;
  
  public class HybridCacheOptions
  {
      public HybridCacheEntryOptions? DefaultEntryOptions { get; set; }
      public bool DisableCompression { get; set; }
      public long MaximumPayloadBytes { get; set; }
      public int MaximumKeyLength { get; set; }
      public bool ReportTagMetrics { get; set; }
+     public object? DistributedCacheServiceKey { get; set; }
  }
  namespace Microsoft.Extensions.DependencyInjection;

  public static class HybridCacheServiceExtensions
  {
      public static IHybridCacheBuilder AddHybridCache(this IServiceCollection services, Action<HybridCacheOptions> setupAction);
      public static IHybridCacheBuilder AddHybridCache(this IServiceCollection services);
+     public static IHybridCacheBuilder AddKeyedHybridCache(this IServiceCollection services, object? serviceKey);
+     public static IHybridCacheBuilder AddKeyedHybridCache(this IServiceCollection services, object? serviceKey, string optionsName);
+     public static IHybridCacheBuilder AddKeyedHybridCache(this IServiceCollection services, object? serviceKey, Action<HybridCacheOptions> setupAction);
+     public static IHybridCacheBuilder AddKeyedHybridCache(this IServiceCollection services, object? serviceKey, string optionsName, Action<HybridCacheOptions> setupAction);
  }

API Usage

var services = new ServiceCollection();
services.AddKeyedSingleton<IDistributedCache, RedisCache>("Redis");
services.AddKeyedSingleton<IDistributedCache, SqlServerCache>("SqlServer", (s, k) => new SqlServerCache(new SqlServerCacheOptions { ConnectionString = "test", SchemaName = "test", TableName = "test" }));
services.AddKeyedHybridCache("HybridWithRedis", options => options.DistributedCacheServiceKey = "Redis");
services.AddKeyedHybridCache("HybridWithSqlServer", options => options.DistributedCacheServiceKey = "SqlServer");

using ServiceProvider provider = services.BuildServiceProvider();
var hybridWithRedis = provider.GetRequiredKeyedService<HybridCache>("HybridWithRedis");
var hybridWithSqlServer = provider.GetRequiredKeyedService<HybridCache>("HybridWithSqlServer");

Alternative Designs

  1. Make DefaultHybridCache public and expand its API surface area to allow specification of the distributed cache service key
  2. Refactor reusable logic out of DefaultHybridCache so that other cache implementations can compose that logic differently, such as using keyed services
  3. Pass the distributed cache service key into AddKeyedHybridCache as another argument instead of adding it as a property to HybridCacheOptions. Adding to the options is preferred as this allows the cache key to be included in the application configuration without code to wire the two together.
  4. Expand the scope of this request to also support adding keyed Redis cache services to DI, rather than relying on constructing the instances without the AddStackExchangeRedisCache helper API.

Risks

  1. There were not existing scenarios around registering keyed DI services that rely on other keyed DI services where named options are also expected to be supported. To support that scenario, we are introducing multiple overloads for providing the optionsName, the setupAction, or both.
  2. This does not add named options support for the non-keyed DI use of HybridCache; we could proactively add that but as of now the need is not compelling.
  3. This does not add support for keyed local/memory cache, and the app's singleton MemoryCache is shared by all hybrid caches. We could add a HybridCacheOptions.LocalCacheServiceKey if we want to proactively add that support.
  4. The AddStackExchangeRedisCache API does not support adding keyed services, and it instantiates an internal, derived RedisCache implementation that augments logging and telemetry that is incompatible with keyed HybridCaches. This has been reviewed though, and it's acceptable for that logging/telemetry not to work, as it's limited only to adding an "HC" library name to the telemetry captured by the hosted Redis service.

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-approvedAPI was approved in API review, it can be implementedarea-Extensions-Cachingpartner-impactThis issue impacts a partner who needs to be kept updated

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions