Skip to content

Commit 433a33d

Browse files
feat: added retry logic to HttpRemoteQueryRunner
1 parent a9216c2 commit 433a33d

File tree

2 files changed

+104
-3
lines changed

2 files changed

+104
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using System.Net;
2+
using System.Text.Json;
3+
using FluentAssertions;
4+
using Moq;
5+
using RestSharp;
6+
using Wemogy.CQRS.Common.ValueObjects;
7+
using Wemogy.CQRS.Extensions.FastEndpoints.Common;
8+
using Wemogy.CQRS.Extensions.FastEndpoints.RemoteQueryRunners;
9+
using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.Queries.RequestTestContext;
10+
using Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.TestApplication.ValueObjects;
11+
12+
namespace Wemogy.CQRS.Extensions.FastEndpoints.UnitTests.HttpRemoteQueryRunners;
13+
14+
public class HttpRemoteQueryRunnerTests
15+
{
16+
[Fact]
17+
public async Task QueryAsync_ShouldRetryRequestAndReturnResult()
18+
{
19+
// Arrange
20+
var query = new RequestTestContextQuery();
21+
var testContext = new TestContext()
22+
{
23+
UserId = Guid.NewGuid().ToString()
24+
};
25+
var resultContent = JsonSerializer.Serialize(testContext, JsonOptions.JsonSerializerOptions);
26+
var responses = new Queue<RestResponse>();
27+
responses.Enqueue(new RestResponse()
28+
{
29+
StatusCode = HttpStatusCode.ServiceUnavailable
30+
});
31+
responses.Enqueue(new RestResponse()
32+
{
33+
StatusCode = HttpStatusCode.OK,
34+
Content = resultContent,
35+
36+
// Set IsSuccessful to true
37+
IsSuccessStatusCode = true,
38+
ResponseStatus = ResponseStatus.Completed
39+
});
40+
41+
var restClientMock = new Mock<IRestClient>();
42+
restClientMock
43+
.Setup(
44+
m => m.ExecuteAsync(
45+
It.IsAny<RestRequest>(),
46+
It.IsAny<CancellationToken>()))
47+
.ReturnsAsync(responses.Dequeue);
48+
49+
var httpRemoteQueryRunner = new HttpRemoteQueryRunner<RequestTestContextQuery, TestContext>(restClientMock.Object, string.Empty);
50+
var request = new QueryRequest<RequestTestContextQuery>(query, new List<CommandQueryDependency>());
51+
52+
// Act
53+
var result = await httpRemoteQueryRunner.QueryAsync(request, CancellationToken.None);
54+
55+
// Assert
56+
result.Should().BeEquivalentTo(testContext);
57+
responses.Should().BeEmpty();
58+
}
59+
60+
[Fact]
61+
public async Task QueryAsync_ShouldThrowWithoutResultAfterMaxRetryReachedRequest()
62+
{
63+
// Arrange
64+
var query = new RequestTestContextQuery();
65+
var testContext = new TestContext()
66+
{
67+
UserId = Guid.NewGuid().ToString()
68+
};
69+
var restClientMock = new Mock<IRestClient>();
70+
restClientMock
71+
.Setup(
72+
m => m.ExecuteAsync(
73+
It.IsAny<RestRequest>(),
74+
It.IsAny<CancellationToken>()))
75+
.ReturnsAsync(() => new RestResponse()
76+
{
77+
StatusCode = HttpStatusCode.ServiceUnavailable
78+
});
79+
80+
var httpRemoteQueryRunner = new HttpRemoteQueryRunner<RequestTestContextQuery, TestContext>(restClientMock.Object, string.Empty);
81+
var request = new QueryRequest<RequestTestContextQuery>(query, new List<CommandQueryDependency>());
82+
83+
// Act
84+
var exception = await Record.ExceptionAsync(() => httpRemoteQueryRunner.QueryAsync(request, CancellationToken.None));
85+
86+
// Assert
87+
exception.Should().NotBeNull();
88+
}
89+
}

src/extensions/fastEndpoints/Wemogy.CQRS.Extensions.FastEndpoints/RemoteQueryRunners/HttpRemoteQueryRunner.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System.Text.Json;
2+
using Polly;
3+
using Polly.Contrib.WaitAndRetry;
24
using RestSharp;
35
using Wemogy.Core.Errors;
46
using Wemogy.Core.Extensions;
@@ -14,25 +16,35 @@ namespace Wemogy.CQRS.Extensions.FastEndpoints.RemoteQueryRunners;
1416
public class HttpRemoteQueryRunner<TQuery, TResult> : IRemoteQueryRunner<TQuery, TResult>
1517
where TQuery : IQuery<TResult>
1618
{
17-
private readonly RestClient _restClient;
19+
private readonly IRestClient _restClient;
1820

1921
/// <summary>
2022
/// This is the sub-path of the client base path
2123
/// </summary>
2224
private readonly string _urlPath;
25+
private readonly IAsyncPolicy<RestResponse> _retryPolicy;
2326

24-
public HttpRemoteQueryRunner(RestClient restClient, string urlPath)
27+
public HttpRemoteQueryRunner(IRestClient restClient, string urlPath)
2528
{
2629
_restClient = restClient;
2730
_urlPath = urlPath;
31+
var retryCount = 3;
32+
var delay = Backoff.ExponentialBackoff(
33+
TimeSpan.FromMilliseconds(100),
34+
retryCount);
35+
_retryPolicy = Policy
36+
.HandleResult<RestResponse>(x => !x.IsSuccessful)
37+
.WaitAndRetryAsync(delay);
2838
}
2939

3040
public async Task<TResult> QueryAsync(QueryRequest<TQuery> query, CancellationToken cancellationToken)
3141
{
3242
var request = new RestRequest(_urlPath)
3343
.AddJsonBody(query);
3444

35-
var response = await _restClient.PostAsync(request, cancellationToken: cancellationToken);
45+
var response = await _retryPolicy.ExecuteAsync(
46+
ct => _restClient.PostAsync(request, cancellationToken: ct),
47+
cancellationToken);
3648

3749
if (!response.IsSuccessful)
3850
{

0 commit comments

Comments
 (0)