Kourosh Faraji
16 یادداشت منتشر شدهچطور یک API خوب طراحی کنیم؟ اصول طراحی API در ۲۰۲۶
## مقدمه
تقریبا هر برنامه ای که امروز استفاده می کنیم، در پشت صحنه با ده ها سرویس دیگر حرف می زند؛ از اپلیکیشن بانکی روی گوشی شما تا فروشگاه اینترنتی و حتی چت بات های هوش مصنوعی. زبان مشترکی که این سرویس ها با آن با هم صحبت می کنند، API است. اما API خوب فقط «کار کردن» نیست؛ API خوب یعنی قراردادی روشن، پایدار و قابل پیش بینی که توسعه دهنده ی سمت مقابل بتواند بدون حدس زدن با آن کار کند.
در این یادداشت، مهم ترین اصول طراحی API در سال ۲۰۲۶ را مرور می کنیم؛ اصولی که هم در مستندات مرجع مثل Microsoft Learn آمده اند و هم در استانداردهای جدیدی مثل RFC 9457 و RFC 9700 تثبیت شده اند.
## ۱. REST را ساده و سازگار نگه دارید
REST هنوز رایج ترین سبک طراحی API است و قدرت آن در سادگی اش است:
- **منابع را با اسم جمع نام گذاری کنید و از فعل در مسیر استفاده نکنید.** مثلا `/users/42/orders` درست است، نه `/getUserOrders`.
- **افعال HTTP را درست به کار ببرید:** GET برای خواندن، POST برای ایجاد یا فراخوانی یک عملیات، PUT برای جایگزینی کامل، PATCH برای به روزرسانی جزئی و DELETE برای حذف.
- **عملیات ها را Idempotent طراحی کنید؛** یعنی تکرار یک درخواست، اثر جانبی دوباره نداشته باشد. اگر به خاطر قطعی شبکه، کلاینت درخواستی را دوباره فرستاد، نباید دو سفارش ثبت شود.
- **جزئیات داخلی را پنهان کنید؛** ساختار پایگاه داده یا پیاده سازی داخلی شما نباید از API معلوم شود.
- **پاسخ ها را با JSON و قراردادی مشخص برگردانید** و این قرارداد را در همه ی اندپوینت ها یکسان نگه دارید.
## ۲. نسخه بندی؛ قول پایداری به مصرف کننده
هیچ APIای برای همیشه ثابت نمی ماند، اما تغییر نباید ناگهانی مصرف کننده ها را غافلگیر کند. رایج ترین روش نسخه بندی، آوردن نسخه در مسیر است: `/v1/orders`. روش های دیگر، نسخه در هدر یا پارامتر پرس وجو هستند؛ مهم این است که یک روش را انتخاب کنید و همیشه همان را مستند و رعایت کنید.
- افزودن یک فیلد اختیاری، تغییر ناسازگار نیست و نسخه ی جدید نمی خواهد.
- اما حذف یک فیلد، تغییر نوع داده یا تغییر معنای یک فیلد، تغییر ناسازگار است و به نسخه ی اصلی (Major) جدید نیاز دارد.
- وقتی نسخه ی جدید منتشر می شود، نسخه ی قدیمی را مدتی پشتیبانی کنید، تاریخ منسوخ شدن را اعلام کنید و راهنمای مهاجرت بنویسید.
- برای هر تغییر کوچک، نسخه ی جدید نسازید؛ در غیر این صورت مصرف کننده ها در میان ده ها نسخه گم می شوند.
## ۳. کدهای وضعیت HTTP را درست انتخاب کنید
کد وضعیت، اولین چیزی است که کلاینت می خواند؛ پس باید دقیق باشد:
- **موفقیت:** 200 برای موفقیت عادی، 201 وقتی منبعی ساخته شده، 202 وقتی درخواست پذیرفته شده ولی هنوز پردازش نشده، و 204 وقتی پاسخی برای برگرداندن نیست.
- **خطای کلاینت:** 400 برای درخواست نامعتبر، 401 وقتی هویت مشخص نیست، 403 وقتی هویت مشخص است ولی اجازه ندارد، 404 وقتی منبع یافت نشد، 409 برای تعارض (مثلا رکورد تکراری)، 422 وقتی اعتبارسنجی داده ها ناموفق است، و 429 وقتی درخواست ها بیش از حد مجاز است.
- **خطای سرور:** 500 برای خطای داخلی، 502 وقتی دروازه ی میانی پاسخ بدی گرفت و 503 وقتی سرویس موقتا در دسترس نیست.
تفاوت 401 و 403 و همچنین تفاوت 404 با 410 (منبع برای همیشه حذف شده) از اشتباه های رایجی است که حتی در APIهای بزرگ دیده می شود. انتخاب کد درست، یعنی کلاینت بداند دقیقا چه اتفاقی افتاده و چه باید بکند.
## ۴. خطاها را استاندارد و قابل اقدام برگردانید
یک پیام خطای خوب سه ویژگی دارد: پایدار است، قابل اقدام است و ماشین می تواند آن را تجزیه کند. استاندارد مرجع در این زمینه، RFC 9457 با عنوان «Problem Details for HTTP APIs» است که در ژوئیه ی ۲۰۲۳ منتشر شد. طبق این استاندارد، خطا با نوع رسانه ی `application/problem+json` و فیلدهایی مثل `type`، `title`، `status`، `detail` و `instance` برگردانده می شود و می توان جزئیاتی مثل شناسه ی پایدار خطا یا فهرست فیلدهای نامعتبر را هم به آن افزود.
دو نکته ی مهم:
- **هرگز جزئیات داخلی را در خطا فاش نکنید؛** Stack Trace و اسرار امنیتی جایی در پاسخ خطا ندارند.
- **خطا باید راه حل بدهد؛** «ایمیل نامعتبر است» بهتر از «خطا رخ داد» است و «ایمیل باید شامل @ باشد» از هر دو بهتر است.
## ۵. صفحه بندی؛ وقتی داده ها زیاد می شوند
هیچ وقت همه ی رکوردها را در یک پاسخ برنگردانید. دو روش اصلی صفحه بندی وجود دارد:
- **Offset/Limit** مثل `?limit=50&offset=500` ساده است و پرش به صفحه ی مشخص را ممکن می کند، اما در مجموعه های بزرگ و در حال تغییر، پرهزینه و ناپایدار است؛ اگر رکوردی اضافه یا حذف شود، ممکن است یک رکورد را دو بار ببینید یا اصلا نبینید.
- **صفحه بندی مبتنی بر Cursor یا Keyset** برای مجموعه های بزرگ یا متغیر ترجیح داده می شود؛ سازگارتر و مقیاس پذیرتر است، هرچند پرش تصادفی به صفحه ی مشخص را به طور طبیعی پشتیبانی نمی کند. برای Cursor از ترتیب قطعی و یکتا، معمولا جفت `(created_at, id)`، با ایندکس پشتیبان استفاده کنید و Cursor را برای کلاینت مات (Opaque) نگه دارید.
## ۶. احراز هویت و مجوز؛ مرز میان «کیستی» و «چه حقی داری»
سه مفهوم را با هم قاطی نکنید:
- **OAuth 2.0** برای دسترسی تفویض شده (مثلا وقتی یک اپلیکیشن با اجازه ی شما به حسابتان دسترسی پیدا می کند).
- **OpenID Connect یا OIDC** برای احراز هویت کاربر و دریافت هویت او.
- **API Key** فقط برای شناسایی ساده ی یک حساب یا سرویس.
- **JWT** فقط یک قالب توکن با ادعاهای امضاشده است؛ به خودی خود پروتکل احراز هویت نیست.
طبق مرجع امنیتی RFC 9700 با عنوان «OAuth 2.0 Security Best Current Practice» که در ژانویه ی ۲۰۲۵ منتشر شد، جریان Authorization Code همراه با PKCE و روش S256 برای کلاینت های تعاملی توصیه می شود و PKCE برای کلاینت های عمومی الزامی است. از جریان های قدیمی مثل Implicit Grant و Resource Owner Password Grant پرهیز کنید، Redirect URI را دقیق تطبیق بدهید و در برابر CSRF محافظت کنید. همیشه از HTTPS استفاده کنید، دسترسی توکن را با Scope و Audience محدود کنید و اصل حداقل دسترسی را اعمال کنید.
## ۷. محدودسازی نرخ و Idempotency
وقتی API شما عمومی می شود، باید از آن محافظت کنید:
- محدودیت ها را بر اساس کاربر، کلید API یا اندپوینت مستند و اعلام کنید.
- در صورت تجاوز، کد 429 همراه با هدر `Retry-After` برگردانید تا کلاینت بداند چه زمانی دوباره تلاش کند.
- کلاینت باید با عقب نشینی نمایی (Exponential Backoff) همراه با Jitter دوباره تلاش کند، نه با فاصله های ثابت و هم زمان.
- برای عملیات ایجاد یا پرداخت، از کلید Idempotency استفاده کنید تا تکرار یک درخواست به خاطر قطعی شبکه، عملیات تکراری نسازد.
## ۸. مستندسازی با OpenAPI؛ قرارداد را ماشین خوان کنید
OpenAPI نسخه ی 3.x امروز استاندارد عملی (de facto) توصیف قرارداد REST است. قراردادی ماشین خوان یعنی: تولید خودکار کلاینت، اعتبارسنجی درخواست، و کشف پذیری API. اما مستندسازی فقط تولید قرارداد نیست؛ باید مثال های واقعی، کدهای خطا و محدودیت ها را هم شامل شود و همیشه از همان قرارداد تولید شود تا با پیاده سازی هماهنگ بماند. مستندی که از کد عقب مانده، بدتر از نداشتن مستند است.
## ۹. REST در برابر GraphQL در ۲۰۲۶
REST همچنان انتخاب غالب برای APIهای عمومی، عملیات ساده ی CRUD، کش HTTP و معناشناسی استاندارد HTTP است. GraphQL وقتی قوی است که نیاز مشتری محور باشد، داده از چند منبع تجمیع شود یا انتخاب انعطاف پذیر فیلدها مهم باشد؛ اما پیچیدگی، کش و مدیریت نسخه را دشوارتر می کند. رویکرد ترکیبی امروز رایج است: REST به عنوان رابط اصلی و GraphQL برای موارد استفاده ی خاص و مشتری محور.
## ۱۰. APIهای آماده برای عامل های هوش مصنوعی
در سال ۲۰۲۶ یک روند تازه هم اضافه شده است: APIها دیگر فقط برای انسان ها و برنامه های سنتی ساخته نمی شوند، بلکه عامل های هوش مصنوعی هم آن ها را مصرف می کنند. برای اینکه یک عامل بتواند API شما را کشف و مصرف کند، API باید خودتوصیف کننده باشد، قرارداد پایدار داشته باشد، مستندات ماشین خوان ارائه دهد و خطا و Idempotency را درست مدیریت کند. سرورهای MCP و ابزارهای مبتنی بر OpenAPI به عنوان پل میان API و عامل ها مطرح اند، هرچند هنوز استاندارد واحدی برای این کار وجود ندارد.
## جمع بندی
API خوب مثل یک قرارداد خوب است: روشن، پایدار و قابل اعتماد. اگر تازه شروع کرده اید، این چک لیست را به خاطر بسپارید:
1. نام گذاری سازگار و افعال HTTP درست؛
2. نسخه بندی از روز اول، با پشتیبانی از نسخه های قدیمی؛
3. کدهای وضعیت دقیق و خطاهای استاندارد و قابل اقدام؛
4. صفحه بندی مناسب اندازه ی داده ها؛
5. احراز هویت مدرن (OAuth 2.0 با PKCE) و HTTPS؛
6. محدودسازی نرخ، Retry-After و Idempotency؛
7. مستندسازی زنده با OpenAPI.
API را نه برای خودتان، بلکه برای توسعه دهنده ای طراحی کنید که هرگز شما را ندیده و فقط مستندات شما را دارد. اگر او بدون پرسیدن سوال بتواند با API شما کار کند، یعنی API خوبی ساخته اید.
---
منابع: Microsoft Learn — Web API Design Best Practices؛ Azure API Management — Versions and revisions؛ RFC 9457 (ژوئیه ی ۲۰۲۳)؛ RFC 9700 (ژانویه ی ۲۰۲۵)؛ RFC 8725 (فوریه ی ۲۰۲۰)؛ OpenID Connect Core 1.0؛ Microsoft Learn — OpenAPI در ASP.NET Core؛ GraphQL Learn؛ WSO2 — API Authentication Best Practices. پژوهش و بررسی موضوع در Perplexity انجام شد.
