## Motivation
The API for establishing the configuration of a connection pool had a lot of jargon and properties that developers had issues keeping straight and understanding what each does.
This commit provides first-class API support for concepts such as retry strategies, and how the pool handles connection counts.
## Changes
- Add: New ConnectionCountBehavior for determining leaky / non-leaky behavior
- Add: New ConnectionRetryStrategy for allowing customization of retry behavior
- Change: RedisConnection.defaultPort to be a computed property
- Change: The logging keys of pool connection retry metadata
- Rename: Several configuration properties to drop prefixes or to be combined into new structures
## Result
Developers should have a much better experience exploring the available configuration options for pools and connections, being able to understand how each piece works with the underlying system.
There are many times that developers want exact control over which EventLoop will be executing their chained EventLoopFuture callbacks
and which Logger will do the logging in calls deep within RediStack.
All commands will now accept an optional EventLoop and Logger to hop to, and using the logger for desired logs.
Motivation:
Following the SSWG guidelines for libraries and log levels, because much of the library's behavior is expressed in the language and NIO framework as errors and failed ELFs, logging at error is "verbose" and takes away control from developers.
Modifications:
Log messages have been adjusted to more accurately represent when and how the log message should be used, especially when ELFs are failed or errors are thrown.
Result:
Developers won't have log messages at error or critical unless they opt-in from their own code, unless the library has no way of expressing the failure condition through the language.
The newly-released Service Discovery framework gives us the interesting
opportunity to make RediStack aware of complex service discovery tools.
This patch supplies a simple adaptor to integrat Service Discovery with
RediStack's pooled client, allowing users to work with arbitrary service
discovery systems.
In some circumstances users may have connection pools configured without
any SocketAddresses ready to go. This is particularly likely in service
discovery configurations. Right now, the effect of attempting to use
such a pool is two fold. First, we'll emit a bunch of error level logs
telling users we have no addresses. Second, we'll fall into the
exponential backoff phase of connection establishment.
The first property is annoying, but the second one is actively harmful.
If your construction is timed incorrectly, we'll have the awkward
problem of burning a bunch of CPU trying to create connections we know
we cannot, and then a lengthy delay after the addresses are actually
configured before we start trying to use them. That's the worst of all
worlds.
This patch adds logic to detect the attempt to create connections when
we don't have any configured addresses and delays them. This path should
improve performance and ergonomics when in this mode.
Motivation:
RediStack today represents a command as a temporary object for the purpose of writing to the channel.
While it is useful to have an object for that purpose, commands handled in this way require immediate execution
and aren't available for other purposes.
Commands can serve a better purpose as a lightweight object to support delayed execution,
so that pipeling as described in issue #63 could be possible.
Modifications:
- Add: `get` overloads for JSON codable interaction on `RedisClient`
- Add: New `RedisZRangeResultOption` type for better interactions with zrange operations that can optionally return scores
- Add: New `RedisHashFieldKey` for type-safety when working with Hash field keys much like `RedisKey`
- Change: A few API types from enums to structs for library evolution
- Change: `RedisCommandHandler` to operate on a tuple of `RESPValue, EventLoopPromise<RESPValue>` rather than `RedisCommand`
- Change: `RedisCommand` to be a generic struct with the keyword, arguments, and a transform closure to defer execution
- Change: Almost all `RedisClient` command extensions to be factory methods on `RedisCommand` instead
- Change: Many response types to be optional to avoid developers having to do `isNull` checks on their own
- Change: `RedisClient.send(command:arguments:)` to be generic with `send(_:)` as the signature
- Rename: RedisClient extensions for scan methods to be more discoverable and legible as `scanHashField`, etc.
Result:
It should be easier to support a clean pipelining API with deferred command execution,
with extensions being easier for 2nd party developers, and the maintenance overhead of all of the command extensions
should be a little lighter when it comes to changing HOW commands are sent such as adding a context parameter
Motivation:
`RedisKeyLifetime` already has "RedisKey" as a prefix so it naturally fits as a nested type.
Modifications:
- Change: `RedisKeyLifetime` to be nested in `RedisKey` and named `Lifetime`
- Rename: `RedisKeyLifetime.Lifetime` to `Duration`
- Deprecate: `RedisKeyLifetime` and the nested type `Lifetime`
Result:
The global namespace is a little less cluttered with the types falling naturally where they already are.
Motivation:
When `RedisConnection.allowSubscriptions` is set to `false`, the connection could still be in a subscription state
leaving further commands to fail slowly from a full roundtrip to Redis, rather than succeeding as expected.
This changes the implementation so that it triggers a full unsubscribe from patterns and channels when set to `false`.
Modifications:
- Change: `RedisConnection.allowSubscriptions` to call `unsubscribe()` and `punsubscribe()` when set to `false`
- Change: `RedisPubSubHandler` to prefix storage of all dictionary keys to avoid name clashes between pattern and channel subscriptions
Result:
Developers should now have more deterministic and unsurprising behavior with PubSub
in regards to subscription management and connection state.