Skip to content

Commit 473b395

Browse files
authored
Merge pull request #5 from Mordil/rev-2
Rev 2
2 parents 5d9db5b + badf342 commit 473b395

30 files changed

+757
-845
lines changed

Package.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
// swift-tools-version:4.2
1+
// swift-tools-version:5.0
22

33
import PackageDescription
44

55
let package = Package(
6-
name: "Redis",
6+
name: "nio-redis",
77
products: [
88
.library(name: "NIORedis", targets: ["NIORedis"]),
9-
.library(name: "Redis", targets: ["Redis"])
9+
.library(name: "DispatchRedis", targets: ["DispatchRedis"])
1010
],
1111
dependencies: [
1212
.package(url: "https://github.com/apple/swift-nio.git", .branch("master"))
1313
],
1414
targets: [
1515
.target(name: "NIORedis", dependencies: ["NIO"]),
16-
.target(name: "Redis", dependencies: ["NIORedis"]),
17-
.testTarget(name: "NIORedisTests", dependencies: ["NIORedis", "NIO"]),
18-
//.testTarget(name: "RedisTests", dependencies: ["Redis"])
16+
.target(name: "DispatchRedis", dependencies: ["NIORedis"]),
17+
.testTarget(name: "NIORedisTests", dependencies: ["NIORedis", "NIO"])
1918
]
2019
)

README.md

Lines changed: 21 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,161 +1,56 @@
1-
# NIORedis: Client for Redis server built on NIO
2-
This package includes two modules: `NIORedis` and `Redis`, which provide clients that handle connection to, authorizing, and
3-
executing commands against a Redis server.
1+
# NIORedis: A Redis Driver built on SwiftNIO
42

5-
`NIORedis` provides channel handlers for encoding / decoding between Swift native types and [Redis' Serialization Protocol (RESP)](https://redis.io/topics/protocol).
6-
7-
`Redis` is an abstraction layer that wraps `NIORedis` to be callback based with `DispatchQueue`.
8-
9-
# Motivation
10-
Implementations of Redis connections have decayed as newer capabilities of the Swift STD Library, SwiftNIO, and the Swift language itself have developed.
11-
12-
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
13-
easier time for feature development on Redis.
14-
15-
# Proposed Solution
16-
A barebones implementation is available at [mordil/nio-redis](https://github.com/mordil/nio-redis).
17-
18-
The following are already implemented, with unit tests:
19-
20-
- [Connection and Authorization](https://github.com/Mordil/nio-redis/blob/master/Sources/NIORedis/NIORedis.swift#L35)
21-
- [Raw commands](https://github.com/Mordil/nio-redis/blob/master/Sources/NIORedis/NIORedisConnection.swift#L33)
22-
- [Convienence methods for:](https://github.com/Mordil/nio-redis/blob/master/Sources/NIORedis/Commands/BasicCommands.swift#L4)
23-
- GET
24-
- SET
25-
- AUTH
26-
- DEL
27-
- SELECT
28-
- EXPIRE
29-
- NIO-wrapped abstractions for
30-
- [Client](https://github.com/Mordil/nio-redis/blob/master/Sources/Redis/Redis.swift)
31-
- [Connection](https://github.com/Mordil/nio-redis/blob/master/Sources/Redis/RedisConnection.swift)
32-
- [Pipelines](https://github.com/Mordil/nio-redis/blob/master/Sources/Redis/RedisPipeline.swift)
33-
- GET command
34-
- Unit tests for
35-
- Response decoding to native Swift
36-
- Message encoding to RESP
37-
- Connections
38-
- implemented commands
39-
- pipelines
40-
41-
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.
42-
43-
Much of this was inspired by the [NIOPostgres pitch](https://forums.swift.org/t/pitch-swiftnio-based-postgresql-client/18020).
44-
45-
# Details Solution
3+
* Pitch discussion: [Swift Server Forums](https://forums.swift.org/t/swiftnio-redis-client/19325/13)
464

475
> **NOTE: This this is written against SwiftNIO 2.0, and as such requires Swift 5.0!**
486
49-
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,
7+
This is to take advantage of the [`Result`](https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md) type in the `DispatchRedis` module,
508
and to stay ahead of development of the next version of SwiftNIO.
519

52-
## NIORedis
53-
Most use of this library will be focused on a `NIORedisConnection` type that works explicitly in a SwiftNIO `EventLoop` context - with
54-
return values all being `EventLoopFuture`.
55-
5610
```swift
5711
import NIORedis
5812

5913
let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1)
60-
let redis = NIORedis(executionModel: .eventLoopGroup(elg))
14+
let driver = RedisDriver(ownershipModel: .external(elg))
6115

6216
// connections
6317

64-
// passing a value to `password` will automatically authenticate with Redis before resolving the connection
18+
// passing a value to 'password' will automatically authenticate with Redis before resolving the connection
6519
let connection = try redis.makeConnection(
6620
hostname: "localhost", // this is the default
6721
port: 6379, // this is the default
68-
password: "MY_PASS" // default is `nil`
22+
password: "MY_PASS" // default is 'nil'
6923
).wait()
70-
print(connection) // NIORedisConnection
7124

72-
// convienence methods for commands
25+
// convenience methods for commands
7326

74-
let result = try connection.set("my_key", to: "some value")
75-
.then {
76-
return connection.get("my_key")
77-
}.wait()
27+
let result = try conneciton.set("my_key", to: "some value")
28+
.then { return connection.get("my_key")}
29+
.wait()
7830
print(result) // Optional("some value")
7931

8032
// raw commands
8133

82-
let keyCount = try connection.command("DEL", [RedisData(bulk: "my_key")])
83-
.thenThrowing { res in
34+
let keyCount = try connection.command("DEL", [RESPValue(bulk: "my_key")])
35+
.thenThrowing { response in
8436
guard case let .integer(count) else {
85-
// throw Error
37+
// throw error
8638
}
8739
return count
88-
}.wait()
40+
}
41+
.wait()
8942
print(keyCount) // 1
9043

91-
// cleanup
44+
// cleanup
9245

9346
connection.close()
94-
try redis.terminate()
95-
try elg.syncShutdownGracefully()
47+
.thenThrowing { try redis.terminate() }
48+
.whenSuccess { try elg.syncShutdownGracefully() }
9649
```
9750

98-
### RedisData & RedisDataConvertible
51+
### RESPValue & RESPValueConvertible
9952
This is a 1:1 mapping enum of the `RESP` types: `Simple String`, `Bulk String`, `Array`, `Integer` and `Error`.
10053

101-
Conforming to `RedisDataConvertible` allows Swift types to more easily convert between `RedisData` and native types.
102-
103-
`Array`, `Data`, `Float`, `Double`, `FixedWidthInteger`, `String`, and of course `RedisData` all conform in this package.
104-
105-
A `ByteToMessageDecoder` and `MessageToByteEncoder` are used for the conversion process on connections.
106-
107-
### NIORedisConnection
108-
This class uses a `ChannelInboundHandler` that handles the actual process of sending and receiving commands.
109-
110-
While it does handle a queue of messages, so as to not be blocking, pipelining is implemented with `NIORedisPipeline`.
111-
112-
### NIORedisPipeline
113-
A `NIORedisPipeline` is a quick abstraction that buffers an array of complete messages as `RedisData`, and executing them in sequence after a
114-
user has invoked `execute()`.
115-
116-
It returns an `EventLoopFuture<[RedisData]>` with the results of all commands executed - unless one errors.
54+
Conforming to `RESPValueConvertible` allows Swift types to more easily convert between `RESPValue` and native types.
11755

118-
## Redis
119-
120-
To support contexts where someone either doesn't want to work in a SwiftNIO context, the `Redis` module provides a callback-based interface
121-
that wraps all of `NIORedis`.
122-
123-
A `Redis` instance manages a `NIORedis` object under the hood, with `RedisConnection` doing the same for `NIORedisConnection`.
124-
125-
```swift
126-
import Redis
127-
128-
let redis = Redis(threadCount: 1) // default is 1
129-
130-
// connections
131-
132-
// passing a value to `password` will automatically authenticate with Redis before resolving the connection
133-
redis.makeConnection(
134-
hostname: "localhost", // this is the default
135-
port: 6379, // this is the default
136-
password: "MY_PASS", // default is `nil`
137-
queue: DispatchQueue(label: "com.MyPackage.redis") // default is `.main`
138-
) { result in
139-
switch result {
140-
case .success(let conn):
141-
showCommands(on: conn)
142-
case .failure(let error):
143-
fatalError("Could not create RedisConnection!")
144-
}
145-
}
146-
147-
// convienence methods for commands
148-
149-
func showCommands(on conn: RedisConnection) {
150-
conn.get("my_key") { result in
151-
switch result {
152-
case .success(let value):
153-
// use value, which is String?
154-
case .failure(let error):
155-
// do something on error
156-
}
157-
}
158-
}
159-
160-
// cleanup is handled by deinit blocks
161-
```
56+
`Array`, `Data`, `Float`, `Double`, `FixedWidthInteger`, `String`, and of course `RESPValue` all conform in this package.

Sources/Redis/Redis.swift renamed to Sources/DispatchRedis/Redis.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import NIORedis
44

55
/// A factory that handles all necessary details for creating `RedisConnection` instances.
66
public final class Redis {
7-
private let driver: NIORedis
7+
private let driver: RedisDriver
88

99
deinit { try? driver.terminate() }
1010

1111
public init(threadCount: Int = 1) {
12-
self.driver = NIORedis(executionModel: .spawnThreads(threadCount))
12+
self.driver = RedisDriver(ownershipModel: .internal(threadCount: threadCount))
1313
}
1414

1515
public func makeConnection(

Sources/Redis/RedisConnection.swift renamed to Sources/DispatchRedis/RedisConnection.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@ import Foundation
22
import NIORedis
33

44
public final class RedisConnection {
5-
let _driverConnection: NIORedisConnection
5+
let _driverConnection: NIORedis.RedisConnection
66

77
private let queue: DispatchQueue
88

99
deinit { _driverConnection.close() }
1010

11-
init(driver: NIORedisConnection, callbackQueue: DispatchQueue) {
11+
init(driver: NIORedis.RedisConnection, callbackQueue: DispatchQueue) {
1212
self._driverConnection = driver
1313
self.queue = callbackQueue
1414
}
1515

1616
/// Creates a `RedisPipeline` for executing a batch of commands.
1717
public func makePipeline(callbackQueue: DispatchQueue? = nil) -> RedisPipeline {
18-
return .init(using: self, callbackQueue: callbackQueue ?? queue)
18+
return .init(connection: self, callbackQueue: callbackQueue ?? queue)
1919
}
2020

2121
public func get(

Sources/Redis/RedisPipeline.swift renamed to Sources/DispatchRedis/RedisPipeline.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ import NIORedis
1414
/// - Important: The larger the pipeline queue, the more memory both the Redis driver and Redis server will use.
1515
/// See https://redis.io/topics/pipelining#redis-pipelining
1616
public final class RedisPipeline {
17-
private let _driverPipeline: NIORedisPipeline
17+
private let _driverPipeline: NIORedis.RedisPipeline
1818
private let queue: DispatchQueue
1919

2020
/// Creates a new pipeline queue using the provided `RedisConnection`, executing callbacks on the provided `DispatchQueue`.
2121
/// - Parameters:
2222
/// - using: The connection to execute the commands on.
2323
/// - callbackQueue: The queue to execute all callbacks on.
24-
public init(using connection: RedisConnection, callbackQueue: DispatchQueue) {
25-
self._driverPipeline = NIORedisPipeline(using: connection._driverConnection)
24+
public init(connection: RedisConnection, callbackQueue: DispatchQueue) {
25+
self._driverPipeline = NIORedis.RedisPipeline(channel: connection._driverConnection.channel)
2626
self.queue = callbackQueue
2727
}
2828

@@ -32,15 +32,15 @@ public final class RedisPipeline {
3232
/// - arguments: The arguments, if any, to send with the command.
3333
/// - Returns: A self-reference to this `RedisPipeline` instance for chaining commands.
3434
@discardableResult
35-
public func enqueue(command: String, arguments: [RedisDataConvertible] = []) throws -> RedisPipeline {
35+
public func enqueue(command: String, arguments: [RESPConvertible] = []) throws -> RedisPipeline {
3636
try _driverPipeline.enqueue(command: command, arguments: arguments)
3737
return self
3838
}
3939

4040
/// Flushes the queue, sending all of the commands to Redis in the same order as they were enqueued.
4141
/// - Important: If any of the commands fail, the remaining commands will not execute and the callback will receive a failure.
4242
/// - Parameter callback: The callback to receive the results of the pipeline of commands, or an error if thrown.
43-
public func execute(_ callback: @escaping (Result<[RedisData], Error>) -> Void) {
43+
public func execute(_ callback: @escaping (Result<[RESPValue], Error>) -> Void) {
4444
_driverPipeline.execute()
4545
.map { results in
4646
self.queue.async { callback(.success(results)) }

Sources/NIORedis/Channel/RedisMessenger.swift

Lines changed: 0 additions & 102 deletions
This file was deleted.

0 commit comments

Comments
 (0)