پرش به مطلب اصلی

اصول طراحی API های RESTful

مقدمه

این سند، اصول بنیادین و بهترین شیوه‌های طراحی API های RESTful در سیستم ما را تبیین می‌کند. به سوءتفاهم‌های رایج درباره معماری REST می‌پردازد و راهنمایی‌های روشنی برای مدل‌سازی منابع، به‌ویژه برای عملیات‌های پیچیده کسب‌وکار فراتر از عملیات‌های پایه CRUD ارائه می‌دهد.

مفاهیم اصلی

منبع (Resource) چیست؟

important

یک منبع یک اسم است، نه یک فعل. منبع نمایانگر یک مفهوم یا موجودیت در دامنه کسب‌وکار ماست که می‌تواند شناسایی، نام‌گذاری، آدرس‌دهی و بین سرور و کلاینت منتقل شود.

مثال‌های خوب: Cart (سبد خرید)، User (کاربر)، Payment (پرداخت)، Product (محصول)، Invoice (فاکتور)، Subscription (اشتراک)

مثال‌های نامناسب: QuickPay (پرداخت سریع)، VerifyPayment (تأیید پرداخت)، AddItemToCart (افزودن آیتم به سبد خرید)

یادداشت

یک منبع لزوماً یک جدول پایگاه داده نیست. منابع در لایه API و کسب‌وکار وجود دارند، در حالی که جداول، جزئیات پیاده‌سازی در لایه داده هستند. این دغدغه‌ها را از هم جدا نگه دارید.

زیرمنبع (Sub-resource) چیست؟

یک زیرمنبع، منبعی است که وجود آن تنها در بستر منبع دیگری معنادار است. این یک رابطه ترکیبی را نشان می‌دهد.

مثال‌ها:

  • یک Comment (نظر) بدون یک Post (پست) بی‌معناست. بنابراین، Comment یک زیرمنبع Post است.
  • یک CartItem (آیتم سبد خرید) نمی‌تواند بدون یک Cart (سبد خرید) وجود داشته باشد. بنابراین، CartItem یک زیرمنبع Cart است.

این رابطه به شکلی زیبا در URI ها منعکس می‌شود:

GET /posts/{postId}/comments  // دریافت تمام نظرات برای یک پست خاص
GET /carts/{cartId}/items // دریافت تمام آیتم‌های یک سبد خرید خاص

سوءتفاهم‌های رایج

"REST فقط برای عملیات CRUD است"

توجه

این یکی از بزرگترین سوءتفاهم‌ها درباره REST است. REST یک سبک معماری است، نه یک پروتکل محدود به چهار عملیات اصلی.

در حالی که عملیات CRUD به طور طبیعی با متدهای HTTP (POST، GET، PUT/PATCH، DELETE) نگاشت می‌شوند، REST می‌تواند عملیات‌های پیچیده را از طریق مدل‌سازی صحیح منابع مدیریت کند.

اصل راهنما: به جای تفکر درباره "فعل"، به این فکر کنید که آن فعل چه "اسمی" را ایجاد می‌کند یا وضعیت کدام "اسم" را تغییر می‌دهد.

مدل‌سازی منابع برای عملیات‌های پیچیده

مثال: تأیید پرداخت

به جای:

POST /payment/verify

از این استفاده کنید:

POST /payments/{paymentId}/verifications

این رویکرد:

  • یک سابقه حسابرسی از تلاش‌های تأیید ایجاد می‌کند
  • امکان عملیات یکتا (idempotency) را فراهم می‌کند (جلوگیری از پردازش تکراری)
  • با ایجاد یک منبع جدید، به اصول REST پایبند است

مطالعات موردی

پردازش پرداخت

مسئله

سیستم ما چندین جریان پرداخت دارد: QuickPay، JustPay و Verify که در حال حاضر به صورت زیر پیاده‌سازی شده‌اند:

  • v1/client/payment/quickpay
  • v1/client/payment/justpay
  • v1/client/payment/verify

این نقاط پایانی، منطق کسب‌وکار را به لایه API نشت می‌دهند و باعث ایجاد وابستگی شدید بین کلاینت و سرور می‌شوند.

راه‌حل

QuickPay (ترکیب ایجاد سبد خرید و پرداخت):

به جای یک نقطه پایانی که دو کار انجام می‌دهد، دغدغه‌ها را جدا کنید:

  1. کلاینت یک سبد خرید ایجاد می‌کند: POST /carts
  2. سرور با جزئیات سبد خرید و اقدامات ممکن پاسخ می‌دهد (HATEOAS)
  3. کلاینت پرداخت را آغاز می‌کند: POST /payments با {"cartId": "cart_123"}

JustPay (پرداخت برای منابع مختلف):

POST /payments

با بدنه درخواست که منبع را مشخص می‌کند:

// پرداخت برای یک سبد خرید
{ "sourceType": "cart", "sourceId": "cart_123" }

// پرداخت برای یک فاکتور
{ "sourceType": "invoice", "sourceId": "inv_456" }

Verify (تأیید پرداخت):

POST /payments/{paymentId}/verifications

این یک منبع تأیید ایجاد می‌کند که تلاش، نتیجه و هر داده مرتبط از بانک را ثبت می‌کند.

ملاحظات معماری

هماهنگ‌سازی سمت سرور

اطلاع

لایه هماهنگ‌سازی برای فرآیندهای کسب‌وکار باید در سمت سرور قرار داشته باشد، نه کلاینت.

مزایا:

  1. امنیت: منطق کسب‌وکار در سرور محافظت می‌شود
  2. سادگی: کلاینت‌ها "ساده" و سبک باقی می‌مانند
  3. یکپارچگی: قوانین کسب‌وکار به طور یکسان اعمال می‌شوند
  4. تکامل‌پذیری: فرآیندهای کسب‌وکار می‌توانند بدون به‌روزرسانی کلاینت تغییر کنند

الگوهای طراحی API

API های مبتنی بر منبع در مقابل API های مبتنی بر فرآیند

هدف: ساخت یک API انعطاف‌پذیر و قابل استفاده مجدد که می‌تواند توسط کلاینت‌های مختلف به روش‌های گوناگون مصرف شود.

رویکرد:

  • API منابع پایدار (اسامی) را ارائه می‌دهد: سبدهای خرید، پرداخت‌ها، کاربران
  • هماهنگ‌سازی توسط کلاینت انجام می‌شود
  • کلاینت توالی عملیات را می‌داند

چه زمانی استفاده کنیم: هنگام ساخت یک پلتفرم که کلاینت‌ها تصمیم می‌گیرند چگونه از بلوک‌های سازنده استفاده کنند.

راهنمای پیاده‌سازی

قراردادهای نام‌گذاری

  1. از اسامی جمع برای منابع مجموعه استفاده کنید: /users، /orders
  2. از نام‌های مشخص و خاص دامنه استفاده کنید: /products به جای /items
  3. از kebab-case برای منابع چند کلمه‌ای استفاده کنید: /shipping-addresses
  4. از افعال در URI ها خودداری کنید مگر برای منابع کنترلر خاص

متدهای HTTP

متدهدفمثال
GETبازیابی منابعGET /products
POSTایجاد یک منبع جدیدPOST /orders
PUTجایگزینی کامل یک منبعPUT /users/123
PATCHبه‌روزرسانی جزئی یک منبعPATCH /products/456
DELETEحذف یک منبعDELETE /carts/789

کدهای وضعیت

کدمعنیزمان استفاده
200OKGET، PUT، PATCH یا DELETE موفق
201CreatedPOST موفق که یک منبع ایجاد کرده است
204No Contentعملیات موفق بدون بدنه پاسخ
400Bad Requestورودی نامعتبر، خطاهای اعتبارسنجی
401Unauthorizedاحراز هویت مورد نیاز است
403Forbiddenاحراز هویت موفق بوده اما مجوز کافی نیست
404Not Foundمنبع وجود ندارد
409Conflictدرخواست با وضعیت فعلی در تضاد است
422Unprocessable Entityخطاهای اعتبارسنجی معنایی
500Internal Server Errorخطای سمت سرور

الگوهای پیشرفته

HATEOAS (هایپرمدیا به عنوان موتور وضعیت برنامه)

پیوندهایی به منابع مرتبط و اقدامات ممکن در پاسخ‌ها قرار دهید:

{
"id": "cart_123",
"items": [],
"totalPrice": 0,
"_links": {
"self": { "href": "/carts/cart_123" },
"items": { "href": "/carts/cart_123/items" },
"checkout": { "href": "/payments", "method": "POST" }
}
}

عملیات یکتا (Idempotency)

برای عملیات‌هایی که نباید به طور تصادفی تکرار شوند:

POST /payments
Idempotency-Key: 123e4567-e89b-12d3-a456-426614174000

سرور بررسی می‌کند که آیا درخواستی با این کلید قبلاً پردازش شده است یا خیر، قبل از پاسخ دادن.

ملاحظات معماری

جداسازی دغدغه‌ها

important

یک API با طراحی مناسب باید جداسازی روشنی بین موارد زیر داشته باشد:

  • کلاینت (Jupiter): نمایش و تعامل کاربر
  • لایه API: نمایش منابع و معنی‌شناسی HTTP
  • لایه سرویس (SUN): منطق کسب‌وکار، هماهنگ‌سازی و قوانین دامنه
  • لایه داده: ذخیره‌سازی و بازیابی

این جداسازی تضمین می‌کند:

  1. منطق کسب‌وکار در سمت سرور باقی می‌ماند
  2. کلاینت‌ها "ساده" و سبک باقی می‌مانند
  3. API ثابت می‌ماند در حالی که پیاده‌سازی می‌تواند تکامل یابد
  4. امنیت به درستی در هر لایه اعمال می‌شود

مدیریت فرآیندهای پیچیده کسب‌وکار

فرآیندهای پیچیده کسب‌وکار مانند QuickPay باید:

  1. در لایه سرویس پیاده‌سازی شوند
  2. از طریق نقاط پایانی منبع‌محور ارائه شوند
  3. در سمت سرور هماهنگ شوند، نه در کلاینت

این رویکرد پیچیدگی کلاینت را کاهش می‌دهد، امنیت را بهبود می‌بخشد و جداسازی تمیز دغدغه‌ها را حفظ می‌کند.

چارچوب تصمیم‌گیری برای رویکرد طراحی API

important

به طور پیش‌فرض از API‌های مبتنی بر منبع (REST خالص) استفاده کنید مگر اینکه دلیل قانع‌کننده‌ای برای استفاده از API‌های مبتنی بر فرآیند وجود داشته باشد.

از چک‌لیست زیر برای تعیین مناسب بودن API مبتنی بر فرآیند برای یک مورد استفاده خاص استفاده کنید:

  1. نیاز به اتمیسیتی

    • آیا عملیات نیاز به اجرای اتمیک به عنوان یک تراکنش واحد دارد؟
    • آیا تقسیم آن به چندین فراخوانی API خطر ایجاد وضعیت ناسازگار را افزایش می‌دهد؟
  2. پیچیدگی کلاینت

    • آیا پیاده‌سازی این مورد با استفاده از API‌های REST خالص، کد کلاینت را بیش از حد پیچیده می‌کند؟
    • آیا کلاینت نیاز به درک بیش از حد از فرآیند کسب‌وکار خواهد داشت؟
  3. کارایی شبکه

    • آیا پیاده‌سازی REST خالص نیاز به چندین رفت و برگشت دارد که به طور قابل توجهی بر عملکرد تأثیر می‌گذارد؟
    • آیا این عملیات به طور مکرر در محیط‌های با تأخیر بالا استفاده می‌شود؟
  4. ملاحظات امنیتی

    • آیا عملیات شامل قوانین کسب‌وکار حساسی است که نباید به کلاینت افشا شود؟
    • آیا افشای مراحل جداگانه، آسیب‌پذیری‌های امنیتی بالقوه ایجاد می‌کند؟
  5. قابلیت‌های کلاینت

    • آیا کلاینت در توانایی خود برای هماهنگ‌سازی جریان‌های پیچیده محدود است (مانند دستگاه‌های IoT، اپلیکیشن‌های موبایل ساده)؟

اگر به یک یا چند مورد از این سؤالات پاسخ "بله" می‌دهید، یک API مبتنی بر فرآیند ممکن است مناسب باشد. با این حال، حتی هنگام استفاده از API‌های مبتنی بر فرآیند:

  • نقطه پایانی باید همچنان یک منبع (اسم) ایجاد یا اصلاح کند
  • URI باید منعکس‌کننده منبعی باشد که ایجاد یا اصلاح می‌شود
  • از متدهای استاندارد HTTP و کدهای وضعیت استفاده شود
  • پاسخ باید شامل پیوندهایی به منابع مرتبط باشد (HATEOAS)

استراتژی مهاجرت

هشدار

هرگز نقاط پایانی API موجود که کلاینت‌ها به آن‌ها وابسته هستند را تغییر ندهید.

  1. نسخه‌بندی API خود: کار روی v4 را با اصول جدید شروع کنید
  2. پیاده‌سازی تدریجی: با منابع ساده‌تر شروع کنید
  3. برنامه منسوخ‌سازی: زمان‌بندی برای حذف تدریجی نسخه‌های قدیمی‌تر را اعلام کنید
  4. مستندسازی: راهنمای مهاجرت روشن برای کلاینت‌ها ارائه دهید

نتیجه‌گیری

API های RESTful که به درستی طراحی شده‌اند، به سیستم‌های قابل نگهداری‌تر، مقیاس‌پذیرتر و امن‌تر منجر می‌شوند. با مدل‌سازی صحیح منابع و نگه داشتن منطق کسب‌وکار در سمت سرور، یک جداسازی تمیز از دغدغه‌ها ایجاد می‌کنیم که هم به تیم توسعه و هم به مصرف‌کنندگان API سود می‌رساند.

به یاد داشته باشید: سرمایه‌گذاری اولیه در طراحی صحیح API، سود قابل توجهی در کاهش هزینه‌های نگهداری و افزایش انعطاف‌پذیری سیستم در طول زمان به همراه دارد.