Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Redis Streams Transport for Wolverine
This PR introduces a first-class Redis Streams transport for Wolverine, providing reliable message publishing and consumption backed by Redis Streams and consumer groups. The implementation follows Wolverine's established transport patterns and includes support for both inline and buffered processing modes.
What's Included
Core Features
0-0) or only consume new messages ($, which is the default)XAUTOCLAIMto reclaim idle entries from the Pending Entries ListHow It Works
The transport uses Redis Streams' consumer group functionality for reliable delivery:
XREADGROUPwith a configured consumer group and consumer nameXACKXAUTOCLAIMoperation reclaims idle messages after a configurable timeout, ensuring stalled messages get reprocessedXADDoperations for improved performanceConfiguration Examples
Setting up the transport:
Publishing to a stream inline (ordered):
Listening with buffered processing, with autoclaiming of stalled messages enabled - if consumerGroup doesn't already exist, create one for new messages only:
Listening with inline processing, without autoclaiming of stalled messages enabled - if consumerGroup doesn't already exist, create one that will process all messages currently on the stream:
Implementation Details
The transport follows Wolverine's standard patterns:
ISenderProtocol-based sender with batched operationsIBrokerQueueinterface for diagnostics and resource managementredis://stream/{databaseId}/{key}?consumerGroup={consumerGroupName}) - thinking here is that in future, when support is added, you can create a listener without a consumer group and it will act as a "topic-style" listener (where messages during process downtime are missed - but smart enough that any redis disconnects whilst the process is up will not miss any messages - e.g. checkpointed in-memory)Testing
Comprehensive test coverage includes:
Current Limitations
A few features are planned for future releases:
Explicit QoS switching: Currently defaults to at-least-once semantics (when AutoClaiming is enabled); at-most-once mode isn't exposed yet
Non-persistent subscriptions: Topic-like subscriptions using
XREADwithout consumer groupsReply stream cleanup: Currently we litter Redis with a reply stream per Redis transport instance that are never reused or cleaned up - automatic cleanup of reply streams is essential, unsure how yet.
Stream expiry: We may want to consider adding an XADD followed by a stream-level EXPIRE which can be a TTL style duration. If we make these two calls as an atomic operation, within a lua script call or a redis transaction, then the streams will be automatically deleted when the last added message reaches a certain age. Note this is regardless of if all consumer groups have consumed the message so fair warning must be given when using this approach, and it isn't retention as such given if new message is always added to the stream within the TTL then the stream will grow infinitely - mix with stream truncation approach below?
Stream truncation: Redis supports automatic trimming of streams to a max length (unfortunately stream TTLs are not directly supported), we should allow publishers to configure the autotrim option when XADDing to a stream. I think this method of retention will need to be mixed with the stream expiry -
Multiple Redis connections: Need to confirm how this should work, if we can name each of the "UseRedisTransport" calls, perhaps the endpoint URI can contain the redis transport name and route to the respective redis transport
Compatibility
This change is fully backwards compatible with existing transports and follows established Wolverine configuration patterns. Documentation and examples are included to help with adoption.
I ask for all comments and criticisms on design decisions and implementation detail. I will be upfront and say that LLMs did help extensively with the code, taking slices of inspiration from other existing Wolverine transports and is still a little rough but I wanted to put this up to gather comments / feedback before cementing the design / polishing the code.