دفتر سفارش یک فروشنده لوازم سفالگری، دو بار با دقیقاً همان درخواست صدا زده میشود:
POST /ordersدو بار، جواب ۲۰۱ و ۲۰۱، باlocation: /orders/1وlocation: /orders/2PUT /orders/1دو بار، جواب ۲۰۰ و ۲۰۰، و دفتر هنوز یک سفارش روی همان نشانی داردDELETE /orders/2دو بار، جواب ۲۰۴ و ۲۰۴، و یکGETبعدش جواب ۴۰۴ میدهد
POST دو بار دو سفارش ساخت و PUT دو بار یکی. این تفاوت بهدردبخورترین چیز این صفحه است و مسئله سلیقه نیست: GET و PUT و DELETE امناند برای فرستادن دوباره و POST نیست، و دلیل اهمیت این موضوع، تلاش دوباره بعد از یک تایماوت است نه مرتب بودن.
تایماوت را تصور کنید. کلاینت درخواستی میفرستد، جواب هرگز نمیرسد، و کلاینت نمیتواند بفهمد که سفارش ساخته شده و جواب گم شده، یا اصلاً هیچ اتفاقی نیفتاده. اگر فراخوانی PUT بوده دوباره میفرستد و دنیا عوض نمیشود. اگر POST بوده دوباره میفرستد و فروشنده دو پالت لعاب بار میزند.
و اینجا همان جایی است که خواننده ایرانی گرانترین نسخهاش را میبیند. درگاه پرداخت، بعد از پرداخت، کاربر را به آدرس بازگشت شما میفرستد و جدا هم یک اعلان میزند. اگر آن نقطه بازگشت یک POST بیمحافظ باشد که مستقیم سفارش را نهایی میکند، یک بار رفرش کردن صفحه یا یک اعلان تکراری، دو سفارش میسازد در حالی که یک بار پول گرفته شده. راهحلش هم دقیقاً همین صفحه است: نهایی کردن را با شناسه پرداخت بسازید تا فراخوانی دوم همان چیزی را ببیند که اولی ساخت.
شکل، یک اسم است و یک فعل
نشانی نام یک چیز است و روش میگوید با آن چه باید کرد. /orders/1 یک سفارش است؛ اینکه دارید میخوانیدش یا جایگزینش میکنید یا برش میدارید کار روش است نه کار نشانی. به همین دلیل /getOrder و /deleteOrder نشانه رابطی هستند که مثل فهرستی از توابع طراحی شده.
وضعیت نیمه دیگر قرارداد است و بیشتر از موفقیت و شکست حمل میکند. کد ۲۰۱ با یک هدر location برمیگردد که نام چیزِ ساختهشده را میگوید، پس فراخوان لازم نیست نشانی چیزی را که همین حالا ساخته حدس بزند. کد ۲۰۴ یعنی کار انجام شد و بدنهای برای فرستادن نیست. و ۵۰۰ تقصیر سرور است و ۴۰۰ تقصیر فراخوان، که همان تمایزی است که تعیین میکند چه کسی نیمهشب بیدار میشود.
DELETE هر دو بار ۲۰۴ داد، چون پاک کردن چیزی که نیست شکست نیست: فراخوان خواسته آن سفارش نباشد و نیست. برگرداندن ۴۰۴ در فراخوانی دوم از نظر فنی قابل دفاع است و باعث میشود هر کلاینتی مجبور شود یک شکست را موفقیت حساب کند. این قراردادها و لبههایشان منبع، فعل و کد وضعیت هستند، و عمدی انتخاب کردنشان نامگذاری، نسخهبندی، و خطاهایی که برمیگردانید.
صدا زدن مال کس دیگر، که کار رایجتری است
بیشتر این کار مصرف کردن است نه طراحی کردن، و خرابیهایش آنجا فرق میکند. کد ۴۲۹ یعنی آهستهتر، و هدر Retry-After میگوید چقدر، و رعایت کردن آن هدر تفاوت کلاینتی است که بازمیگردد با کلاینتی که مسدود میشود. حلقه تلاش دوباره بدون هیچ صبری میان تلاشها، یک محدودیت کوتاه را به قطعی ساخته دست خودتان تبدیل میکند.
بقیهاش بیزرقوبرق است و تعیین میکند این اتصال دوام میآورد یا نه: کلیدی که داخل کد نیست، یک تایماوت روی هر فراخوانی چون سرویسی که هرگز جواب نمیدهد از سرویسی که خطا میدهد بدتر است، و برنامهای برای اینکه جواب دیر بیاید نه غلط. آن کلید، محدودیت نرخ، و کنار آمدن با خرابی است. دو جایگزین این شکل هم دانستنشان میارزد: خواستن دقیقاً همان فیلدهایی که میخواهید پرسیدن دقیقاً همان فیلدهایی که میخواهید است، و خبر داده شدن بهجای مدام پرسیدن خبر گرفتن بهجای پرسیدن، که همه چیز این صفحه را وارونه میکند.
بعد از یک تایماوت
کلاینت کدام فراخوانیها را میتواند بیخطر دوباره بفرستد
پرسش این نیست که فراخوانی موفق شد یا نه. این است که اگر دو بار برسد چه اتفاقی میافتد.
دو بار فرستادهشده، از کلاینتی که بار اول جوابی ندید
- بلهGET /orders/1میخواند. هر چند بار هم که برسد چیزی عوض نمیشود.بله
- بلهPUT /orders/1جایگزین میکند. فراخوانی دوم همان وضعیت اولی را باقی میگذارد.بله
- بلهDELETE /orders/2دو بار ۲۰۴ داد. نبودن، نبودن است.بله
- خیرPOST /ordersسفارش دومی با نشانی دوم ساخت. دو پالت.خیر
- بررسی نشدهاینکه سرویسی که صدا میزنید اصلاً اینها را رعایت میکند یا نهاین یک قرارداد است نه یک اجبار، و رابطهای واقعی زیادی رعایتش نمیکنند.بررسی نشده
سطر آخر دلیل این است که یک کلاینت روی هر چیزی که نمیتواند دو بار اتفاق بیفتد یک شناسه یکتا میفرستد.
ابزار، و سالی که این حرف درست است
OpenAPI قالب توصیف است و رایگان. جای صفحهای از مستندات را گرفت که از پیادهسازی جدا میافتاد، و دلیل استفادهاش خود مستندات نیست: یک رابط توصیفشده، کلاینت و سرور ساختگی و مجموعه درخواست تولید میکند، پس قرارداد بررسی میشود نه توصیف.
یک کلاینت درخواست با مجموعه ذخیرهشده، که نسخه رایگان هم دارد، جای دوباره تایپ کردن یک فراخوانی با سه هدر را گرفت. ارزش واقعیاش همان شکست ذخیرهشده است: دقیقاً همان درخواستی که هفته پیش ۴۲۲ برگرداند، هفته بعد هنوز آنجاست.
تلاش دوباره با عقبنشینی نمایی و کمی تصادف، یک الگوست نه یک محصول، و هر زبانی کتابخانه رایگانش را دارد. جای همان حلقهای را گرفت که سه بار بیفاصله دوباره تلاش میکرد، که کدی است که یک وابستگی کند را به یک قطعی تبدیل میکند.
اشتباه: طراحی رابط دور صفحه
گیر اینجا فعلها نیستند. این است که نسخه اول یک رابط معمولاً شکل صفحهای را دارد که صدایش زده: یک نقطه پایانی بهازای هر صفحه، که دقیقاً همان فیلدهایی را برمیگرداند که آن صفحه لازم داشت، و به اسم همان صفحه نامیده شده.
کار میکند، و تا فراخوانِ دوم کار میکند. یک برنامه موبایل میرسد و همان سفارش را با چهار فیلد کمتر میخواهد؛ جواب یک نقطه پایانی دوم است. بعد یک گزارش آنها را دستهجمعی میخواهد؛ سومی. دفتر سفارش آن فروشنده یازده نقطه پایانی داشت که هفتتایشان یک سفارش را با شکلهای کمی متفاوت برمیگرداندند، و هیچ دوتایی سر قالب تاریخ با هم موافق نبودند.
عادتی که جلویش را میگیرد سر شروع هیچ خرجی ندارد: چیزهایی را که سامانه دارد نام ببرید، نه صفحههایی را که نشانشان میدهند، و بگذارید فراخوان بگوید از آنها چه میخواهد. چیزی که این صفحه پوشش نمیدهد این است که همه آنچه بالا آمد از بیرون میرسد و به هیچکدامش نمیشود اعتماد کرد، که امنیت وب است. ماشینی هم که یک نشانی را به یک تابع تبدیل میکند بکاند است، و همهاش زیر وب.