diff --git a/README.md b/README.md index c923505..8a9a3db 100644 --- a/README.md +++ b/README.md @@ -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 +```