我正在嘗試使 Refit 在 ASP.NET Core 6 應用程式中與 Polly 一起作業。我有一個作業版本,但我覺得每個新方法/使用的 API 端點都涉及太多代碼。
我現在想通過定義重試策略并將其用于多個端點來保持簡單。我的代碼如下:
再審政策
private static IServiceCollection ConfigureResilience(this IServiceCollection services)
{
var retryPolicy = Policy<IApiResponse>
.Handle<ApiException>()
.OrResult(x => x.StatusCode is >= HttpStatusCode.InternalServerError or HttpStatusCode.RequestTimeout)
.WaitAndRetryAsync(Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromSeconds(1), RetryPolicyMaxCount));
var register = new PolicyRegistry()
{
{ "DefaultRetrialPolicy", retryPolicy }
};
services.AddPolicyRegistry(register);
return services;
}
與 Refit 一起使用的介面為外部應用程式生成 HTTP 呼叫:
[Headers("Authorization: Bearer")]
public interface IBarIntegration
{
[Get("/api/ext/Foo/GetFooBriefInfo")]
Task<ApiResponse<GetFooBriefInfoForFooDto>> GetFooBriefInfo(GetFooBriefInfoForFooInputDto inputData);
}
為 Refit 配置身份驗證的工廠類。身份驗證依賴于基于當前登錄用戶訪問令牌生成的 OnBehalfOf 令牌。
internal sealed class BarApiClientHelper : IBarApiClientHelper
{
private readonly IOptionsSnapshot<BarApiSettings> _BarSettings;
private readonly IAccessTokenHelperService _accessTokenHelperService;
public BarApiClientHelper(IOptionsSnapshot<BarApiSettings> BarSettings, IAccessTokenHelperService accessTokenHelperService)
{
_BarSettings = BarSettings;
_accessTokenHelperService = accessTokenHelperService;
}
public async Task<TApiClient> CreateApiClient<TApiClient>(CancellationToken token)
{
string baseUrl = _BarSettings.Value.BaseUrl;
string accessToken = await _accessTokenHelperService.GetAccessToken(token);
var refitClient = RestService.For<TApiClient>(baseUrl, new RefitSettings
{
AuthorizationHeaderValueGetter = () => Task.FromResult(accessToken)
});
return refitClient;
}
}
將由業務層呼叫的示例(基礎架構)方法。
public async Task<GetFooBriefInfoForFooDto> GetFooBriefInfo(string upn, CancellationToken token)
{
var apiClient = await _clientHelper.CreateApiClient<IBarIntegration>(token);
var retrialPolicy = _registry.Get<AsyncRetryPolicy<IApiResponse>>(DefaultRetrialPolicy);
var func = async () => (IApiResponse) await apiClient.GetFooBriefInfo(new GetFooBriefInfoForFooInputDto { FooContactUpn = upn });
var FooInfo = (ApiResponse<GetFooBriefInfoForFooDto>) await retrialPolicy.ExecuteAsync(func);
await FooInfo.EnsureSuccessStatusCodeAsync();
return FooInfo.Content!;
}
這種方法似乎作業正常,但我對每個特定于業務的方法(GetFooBriefInfo 函式)所需的代碼量不滿意。有什么辦法可以簡化這一點,我覺得我有點違反 DRY 讓每個方法都獲得重試策略,執行并確保成功代碼。
uj5u.com熱心網友回復:
IServiceCollectionRefit 定義了一個針對被呼叫的擴展方法,AddRefitClient該方法回傳一個IHttpClientBuilder. 這對我們有好處,因為它與AddHttpClient擴展方法回傳的介面相同。所以,我們也可以使用AddPolicyHandler,AddTransientHttpErrorPolicy或AddPolicyHandlerFromRegistry方法。
改裝客戶端 Polly 政策
因為我們可以鏈接AddRefitClient,ConfigureHttpClient和AddPolicyHandler方法呼叫,所以我們可以
- 避免使用
PolicyRegistry - 避免使用
BarApiClientHelper - 避免明確使用策略本身
登記
services
.AddRefitClient(typeof(IBarIntegration), (sp) =>
{
var accessTokenHelperService = sp.GetRequiredService<IAccessTokenHelperService>();
string accessToken = accessTokenHelperService.GetAccessToken(CancellationToken.None).GetAwaiter().GetResult();
return new RefitSettings
{
AuthorizationHeaderValueGetter = () => Task.FromResult(accessToken)
};
})
.ConfigureHttpClient((sp, client) =>
{
var barSettings = sp.GetRequiredService<IOptionsSnapshot<BarApiSettings>>();
string baseUrl = barSettings.Value.BaseUrl;
client.BaseAddress = new Uri(baseUrl);
})
.AddPolicyHandler(Policy<IApiResponse>
.Handle<ApiException>()
.OrResult(x => x.StatusCode is >= HttpStatusCode.InternalServerError or HttpStatusCode.RequestTimeout)
.WaitAndRetryAsync(Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromSeconds(1), RetryPolicyMaxCount)));
- 接收一個 Type ,
AddRefitClient用于注冊一個型別化的客戶端- 它還預期一個
settingsAction函式來設定一個RefitSettings- 不幸的是,這個委托是同步的,這就是為什么我不得不使用
.GetAwaiter().GetResult()而不是await - 我已將 a 傳遞
CancellationToken.None給GetAccessToken,因為在這里我們不想取消此呼叫
- 不幸的是,這個委托是同步的,這就是為什么我不得不使用
- 它還預期一個
ConfigureHttpClient用于設定改裝客戶端將使用的BaseAddress底層證券HttpClientAddPolicyHandler用于設定改裝客戶端的彈性策略
用法
private readonly IBarIntegration apiClient; //injected via ctor
public async Task<GetFooBriefInfoForFooDto> GetFooBriefInfo(string upn, CancellationToken token)
{
var fooInfo = await apiClient.GetFooBriefInfo(new GetFooBriefInfoForFooInputDto { FooContactUpn = upn });
await fooInfo.EnsureSuccessStatusCodeAsync();
return fooInfo.Content!;
}
- 不需要使用,
IBarApiClientHelper因為IBarIntegration可以直接注入 - 不需要再去獲取Polly策略和手動修飾
GetFooBriefInfo方法呼叫,因為我們在DI注冊的時候已經集成了refit客戶端和策略
更新 #1
根據Can Bilgin的建議,注冊碼可以這樣重寫:
services
.AddRefitClient(typeof(IBarIntegration), (sp) =>
{
var accessTokenHelperService = sp.GetRequiredService<IAccessTokenHelperService>();
return new RefitSettings
{
AuthorizationHeaderValueGetter = async () => await accessTokenHelperService.GetAccessToken(CancellationToken.None)
};
})
.ConfigureHttpClient((sp, client) =>
{
var barSettings = sp.GetRequiredService<IOptionsSnapshot<BarApiSettings>>();
client.BaseAddress = new Uri(barSettings.Value.BaseUrl);
})
...
uj5u.com熱心網友回復:
從 Peter 的出色回答開始,基礎設施代碼如下所示:
private static IServiceCollection ConfigureResilience(this IServiceCollection services)
{
services
.AddRefitClient(typeof(IBarIntegration), (sp) =>
{
var accessTokenHelperService = sp.GetRequiredService<IAccessTokenHelperService>();
return new RefitSettings
{
AuthorizationHeaderValueGetter = () => accessTokenHelperService.GetAccessToken(default)
};
})
.ConfigureHttpClient((sp, client) =>
{
var BarSettings = sp.GetRequiredService<IOptions<BarApiSettings>>();
string baseUrl = BarSettings.Value.BaseUrl;
client.BaseAddress = new Uri(baseUrl);
})
.AddPolicyHandler(Policy<HttpResponseMessage>
.Handle<HttpRequestException>()
.OrResult(x => x.StatusCode is >= HttpStatusCode.InternalServerError or HttpStatusCode.RequestTimeout)
.WaitAndRetryAsync(Backoff.DecorrelatedJitterBackoffV2(TimeSpan.FromSeconds(1), RetryPolicyMaxCount)));
return services;
}
我做了一些改變,我將在下面澄清它們(我不確定其中一些有多好):
AuthorizationHeaderValueGetter = () => accessTokenHelperService.GetAccessToken(default)更直接(或者可能比使用更安全.Result。但是,這意味著將為每個 HTTP 呼叫呼叫它,并且獲取訪問令牌應該使用一些快取。在我的情況下,我使用的是 OBO,它為每個呼叫增加了約 400 毫秒,所以使用快取是必須的。
在這種情況下,從令牌獲取的角度來看,我不確定這種更改是否可行(我想這在 async..await 世??界中是一個更好的選擇)。
- 策略處理程式必須處理
HttpResponseMessage并且HttpRequestException因為 Polly 應用于HttpClient.
實際的改裝界面已簡化為:
[Headers("Authorization: Bearer")]
public interface IBarIntegration
{
[Get("/api/ext/Foo/GetFooBriefInfo")]
Task<GetFooBriefInfoForFooDto> GetFooBriefInfo(GetFooBriefInfoForFooInputDto inputData);
}
IBarIntegration現在可以直接注入了。我唯一不喜歡的一點是我必須在“應用程式”層中參考 Refit(解決方案是“清潔架構”),但我喜歡簡單而不是另一個抽象。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/516918.html
