mirror of
https://github.com/swift-server/RediStack.git
synced 2026-05-03 07:32:28 +00:00
Add pitch doc
This commit is contained in:
@@ -1,2 +1,155 @@
|
||||
# nio-redis
|
||||
Non-blocking, event-driven Swift client for Redis.
|
||||
# NIORedis: Client for Redis server built on NIO
|
||||
This package includes two modules: `NIORedis` and `Redis`, which provide clients that handle connection to, authorizing, and
|
||||
executing commands against a Redis server.
|
||||
|
||||
`NIORedis` provides channel handlers for encoding / decoding between Swift native types and [Redis' Serialization Protocol (RESP)](https://redis.io/topics/protocol).
|
||||
|
||||
`Redis` is an abstraction layer that wraps `NIORedis` to be callback based with `DispatchQueue`.
|
||||
|
||||
# Motivation
|
||||
Implementations of Redis connections have decayed as newer capabilities of the Swift STD Library, SwiftNIO, and the Swift language itself have developed.
|
||||
|
||||
As part of the iniative of trying to push the ecosystem to be centered around SwiftNIO, a framework-agnostic driver on Redis can provide an
|
||||
easier time for feature development on Redis.
|
||||
|
||||
# Proposed Solution
|
||||
A barebones implementation is available at [mordil/nio-redis](https://github.com/mordil/nio-redis).
|
||||
|
||||
The following are already implemented, with unit tests:
|
||||
|
||||
- Connection and Authorization
|
||||
- Raw commands
|
||||
- Convienence methods for:
|
||||
- GET
|
||||
- SET
|
||||
- AUTH
|
||||
- DEL
|
||||
- SELECT
|
||||
- EXPIRE
|
||||
- NIO-wrapped abstractions for
|
||||
- Client
|
||||
- Connection
|
||||
- GET command
|
||||
- Unit tests for
|
||||
- Response decoding to native Swift
|
||||
- Message encoding to RESP
|
||||
- Connections
|
||||
- implemented commands
|
||||
|
||||
This package is a re-implementation of [vapor/redis](https://github.com/vapor/redis) stripped down to only build on SwiftNIO to be framework agnostic.
|
||||
|
||||
Much of this was inspired by the [NIOPostgres pitch](https://forums.swift.org/t/pitch-swiftnio-based-postgresql-client/18020).
|
||||
|
||||
# Details Solution
|
||||
|
||||
> **NOTE: This this is written against SwiftNIO 2.0, and as such requires Swift 5.0!**
|
||||
|
||||
This is to take advantage of the [`Result`](https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md) type in the `Redis` module,
|
||||
and to stay ahead of development of the next version of SwiftNIO.
|
||||
|
||||
## NIORedis
|
||||
Most use of this library will be focused on a `NIORedisConnection` type that works explicitly in a SwiftNIO `EventLoop` context - with
|
||||
return values all being `EventLoopFuture`.
|
||||
|
||||
```swift
|
||||
import NIORedis
|
||||
|
||||
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
|
||||
let redis = NIORedis(executionModel: .eventLoopGroup(elg))
|
||||
|
||||
// connections
|
||||
|
||||
// passing a value to `password` will automatically authenticate with Redis before resolving the connection
|
||||
let connection = try redis.makeConnection(
|
||||
hostname: "localhost", // this is the default
|
||||
port: 6379, // this is the default
|
||||
password: "MY_PASS" // default is `nil`
|
||||
).wait()
|
||||
print(connection) // NIORedisConnection
|
||||
|
||||
// convienence methods for commands
|
||||
|
||||
let result = try connection.set("my_key", to: "some value")
|
||||
.then {
|
||||
return connection.get("my_key")
|
||||
}.wait()
|
||||
print(result) // Optional("some value")
|
||||
|
||||
// raw commands
|
||||
|
||||
let keyCount = try connection.command("DEL", [RedisData(bulk: "my_key")])
|
||||
.thenThrowing { res in
|
||||
guard case let .integer(count) else {
|
||||
// throw Error
|
||||
}
|
||||
return count
|
||||
}.wait()
|
||||
print(keyCount) // 1
|
||||
|
||||
// cleanup
|
||||
|
||||
connection.close()
|
||||
try redis.terminate()
|
||||
try elg.syncShutdownGracefully()
|
||||
```
|
||||
|
||||
### RedisData & RedisDataConvertible
|
||||
This is a 1:1 mapping enum of the `RESP` types: `Simple String`, `Bulk String`, `Array`, `Integer` and `Error`.
|
||||
|
||||
Conforming to `RedisDataConvertible` allows Swift types to more easily convert between `RedisData` and native types.
|
||||
|
||||
`Array`, `Data`, `Float`, `Double`, `FixedWidthInteger`, `String`, and of course `RedisData` all conform in this package.
|
||||
|
||||
A `ByteToMessageDecoder` and `MessageToByteEncoder` are used for the conversion process on connections.
|
||||
|
||||
### NIORedisConnection
|
||||
This class uses a `ChannelInboundHandler` that handles the actual process of sending and receiving commands.
|
||||
|
||||
While it does handle a "pipeline" queue of messages, so as to not be blocking, it is _not_ the same as [Redis' Pipelining](https://redis.io/topics/pipelining).
|
||||
|
||||
That is a feature for future development.
|
||||
|
||||
## Redis
|
||||
|
||||
To support contexts where someone either doesn't want to work in a SwiftNIO context, the `Redis` module provides a callback-based interface
|
||||
that wraps all of `NIORedis`.
|
||||
|
||||
A `Redis` instance manages a `NIORedis` object under the hood, with `RedisConnection` doing the same for `NIORedisConnection`.
|
||||
|
||||
```swift
|
||||
import Redis
|
||||
|
||||
let redis = Redis(threadCount: 1) // default is 1
|
||||
|
||||
// connections
|
||||
|
||||
// passing a value to `password` will automatically authenticate with Redis before resolving the connection
|
||||
redis.makeConnection(
|
||||
hostname: "localhost", // this is the default
|
||||
port: 6379, // this is the default
|
||||
password: "MY_PASS", // default is `nil`
|
||||
queue: DispatchQueue(label: "com.MyPackage.redis") // default is `.main`
|
||||
) { result in
|
||||
switch result {
|
||||
case .success(let conn):
|
||||
showCommands(on: conn)
|
||||
case .failure(let error):
|
||||
fatalError("Could not create RedisConnection!")
|
||||
}
|
||||
}
|
||||
|
||||
// convienence methods for commands
|
||||
|
||||
func showCommands(on conn: RedisConnection) {
|
||||
conn.get("my_key") { result in
|
||||
switch result {
|
||||
case .success(let value):
|
||||
// use value, which is String?
|
||||
case .failure(let error):
|
||||
// do something on error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup is handled by deinit blocks
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user