up
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Messaging.Abstractions;
|
||||
using StackExchange.Redis;
|
||||
|
||||
namespace StellaOps.Messaging.Transport.Valkey;
|
||||
|
||||
/// <summary>
|
||||
/// Valkey/Redis implementation of <see cref="ISetStore{TKey, TElement}"/>.
|
||||
/// Uses set commands (SADD, SMEMBERS, SISMEMBER, SREM, etc.).
|
||||
/// </summary>
|
||||
public sealed class ValkeySetStore<TKey, TElement> : ISetStore<TKey, TElement>
|
||||
where TKey : notnull
|
||||
{
|
||||
private readonly ValkeyConnectionFactory _connectionFactory;
|
||||
private readonly string _name;
|
||||
private readonly ILogger<ValkeySetStore<TKey, TElement>>? _logger;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
private readonly Func<TKey, string> _keySerializer;
|
||||
|
||||
public ValkeySetStore(
|
||||
ValkeyConnectionFactory connectionFactory,
|
||||
string name,
|
||||
ILogger<ValkeySetStore<TKey, TElement>>? logger = null,
|
||||
JsonSerializerOptions? jsonOptions = null,
|
||||
Func<TKey, string>? keySerializer = null)
|
||||
{
|
||||
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
|
||||
_name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
_logger = logger;
|
||||
_jsonOptions = jsonOptions ?? new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = false
|
||||
};
|
||||
_keySerializer = keySerializer ?? (key => key?.ToString() ?? throw new ArgumentNullException(nameof(key)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ProviderName => "valkey";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<bool> AddAsync(
|
||||
TKey setKey,
|
||||
TElement element,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var redisKey = BuildKey(setKey);
|
||||
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var serialized = Serialize(element);
|
||||
return await db.SetAddAsync(redisKey, serialized).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<long> AddRangeAsync(
|
||||
TKey setKey,
|
||||
IEnumerable<TElement> elements,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(elements);
|
||||
|
||||
var redisKey = BuildKey(setKey);
|
||||
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var values = elements.Select(e => (RedisValue)Serialize(e)).ToArray();
|
||||
if (values.Length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return await db.SetAddAsync(redisKey, values).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<IReadOnlySet<TElement>> GetMembersAsync(
|
||||
TKey setKey,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var redisKey = BuildKey(setKey);
|
||||
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var members = await db.SetMembersAsync(redisKey).ConfigureAwait(false);
|
||||
var result = new HashSet<TElement>();
|
||||
|
||||
foreach (var member in members)
|
||||
{
|
||||
if (!member.IsNullOrEmpty)
|
||||
{
|
||||
var element = Deserialize((string)member!);
|
||||
if (element is not null)
|
||||
{
|
||||
result.Add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<bool> ContainsAsync(
|
||||
TKey setKey,
|
||||
TElement element,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var redisKey = BuildKey(setKey);
|
||||
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var serialized = Serialize(element);
|
||||
return await db.SetContainsAsync(redisKey, serialized).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<bool> RemoveAsync(
|
||||
TKey setKey,
|
||||
TElement element,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var redisKey = BuildKey(setKey);
|
||||
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var serialized = Serialize(element);
|
||||
return await db.SetRemoveAsync(redisKey, serialized).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<long> RemoveRangeAsync(
|
||||
TKey setKey,
|
||||
IEnumerable<TElement> elements,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(elements);
|
||||
|
||||
var redisKey = BuildKey(setKey);
|
||||
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var values = elements.Select(e => (RedisValue)Serialize(e)).ToArray();
|
||||
if (values.Length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return await db.SetRemoveAsync(redisKey, values).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<bool> DeleteAsync(
|
||||
TKey setKey,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var redisKey = BuildKey(setKey);
|
||||
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return await db.KeyDeleteAsync(redisKey).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<long> CountAsync(
|
||||
TKey setKey,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var redisKey = BuildKey(setKey);
|
||||
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return await db.SetLengthAsync(redisKey).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask SetExpirationAsync(
|
||||
TKey setKey,
|
||||
TimeSpan ttl,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var redisKey = BuildKey(setKey);
|
||||
var db = await _connectionFactory.GetDatabaseAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await db.KeyExpireAsync(redisKey, ttl).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private string BuildKey(TKey setKey)
|
||||
{
|
||||
var keyString = _keySerializer(setKey);
|
||||
return $"set:{_name}:{keyString}";
|
||||
}
|
||||
|
||||
private string Serialize(TElement element)
|
||||
{
|
||||
// For primitive types, use ToString directly
|
||||
if (element is string s)
|
||||
{
|
||||
return s;
|
||||
}
|
||||
|
||||
return JsonSerializer.Serialize(element, _jsonOptions);
|
||||
}
|
||||
|
||||
private TElement? Deserialize(string value)
|
||||
{
|
||||
// For string types, return directly
|
||||
if (typeof(TElement) == typeof(string))
|
||||
{
|
||||
return (TElement)(object)value;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<TElement>(value, _jsonOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating Valkey set store instances.
|
||||
/// </summary>
|
||||
public sealed class ValkeySetStoreFactory : ISetStoreFactory
|
||||
{
|
||||
private readonly ValkeyConnectionFactory _connectionFactory;
|
||||
private readonly ILoggerFactory? _loggerFactory;
|
||||
private readonly JsonSerializerOptions? _jsonOptions;
|
||||
|
||||
public ValkeySetStoreFactory(
|
||||
ValkeyConnectionFactory connectionFactory,
|
||||
ILoggerFactory? loggerFactory = null,
|
||||
JsonSerializerOptions? jsonOptions = null)
|
||||
{
|
||||
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
|
||||
_loggerFactory = loggerFactory;
|
||||
_jsonOptions = jsonOptions;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ProviderName => "valkey";
|
||||
|
||||
/// <inheritdoc />
|
||||
public ISetStore<TKey, TElement> Create<TKey, TElement>(string name)
|
||||
where TKey : notnull
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(name);
|
||||
return new ValkeySetStore<TKey, TElement>(
|
||||
_connectionFactory,
|
||||
name,
|
||||
_loggerFactory?.CreateLogger<ValkeySetStore<TKey, TElement>>(),
|
||||
_jsonOptions);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user