1 package ldredis 2 3 import ( 4 "fmt" 5 6 r "github.com/gomodule/redigo/redis" 7 8 "github.com/launchdarkly/go-sdk-common/v3/ldvalue" 9 "github.com/launchdarkly/go-server-sdk/v6/subsystems" 10 ) 11 12 const ( 13 // DefaultURL is the default value for StoreBuilder.URL. 14 DefaultURL = "redis://localhost:6379" 15 // DefaultPrefix is the default value for StoreBuilder.Prefix. 16 DefaultPrefix = "launchdarkly" 17 ) 18 19 // DataStore returns a configurable builder for a Redis-backed persistent data store. 20 // 21 // This is for the main data store that holds feature flag data. To configure a data store for 22 // Big Segments, use [BigSegmentStore] instead. 23 // 24 // You can use methods of the builder to specify any non-default Redis options you may want, 25 // before passing the builder to [github.com/launchdarkly/go-server-sdk/v6/ldcomponents.PersistentDataStore]. 26 // In this example, the store is configured to use a Redis host called "host1": 27 // 28 // config.DataStore = ldcomponents.PersistentDataStore( 29 // ldredis.DataStore().HostAndPort("host1", 6379)) 30 // 31 // Note that the SDK also has its own options related to data storage that are configured 32 // at a different level, because they are independent of what database is being used. For 33 // instance, the builder returned by [github.com/launchdarkly/go-server-sdk/v6/ldcomponents.PersistentDataStore] 34 // has options for caching: 35 // 36 // config.DataStore = ldcomponents.PersistentDataStore( 37 // ldredis.DataStore().HostAndPort("host1", 6379), 38 // ).CacheSeconds(15) 39 func DataStore() *StoreBuilder[subsystems.PersistentDataStore] { 40 return &StoreBuilder[subsystems.PersistentDataStore]{ 41 builderOptions: builderOptions{ 42 prefix: DefaultPrefix, 43 url: DefaultURL, 44 }, 45 factory: createPersistentDataStore, 46 } 47 } 48 49 // BigSegmentStore returns a configurable builder for a Redis-backed Big Segment store. 50 // 51 // You can use methods of the builder to specify any non-default Redis options you may want, 52 // before passing the builder to [github.com/launchdarkly/go-server-sdk/v6/ldcomponents.BigSegments]. 53 // In this example, the store is configured to use a Redis host called "host2": 54 // 55 // config.BigSegments = ldcomponents.BigSegments( 56 // ldredis.BigSegmentStore().HostAndPort("host2", 6379)) 57 // 58 // Note that the SDK also has its own options related to Big Segments that are configured 59 // at a different level, because they are independent of what database is being used. For 60 // instance, the builder returned by [github.com/launchdarkly/go-server-sdk/v6/ldcomponents.BigSegments] 61 // has an option for the status polling interval: 62 // 63 // config.BigSegments = ldcomponents.BigSegments( 64 // ldredis.BigSegmentStore().HostAndPort("host2", 6379), 65 // ).StatusPollInterval(time.Second * 30) 66 func BigSegmentStore() *StoreBuilder[subsystems.BigSegmentStore] { 67 return &StoreBuilder[subsystems.BigSegmentStore]{ 68 builderOptions: builderOptions{ 69 prefix: DefaultPrefix, 70 url: DefaultURL, 71 }, 72 factory: createBigSegmentStore, 73 } 74 } 75 76 // StoreBuilder is a builder for configuring the Redis-based persistent data store and/or Big 77 // Segment store. 78 // 79 // Both [DataStore] and [BigSegmentStore] return instances of this type. You can use methods of the 80 // builder to specify any ny non-default Redis options you may want, before passing the builder to 81 // either [github.com/launchdarkly/go-server-sdk/v6/ldcomponents.PersistentDataStore] or 82 // [github.com/launchdarkly/go-server-sdk/v6/ldcomponents.BigSegments] as appropriate. The two types 83 // of stores are independent of each other; you do not need a Big Segment store if you are not using 84 // the Big Segments feature, and you do not need to use the same database for both. 85 // 86 // In this example, the main data store uses a Redis host called "host1", and the Big Segment 87 // store uses a Redis host called "host2": 88 // 89 // config.DataStore = ldcomponents.PersistentDataStore( 90 // ldredis.DataStore().URL("redis://host1:6379") 91 // config.BigSegments = ldcomponents.BigSegments( 92 // ldredis.DataStore().URL("redis://host2:6379") 93 // 94 // Note that the SDK also has its own options related to data storage that are configured 95 // at a different level, because they are independent of what database is being used. For 96 // instance, the builder returned by [github.com/launchdarkly/go-server-sdk/v6/ldcomponents.PersistentDataStore] 97 // has options for caching: 98 // 99 // config.DataStore = ldcomponents.PersistentDataStore( 100 // ldredis.DataStore().HostAndPort("host1", 6379), 101 // ).CacheSeconds(15) 102 type StoreBuilder[T any] struct { 103 builderOptions builderOptions 104 factory func(*StoreBuilder[T], subsystems.ClientContext) (T, error) 105 } 106 107 type builderOptions struct { 108 prefix string 109 pool Pool 110 url string 111 dialOptions []r.DialOption 112 } 113 114 // Prefix specifies a string that should be prepended to all Redis keys used by the data store. 115 // A colon will be added to this automatically. If this is unspecified or empty, [DefaultPrefix] will be used. 116 func (b *StoreBuilder[T]) Prefix(prefix string) *StoreBuilder[T] { 117 if prefix == "" { 118 prefix = DefaultPrefix 119 } 120 b.builderOptions.prefix = prefix 121 return b 122 } 123 124 // URL specifies the Redis host URL. If not specified, the default value is [DefaultURL]. 125 // 126 // Note that some Redis client features can also be specified as part of the URL: Redigo supports 127 // the redis:// syntax (https://www.iana.org/assignments/uri-schemes/prov/redis), which can include a 128 // password and a database number, as well as rediss:// 129 // (https://www.iana.org/assignments/uri-schemes/prov/rediss), which enables TLS. 130 func (b *StoreBuilder[T]) URL(url string) *StoreBuilder[T] { 131 if url == "" { 132 url = DefaultURL 133 } 134 b.builderOptions.url = url 135 return b 136 } 137 138 // HostAndPort is a shortcut for specifying the Redis host address as a hostname and port. 139 func (b *StoreBuilder[T]) HostAndPort(host string, port int) *StoreBuilder[T] { 140 return b.URL(fmt.Sprintf("redis://%s:%d", host, port)) 141 } 142 143 // Pool specifies that the data store should use a specific connection pool configuration. If not 144 // specified, it will create a default configuration (see package description). Specifying this 145 // option will cause any address specified with URL or HostAndPort to be ignored. 146 // 147 // If you only need to change basic connection options such as providing a password, it is 148 // simpler to use DialOptions. 149 // 150 // Use PoolInterface if you want to provide your own implementation of a connection pool. 151 func (b *StoreBuilder[T]) Pool(pool *r.Pool) *StoreBuilder[T] { 152 b.builderOptions.pool = pool 153 return b 154 } 155 156 // PoolInterface is equivalent to Pool, but uses an interface type rather than a concrete 157 // implementation type. This allows implementation of custom behaviors for connection management. 158 func (b *StoreBuilder[T]) PoolInterface(pool Pool) *StoreBuilder[T] { 159 b.builderOptions.pool = pool 160 return b 161 } 162 163 // DialOptions specifies any of the advanced Redis connection options supported by Redigo, such as 164 // DialPassword. 165 // 166 // import ( 167 // redigo "github.com/garyburd/redigo/redis" 168 // ldredis "github.com/launchdarkly/go-server-sdk-redis-redigo/v2" 169 // ) 170 // config.DataSource = ldcomponents.PersistentDataStore( 171 // ldredis.DataStore().DialOptions(redigo.DialPassword("verysecure123")), 172 // ) 173 // Note that some Redis client features can also be specified as part of the URL: see URL(). 174 func (b *StoreBuilder[T]) DialOptions(options ...r.DialOption) *StoreBuilder[T] { 175 b.builderOptions.dialOptions = options 176 return b 177 } 178 179 // Build is called internally by the SDK. 180 func (b *StoreBuilder[T]) Build(context subsystems.ClientContext) (T, error) { 181 return b.factory(b, context) 182 } 183 184 // DescribeConfiguration is used internally by the SDK to inspect the configuration. 185 func (b *StoreBuilder[T]) DescribeConfiguration() ldvalue.Value { 186 return ldvalue.String("Redis") 187 } 188 189 // Pool is an interface representing a Redis connection pool. 190 // 191 // The methods of this interface are the same as the basic methods of the Pool type in 192 // the Redigo client. Any type implementing the interface can be passed to 193 // StoreBuilder.PoolInterface to provide custom connection behavior. 194 type Pool interface { 195 // Get obtains a Redis connection. 196 // 197 // See: https://pkg.go.dev/github.com/gomodule/redigo/redis#Pool.Get 198 Get() r.Conn 199 200 // Close releases the resources used by the pool. 201 // 202 // See: https://pkg.go.dev/github.com/gomodule/redigo/redis#Pool.Close 203 Close() error 204 } 205 206 func createPersistentDataStore( 207 builder *StoreBuilder[subsystems.PersistentDataStore], 208 clientContext subsystems.ClientContext, 209 ) (subsystems.PersistentDataStore, error) { 210 store := newRedisDataStoreImpl(builder.builderOptions, clientContext.GetLogging().Loggers) 211 return store, nil 212 } 213 214 func createBigSegmentStore( 215 builder *StoreBuilder[subsystems.BigSegmentStore], 216 clientContext subsystems.ClientContext, 217 ) (subsystems.BigSegmentStore, error) { 218 store := newRedisBigSegmentStoreImpl(builder.builderOptions, clientContext.GetLogging().Loggers) 219 return store, nil 220 } 221