|
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 |
4 | 2 |
|
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) |
46 | 4 |
|
47 | 5 | > **NOTE: This this is written against SwiftNIO 2.0, and as such requires Swift 5.0!** |
48 | 6 |
|
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, |
50 | 8 | and to stay ahead of development of the next version of SwiftNIO. |
51 | 9 |
|
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 | | - |
56 | 10 | ```swift |
57 | 11 | import NIORedis |
58 | 12 |
|
59 | 13 | let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) |
60 | | -let redis = NIORedis(executionModel: .eventLoopGroup(elg)) |
| 14 | +let driver = RedisDriver(ownershipModel: .external(elg)) |
61 | 15 |
|
62 | 16 | // connections |
63 | 17 |
|
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 |
65 | 19 | let connection = try redis.makeConnection( |
66 | 20 | hostname: "localhost", // this is the default |
67 | 21 | port: 6379, // this is the default |
68 | | - password: "MY_PASS" // default is `nil` |
| 22 | + password: "MY_PASS" // default is 'nil' |
69 | 23 | ).wait() |
70 | | -print(connection) // NIORedisConnection |
71 | 24 |
|
72 | | -// convienence methods for commands |
| 25 | +// convenience methods for commands |
73 | 26 |
|
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() |
78 | 30 | print(result) // Optional("some value") |
79 | 31 |
|
80 | 32 | // raw commands |
81 | 33 |
|
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 |
84 | 36 | guard case let .integer(count) else { |
85 | | - // throw Error |
| 37 | + // throw error |
86 | 38 | } |
87 | 39 | return count |
88 | | - }.wait() |
| 40 | + } |
| 41 | + .wait() |
89 | 42 | print(keyCount) // 1 |
90 | 43 |
|
91 | | -// cleanup |
| 44 | +// cleanup |
92 | 45 |
|
93 | 46 | connection.close() |
94 | | -try redis.terminate() |
95 | | -try elg.syncShutdownGracefully() |
| 47 | + .thenThrowing { try redis.terminate() } |
| 48 | + .whenSuccess { try elg.syncShutdownGracefully() } |
96 | 49 | ``` |
97 | 50 |
|
98 | | -### RedisData & RedisDataConvertible |
| 51 | +### RESPValue & RESPValueConvertible |
99 | 52 | This is a 1:1 mapping enum of the `RESP` types: `Simple String`, `Bulk String`, `Array`, `Integer` and `Error`. |
100 | 53 |
|
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. |
117 | 55 |
|
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. |
0 commit comments