Skip to content

Commit 8ae31c6

Browse files
authored
feat: add Connection method to RedisStoreBuilder for external Redis connections (#147)
1 parent f5358d9 commit 8ae31c6

File tree

6 files changed

+145
-14
lines changed

6 files changed

+145
-14
lines changed

pkgs/dotnet-server-sdk-redis/src/RedisBigSegmentStoreImpl.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ internal sealed class RedisBigSegmentStoreImpl : RedisStoreImplBase, IBigSegment
1717
private readonly string _excludedKeyPrefix;
1818

1919
internal RedisBigSegmentStoreImpl(
20-
ConfigurationOptions redisConfig,
20+
Func<IConnectionMultiplexer> connectionFactory,
2121
string prefix,
2222
Logger log
23-
) : base(redisConfig, prefix, log)
23+
) : base(connectionFactory, prefix, log)
2424
{
2525
_syncTimeKey = prefix + ":big_segments_synchronized_on";
2626
_includedKeyPrefix = prefix + ":big_segment_include:";

pkgs/dotnet-server-sdk-redis/src/RedisDataStoreImpl.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ internal sealed class RedisDataStoreImpl : RedisStoreImplBase, IPersistentDataSt
4040
private readonly string _initedKey;
4141

4242
internal RedisDataStoreImpl(
43-
ConfigurationOptions redisConfig,
43+
Func<IConnectionMultiplexer> connectionFactory,
4444
string prefix,
4545
Logger log
46-
) : base(redisConfig, prefix, log)
46+
) : base(connectionFactory, prefix, log)
4747
{
4848
_initedKey = prefix + ":$inited";
4949
}
@@ -70,7 +70,7 @@ public void Init(FullDataSet<SerializedItemDescriptor> allData)
7070
txn.StringSetAsync(_initedKey, "");
7171
txn.Execute();
7272
}
73-
73+
7474
public SerializedItemDescriptor? Get(DataKind kind, string key)
7575
{
7676
IDatabase db = _redis.GetDatabase();

pkgs/dotnet-server-sdk-redis/src/RedisStoreBuilder.cs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ namespace LaunchDarkly.Sdk.Server.Integrations
5757
public abstract class RedisStoreBuilder<T> : IComponentConfigurer<T>, IDiagnosticDescription
5858
{
5959
internal ConfigurationOptions _redisConfig = new ConfigurationOptions();
60+
internal IConnectionMultiplexer _externalConnection;
6061
internal string _prefix = Redis.DefaultPrefix;
6162

6263
internal RedisStoreBuilder()
@@ -224,6 +225,35 @@ public RedisStoreBuilder<T> RedisConfigChanges(Action<ConfigurationOptions> modi
224225
return this;
225226
}
226227

228+
/// <summary>
229+
/// Specifies a pre-configured Redis connection multiplexer to use and will ignore
230+
/// all other redis configuration options. Once you provide a multiplexer the SDK
231+
/// will own it and will dispose it.
232+
/// </summary>
233+
/// <param name="connection">the pre-configured connection multiplexer</param>
234+
/// <returns>the builder</returns>
235+
public RedisStoreBuilder<T> Connection(IConnectionMultiplexer connection)
236+
{
237+
_externalConnection = connection ?? throw new ArgumentNullException(nameof(connection));
238+
return this;
239+
}
240+
241+
/// <summary>
242+
/// Gets the connection to use - either the externally provided one or creates a new one
243+
/// from configuration.
244+
/// </summary>
245+
/// <returns>the Redis connection multiplexer to use</returns>
246+
protected IConnectionMultiplexer ConnectionMultiplexerFactory()
247+
{
248+
if (_externalConnection != null)
249+
{
250+
return _externalConnection;
251+
}
252+
253+
var redisConfigCopy = _redisConfig.Clone();
254+
return ConnectionMultiplexer.Connect(redisConfigCopy);
255+
}
256+
227257
/// <inheritdoc/>
228258
public abstract T Build(LdClientContext context);
229259

@@ -234,13 +264,23 @@ public LdValue DescribeConfiguration(LdClientContext context) =>
234264

235265
internal sealed class BuilderForDataStore : RedisStoreBuilder<IPersistentDataStore>
236266
{
237-
public override IPersistentDataStore Build(LdClientContext context) =>
238-
new RedisDataStoreImpl(_redisConfig, _prefix, context.Logger.SubLogger("DataStore.Redis"));
267+
public override IPersistentDataStore Build(LdClientContext context)
268+
{
269+
return new RedisDataStoreImpl(
270+
ConnectionMultiplexerFactory,
271+
_prefix,
272+
context.Logger.SubLogger("DataStore.Redis"));
273+
}
239274
}
240275

241276
internal sealed class BuilderForBigSegments : RedisStoreBuilder<IBigSegmentStore>
242277
{
243-
public override IBigSegmentStore Build(LdClientContext context) =>
244-
new RedisBigSegmentStoreImpl(_redisConfig, _prefix, context.Logger.SubLogger("BigSegments.Redis"));
278+
public override IBigSegmentStore Build(LdClientContext context)
279+
{
280+
return new RedisBigSegmentStoreImpl(
281+
ConnectionMultiplexerFactory,
282+
_prefix,
283+
context.Logger.SubLogger("BigSegments.Redis"));
284+
}
245285
}
246286
}

pkgs/dotnet-server-sdk-redis/src/RedisStoreImplBase.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,21 @@ namespace LaunchDarkly.Sdk.Server.Integrations
1010
{
1111
internal abstract class RedisStoreImplBase : IDisposable
1212
{
13-
protected readonly ConnectionMultiplexer _redis;
13+
protected readonly IConnectionMultiplexer _redis;
1414
protected readonly string _prefix;
1515
protected readonly Logger _log;
1616

1717
protected RedisStoreImplBase(
18-
ConfigurationOptions redisConfig,
18+
Func<IConnectionMultiplexer> connectionFactory,
1919
string prefix,
2020
Logger log
2121
)
2222
{
2323
_log = log;
24-
var redisConfigCopy = redisConfig.Clone();
25-
_redis = ConnectionMultiplexer.Connect(redisConfigCopy);
2624
_prefix = prefix;
25+
_redis = connectionFactory();
2726
_log.Info("Using Redis data store at {0} with prefix \"{1}\"",
28-
string.Join(", ", redisConfig.EndPoints.Select(DescribeEndPoint)), prefix);
27+
string.Join(", ", _redis.GetEndPoints().Select(DescribeEndPoint)), prefix);
2928
}
3029

3130
public void Dispose()
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using StackExchange.Redis;
3+
using Xunit;
4+
5+
namespace LaunchDarkly.Sdk.Server.Integrations
6+
{
7+
public class RedisBigSegmentStoreBuilderTest
8+
{
9+
[Fact]
10+
public void ConnectionWithNull()
11+
{
12+
var builder = Redis.BigSegmentStore();
13+
14+
// Setting null connection should throw ArgumentNullException
15+
Assert.Throws<ArgumentNullException>(() => builder.Connection(null));
16+
}
17+
18+
[Fact]
19+
public void ConnectionMethodSetsExternalConnection()
20+
{
21+
var builder = Redis.BigSegmentStore();
22+
var connection = ConnectionMultiplexer.Connect(new ConfigurationOptions()
23+
{
24+
EndPoints = { "localhost:6379" }
25+
});
26+
27+
// Initially no external connection
28+
Assert.Null(builder._externalConnection);
29+
30+
// Set the connection
31+
builder.Connection(connection);
32+
33+
// Verify the connection was set
34+
Assert.Same(connection, builder._externalConnection);
35+
}
36+
37+
[Fact]
38+
public void ConnectionWorksWithOtherBuilderMethods()
39+
{
40+
var builder = Redis.BigSegmentStore();
41+
42+
// Chain with other builder methods
43+
builder.Prefix("test-prefix");
44+
45+
Assert.Equal("test-prefix", builder._prefix);
46+
}
47+
}
48+
}

pkgs/dotnet-server-sdk-redis/test/RedisDataStoreBuilderTest.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Net;
4+
using StackExchange.Redis;
45
using Xunit;
56

67
namespace LaunchDarkly.Sdk.Server.Integrations
@@ -113,5 +114,48 @@ public void Prefix()
113114
builder.Prefix(null);
114115
Assert.Equal(Redis.DefaultPrefix, builder._prefix);
115116
}
117+
118+
[Fact]
119+
public void ConnectionWithNull()
120+
{
121+
var builder = Redis.DataStore();
122+
123+
// Setting null connection should throw ArgumentNullException
124+
Assert.Throws<ArgumentNullException>(() => builder.Connection(null));
125+
}
126+
127+
[Fact]
128+
public void ConnectionMethodSetsExternalConnection()
129+
{
130+
var builder = Redis.DataStore();
131+
var connection = ConnectionMultiplexer.Connect(new ConfigurationOptions()
132+
{
133+
EndPoints = { "localhost:6379" }
134+
});
135+
136+
// Initially no external connection
137+
Assert.Null(builder._externalConnection);
138+
139+
// Set the connection
140+
builder.Connection(connection);
141+
142+
// Verify the connection was set
143+
Assert.Same(connection, builder._externalConnection);
144+
}
145+
146+
[Fact]
147+
public void ConnectionWorksWithOtherBuilderMethods()
148+
{
149+
var builder = Redis.DataStore();
150+
151+
// Chain with other builder methods
152+
builder.HostAndPort("test", 9999)
153+
.Prefix("test-prefix");
154+
155+
// Config should be set
156+
Assert.Collection(builder._redisConfig.EndPoints,
157+
e => Assert.Equal(new DnsEndPoint("test", 9999), e));
158+
Assert.Equal("test-prefix", builder._prefix);
159+
}
116160
}
117161
}

0 commit comments

Comments
 (0)