اتصال فروشگاه به شرکت حملونقل: طراحی API و مدیریت خطا
7 دقیقه مطالعه

برای اتصال به API حملونقل، قرارداد داخلی، timeout و retry با backoff و jitter، idempotency در ساخت مرسوله و آشتی وضعیت را طراحی کنید.
برچسب حمل، کد رهگیری، «بسته تحویل شد»: ظاهراً سه فراخوانی ساده به API شرکت حملونقل. مشکل از جایی شروع میشود که درخواست ساخت برچسب timeout میخورد و نمیدانید برچسب ساخته شده یا نه؛ یا وضعیت «تحویل شد» دو بار میرسد؛ یا شرکت حمل وضعیتهایی دارد که در سفارش شما معادل ندارند. این مقاله یک طراحی مشخص برای اتصال فروشگاه به حملونقل میدهد: قرارداد داخلی، مدیریت خطا، retry امن و آشتی وضعیت.
مرزها را صریح بگوییم. ما API هیچ شرکت حملونقل ایرانی (مثل پست یا پیکهای اینترنتی) را بررسی نکردهایم، پس نام فیلد، کد خطا یا نحوهٔ رهگیری هیچکدام را نقل نمیکنیم و فقط الگوی عمومی را میگوییم. الگوهای retry، backoff و idempotency از مستند Amazon Builders Library و Stripe آمدهاند؛ آنها دربارهٔ سرویسهای ابریاند، اما اصلشان برای هر API راه دور برقرار است. بخشهای «توصیهٔ نویسنده» مستقل از منبعاند. درباره قرارداد حمل، تعرفه یا الزامات حقوقی ارسال هم ادعایی نداریم.
قرارداد داخلی حملونقل
توصیهٔ نویسنده: کد سفارش نباید نام فیلدهای یک شرکت حمل را بشناسد. یک واسط داخلی کوچک بنویسید و هر شرکت را پشت آن بگذارید:
| عملیات | ورودی | خروجی داخلی | ریسک اصلی |
|---|---|---|---|
create_shipment(order) | آدرس، وزن/ابعاد، شناسهٔ سفارش | شناسهٔ مرسوله + برچسب/کد | تکرار ساخت (دو برچسب برای یک سفارش) |
get_tracking(shipment) | شناسهٔ مرسوله | فهرست رویدادهای وضعیت | تأخیر یا ترتیب ناهمگون |
cancel_shipment(shipment) | شناسهٔ مرسوله | نتیجهٔ لغو | لغو بعد از تحویل به حامل |
parse_webhook(request) | درخواست ورودی (اگر شرکت دارد) | رویدادهای وضعیت داخلی | جعل و تکرار |
وضعیتهای شرکت حمل را به وضعیتهای خودتان نگاشت کنید: مثلاً label_created، handed_over، in_transit، out_for_delivery، delivered، delivery_failed، returned. وضعیتی که نگاشت ندارد را دور نیندازید؛ آن را ذخیره کنید و هشدار بدهید تا بعداً نگاشت اضافه شود.

timeout: هر فراخوانی راه دور سقف دارد
مستند AWS میگوید برای هر فراخوانی راه دور باید timeout گذاشت، هم برای برقراری اتصال و هم برای خود درخواست. دلیلش این است که کلاینتی که منتظر میماند، منابعش (حافظه، رشته، اتصال) را نگه میدارد و اگر چندین درخواست همینطور بمانند، سرور منابعش را از دست میدهد. انتخاب عدد سخت است: timeout بلند کاراییاش را از دست میدهد و timeout کوتاه باعث میشود درخواستها بیدلیل retry شوند و ترافیک و تأخیر بالا برود. Amazon برای فراخوانیهای داخلی از تأخیر صدکهای سرویس مقصد (مثلاً p99.9) شروع میکند؛ اما همین مستند میگوید این روش برای مسیرهای اینترنتی با تأخیر شبکهٔ زیاد جواب نمیدهد و باید بدترین تأخیر معقول شبکه را هم حساب کرد.
در عمل برای شما: از اندازهگیری شروع کنید. زمان پاسخ واقعی API حمل را چند روز ثبت کنید، آنگاه timeout را بالاتر از رفتار عادی و پایینتر از تحمل کاربر بگذارید. اینکه عدد دقیق چقدر باشد از این مقاله برنمیآید، چون به API و زیرساخت شما بستگی دارد.
retry با backoff و jitter
AWS توضیح میدهد retry شکستهای گذرا و جزئی را پوشش میدهد، اما اگر سیستم مقصد بهخاطر اضافهبار شکست میخورد، retry بار را بیشتر میکند. راهحل backoff است: فاصلهٔ بین retryها زیاد میشود. و چون ترافیک معمولاً انفجاری است (مثلاً یک cron که هر شب در یک ساعت اجرا میشود)، اگر همهٔ کلاینتها همزمان retry کنند باز مشکل برمیگردد؛ برای همین jitter، یعنی مقدار تصادفی تأخیر، اضافه میشود. قواعد پیشنهادی (توصیهٔ نویسنده، مبتنی بر این اصول):
- retry فقط برای خطاهای گذرا (timeout، خطای شبکه، پاسخهای «بعداً امتحان کن») و نه برای خطاهای دائمی مثل «آدرس نامعتبر».
- سقف تعداد retry و سقف زمان کل مشخص باشد؛ بعد از آن سفارش به صف بررسی دستی برود.
- تأخیر بین retryها افزایشی و دارای jitter باشد.
- کار ارسال از مسیر پاسخ به کاربر جدا باشد: مشتری نباید منتظر API حمل بماند. سفارش را ثبت کنید و ساخت مرسوله را در صف انجام دهید.
idempotency: برچسب دوم نساز
AWS تأکید میکند timeout یا خطا الزاماً به معنی نبودن اثر جانبی نیست. اگر درخواست «بساز» timeout خورد، ممکن است سمت شرکت حمل انجام شده باشد. اگر تکرار اثر ناخواسته است، توصیهٔ AWS طراحی API بهصورت idempotent است. Stripe همین را با کلید idempotency نشان میدهد: درخواست پس از خطای اتصال امن تکرار میشود، پاسخ اولین درخواست برمیگردد و تغییر پارامترها خطا میدهد. برای حملونقل اینطور عمل کنید:
- اگر API شرکت حمل کلید idempotency یا «شناسهٔ مرجع پذیرنده» دارد، شناسهٔ سفارش خودتان را آنجا بدهید. اینکه هر شرکت چنین امکانی دارد یا نه را از مستند خودش بخوانید؛ ما بررسی نکردهایم.
- اگر ندارد، پیش از ساخت مرسوله، «تلاش ساخت» را با وضعیت
creatingدر دیتابیس خود ثبت کنید. بعد از timeout، قبل از تلاش دوباره، با شناسهٔ سفارش در API حمل جستوجو کنید (اگر چنین جستوجویی دارد) تا برچسب دوم نسازید. - برای هر مرسوله فقط یک رکورد فعال داشته باشید (قید یکتایی در دیتابیس).
وضعیت: webhook یا polling؟
اگر شرکت حمل webhook میفرستد، قواعد مستند Stripe بسیار آموزندهاند: رویدادها ممکن است تکراری برسند و ترتیب تضمینشده ندارند، پس شناسهٔ رویدادها را ثبت کنید و بر زمان رویداد برای ترتیبدهی اتکا نکنید؛ پیش از منطق سنگین سریع پاسخ 2xx بدهید و پردازش را در صف ناهمگام انجام دهید. اگر شرکت webhook ندارد، polling دورهای برای مرسولههای فعال لازم است. توصیهٔ نویسنده برای polling: فاصلهٔ استعلام را بر اساس وضعیت تنظیم کنید (مرسولهٔ تازهتحویلشدهبهحامل کمتر از مرسولهٔ در حال تحویل به مشتری)، و مرسولههای تحویلشده را از چرخه خارج کنید تا هزینهٔ فراخوانی و محدودیت نرخ API زیاد نشود.
ورود یک رویداد جدید باید این قاعده را دنبال کند: وضعیت را فقط «جلو» ببرید. اگر رویداد in_transit بعد از delivered رسید، ثبتش کنید ولی وضعیت سفارش را عقب نبرید. جدول گذارهای مجاز برای مرسوله بنویسید، همانطور که برای سفارش.
آشتی وضعیت سفارش و مرسوله
وضعیت سفارش را از وضعیت مرسوله جدا نگه دارید و با قاعدهٔ مشخص بههم وصل کنید: «وقتی مرسوله delivered شد، سفارش delivered میشود». اما انتظار داشته باشید گاهی ناهماهنگی پیش بیاید؛ مثلاً سفارش در انبار «ارسالشده» است ولی شرکت حمل میگوید «برچسب ساخته شده و تحویل نگرفتهام». یک کار دورهای بنویسید که این موارد را پیدا کند:
- سفارشهایی که بیش از آستانهای (که خودتان تعیین میکنید) در
label_createdماندهاند. - مرسولههایی که وضعیتشان مدتی تغییر نکرده است.
- مرسولههای بدون سفارش یا سفارشهای بدون مرسوله.
این موارد را به هشدار و صف بررسی ببرید. برای مبانی هشدار، Monitoring چیست؟ را ببینید. شاخصهای مفید: تعداد retry برای هر مرسوله، درصد خطای هر شرکت، و تعداد وضعیتهای بدون نگاشت.
دستهبندی خطاها و رفتار پیشنهادی
| نوع خطا | مثال | رفتار |
|---|---|---|
| گذرا | timeout، قطع شبکه، پاسخ «مشغول» | retry با backoff و jitter، با سقف |
| دائمی دادهٔ ورودی | آدرس یا کد پستی نامعتبر | بدون retry؛ سفارش به صف «اصلاح آدرس» |
| دائمی پیکربندی | کلید نامعتبر، حساب غیرفعال | هشدار فوری؛ توقف صف تا رفع |
| نامشخص | timeout روی ساخت | ابتدا جستوجو برای مرسولهٔ موجود، بعد تصمیم |
چکلیست پیادهسازی
- واسط داخلی حمل با نگاشت وضعیتها وجود دارد.
- همهٔ فراخوانیها timeout (اتصال و درخواست) دارند و مقدار آنها از اندازهگیری آمده است.
- retry فقط برای خطای گذرا، با backoff، jitter و سقف.
- ساخت مرسوله idempotent است یا پیش از تلاش دوباره جستوجو میشود.
- رویدادهای تکراری و نامرتب بیاثرند.
- وضعیت فقط به جلو میرود.
- کار دورهای آشتی و هشدار برقرار است.
- شرایط ارسال، تعرفه و الزامات حقوقی از قرارداد و منبع رسمی بررسی شدهاند، نه از این مقاله.
برای تصمیمهای معماری بزرگتر، انتخاب معماری اپلیکیشن وب را ببینید.
منابع و مراجع
- Timeouts, retries, and backoff with jitter — Amazon Builders' Library (AWS)، 2019-12-05.
- Idempotent requests (Stripe API) — Stripe.
- Receive Stripe events in your webhook endpoint — Stripe.
نویسنده
سا
سهیل ابراهیمپور بنیانگذار FutureForge است. روی طراحی محصول، معماری و استقرار نرمافزار سفارشی کار میکند.
یادداشتهای مرتبط
برای انتخاب مسیر فنی پروژهتان کمک میخواهید؟
از مسئله و محدودیتهایتان بگویید؛ اگر بتوانیم همراهیتان کنیم، برای گفتوگو هماهنگ میکنیم.




