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).

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.

The following are already implemented, with unit tests:

This package is a re-implementation of vapor/redis stripped down to only build on SwiftNIO to be framework agnostic.

Much of this was inspired by the NIOPostgres pitch.

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 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.

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 queue of messages, so as to not be blocking, pipelining is implemented with NIORedisPipeline.

NIORedisPipeline

A NIORedisPipeline is a quick abstraction that buffers an array of complete messages as RedisData, and executing them in sequence after a user has invoked execute().

It returns an EventLoopFuture<[RedisData]> with the results of all commands executed - unless one errors.

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.

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
S
Description
Non-blocking, event-driven Swift client for Redis.
Readme 2 MiB
Languages
Swift 99.7%
Shell 0.3%