< Summary - AsyncResponse (Release / net8.0+net10.0 / unit+integration)

Information
Class: AsyncResponse.Transports.Redis.RedisAsyncResponseTransportOptions
Assembly: AsyncResponse.Transports.Redis
File(s): /_/src/Transports/AsyncResponse.Transports.Redis/RedisAsyncResponseTransportOptions.cs
Line coverage
100%
Covered lines: 38
Uncovered lines: 0
Coverable lines: 38
Total lines: 176
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/_/src/Transports/AsyncResponse.Transports.Redis/RedisAsyncResponseTransportOptions.cs

#LineLine coverage
 1namespace AsyncResponse.Transports.Redis;
 2
 3/// <summary>
 4/// Options for the Redis Streams AsyncResponse transport. Runs against Redis 5+ and any RESP-compatible
 5/// server that implements Redis Streams and consumer groups — validated on Redis 8, Valkey, and
 6/// Dragonfly. (Garnet does not implement stream commands, so it works as a <em>channel</em> but not as
 7/// this transport.) Publish-time trimming uses plain <c>XADD … MAXLEN ~ N</c> (no Redis 8 trim-mode
 8/// token), so it stays portable across all of these servers.
 9/// </summary>
 10public sealed class RedisAsyncResponseTransportOptions
 11{
 12    public const string TransportName = "redis";
 13
 14    /// <summary>
 15    /// Prefix used when a stream name is not explicitly configured. The default worker stream is
 16    /// <c>{KeyPrefix}:transport:worker</c>, the default response stream is
 17    /// <c>{KeyPrefix}:transport:response</c>, and the default dead-letter stream is
 18    /// <c>{KeyPrefix}:transport:deadletter</c>. Use a unique prefix per app/environment when several
 19    /// deployments share one Redis.
 20    /// </summary>
 1990921    public string KeyPrefix { get; set; } = "asyncresponse";
 22
 23    /// <summary>
 24    /// Redis stream used by <see cref="RedisWorkerTransport"/> to publish worker jobs. When null,
 25    /// <see cref="KeyPrefix"/> determines the stream name.
 26    /// </summary>
 1041627    public string? WorkerStream { get; set; }
 28
 29    /// <summary>Consumer group used by the hosted worker subscriber.</summary>
 617330    public string WorkerConsumerGroup { get; set; } = "asyncresponse-workers";
 31
 32    /// <summary>Worker stream handling options.</summary>
 679033    public RedisSubscriberOptions WorkerSubscriber { get; } = new();
 34
 35    /// <summary>
 36    /// Redis stream remote systems can append response payloads to. The hosted response-ingress
 37    /// subscriber reads this stream and forwards payloads into <see cref="IAsyncResponseIngress"/>.
 38    /// When null, <see cref="KeyPrefix"/> determines the stream name.
 39    /// </summary>
 626140    public string? ResponseStream { get; set; }
 41
 42    /// <summary>Consumer group used by the hosted response-ingress subscriber.</summary>
 511743    public string ResponseConsumerGroup { get; set; } = "asyncresponse-responses";
 44
 45    /// <summary>Response stream handling options.</summary>
 556746    public RedisSubscriberOptions ResponseSubscriber { get; } = new();
 47
 48    /// <summary>
 49    /// Consumer name used inside Redis consumer groups. When null, the package generates a stable
 50    /// process-local name, <c>{machine}-{pid}-{guid}</c>, kept within 64 characters by shortening
 51    /// the MACHINE NAME only — the process id and the GUID are what make it unique, so a long host
 52    /// name (a Kubernetes pod name, a host at HOST_NAME_MAX) never costs two processes on one host
 53    /// their separate identities. The subscriber role is appended to either form. Consumers that
 54    /// share a name share one pending-entry list, so configure this only when your orchestrator
 55    /// guarantees uniqueness per running process.
 56    /// </summary>
 42057    public string? ConsumerName { get; set; }
 58
 59    /// <summary>
 60    /// Creates the worker and response consumer groups on subscriber startup. The groups start at
 61    /// the beginning of the stream so messages published before the first subscriber starts are not
 62    /// skipped. Corollary: pointing a brand-new consumer group at a stream that already holds history
 63    /// replays that entire backlog (re-running old worker jobs, re-ingesting old responses). Use a
 64    /// fresh <see cref="KeyPrefix"/>/stream per deployment, or only rename groups while the stream is empty.
 65    /// </summary>
 105066    public bool CreateConsumerGroups { get; set; } = true;
 67
 68    /// <summary>
 69    /// Maximum stream length used by XADD for worker and response messages. Redis trims
 70    /// approximately by default, so streams stay bounded without making every publish pay the exact
 71    /// trim cost. Set null to disable publish-time trimming.
 72    /// </summary>
 265273    public long? StreamMaxLength { get; set; } = 100_000;
 74
 75    /// <summary>
 76    /// Uses approximate MAXLEN trimming for <see cref="StreamMaxLength"/>. Approximate trimming is
 77    /// much cheaper on hot streams and is the recommended default.
 78    /// </summary>
 89579    public bool UseApproximateStreamTrimming { get; set; } = true;
 80
 81    /// <summary>
 82    /// Enables dead-lettering when a message reaches <see cref="RedisSubscriberOptions.MaxDeliveryAttempts"/>
 83    /// or a background handler fails after early ACK. When true and <see cref="DeadLetterStream"/> is
 84    /// null, <see cref="KeyPrefix"/> determines the stream name.
 85    /// </summary>
 86386    public bool DeadLetterEnabled { get; set; } = true;
 87
 88    /// <summary>Redis stream that receives poison messages and already-ACKed background failures.</summary>
 187189    public string? DeadLetterStream { get; set; }
 90
 91    /// <summary>Maximum dead-letter stream length. Set null to disable dead-letter stream trimming.</summary>
 225892    public long? DeadLetterStreamMaxLength { get; set; } = 100_000;
 93
 94    /// <summary>The logical reply target name used by <c>WithReplyTarget()</c>. Default: <c>default</c>.</summary>
 225195    public string DefaultReplyTargetName { get; set; } = "default";
 96
 97    /// <summary>
 98    /// Named reply targets exposed to Core through <see cref="IAsyncResponseReplyTargetProvider"/>.
 99    /// When empty, the resolved <see cref="ResponseStream"/> becomes the default target.
 100    /// </summary>
 465101    public Dictionary<string, RedisReplyTargetOptions> ReplyTargets { get; } = new(StringComparer.Ordinal);
 102
 103    /// <summary>
 104    /// Redis stream field carrying the AsyncResponse correlation id. Response messages may omit it
 105    /// when the id is present in the JSON body via <see cref="CorrelationIdJsonPaths"/>.
 106    /// </summary>
 3160107    public string CorrelationIdField { get; set; } = "correlationId";
 108
 109    /// <summary>
 110    /// Redis stream field containing the serialized JSON payload. Worker messages contain a
 111    /// serialized <see cref="WorkerJobEnvelope"/>; response messages contain the remote payload JSON.
 112    /// </summary>
 3534113    public string PayloadField { get; set; } = "payload";
 114
 115    /// <summary>
 116    /// JSON paths inspected when a response message does not carry the correlation id in
 117    /// <see cref="CorrelationIdField"/>. Paths are case-insensitive and support nested JSON strings.
 118    /// </summary>
 46119    public string[] CorrelationIdJsonPaths { get; set; } =
 439120    [
 439121        "CorrelationId",
 439122        "CustomParameters",
 439123        "CustomParameters.CorrelationId",
 439124        "PubSubParams.CustomParameters",
 439125        "PubSubParams.CustomParameters.CorrelationId",
 439126        "DagJsonParameters.CorrelationId"
 439127    ];
 128
 129    /// <summary>Per-command timeout applied by the transport wrapper before retry/backoff logic.</summary>
 3025130    public TimeSpan OperationTimeout { get; set; } = TimeSpan.FromSeconds(10);
 131
 132    /// <summary>Maximum attempts for Redis publish commands. Set to 1 to disable publish retries.</summary>
 2853133    public int PublishMaxAttempts { get; set; } = 3;
 134
 135    /// <summary>Initial delay before retrying a failed publish command.</summary>
 4426136    public TimeSpan PublishRetryBaseDelay { get; set; } = TimeSpan.FromMilliseconds(50);
 137
 138    /// <summary>Maximum delay between publish retry attempts.</summary>
 4635139    public TimeSpan PublishRetryMaxDelay { get; set; } = TimeSpan.FromSeconds(1);
 140
 141    /// <summary>Initial delay after a subscriber loop Redis failure.</summary>
 4029142    public TimeSpan SubscriberRetryBaseDelay { get; set; } = TimeSpan.FromMilliseconds(100);
 143
 144    /// <summary>Maximum delay after repeated subscriber loop Redis failures.</summary>
 4029145    public TimeSpan SubscriberRetryMaxDelay { get; set; } = TimeSpan.FromSeconds(5);
 146
 147    /// <summary>
 148    /// The hosting shutdown budget that must contain
 149    /// <see cref="RedisSubscriberOptions.BackgroundDrainTimeout"/> when a subscriber uses
 150    /// <see cref="RedisAckMode.AckAfterEnqueue"/>.
 151    /// </summary>
 2267152    public TimeSpan? HostShutdownTimeout { get; set; } = TimeSpan.FromSeconds(30);
 153
 154    /// <summary>Adds or replaces a named Redis reply target.</summary>
 155    public RedisAsyncResponseTransportOptions AddReplyTarget(string name, string responseStream)
 156    {
 8157        ArgumentException.ThrowIfNullOrWhiteSpace(name);
 8158        ArgumentException.ThrowIfNullOrWhiteSpace(responseStream);
 159
 8160        ReplyTargets[name] = new RedisReplyTargetOptions { ResponseStream = responseStream };
 8161        return this;
 162    }
 163}
 164
 165/// <summary>Options for one named Redis Streams async-response reply target.</summary>
 166public sealed class RedisReplyTargetOptions
 167{
 168    /// <summary>Redis stream remote systems should XADD response payloads to.</summary>
 169    public string? ResponseStream { get; set; }
 170
 171    /// <summary>Consumer group that receives responses for this target. Optional metadata.</summary>
 172    public string? ConsumerGroup { get; set; }
 173
 174    /// <summary>Additional values copied to the transport-neutral reply target.</summary>
 175    public Dictionary<string, string> Properties { get; } = new(StringComparer.Ordinal);
 176}