using MessageFlow; namespace MessageFlow.Samples; /// /// A custom that retries the rest of the chain. /// /// The maximum number of attempts, including the first one. public sealed class RetryHandler(int maxAttempts) : IHandler { /// public async ValueTask HandleAsync( string request, NextHandler nextHandler, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(nextHandler); for (var attempt = 1; ; attempt++) { try { return await nextHandler(request, cancellationToken).ConfigureAwait(false); } catch (InvalidOperationException) when (attempt < maxAttempts) { // Try again until the last attempt, which is allowed to fail. } } } } /// /// Shows a hand-written implementing a retry policy /// around the remainder of the chain. /// public static class RetrySample { /// /// Builds a chain that retries a flaky terminal step. /// /// The number of failures the terminal step produces. /// The maximum number of attempts performed by the retry handler. /// The composed chain. public static IChain BuildChain(int failuresBeforeSuccess, int maxAttempts) { var failures = 0; return Chain.Create() .Use(new RetryHandler(maxAttempts)) .WithFallback((request, _) => { if (failures < failuresBeforeSuccess) { failures++; throw new InvalidOperationException($"transient failure {failures}"); } return new ValueTask($"completed:{request} after {failures} failure(s)"); }) .Build(); } /// /// Executes a request and turns a final failure into its message. /// /// The chain to execute. /// The request to send. /// A token used to cancel the operation. /// The response, or the message of the last failure. public static async Task DescribeAsync( IChain chain, string request, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(chain); try { return await chain.ExecuteAsync(request, cancellationToken).ConfigureAwait(false); } catch (InvalidOperationException exception) { return exception.Message; } } /// /// Runs the sample once with a recoverable step and once with a permanently failing step. /// /// The writer receiving the sample output. /// A token used to cancel the operation. /// The successful response followed by the message of the final failure. public static async Task> RunAsync( TextWriter output, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(output); var chains = new[] { BuildChain(failuresBeforeSuccess: 2, maxAttempts: 3), BuildChain(failuresBeforeSuccess: 5, maxAttempts: 2), }; var results = new List(); foreach (var chain in chains) { var description = await DescribeAsync(chain, "import", cancellationToken).ConfigureAwait(false); results.Add(description); await output.WriteLineAsync($"import => {description}").ConfigureAwait(false); } return results; } }