|
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