دسترسی به REST API با HttpClient، Refit و Polly
منبع: Coding Clean, Reliable, and Safe REST APIs with ASP.NET Core 8 — Anthony Giretti
اعتبار ترجمه: ترجمه با کمک هوش مصنوعی
فروش یا انتشار این ترجمه منوط به داشتن مجوز لازم از صاحب حقوق اثر است.
دسترسی به داده با HttpClient و REST API
منبع داده همیشه SQL Database نیست. از آنجا که REST APIها بسیار رایجاند، برنامهها اغلب بخشی از دادهٔ خود را از یک API راهدور دریافت میکنند. در .NET این کار با HttpClient امکانپذیر است. نمونهٔ سادهٔ زیر محتوای یک تصویر را با درخواست GET دریافت میکند.
using (var client = new HttpClient())
{
byte[] fileBytes = await client.GetByteArrayAsync("https://anthonygiretti.blob.core.windows.net/countryflags/ca.png");
}
Listing 6-11 — استفادهٔ مستقیم از HttpClient
ساخت یک HttpClient جدید برای هر عملیات، از نظر Performance (کارایی) الگوی مناسبی نیست. هر Client از HttpMessageHandler استفاده میکند و تعداد زیاد Handler میتواند به Socket Exhaustion منجر شود. IHttpClientFactory Handlerها را مدیریت و برای استفادهٔ مجدد Pool میکند. کتاب در ادامه Refit را بهعنوان روش مورد علاقهٔ نویسنده معرفی میکند؛ Refit نیز بر Typed Clientها و IHttpClientFactory تکیه دارد.
استفاده از IHttpClientFactory
IHttpClientFactory Interface استاندارد .NET برای ساخت و مدیریت کارآمد HTTP Clientهاست. برای استفاده از آن بستهٔ زیر نصب میشود:
Microsoft.Extensions.Http
و سپس Factory در DI ثبت میشود:
builder.Services.AddHttpClient();
مطابق معماری فصل، Interface Repository در Domain قرار میگیرد و Implementation آن در Infrastructure.Http. قرارداد زیر محتوای تصویر و MIME Type را بهصورت Tuple برمیگرداند.
namespace Domain.Repositories;
public interface IMediaRepository
{
Task<(byte[] Content, string MimeType)>
GetCountryFlagContent(string countryShortName);
}
Listing 6-12 — رابط IMediaRepository
using Domain.Repositories;
namespace Infrastructure.Http.Repositories;
public class MediaRepository : IMediaRepository
{
private readonly IHttpClientFactory _httpClientFactory;
public MediaRepository(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
}
public async Task<(byte[] Content, string MimeType)>
GetCountryFlagContent(string countryShortName)
{
byte[] fileBytes;
using HttpClient client = _httpClientFactory.CreateClient();
fileBytes = await client.GetByteArrayAsync($"https://anthonygiretti.blob.core.windows.net/countryflags/{countryShortName}.png");
return (fileBytes, "image/png");
}
}
Listing 6-13 — کلاس MediaRepository با IHttpClientFactory
شیوههای دیگری مانند Named Client و Typed Client نیز روی همین Factory وجود دارند، اما کتاب برای کاهش کد و تمرکز بر یک الگوی سادهتر، ادامه را با Refit پیش میبرد.
استفاده از Refit برای درخواست HTTP
Refit یک Typed HTTP Client را از روی Interface تولید میکند. Route، پارامترها، Body و Headerها بهصورت Attribute روی Interface تعریف میشوند. ابتدا بستهٔ refit نصب میشود.
IMediaRepository را میتوان بهشکل زیر برای Refit تعریف کرد:
using Refit;
namespace Domain.Repositories;
public interface IMediaRepository
{
[Get("/countryflags/{countryShortName}.png")]
Task<byte[]> GetCountryFlagContent(string countryShortName);
}
Listing 6-14 — طراحی IMediaRepository با Refit
Attribute Get فعل و Route را مشخص میکند و مقدار countryShortName به پارامتر Route تبدیل میشود. Base URL لازم نیست داخل Interface تکرار شود و هنگام ثبت Client یکبار تعیین میشود. یکی از محدودیتهای مثال این است که Refit مستقیماً Tuple مورد استفادهٔ Repository قبلی را برنمیگرداند؛ بنابراین MIME Type باید در Service Layer تکمیل شود.
برای اتصال Refit به HttpClientFactory بستهٔ Refit.HttpClientFactory نصب و Client اینگونه ثبت میشود:
builder.Services.AddRefitClient<IMediaRepository>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://anthonygiretti.blob.core.windows.net"));
Listing 6-15 — ثبت Refit در Program.cs
افعال دیگر Refit نیز با Attributeهای مشابه تعریف میشوند. هدف این فصل پوشش کامل Refit نیست، بلکه نشان دادن بهترین شیوهٔ ساخت Clientهای REST راهدور است. Service میانی میان Repository و Endpoint نیز مطابق معماری قبلی قابل پیادهسازی است.
Resilience درخواست HTTP با Polly
Polly الگوهای Retry و Circuit Breaker را برای خطاهای HTTP پیادهسازی میکند. Retry چند تلاش مجدد انجام میدهد. اگر همهٔ تلاشها شکست بخورند، Circuit Breaker برای مدت قابل تنظیم درخواستهای بعدی را سریع متوقف میکند تا منبع خراب یا شبکه با درخواستهای بیشتر Overload نشود و فرصت بازیابی داشته باشد.
مزیت Typed Clientهایی مانند Refit این است که Policyهای Resilience خارج از Repository تعریف میشوند. بنابراین منطق دسترسی به داده با Retry و Circuit Breaker آلوده نمیشود و Separation of Concerns بهتر حفظ میشود. برای این نمونه بستهٔ زیر نصب میشود:
Microsoft.Extensions.Http.Polly
کلاس Extension زیر Policy پیشفرض را روی IHttpClientBuilder اعمال میکند.
using Polly;
using Polly.Extensions.Http;
namespace AspNetCore8MinimalApis.Resiliency.Http;
public static class RetryPolicy
{
public static void AddFaultHandlingPolicy(this IHttpClientBuilder builder)
{
var retryPolicy = HttpPolicyExtensions
.HandleTransientHttpError() // Handles 5XX and 408
.WaitAndRetryAsync(3, retryDelayInSeconds =>
TimeSpan.FromSeconds(3));
var circuitBreakerPolicy =
HttpPolicyExtensions
.HandleTransientHttpError() // Handles 5XX and 408
.CircuitBreakerAsync(4, TimeSpan.FromSeconds(15));
var policy = retryPolicy.WrapAsync(circuitBreakerPolicy);
builder.AddPolicyHandler(policy);
}
}
Listing 6-16 — کلاس RetryPolicy
HandleTransientHttpError خطاهای 5xx و 408 را Match میکند. WaitAndRetryAsync سه Retry با فاصلهٔ سه ثانیه تعریف میکند. Circuit Breaker پس از چهار شکست متوالی ــ درخواست اولیه بهعلاوهٔ سه Retry ــ باز میشود و برای ۱۵ ثانیه Callهای خراب را متوقف میکند. در پایان WrapAsync دو Policy را ترکیب و AddPolicyHandler آن را به Client متصل میکند.
Client ساختهشده با Refit اکنون فقط با یک فراخوانی Extension، Retry و Circuit Breaker را دریافت میکند.
builder.Services.AddRefitClient<IMediaRepository>()
.ConfigureHttpClient(c => c.BaseAddress =
new Uri("https://anthonygiretti.blob.core.windows.net"))
.AddFaultHandlingPolicy();
Listing 6-17 — Refit HttpClient همراه با Retry و Circuit Breaker
در نتیجه، هنگام خطای گذرای HTTP تعداد مشخصی Retry انجام میشود و پس از شکستهای متوالی Circuit Breaker برای زمان معین دسترسی به منبع خراب را متوقف میکند. نویسنده پیادهسازی Retry و Circuit Breaker را از بهترین شیوههای ضروری در دسترسی به منابع راهدور میداند.
جمعبندی فصل
این فصل مبانی Entity Framework Core برای دسترسی به SQL، Connection Pooling و Retryهای SQL را پوشش داد و سپس دسترسی HTTP را با IHttpClientFactory، Refit و Polly بررسی کرد. فصل بعد به Optimizationهای API، بهویژه در ارتباط با I/O و دسترسی به داده، میپردازد.