مرتضی دلیل
مرتضی دلیل
خواندن ۱۴ دقیقه·۲ سال پیش

آشنایی با REST و پیاده سازی GET/POST/PUT/DELETE

قبل از هر چیز بهتر است بدانیم که Best Practice چیست؟

معمولا به روش ها و الگوهای پایه و پرکاربرد که توسط تجربه یا تحقیق نتایج بهینه داده اند، Best Practice میگویند. این روش ها را میتوان به منظور استفاده چند باره استاندارد سازی و استفاده کرد.

این مقاله مقدمه مجموعه ایست که طی آن قصد دارم Best Practice ها در دات نت کور را بررسی کنیم. نام این مجموعه «معرفی Best Practice ها در Asp.Net Core» است.
این مجموعه با نگاهی به ویدیوی آموزشی Best Practice در Pluralsight که توسط Steve Smith تهیه شده، گردآوری شده است.
این مجموعه مقالات به شکل ویدیوی نیز در کانال تلگرامم قرار میگیره. خوشحال میشم برای با خبر شدن از مقالات و نوشته ها و ویدیوهای آموزشی در این کانال حضور داشته باشید و نظراتتون رو بنویسید.
https://t.me/mediapub_channel

فهرست مقالات مجموعه معرفی Best Practice های دات نت :


فواید استفاده از Best Practice ها چیست؟

  • جلوگیری از گیج شدن : به کمک این روش ها ما روش های عجیب، تکنیک های کاستوم و راه حل های یک دفعه ای را کاهش میدهیم. ما از روش های معروف و آزموده شده استفاده خواهیم کرد. api ما با این روش کاربرد و نگهداری ساده تر خواهد داشت.
  • بهره وری بیشتر : با نوشتن کدهای کمتر و اعمال اقدامات های با ثبات تر. به حداقل رساندن رویه های کاستوم در هر api یعنی تغییرات آینده بسیار سریعتر و آسانتر قابل پیاده سازی خواهند بود.
  • باگ های کمتر در کد : رعایت موارد فوق باعث جلوگیری از ایجاد باگ نیز خواهد شد. الگوی یکسان برای api ها و عدم انجام عملیات تکراری، منجر به رفتار از پیش تعریف شده و ساده تر از سمت کلاینت می شود. کیفیت نرم افزار با توجه به این قواعد بالاتر خواهد رفت.
  • کاهش مشکلات امنیتی : استفاده از Best Practice ها به لحاظ امنیتی نیز به کد ما کمک میکند. چون این روش ها از قبل تهیه و آزمایش شده اند. انجام محافظت های استاندارد در ورودی های سمت کلاینت و کنترل رفتار کلاینت مانع نفوذ به سیستم خواهد شد.

درباره Rest and Resources

«روی فیلدینگ» پایان نامه دکتری خود را در مورد شیوه معماری نرم افزارهای بر پایه شبکه در سال 2000 ارائه داد. (در دانشگاه UC Irvine) نام این عملیات را Rest گذاشت. بر این اساس میخواهیم روی روش کد نویسی با این شیوه صحبت کنیم.

اول باید بدانیم Rest دقیقا چه چیزی است و چه چیزی نیست.

یک شیوه معماری برای ایجاد سیستم های توزیع شده بر پایه hypermedia و designed around resource است.

مفهوم HyperMedia یعنی آمیزه ای از صدا و تصویر و متن و لینک ها. این اطلاعات non-linear هستند به این مفهوم که اطلاعات توسط تعدادی لینک قابل بررسی و وارسی کردن هستند. هر صوتی یک رسانه linear محسوب میشود، هر کتاب عادی به دلیل روند سنتی آن یک رسانه linear است. اما کتابی را تصور کنید که شما را مجبور به جابجا شدن بین صفحات میکند. شما تصمیم میگیرید تحت رفتاری non-linear بین صفحات جابجا شوید.

معماری Rest بر این فرضیه استوار است که HyperMedia در کنار data به منظور تسهیل کردن تعامل با سیستم ایجاد شده است. نیازی نیست کلاینت در لحظه همه داده های موجود را در اختیار داشته باشد. وجود لینک های مرتبط در Response همین hypermedia است. ممکن است گیج شده باشید. به عنوان مثال یک api برای ثبت اطلاعات مشتری تصور کنید که در ریسپانس علاوه برای شناسه مشتری، یک آدرس به منظور دسترسی به اطلاعات این مشتری برگرداند. اما تحت چه فرمتی؟

استاندارد واحدی برای نحوه تولید HyperMedia در پاسخ های Rest Api وجود ندارد. فرمت های متفاوتی وجود دارد. نمونه های از این روش ها در ادامه آورده شده :
HAL (Hypertext Application Language)
JSON LD (JSON for Linking Data)
Collection+JSON
Siren
JSON Hyper Schema

هر نوع آبجکت، دیتا یا سرویسی که توسط کلاینت قابل دسترسی باشد Resource می نامند. ریسورس ها یک identifier یا مشخص کننده دارند که یک URI معرف آن است. (شاید پیش از این معرف هر ریسورس را شناسه آن در نظر میگرفتید و آن را به نام Id میشناختید، URI معمولا بر اساس همین Id ساخته میشود)

طراحی URI برای ریسورس ها باید تحت اصول زیر باشد :

  1. از اسم در URI استفاده کنید و نه فعل.
  2. از اسم های جمع استفاده کنید و نه مفرد.
  3. برای مشخص کردن یک آیتم از ریسورس، از identifier استفاده کنید.

نکته 1 : در تهیه api اصل سادگی را دنبال کنید و از غافلگیر کردن کسی که از api شما برای client استفاده میکند پرهیز کنید.

نکته 2 : از نمایش آنچه غیر لازم است و detail داخلی محسوب میشود پرهیز کنید. ریسورس های api شما نباید هم نام با اسکیمای دیتابیس یا فرمت داکیومنت های ذخیره شده و یا Entity های شما باشند. باید جزئیات غیرلازم به عنوان ریسورس را کپسوله کنید.

نکته 3 : Rest تماما در مورد State است و نه Behavior. این جمله کلیدی برای درک مفهوم Rest است. همینکه همه چیز در api های شما باید با کلمات noun(اسم) نامگذاری شود و نه verb(فعل) همین موضوع را نشان میدهد.

نکته 4 : به متدهای HTTP که درخواست ها تحت آن ارسال میشوند verb یا متد میگویند.

انواع VERB

درخواست GET : مرورگر ها برای نمایش یک صفحه وب از این نوع ریکوئست استفاده میکنند. معمولا additional resource ها هم از نوع GET هستند. درخواست های GET باید Read Only و قابل کش شدن و بدون هیچ گونه ساید افکتی باشند.

درخواست POST : مرورگر ها برای سابمیت فرم معمولا از روش POST استفاده میکنند. سرور با این نوع درخواست یک resource جدید با اطلاعات فراهم شده می سازد.

درخواست PUT : برای آپدیت ریسورس ها استفاده میشود. در این حالت انتظار می رود آنچه به عنوان اطلاعات جدید برای آپدیت ریسورس ارسال میشود کل اطلاعات آن ریسورس باشد.(فقط بخشی که آپدیت میشود نباشد).

درخواست DELETE : برای حذف ریسورس از این نوع درخواست استفاده میشود.

درخواست PATCH : برای آپدیت ریسورس به شکلی که فقط بخشی از آن ارسال و همان بخش فقط آپدیت شود این نوع درخواست استفاده میشود.

باید از استفاده غیراستاندارد این درخواست ها پرهیز کرد. اگر از الگوی استاندارد استفاده کنید، API های شما قابل فهم تر برای دیگران و حتا برای ابزارهای توسعه خواهد بود.

ملحقات VERB

متد HEAD : بخشی از دیتاها که برای افزایش پرفورمنس و جلوگیری از ارسال همه ی ریسورس های وابسته می توان از این بخش استفاده کرد. در پاسخ این ریکوئست بدنه حاوی اطلاعات وجود ندارد.

متد OPTIONS : یک روش سبک و ساده برای دریافت اطلاعات سرور در مورد نوع ریکوئست هایی که سرور پشتیبانی میکند.

این جدول ملاحظاتی را که برای انتخاب مناسب متدها برای انجام یک عملیات باید انجام شود نشان میدهد. ستون idempotent یعنی آیا این متد با تکرار خود روی وضعیت ریسورسها بی اثر است؟ مثلا متد GET یا DELETE یک آیتم اگر بارها هم تکرار شوند تغییر جدیدی روی ریسورسها اعمال نمیکنند (آیتم همان بار اول حذف میشود و تکرار این حذف اثر خاصی ندارد). مثلا متد POST با هر بار صدا زده شدن، باید باعث تغییر ریسورسها شود و اطلاعات را دوباره ثبت کند.
ستون Safe یعنی آیا State ریسورس ها با هر متد دستخوش تغییر میشود یا نه. درصورتی یک متد Safe است که State ریسورسها را تغییر ندهد.

نکاتی که باید برای متدهای POST و PUT رعایت کرد

برای اضافه کردن یک ریسورس جدید از متد POST و برای جایگزینی یک ریسورس موجود با یک ریسورس جدید از متد PUT استفاده میشود.
متد POST معمولا روی یک مجموعه ریسورس اعمال میشود مثلا روی authors/ ، ولی متد PUT معمولا روی یک تک آیتم ریسورس اعمال میشود مثل 1/authors/ .

متد POST حاوی شناسه یا Id نیست و خود سرور مسئول تولید شناسه برای این ریسورس است ولی متد PUT باید مشخص کند که روی چه شناسه ای قرار است تغییرات اعمال کند. این شناسه در خود آدرس یا بدنه درخواست باید قید شود.

دو حالت برای برخورد با شناسه در آدرس و بدنه وجود دارد :
حالت اول : اگر سیستم ما اجازه تغییر Id را بدهد باید Id بدونه جایگزین Id قبلی شود، پس هنگام ارسال درخواست PUT شناسه ای که در آدرس ارسال میشود با شناسه موجود در بدنه متفاوت است.
حالت دوم : زمانیست که سیستم ما اجازه تغییر Id را ندارد. طبق استاندارد باید Id موجود در آدرس را با Id بدنه مقایسه کرد، اگر برابر نبودند خطای Bad Request صادر شود.


در تصویر فوق نمونه CRUD برای Author را میبینید که از کالکشن POSTMAN عکس گرفته شده است.

تصویر کامل Postman بعد از درخواست GET و نحوه مقداردهی به ورودی و کد خروجی 200
تصویر کامل Postman بعد از درخواست GET و نحوه مقداردهی به ورودی و کد خروجی 200


تصویر کامل Postman بعد از درخواست POST و نحوه مقداردهی به ورودی و کد خروجی 201
تصویر کامل Postman بعد از درخواست POST و نحوه مقداردهی به ورودی و کد خروجی 201


تصویر کامل Postman بعد از درخواست POST و وجود آدرس منطبق با ریسورس جدید در اتریبیوت لوکیشن موجود در هدر خروجی (Response Header)
تصویر کامل Postman بعد از درخواست POST و وجود آدرس منطبق با ریسورس جدید در اتریبیوت لوکیشن موجود در هدر خروجی (Response Header)


تصویر کامل Postman بعد از درخواست DELETE و نحوه مقداردهی به ورودی و کد خروجی 204 بدون بدنه خروجی
تصویر کامل Postman بعد از درخواست DELETE و نحوه مقداردهی به ورودی و کد خروجی 204 بدون بدنه خروجی


تصویر کامل Postman بعد از درخواست GET برای نمایش لیست و کد خروجی 200
تصویر کامل Postman بعد از درخواست GET برای نمایش لیست و کد خروجی 200


تصویر کامل Postman بعد از درخواست PUT برای به روز رسانی یک ریسورس و کد خروجی 200 . بدنه و آدرس هر دو مقدار شناسه را دارند.
تصویر کامل Postman بعد از درخواست PUT برای به روز رسانی یک ریسورس و کد خروجی 200 . بدنه و آدرس هر دو مقدار شناسه را دارند.


تصویر کامل Postman بعد از درخواست PATCH برای به روز رسانی بخشی از یک ریسورس و کد خروجی 200. بدنه به شکل قراردادی به شکلیست که نشان میدهد چه اتفاقی (replace) برای کدام پراپرتی (twitterAlias) و با چه مقدار جدیدی (jlerman) بیفتد.
تصویر کامل Postman بعد از درخواست PATCH برای به روز رسانی بخشی از یک ریسورس و کد خروجی 200. بدنه به شکل قراردادی به شکلیست که نشان میدهد چه اتفاقی (replace) برای کدام پراپرتی (twitterAlias) و با چه مقدار جدیدی (jlerman) بیفتد.



نکاتی در مورد Status Codes

کدهای دریافت شده در Response ها در پنج دسته یا کلاس جای میگیرند.

100-199 با هدف اطلاع رسانی و به ندرت استفاده میشوند.(Informational)

200-299 به منظور نمایش موفقیت در انجام عملیات خواسته شده استفاده میشوند.(Successful)

300-399 به منظور نمایش اینکه Resource در جای دیگری باید یافت شود استفاده میشند.(Redirection)

400-499 به منظور نمایش خطاهای مختلف مرتبط با کلاینت مثل Authentication یا Authorization یا پیدا نشدن ریسورس استفاده میشوند.(Client Error)

500-599 به منظور نمایش خطاهای سمت سرور استفاده میشوند.(Server Error)

نکته : کلاینت باید رفتار با این کدها را مشخص کند. رفتار با کدهای نامشخص باید بر اساس رقم اول صورت گیرد.(یعنی x00)

عملیات مختلف دارای status code های مختلف و مناسب خودشان در response هستند.

در ریکوئست های GET برای دریافت یک ریسورس :

پس از دریافت اطلاعات استتوس کد 200 یعنی موفق بوده است.
اگر ریسورس خواسته شده منتقل شده باشد کد 301 یا 302 برگردانده میشود.
اگر از Cache استفاده شده باشد، کد 304 به معنی Not Modified برگردانده میشود یعنی از همان کپی ریسورس قبلی برای این ریکوئست استفاده شده است.
اگر ریسورس درخواست شده موجود نباشد خطای 404 برگردانده میشود.
اگر در یک محدوده زمانی مشخص تعداد زیادی ریکوئست به سمت سرور ارسال شود خطای 429 به معنی تعداد ریکوئست زیاد صادر میشود.

در ریکوئست های POST برای ایجاد یک ریسورس :

اگر درخواست POST برای گرفتن Access Token بوده باشد و هیچ ریسورسی تغییر نکرده یا ساخته نشده باشد کد 200 برگردانده میشود.
اگر درخواست POST با ایجاد یک ریسورس جدید در سمت سرور و ایجاد یک لینک به عنوان URI برای دسترسی به آن ریسورس و قرار دادن آن در هدر با عنوان location صورت گیرد، در این حالت کد 201 برگردانده میشود.
اگر درخواست POST به شکل یک پردازش Async باشد یعنی ریکوئست به سرور رسیده ولی پراسس همان لحظه تکمیل نشده و به پایان نرسیده باشد( ریسپانس میتواند هدری حاوی URI به عنوان endpoint داشته باشد) کد 202 برگردانده میشود.
اگر درخواست POST هیچ نوع URIی در هدر ریسپانس به عنوان location نداشته باشد، کد 204 برگردانده میشود.(به ندرت چنین چیزی ممکن است)
اگر درخواست POST مانند ایجاد یک ریسورس جدید باشد اما برای caching طراحی شده باشد از کد 303 استفاده میشود.
اگر درخواست POST دارای فرمت ورودی غیرعادی باشد یا عملیات با خطا مواجه شود کد 400 برگردانده میشود.
اگر در یک محدوده زمانی مشخص تعداد زیادی ریکوئست به سمت سرور ارسال شود خطای 429 به معنی تعداد ریکوئست زیاد صادر میشود.

در ریکوئست های PUT یا PATCH برای تغییر یک ریسورس :

اگر درخواست PUT یا PATCH بدون body یا با body در ورودی باشد در هر صورت در حالت موفقیت کد 200 برگردانده میشود.
اگر تمایلی به برگرداندن body در خروجی ندارید در حالت موفقیت کد 204 مناسب تر است.
اگر ریسورسی ساخته شد که یک URI قابلیت اشاره به آن را داشت کد 201 مناسب است.
اگر ریکوئست منتج به یک پردازش طولانی (longer running process) شد و همان لحظه کامل نشد یا حتا شروع نشده باشد. (پاسخ میتواند حاوی یک URI باشد که کلاینت به کمک آن بتواند وضعیت را چک کند). در این حالت از کد 202 استفاده میشود.
اگر درخواست PUT یا PATCH هیچ بدنه یا هیچ نوع URIی در هدر ریسپانس به عنوان location نداشته باشد، کد 204 برگردانده میشود.
اگر درخواست PUT یا PATCH دارای فرمت ورودی غیرعادی باشد یا عملیات با خطا مواجه شود کد 400 برگردانده میشود.
اگر آدرس درخواست متناظر یا هیچ آدرس از پیش تعریف شده برای دسترسی به ریسورس ها نباشد خطای 404 برگردانده میشود.
اگر کلاینت نتواند یک ریسورس را تغییر دهد به دلیل اینکه نسبت به آخرین دریافت کلاینت، ریسورس تغییر کرده است در این حالت کد 409 برگردانده میشود. اطلاعات payload باید کلاینت را برای رفع این مشکل راهنمایی کند.
اگر در یک محدوده زمانی مشخص تعداد زیادی ریکوئست به سمت سرور ارسال شود خطای 429 به معنی تعداد ریکوئست زیاد صادر میشود.

در ریکوئست های DELETE برای حذف یک ریسورس :

اگر موفقیت آمیز باشد 200 برگردانده میشود.
اگر درخواست به شکل async و نیازمند به پردازش بلند مدت باشد و همان لحظه کامل نشود از کد 202 استفاده میشود.
اگر موفقیت آمیز باشد ولی چیزی برگردانده نشود از کد 204 استفاده میشود.(حالت رایج)
اگر آیتم در ریسورس پیدا نشود یا قبلا حذف شده باشد از کد 404 استفاده میشود.
اگر در یک محدوده زمانی مشخص تعداد زیادی ریکوئست به سمت سرور ارسال شود خطای 429 به معنی تعداد ریکوئست زیاد صادر میشود.

در ریکوئست های مرتبط با Security و Permission :

رایج ترین پاسخ در این نوع ریکوئست ها در زمانی که مجوز دسترسی وجود نداشته باشد 401 است.
اگر کلاینت اعتبار سنجی شود، یعنی از سمت سرور شناخته شده باشد اما مجوز دسترسی به ریسورس خواسته شده را نداشته باشد از کد 403 استفاده میشود.
اگر ریسورس پیدا نشود یا بَکند نخواهد به دلایل امنیتی وجود ریسورس را برای کلاینت فاش کند از کد 404 استفاده میشود. (مثلا برای دسترسی به یک ریپازیتوری پرایویت از Github شما خطای 404 میگیرید)

خطاهای هندل نشده :

تنها چیزی که برگردانده میشود خطای 500 است. احتمالا به همراه یک سری رفرنس برای یافتن دلیل خطا در فایل log در سرور.

نکته مهم :
هرگز خطا را در پاسخ ریکوئست هایی که ریسپانس با کد 200 دارند قرار ندهید یا به عبارت دیگر اگر StatusCode برابر با 2xx باشد یعنی هیچ خطایی رخ نداده است یا به عبارت دیگر اگر خطایی در انجام عملیات روی ریسورس رخ داد آن را با کد 2xx به کلاینت نمایش ندهید.
api هایی که با روش غلط پیاده سازی شده باشند برای استفاده در برنامه های دیگر به سختی قابل استفاده هستند چون بررسی دوباره ریسپانس های 2xx به منظور یافتن خطا و مدیریت آن باید انجام شود!
مثالی برای نمایش خطا وقتی برای یک ریکوئست از نوع POST یکی از پراپرتی های لازم، خالی ارسال شده است.
مثالی برای نمایش خطا وقتی برای یک ریکوئست از نوع POST یکی از پراپرتی های لازم، خالی ارسال شده است.
مثالی برای نمایش خطای 500 که با دستکاری endpoint ایجاد کردیم. در این حالت چون در حالت development هستیم خطا را نمایش میدهیم. با تنظیمات در کد میتوانیم نمایش جزئیات خطا را در حالت production محدود کنیم یا آن را با کد و متن مشخصی معادل کرده و آن را نمایش دهیم.
مثالی برای نمایش خطای 500 که با دستکاری endpoint ایجاد کردیم. در این حالت چون در حالت development هستیم خطا را نمایش میدهیم. با تنظیمات در کد میتوانیم نمایش جزئیات خطا را در حالت production محدود کنیم یا آن را با کد و متن مشخصی معادل کرده و آن را نمایش دهیم.


هرگز به کمک try/catch خطای 500 را به 200 تبدیل نکنید، چون استفاده از کدی که اینگونه نوشته شده مشکل خواهد بود و مدیریت خطا از سمت کلاینت غیراستاندارد خواهد شد.

نتیجه گیری :

  • برای متدهای CRUD خود اسم جمع انتخاب کنید و هر کدام به طور مستقل اسم جداگانه نداشته باشد و حتما با متدهای HTTP از هم تمیز داده شوند.
  • در خروجی POST حتما در بخش هدر، اتریبیوت Location را با آدرس مرتبط با آنچه به ریسورس ها افزوده اید تکمیل کنید و از یک URI استفاده کنید.
  • در متد های PUT حتما شناسه را در آدرس و هم در بدنه دریافت و مقایسه کنید. این دو شناسه در سیستم هایی که Id قابل تغییر نیست باید برابر باشند. در غیر اینصورت خطای Bad Request تولید کنید.
  • خطاهای خود را با کد 200 به سمت کلاینت نفرستید.


best practiceبرنامه نویسیrest apiسمت سرورسمت کلاینت
برنامه نویس و علاقمند به برنامه نویسی، سینما، فلسفه و هر چیزی که هیجان انگیز باشد. در ویرگول از روزمرگیهای مرتبط با علاقمندیهام خواهم نوشت. در توئیتر و جاهای دیگر @mortezadalil هستم.
شاید از این پست‌ها خوشتان بیاید