💡 API Versioning در ASP.NET Core

💡 API Versioning در ASP.NET Core

@CSharpGeeks

در سال گذشته، یک Public API بزرگ را ساخته و نگهداری کردم. این API ده‌ها integration داشت و بیشتر برای اپلیکیشن‌های موبایل استفاده می‌شد.

وقتی API شما به این تعداد client سرویس می‌دهد، breaking change هزینه‌ی زیادی دارد.
بنابراین، هر چیزی که روی Public API پیاده‌سازی می‌کردم باید از قبل برنامه‌ریزی می‌شد.
اضافه کردن یک field جدید؟ فراموشش کن.
تغییر نام fieldهای موجود؟ فراموشش کن.
اگر می‌خواستم breaking change ایجاد کنم، باید API را version می‌کردم.
امروز می‌خواهم نشان بدهم چطور API Versioning را در ASP.NET Core پیاده‌سازی کنیم.

🔄 ءAPI Versioning چیست؟

ءAPI versioning روشی است برای اختصاص دادن identifierهای متفاوت به نسخه‌های مختلف یک API، به‌طوری که clientها بتوانند یک نسخه‌ی مشخص را هدف قرار دهند و حتی با تکامل API، همچنان به کار خود ادامه دهند.

بدون versioning، هر تغییری که در API ایجاد می‌کنید می‌تواند بالقوه یک breaking change برای clientهایی باشد که از قبل به آن متصل شده‌اند.

با NET API versioning. می‌توانید یک endpoint جدید با نام v2 و behavior جدید معرفی کنید، در حالی که v1 دقیقاً مانند قبل به کار خود ادامه می‌دهد.

ءClientها بر اساس زمان‌بندی خودشان migrate می‌کنند و شما کنترل کاملی دارید که چه نسخه‌های قدیمی را deprecate و در نهایت حذف کنید.

این همان چیزی است که API versioning را برای هر Public API یا API بلندمدت در .NET ضروری می‌کند.
همین اصول، چه در حال ساخت API با ASP.NET Core MVC Controllers باشید و چه با Minimal APIs، کاربرد دارند.

🎯 چرا API Versioning در NET API. ها اهمیت دارد؟

ءAPI versioning به API شما اجازه می‌دهد مستقل از clientهایی که از آن استفاده می‌کنند، تکامل پیدا کند.
ایجاد breaking change در API، تجربه‌ی کاربری خوبی ایجاد نمی‌کند. API versioning مکانیزمی در اختیار شما قرار می‌دهد تا از expose کردن breaking change به clientها جلوگیری کنید.

به‌جای ایجاد یک breaking change، یک API version جدید معرفی می‌کنید.
اما تعریف breaking change چیست؟
این فهرست کامل نیست، اما چند نمونه از breaking changeها عبارت‌اند از:

  • حذف یا تغییر نام APIها یا API parameterها
  • تغییر behavior APIهای موجود
  • تغییر API response contract
  • تغییر API error codeها

شما می‌توانید مشخص کنید که در API خودتان چه چیزی breaking change محسوب می‌شود.
برای مثال، اضافه کردن یک field جدید به response لزوماً نباید breaking change باشد.
حالا ببینیم API versioning را چطور پیاده‌سازی کنیم. 🚀

🛠️ نحوه پیاده‌سازی API Versioning در NET Core.

ابتدا سه NuGet packageای را که برای پیاده‌سازی API versioning نیاز داریم نصب می‌کنیم:

  • Asp.Versioning.Http
  • Asp.Versioning.Mvc
  • Asp.Versioning.Mvc.ApiExplorer
Install-Package Asp.Versioning.Http # This is needed for Minimal APIs
Install-Package Asp.Versioning.Mvc # This is needed for Controllers
Install-Package Asp.Versioning.Mvc.ApiExplorer

این packageها به ما اجازه می‌دهند AddApiVersioning را فراخوانی کنیم و یک delegate برای configure کردن ApiVersioningOptions در اختیار آن قرار دهیم.

builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1);
    options.ReportApiVersions = true;
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ApiVersionReader = ApiVersionReader.Combine(
        new UrlSegmentApiVersionReader(),
        new HeaderApiVersionReader("X-Api-Version"));
})
.AddMvc() // This is needed for controllers
.AddApiExplorer(options =>
{
    options.GroupNameFormat = "'v'V";
    options.SubstituteApiVersionInUrl = true;
});

توضیح propertyهای ApiVersioningOptions:

  • ءDefaultApiVersion — نسخه‌ی پیش‌فرض API را مشخص می‌کند. معمولاً این مقدار v1.0 خواهد بود.
  • ءReportApiVersions — نسخه‌های پشتیبانی‌شده‌ی API را در response header با نام api-ءsupported-versions گزارش می‌کند.
  • ءAssumeDefaultVersionWhenUnspecified — زمانی که client یک version مشخص ارائه نکرده باشد، از DefaultApiVersion استفاده می‌کند.
  • ءApiVersionReader — مشخص می‌کند API versionای که client تعیین کرده چگونه خوانده شود. مقدار پیش‌فرض آن QueryStringApiVersionReader است.

متد AddApiExplorer زمانی مفید است که از Swagger استفاده می‌کنید.
این متد routeهای endpointها را اصلاح می‌کند و API version را جایگزین route parameter می‌کند.

🧭 انواع API Versioning در NET.

چندین strategy برای API versioning در NET. وجود دارد که هرکدام trade-offهای متفاوتی دارند.

🔗 URL Versioning

در URL versioning، نسخه مستقیماً داخل request path قرار می‌گیرد:

GET https://localhost:5001/api/v1/workouts

این روش صریح‌ترین رویکرد است.
نسخه در نگاه اول قابل مشاهده است، تست کردن آن در browser یا با curl بسیار ساده است و یکی از رایج‌ترین strategyها برای Public APIهای NET. محسوب می‌شود.
ءUrlSegmentApiVersionReader
در Asp.Versioning.Http این کار را به‌صورت خودکار انجام می‌دهد.

📨 Header Versioning

در Header versioning، API version از طریق یک custom request header ارسال می‌شود:

GET https://localhost:5001/api/workouts با header X-Api-Version: 1

این روش URLهای شما را تمیز نگه می‌دارد، اما version تا زمانی که client headerهای خروجی را بررسی نکند قابل مشاهده نیست. HeaderApiVersionReader از این strategy پشتیبانی می‌کند.

🔎 Query String Versioning

در Query string versioning، نسخه به‌عنوان یک query parameter به request اضافه می‌شود:

GET https://localhost:5001/api/workouts?api-version=1

این behavior پیش‌فرض در Asp.Versioning.Http است و از QueryStringApiVersionReader استفاده می‌کند.
در زمان development و testing استفاده از آن ساده است و به ابزار خاصی نیاز ندارد.
چند روش دیگر نیز برای پیاده‌سازی API versioning وجود دارد، مانند استفاده از headerهای accept یا content-type، اما این روش‌ها چندان رایج نیستند.

کتابخانه‌ی Asp.Versioning.Http چند implementation از IApiVersionReader برای پشتیبانی از این strategyها ارائه می‌دهد:

  • UrlSegmentApiVersionReader
  • HeaderApiVersionReader
  • QueryStringApiVersionReader
  • MediaTypeApiVersionReader

راهنماهای API versioning مایکروسافت استفاده از versioning از طریق URL یا query string parameter را پیشنهاد می‌کنند.

🎮 ءVersioning کردن Controllerها

برای پیاده‌سازی API versioning در ASP.NET Controllerها، باید controller را با attribute مربوط به ApiVersion مشخص کنید. Attribute ApiVersion به شما اجازه می‌دهد مشخص کنید WorkoutsController از چه API versionهایی پشتیبانی می‌کند.
در این مثال، controller هم از v1 و هم از v2 پشتیبانی می‌کند.
از attribute مربوط به MapToApiVersion روی endpointها استفاده می‌کنید تا API version مشخص هر endpoint را تعیین کنید. Route parameter یعنی v{v:apiVersion} به شما اجازه می‌دهد API version را با استفاده از v1 یا v2 در URL مشخص کنید.

[ApiVersion(1)]
[ApiVersion(2)]
[ApiController]
[Route("api/v{v:apiVersion}/workouts")]
public class WorkoutsController : ControllerBase
{
    [MapToApiVersion(1)]
    [HttpGet("{workoutId}")]
    public IActionResult GetWorkoutV1(Guid workoutId)
    {
        return Ok(new GetWorkoutByIdQuery(workoutId).Handle());
    }

    [MapToApiVersion(2)]
    [HttpGet("{workoutId}")]
    public IActionResult GetWorkoutV2(Guid workoutId)
    {
        return Ok(new GetWorkoutByIdQuery(workoutId).Handle());
    }
}

⚠️ ءDeprecate کردن API Versionها

اگر می‌خواهید یک API version قدیمی را deprecate کنید، می‌توانید property مربوط به Deprecated را روی attribute ApiVersion قرار دهید. API versionهای deprecated از طریق response header با نام api-deprecated-versions گزارش می‌شوند.

[ApiVersion(1, Deprecated = true)]
[ApiVersion(2)]
[ApiController]
[Route("api/v{v:apiVersion}/workouts")]
public class WorkoutsController : ControllerBase
{
}

⚡ ءVersioning کردن Minimal APIها

ءVersioning کردن Minimal APIها نیازمند تعریف یک ApiVersionSet است که آن را به endpointها می‌دهید.

  • ءNewApiVersionSet — یک ApiVersionSetBuilder جدید ایجاد می‌کند که می‌توانید با استفاده از آن ApiVersionSet را configure کنید.
  • ءHasApiVersion — مشخص می‌کند ApiVersionSet از API version مشخص‌شده پشتیبانی می‌کند.
  • ءReportApiVersions — مشخص می‌کند تمام APIهای موجود در ApiVersionSet نسخه‌های خود را report کنند.

بعد از ایجاد ApiVersionSet، باید آن را با فراخوانی WithApiVersionSet به یک Minimal API endpoint بدهید.
همچنین می‌توانید با فراخوانی MapToApiVersion، endpoint را به یک API version مشخص map کنید.

ApiVersionSet apiVersionSet = app.NewApiVersionSet()
    .HasApiVersion(new ApiVersion(1))
    .HasApiVersion(new ApiVersion(2))
    .ReportApiVersions()
    .Build();

app.MapGet("api/v{version:apiVersion}/workouts/{workoutId}", async (
    Guid workoutId,
    ISender sender,
    CancellationToken ct) =>
{
    var query = new GetWorkoutByIdQuery(workoutId);

    Result<WorkoutResponse> result = await sender.Send(query, ct);

    return result.Match(Results.Ok, CustomResults.Problem);
})
.WithApiVersionSet(apiVersionSet)
.MapToApiVersion(1);

مشخص کردن ApiVersionSet برای هر Minimal API endpoint می‌تواند دست‌وپاگیر باشد.
بنابراین می‌توانید یک route group تعریف کنید و ApiVersionSet را فقط یک بار روی آن تنظیم کنید. Route groupها همچنین کاربردی هستند، چون به شما اجازه می‌دهند route prefix را مشخص کنید.

ApiVersionSet apiVersionSet = app.NewApiVersionSet()
    .HasApiVersion(new ApiVersion(1))
    .ReportApiVersions()
    .Build();

RouteGroupBuilder group = app
    .MapGroup("api/v{version:apiVersion}")
    .WithApiVersionSet(apiVersionSet);

group.MapGet("workouts", ...);
group.MapGet("workouts/{workoutId}", ...);

✅ ءBest Practiceهای API Versioning در NET.

هنگام پیاده‌سازی API versioning در .NET Core چند practice مهم را در نظر داشته باشید:

  • از همان روز اول version کنید. اضافه کردن API versioning قبل از اینکه clientهای خارجی داشته باشید بسیار ساده‌تر است. حتی یک نسخه‌ی واحد (v1) هم به شما فضای لازم را می‌دهد تا بعداً بدون migrationهای دردناک v2 را معرفی کنید.
  • تعریف کنید چه چیزی breaking change محسوب می‌شود. به‌عنوان یک تیم روی این موضوع توافق کنید که چه چیزی breaking change است؛ مانند حذف fieldها، تغییر response contract یا تغییر error codeها و آن را مستند کنید. اضافه کردن یک field جدید optional یا یک endpoint جدید معمولاً امن است.
  • ءDeprecate کنید، حذف نکنید. هنگام بازنشسته کردن یک version، ابتدا آن را با استفاده از [ApiVersion(1, Deprecated = true)] deprecated کنید. به clientها زمان بدهید تا قبل از حذف کامل، migration را انجام دهند.
  • نسخه‌های پشتیبانی‌شده را report کنید. مقدار ReportApiVersions = true را تنظیم کنید تا clientها بتوانند versionهای موجود را از طریق response header با نام api-supported-versions پیدا کنند.
  • نسخه‌های قدیمی را پایدار نگه دارید. زمانی که یک version منتشر شد، با آن مانند چیزی immutable رفتار کنید. featureهای جدید باید وارد version جدید شوند، نه version قدیمی.
  • برای Public APIها URL versioning را ترجیح دهید. این روش قابل کشف‌تر است، cache کردن آن ساده‌تر است و با تمام HTTP clientها بدون configuration خاص کار می‌کند.

🎯 نکته نهایی

ءAPI versioning یکی از Best Practiceهای مهم برای طراحی APIهای مدرن است.
از همان اولین release، پیاده‌سازی API versioning را در نظر بگیرید.

این کار پشتیبانی clientها از API versionهای آینده را ساده‌تر می‌کند و باعث می‌شود تیم شما از همان ابتدا به مدیریت breaking changeها و version کردن API عادت کند.
می‌توانید از کتابخانه‌ی Asp.Versioning.Http برای اضافه کردن API versioning به ASP.NET Core استفاده کنید. API versionهای پشتیبانی‌شده را تعریف کنید و شروع به استفاده از آن‌ها در endpointهای خود کنید.
به یاد داشته باشید که به‌عنوان یک تیم روی این موضوع توافق کنید که چه چیزی breaking change محسوب می‌شود.

این موضوع باید به‌خوبی در API design guidelines تیم مستند شده باشد.
روش مورد علاقه‌ی من برای پیاده‌سازی API versioning، استفاده از URL versioning است.
ساده و صریح است. و از آنجا که این آخرین issue سال است، برای شما سال نو شاد و پربرکتی آرزو می‌کنم. 🎉

از اینکه خواندید متشکرم و همچنان عالی باشید!

❓ سؤالات متداول

ءAPI versioning چیست؟

ءAPI versioning یک strategy است که به API شما اجازه می‌دهد مستقل از clientهایی که از آن استفاده می‌کنند، تکامل پیدا کند.

به‌جای ایجاد breaking changeهایی که همه‌ی clientها را مجبور می‌کنند هم‌زمان update شوند، یک version جدید از API معرفی می‌کنید و به clientها اجازه می‌دهید بر اساس زمان‌بندی خودشان migration را انجام دهند.

آیا API versioning در ASP.NET Core اجباری است؟

ءAPI versioning به‌صورت پیش‌فرض در ASP.NET Core اجباری نیست، اما برای Public APIها یا APIهایی که قرار است مدت زیادی مورد استفاده قرار بگیرند، شدیداً توصیه می‌شود. NuGet package مربوط به Asp.Versioning.Mvc پشتیبانی کامل از versioning را اضافه می‌کند.

بهترین strategy برای API versioning در NET. چیست؟

رایج‌ترین strategy، URL segment versioning است؛ برای مثال /api/v1/resource، چون صریح است و تست کردن آن در browser ساده است.Header versioning تمیزتر است، اما discoverability کمتری دارد.بهترین انتخاب به clientهای شما بستگی دارد.

آیا ASP.NET Core به‌صورت built-in از API versioning پشتیبانی می‌کند؟

به‌صورت native، خیر. باید packageهای Asp.Versioning.Http و Asp.Versioning.Mvc را نصب کنید و سپس AddApiVersioning() را در service registration فراخوانی کنید.

چگونه Minimal APIها را در NET version. کنیم؟

در Minimal APIها، endpoint groupها را با استفاده از MapGroup به یک API version group اختصاص می‌دهید و آن‌ها را با ApiVersion مناسب مشخص می‌کنید. Package مربوط به Asp.Versioning.Http extension methodهای لازم را فراهم می‌کند.

آیا اضافه کردن یک field جدید به response یک breaking change است؟

اضافه کردن یک field جدید به response معمولاً برای clientهایی که API را به‌درستی پیاده‌سازی کرده‌اند، breaking change نیست.

اما حذف یا تغییر نام fieldها، تغییر typeها یا تغییر behavior، همگی breaking change محسوب می‌شوند.

در NET. از URL versioning استفاده کنم یا Header versioning؟

ءURL versioning ساده‌تر پیاده‌سازی می‌شود و مصرف آن برای clientها نیز ساده‌تر است. Header versioning URLها را تمیز نگه می‌دارد، اما clientها باید یک custom header تنظیم کنند. بیشتر Public APIها از URL versioning استفاده می‌کنند.

برای مطالب بیشتر به چنل بپیوندید❤️

CSharpGeeks(.NET)

Report Page