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;
}
}