Skip to content

Commit be9ccd8

Browse files
committed
Add cache key versioning and environment prefix
Introduces configurable cache key versions for invalidation. Adds optional environment prefix for cache key isolation in multi-environment setups. Updates `CreateCacheKey` to incorporate these new components. Includes new options for `MessagePackDistributedCache` and `RedisHybridCache`.
1 parent 7bb241e commit be9ccd8

8 files changed

Lines changed: 389 additions & 4 deletions

File tree

Neolution.Extensions.Caching.Abstractions/DistributedCache.cs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ public abstract class DistributedCache<TCacheId> : IDistributedCache<TCacheId>
1919
/// </value>
2020
private static string CacheIdName => typeof(TCacheId).Name;
2121

22+
/// <summary>
23+
/// Gets the cache key version for invalidation purposes.
24+
/// If null, version is not included in the cache key.
25+
/// </summary>
26+
protected virtual int? Version => null;
27+
28+
/// <summary>
29+
/// Gets the optional environment prefix for cache key isolation.
30+
/// If null or empty, environment prefix is not included in the cache key.
31+
/// </summary>
32+
protected virtual string? EnvironmentPrefix => null;
33+
2234
/// <inheritdoc />
2335
public T? Get<T>(TCacheId id)
2436
where T : class
@@ -206,15 +218,31 @@ protected abstract Task SetCacheObjectAsync<T>(string key, T value, CacheEntryOp
206218
/// <param name="id">The cache id.</param>
207219
/// <param name="key">The key of the cache entry.</param>
208220
/// <returns>The caching key.</returns>
209-
private static string CreateCacheKey(TCacheId id, string? key = null)
221+
private string CreateCacheKey(TCacheId id, string? key = null)
210222
{
211223
var cacheKey = id.ToString();
212224
if (!string.IsNullOrWhiteSpace(key))
213225
{
214226
cacheKey = $"{cacheKey}_{key}";
215227
}
216228

217-
return $"{CacheIdName}:{cacheKey}";
229+
var fullKey = CacheIdName;
230+
231+
// Add version if specified
232+
if (this.Version.HasValue)
233+
{
234+
fullKey = $"{fullKey}:v{this.Version.Value}";
235+
}
236+
237+
fullKey = $"{fullKey}:{cacheKey}";
238+
239+
// Add environment prefix if specified
240+
if (!string.IsNullOrWhiteSpace(this.EnvironmentPrefix))
241+
{
242+
fullKey = $"{this.EnvironmentPrefix}:{fullKey}";
243+
}
244+
245+
return fullKey;
218246
}
219247
}
220248
}

Neolution.Extensions.Caching.Distributed/MessagePackDistributedCache.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ public MessagePackDistributedCache(IDistributedCache cache, IOptions<MessagePack
4242
}
4343

4444
var options = optionsAccessor.Value;
45+
this.Version = options.Version;
46+
this.EnvironmentPrefix = options.EnvironmentPrefix;
47+
4548
if (options.RequireMessagePackObjectAnnotation)
4649
{
4750
this.serializerOptions = MessagePackSerializerOptions.Standard;
@@ -53,6 +56,12 @@ public MessagePackDistributedCache(IDistributedCache cache, IOptions<MessagePack
5356
}
5457
}
5558

59+
/// <inheritdoc />
60+
protected override int? Version { get; }
61+
62+
/// <inheritdoc />
63+
protected override string? EnvironmentPrefix { get; }
64+
5665
/// <inheritdoc />
5766
protected override T? GetCacheObject<T>(string key)
5867
where T : class

Neolution.Extensions.Caching.Distributed/MessagePackDistributedCacheOptions.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,27 @@ public class MessagePackDistributedCacheOptions : IOptions<MessagePackDistribute
2121
/// </value>
2222
public bool RequireMessagePackObjectAnnotation { get; set; }
2323

24+
/// <summary>
25+
/// Gets or sets the cache key version to use for cache invalidation.
26+
/// If not set, version is not included in the cache key.
27+
/// Changing this version will invalidate all existing cache keys with that version.
28+
/// The version will be formatted as "v{number}" in the cache key (e.g., v1, v2).
29+
/// </summary>
30+
/// <value>
31+
/// The cache key version. Defaults to null (no version in cache key).
32+
/// </value>
33+
public int? Version { get; set; }
34+
35+
/// <summary>
36+
/// Gets or sets the optional environment prefix for cache key isolation.
37+
/// If not set, environment prefix is not included in the cache key.
38+
/// Useful for multi-environment scenarios (e.g., "dev", "staging", "prod").
39+
/// </summary>
40+
/// <value>
41+
/// The environment prefix. Defaults to null (no environment prefix in cache key).
42+
/// </value>
43+
public string? EnvironmentPrefix { get; set; }
44+
2445
/// <inheritdoc />
2546
public MessagePackDistributedCacheOptions Value => this;
2647
}

Neolution.Extensions.Caching.RedisHybrid/Neolution.Extensions.Caching.RedisHybrid.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<ItemGroup>
1010
<PackageReference Include="Foundatio.Redis" Version="10.6.0" />
1111
<PackageReference Include="MessagePack" Version="2.5.198" />
12+
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
1213
<PackageReference Include="Neolution.CodeAnalysis" Version="2.6.1">
1314
<PrivateAssets>all</PrivateAssets>
1415
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

Neolution.Extensions.Caching.RedisHybrid/RedisHybridCache.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Threading;
55
using System.Threading.Tasks;
66
using Foundatio.Caching;
7+
using Microsoft.Extensions.Options;
78
using Neolution.Extensions.Caching.Abstractions;
89

910
/// <summary>
@@ -25,11 +26,27 @@ public class RedisHybridCache<TCacheId> : DistributedCache<TCacheId>
2526
/// Initializes a new instance of the <see cref="RedisHybridCache{TCacheId}"/> class.
2627
/// </summary>
2728
/// <param name="cacheClient">The cache client.</param>
28-
public RedisHybridCache(ICacheClient cacheClient)
29+
/// <param name="optionsAccessor">The options accessor.</param>
30+
public RedisHybridCache(ICacheClient cacheClient, IOptions<RedisHybridCacheOptions> optionsAccessor)
2931
{
30-
this.cacheClient = cacheClient;
32+
this.cacheClient = cacheClient ?? throw new ArgumentNullException(nameof(cacheClient));
33+
34+
if (optionsAccessor == null)
35+
{
36+
throw new ArgumentNullException(nameof(optionsAccessor));
37+
}
38+
39+
var options = optionsAccessor.Value;
40+
this.Version = options.Version;
41+
this.EnvironmentPrefix = options.EnvironmentPrefix;
3142
}
3243

44+
/// <inheritdoc />
45+
protected override int? Version { get; }
46+
47+
/// <inheritdoc />
48+
protected override string? EnvironmentPrefix { get; }
49+
3350
/// <inheritdoc />
3451
protected override T? GetCacheObject<T>(string key)
3552
where T : class
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
namespace Neolution.Extensions.Caching.RedisHybrid
2+
{
3+
using Microsoft.Extensions.Options;
4+
5+
/// <summary>
6+
/// The options for the Redis hybrid cache implementation.
7+
/// </summary>
8+
public class RedisHybridCacheOptions : IOptions<RedisHybridCacheOptions>
9+
{
10+
/// <summary>
11+
/// Gets or sets the cache key version to use for cache invalidation.
12+
/// If not set, version is not included in the cache key.
13+
/// Changing this version will invalidate all existing cache keys with that version.
14+
/// The version will be formatted as "v{number}" in the cache key (e.g., v1, v2).
15+
/// </summary>
16+
/// <value>
17+
/// The cache key version. Defaults to null (no version in cache key).
18+
/// </value>
19+
public int? Version { get; set; }
20+
21+
/// <summary>
22+
/// Gets or sets the optional environment prefix for cache key isolation.
23+
/// If not set, environment prefix is not included in the cache key.
24+
/// Useful for multi-environment scenarios (e.g., "dev", "staging", "prod").
25+
/// </summary>
26+
/// <value>
27+
/// The environment prefix. Defaults to null (no environment prefix in cache key).
28+
/// </value>
29+
public string? EnvironmentPrefix { get; set; }
30+
31+
/// <inheritdoc />
32+
public RedisHybridCacheOptions Value => this;
33+
}
34+
}

Neolution.Extensions.Caching.RedisHybrid/ServiceCollectionExtensions.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Namespace adjusted for easier recognition of these extension methods in composition roots.
22
namespace Microsoft.Extensions.DependencyInjection
33
{
4+
using System;
45
using Foundatio.Caching;
56
using Microsoft.Extensions.Logging;
67
using Neolution.Extensions.Caching.Abstractions;
@@ -19,13 +20,31 @@ public static class ServiceCollectionExtensions
1920
/// <param name="redisConnectionString">The redis connection string.</param>
2021
public static void AddRedisHybridCache(this IServiceCollection services, string redisConnectionString)
2122
{
23+
services.AddRedisHybridCache(redisConnectionString, _ => { });
24+
}
25+
26+
/// <summary>
27+
/// Adds the Neolution default non-distributed memory caching implementation with configuration options.
28+
/// </summary>
29+
/// <param name="services">The services.</param>
30+
/// <param name="redisConnectionString">The redis connection string.</param>
31+
/// <param name="configureOptions">The setup action.</param>
32+
public static void AddRedisHybridCache(this IServiceCollection services, string redisConnectionString, Action<RedisHybridCacheOptions> configureOptions)
33+
{
34+
if (configureOptions == null)
35+
{
36+
throw new ArgumentNullException(nameof(configureOptions));
37+
}
38+
2239
services.AddSingleton<IConnectionMultiplexer>(sp => ConnectionMultiplexer.Connect(redisConnectionString));
2340
services.AddSingleton<ICacheClient>(sp => new RedisHybridCacheClient(new RedisHybridCacheClientOptions
2441
{
2542
ConnectionMultiplexer = sp.GetService<IConnectionMultiplexer>(),
2643
LoggerFactory = sp.GetService<ILoggerFactory>(),
2744
}));
2845

46+
services.AddOptions();
47+
services.Configure(configureOptions);
2948
services.AddSingleton(typeof(IDistributedCache<>), typeof(RedisHybridCache<>));
3049
}
3150
}

0 commit comments

Comments
 (0)