Versioning در API چیست و چرا نباید تغییرات ناسازگار را مستقیماً اعمال کرد؟

تصور کنید تیمی که سرویس محاسبه کرایه یک اپلیکیشن حملونقل شهری را نوشته، تصمیم میگیرد ساختار پاسخ را تمیزتر کند: نام یکی از فیلدها عوض میشود و دو فیلد کهنه حذف میشوند. تغییر در سمت سرور اعمال میشود، تستهای سرویس سبز میشوند و تیم خسته اما خشنود خداحافظی میکند. فردا صبح، نسخههای قدیمی اپ مسافر و راننده که هنوز روی گوشی کاربران نصباند، فیلدهایی را میبینند که دیگر نیستند و صفحه نمایش کرایه خالی میماند.
این سناریو یکی از پرتکرارترین و گرانترین اشتباهات در طراحی سرویسهاست. نسخهبندی (Versioning) دقیقاً برای اینکه چنین صبحی هرگز از راه نرسد به وجود آمده است؛ مقاله پیشرو دو رویکرد مقابل هم را کنار هم میگذارد تا روشن شود چرا تغییر ناسازگار باید در قالب نسخه جدید منتشر شود، نه روی سرویس فعال.
دو رویکرد در برابر یک تغییر
رویکرد نخست: تغییر مستقیم روی سرویس فعال
در این رویکرد، قرارداد سرویس دارایی تیم است و تیم هر وقت بخواهد آن را تغییر میدهد. مزیتش سادگی است: هیچ نسخه موازیای وجود ندارد، کد قدیمی باید نگه داشته نشود و تیم روی یک مسیر حرکت میکند. هزینهاش هم پنهان نیست: هر کلاینتی که قرارداد قبلی را میشناسد، در معرض شکست قرار میگیرد و تیم سرویس از پشت دیوار سرور، خبری از آن شکستها نمیگیرد تا وقتی شکایتها برسد.
رویکرد دوم: انتشار نسخههای موازی
در این رویکرد، تغییر ناسازگار در قالب نسخه جدید ارائه میشود و نسخه قبلی مطابق تعهد اعلامشده، برای مدتی معین به کار ادامه میدهد. کلاینتها با آگاهی مهاجرت میکنند و تیم سرویس، زمانبندی بازنشستگی نسخههای قدیمی را خودش مدیریت میکند. هزینهاش پیچیدگی نگهداری چند قرارداد همزمان است؛ مزیتش این است که هیچ کلاینتی غافلگیر نمیشود و اعتماد مصرفکنندگان سرویس سرمایهگذاری بلندمدت تیم میماند.
تغییر سازگار و تغییر ناسازگار
همه تغییرات مشکلساز نیستند. افزودن یک فیلد تازه به پاسخ معمولاً سازگار رو به عقب (Backward Compatible) است، چون کلاینتهای قدیمی آن را نادیده میگیرند. اما هر تغییری که برداشت کلاینت از داده را به هم میریزد، یک تغییر ناسازگار (Breaking Change) است و همان صبح تلخ را میسازد. تفکیک این دو، نیمه نخست راه نسخهبندی است:
- سازگار: افزودن فیلد اختیاری به پاسخ، افزودن پارامتر اختیاری به درخواست، بازتر کردن مقادیر پذیرفتهشده در ورودی.
- ناسازگار: حذف یا تغییر نام فیلد، تغییر نوع یا معنای یک مقدار، سختگیرانهتر شدن اعتبارسنجی ورودی، تغییر معنای کدهای خطا.
مرز میان این دو گاهی ظریف است. مثلاً افزودن مقدار تازه به یک فیلد انتخابی در پاسخ، برای کلاینتی که مقادیر ناشناخته را نمیشناسد و برنامهاش میشکند، عملاً ناسازگار است. تغییراتی هم هست که در گزارش کد بهچشم کوچک دیده میشوند اما در عمل سنگیناند: تغییر ساختار فهرست تودرتو، تغییر ترتیب اهمیت فیلدها، یا محدود کردن بازه مقادیری که تا پیش از آن آزاد بود. به همین دلیل تصمیم درباره سازگاری باید به قرارداد مستند تکیه کند، نه به برداشت روز انتشار.
چرا کلاینتها را نمیتوان همزمان بهروزرسانی کرد
فرض ساده این است: اعلام میکنیم، همه بهروزرسانی میکنند. اما واقعیت قدمهای دیگری دارد. اپلیکیشن موبایل باید از فرآیند بازبینی فروشگاههای اپ بگذرد و حتی پس از انتشار، کاربران با سرعتهای متفاوت نسخه میگیرند؛ بعضی هرگز. طرفهای سوم و همکاران سازمانی با تقویمهای خودشان ادغام میشوند و برخی سیستمهای قدیمی سالها همانجا میمانند. در چنین فضایی، سرویسِ بدون نسخه عملاً به هر کلاینت میگوید: یا با من همقدم باش، یا از دسترسی محروم شو.
نسخه را کجا اعلام میکنند؟
چند روش رایج برای اعلام نسخه وجود دارد و تیمها بر اساس مصرفکنندگانشان یکی را انتخاب میکنند. در روش نخست، نسخه در مسیر درخواست قرار میگیرد؛ سادهترین شکل رهگیری است و از روی خود درخواست معلوم میشود کدام قرارداد صدا زده شده و عیبیابی در لاگها هم راحت است. در روش دوم، نسخه در سرصفحه (Header) درخواست اعلام میشود؛ مسیرها تمیز میمانند و برای تیمهایی که نمیخواهند آدرسها با هر نسخه عوض شوند جذاب است، اما در عوض کلاینت باید سرصفحه را درست بفرستد و عیبیابی کمی تخصصیتر میشود.
روش سوم، مذاکره محتوا (Content Negotiation) است: نسخه از روی ویژگیهایی که کلاینت اعلام میکند میپذیرد، انتخاب میشود. این روش انعطاف بیشتری میدهد و میتواند مهاجرت تدریجی را نرمتر کند، اما پیادهسازی و مستندسازیاش پیچیدهتر است. در سطوح دیگری مثل کتابخانههای مصرفشونده، نسخهگذاری معنایی (Semantic Versioning) هم رایج است که با شمارههای چندجزئی، نوع تغییر را در خود عدد منتقل میکند. انتخاب میان این روشها مهم است، اما از آن مهمتر، ثبات روی انتخاب و در دسترس بودن نسخه در مستندات و لاگهاست؛ نسخهای که در عیبیابی پیدا نشود، در جلسات پشتیبانی به کابوس تبدیل میشود.
| معیار | اعمال مستقیم تغییرات | انتشار نسخههای موازی |
|---|---|---|
| ریسک شکستن کلاینتها | بالا؛ هر مصرفکنندهای که متوجه تغییر نشود، خراب میشود | کم؛ قرارداد هر کلاینت تا بازنشستگی صریح ثابت میماند |
| سرعت حرکت تیم سرویس | در نگاه اول سریع، اما با هر خرابی کشفنشده متوقف میشود | کندتر در هر انتشار، اما بدون توقفهای اضطراری و شببیداریهای اجباری |
| پیچیدگی نگهداری | حداقل؛ فقط یک نسخه وجود دارد | بیشتر؛ چند قرارداد همزمان باید پشتیبانی و آزمون شوند |
| اعتماد مصرفکنندگان | فرساینده؛ رفتار سرویس قابل پیشبینی نیست | سازنده؛ تغییرات اعلامشده و زماندار است |
| توانایی بازگشت | تقریباً هیچ؛ تغییر اعمالشده جای خود را باز میکند | بالا؛ کلاینت میتواند تا زمان مهاجرت روی نسخه قبلی بماند |
مثال کاربردی: سرویس محاسبه کرایه در دو نسخه
به سناریوی آغاز مقاله برگردیم؛ اینبار با نسخهبندی. تیم، پاسخ بازطراحیشده را در نسخه دوم منتشر میکند و نسخه اول را سر جایش نگه میدارد. اپهای فعلی، بیخبر از جهان، همچنان با نسخه اول کار میکنند؛ اپهای تازه مستقیم به نسخه دوم میروند. تیم در مستندات، تفاوت دو نسخه را فهرست میکند و برای نسخه اول، تاریخ بازنشستگی (Sunset) اعلام میکند.
در طول دوره انتقال، سرویس مصرف هر نسخه را پایش میکند تا معلوم شود مهاجرت کدام مصرفکنندهها باقی مانده است. اپهای قدیمی که دیگر بهروزرسانی نمیگیرند، با نسخهای از سرویس کار میکنند که برایشان طراحی شده و رفتارش قابل پیشبینی است. وقتی آخرین مصرفکننده شناختهشده جابهجا شد، نسخه اول با اطلاع قبلی بازنشسته میشود. نتیجه: تغییر بزرگ تیم، هیچ کاربری را نمیسوزاند.
چرخه عمر نسخه: از منسوخسازی تا بازنشستگی
نسخهبندی بدون برنامه بازنشستگی، انبار نسخههای فرسوده میسازد. چرخه سالم این است: نسخه جدید اعلام میشود؛ نسخه قدیمی منسوخ (Deprecated) علامت میخورد؛ در پاسخهایش نشانهای روشن برای مصرفکنندگان قرار میگیرد؛ مستندات، تاریخ مقرر را اعلام میکند و تیم، مصرفکنندگان مهم را شخصاً پیگیری میکند. بازنشستگی نسخه باید رویدادی برنامهریزیشده باشد، نه اتفاقی که روزی از میان یک انتشار عادی بیرون بزند.
خطاهای رایج در نسخهبندی
- نسخهبندی برای هر تغییر جزئی؛ اگر همهچیز نسخه میگیرد، مفهوم نسخه بیمعنا میشود. نسخه برای تغییرات ناسازگار است، نه برای هر ویرایش.
- رها کردن بیپایان نسخههای قدیمی؛ وقتی هیچ نسخهای بازنشسته نمیشود، تیم روی چند مسیر همزمان میدود و کیفیت همه مسیرها پایین میآید.
- نسخهگذاری در سکوت؛ اعلام نسخه بدون اطلاعرسانی به مصرفکنندگان، عملاً همان تغییر مستقیم است که با شکل رسمی پوشانده شده است.
- مستندات بیبهروز؛ نسخهای که تفاوتش با نسخه قبل مستند نشده، هزینه کشف را به مصرفکننده میاندازد و اعتماد را میفرساید.
- مخفی کردن تغییرات ناسازگار درون نسخهای که ادعای سازگاری دارد؛ بدترین حالت ممکن، قراردادی است که خودش دروغ میگوید.
کاربرد عملی: سه پرسش پیش از هر تغییر قرارداد
پیش از اعمال هر تغییر در یک سرویس فعال، سه پرسش کوتاه جلوی بسیاری از فاجعهها را میگیرد: آیا این تغییر برای مصرفکنندگان فعلی سازگار است؟ اگر ناسازگار است، کدام نسخه جدید باید آن را حمل کند و نسخه فعلی تا کی میماند؟ و مصرفکنندگان از کجا و کی میفهمند؟ پاسخهای نوشتهشده به این سه پرسش میتوانند بخشی از تعریف آمادهبهکار هر تغییر قرارداد سرویس باشند.
برای تیمهای در حال رشد، آزمون سازگاری هم ابزاری ساده و مؤثر است: مجموعهای از تستها که قرارداد نسخه فعلی را در هر انتشار بررسی میکنند و اگر تغییری ناسازگار بیاجازه سرک کشید، پیش از رسیدن به مصرفکنندگان هشدار میدهند. چنین آزمونی جایگزین مذاکره نیست، اما شبهای قبل از انتشار را آرامتر میکند.
نکات کلیدی این مقاله
- تغییر سازگار میتواند مستقیم اعمال شود؛ تغییر ناسازگار فقط در نسخه جدید انتشار مییابد.
- کلاینتها همزمان بهروز نمیشوند؛ فروشگاههای اپ، طرفهای سوم و کاربرانی که نسخه نمیگیرند، زمانبندی شما را تعیین میکنند.
- روش اعلام نسخه در مسیر یا سرصفحه تفاوتهای واقعی دارد؛ اما ثبات روی انتخاب و پیدا شدن نسخه در مستندات مهمتر است.
- نسخهبندی بدون برنامه بازنشستگی بار اضافه میسازد؛ منسوخسازی و تاریخ مقرر را جدی بگیرید.
- آزمون سازگاری، سادهترین نگهبان قرارداد سرویس شما پیش از هر انتشار است.
سوالات متداول
آیا نسخهبندی فقط برای APIهای عمومی لازم است؟
نه. سرویسهای داخلی هم چند مصرفکننده دارند و همزمانی انتشار آنها تضمین نمیشود. تفاوت نسخه داخلی و عمومی در رتبه اهمیت است، نه در اصل موضوع؛ تغییر ناسازگار در سرویس داخلی هم میتواند صفحات و سرویسهای دیگری را نیمهشب از کار بیندازد.
چگونه دو نسخه را همزمان نگه داریم بدون دوبارهکاری؟
منطق مشترک را در لایهای پایینتر متمرکز کنید و تفاوت دو نسخه را در لایهای نازک از تبدیل داده نگه دارید؛ یعنی هر نسخه فقط «قالب» قرارداد را حمل کند، نه یک پیادهسازی کامل جداگانه. اینطور هزینه نگهداری نسخههای موازی بهجای دو برابر شدن، در حد قابل تحمل میماند.
از کجا بفهمیم وقت بازنشستگی نسخه قدیمی رسیده است؟
سه نشانه را کنار هم بگذارید: پایش مصرف واقعی نشان دهد مصرفکننده مهمی باقی نمانده، مهلت اعلامشده در مستندات گذشته باشد و مصرفکنندگان مهم شخصاً تأیید مهاجرت کرده باشند. بازنشستگی بر اساس یک منبع تنها، معمولاً کسی را غافلگیر میکند.



