Thanks @Mordil for sending this proposal, this looks like a great start. Redis is very important and fits really well with the goals of the SSWG.
Also having NIORedis
and Redis
as separate modules (where Redis
uses NIORedis
) feels like a really good fit for the side-side Swift ecosystem.
Personally I'm not super keen on the design of the NIORedis
module: The only NIO-things it exposes are the EventLoopFuture
/EventLoopPromise
types. One of the key design points in NIO is that it makes things composeable through the ChannelPipeline
. But if I see this correctly, there's no way for me to create a NIO Channel
and add the NIORedis
handlers to the ChannelPipeline
.
As a super dumb and contrived example, let's imagine I would like to implement a library that implements a 'kitten store' on top of some data store. So we would have our main entity
struct Kitten {
var name: String
... // some properties
}
and a few 'kitten store requests' which allow the user to save and receive kittens from the data store
enum KittenStoreRequest {
case saveKitten(Kitten)
case receiveKitten(name: String)
}
after each command, the kitten store would reply with some response
enum KittenStoreResponse {
case ok(Kitten)
case error(Error)
}
Now, as an implementation detail, the kitten store could be implemented with Redis, and if I choose NIO to implement it all, I'd expect to be able to do this:
let channel = ClientBootstrap(group: group)
.channelInitializer { channel in
channel.pipeline.add(RedisDecoder()).then {
channel.pipeline.add(RedisEncoder())
}.then {
channel.pipeline.add(KittenStoreOnRedisHandler())
}
}.connect(...)
channel.then { channel in
channel.write(KittenStoreRequest.saveKitten(Kitten(name: "Lisa", ...)), promise: nil)
channel.write(KittenStoreRequest.saveKitten(Kitten(name: "Bart", ...)), promise: nil)
channel.writeAndFlush(KittenStoreRequest.saveKitten(Kitten(name: "Foobar", ...)))
}
The KittenStoreOnRedisHandler
would probably be a ChannelDuplexHandler
that transforms KittenStoreRequests
into the required Redis commands to store/retrieve a Redis-encoded kitten from the store. KittenStoreOnRedisHandler
would be more than a encoder/decoder as we might require multiple Redis commands to fully execute one KittenStoreRequest
.
I do realise that most people are not direct NIO users and therefore a fully abstracted interface to Redis (like your Redis
module) is absolutely necessary but I do think we should offer a proper NIO interface which uses the ChannelPipeline
as the composition mechanism. That would allow writing libraries that use Redis as an underlying implementation detail to use all the NIO features for composition.
That's also why I believe that NIORedis
had to reimplement functionality that NIO already provides. For example NIORedisPipeline
has a way to enqueue outgoing messages (enqueue
) and a way to flush them (execute
). NIO implements this already with a way to enqueue messages (write
) and a way to flush them (flush
).
In other words: I think the low-level parts of a Redis library should provide all the 'bricks' which are necessary to compose a NIO application which means pretty much all of it would be ChannelHandler
s and some extra convenience.
Does that make sense? And do you see the value in being able to use NIO's ChannelPipeline
as the composition mechanism? Also please don't get me wrong, I think a lot of the design works well but I think we could implement (and expose) more of the functionality with the tools that NIO offers rather than needing to reimplement many of them.