Amirali Yaghoutiمهندس ارشد نرم‌افزار

مطالعه موردی مهندسی

پلتفرم پیامک تراکنشی

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

مسئله تجاری

یک فروشگاه ساعت و جواهر از هشت جا پیامک می‌فرستاد: کد ورود یک‌بارمصرف، بازیابی سبد رهاشده و سفارش ناموفق، کوپن پاداش بعد از خرید، اعلان موجود‌شدن کالا، باشگاه مشتریان، میز تعمیرات، مارکت‌پلیس، و تأیید سفارش از یک افزونه‌ی فاکتور. یک حساب درگاه مشترک داشتند و هیچ‌چیز دیگر. بعضی مسیرها از افزونه‌ای رد می‌شد که غیرفعال شده بود، بعضی از SDK خود درگاه، و هیچ‌کدام نمی‌نوشت چه فرستاده و درگاه چه جواب داده.

شکست‌ها به‌طور ساختاری نامرئی بودند. درگاه برای الگویی که ثبت نشده یا هنوز تأیید نشده، HTTP 200 برمی‌گرداند با کد وضعیت ۴۲۴؛ پس یک الگوی تأییدنشده برای کدی که فقط وضعیت HTTP را نگاه می‌کند شبیه موفقیت است. هیچ endpointی الگوهای یک حساب را فهرست نمی‌کند و فهرست خروجی این حساب هم مسدود بود، پس تنها راه فهمیدن اینکه الگویی وجود دارد، فرستادن با آن بود. دو سیستم کامل هفته‌ها بی‌صدا مرده بودند.

آنچه تحویل دادم

  • یک تابع ارسال که واقعاً می‌رساند، در یک افزونه‌ی must-use، و بازنشستگی هر مسیر دیگر: مسیر افزونه‌ی پیامک غیرفعال مرده بود و کدی که هنوز صدایش می‌زد به همانی وصل شد که کار می‌کند.
  • یک لاگ ممیزی منفعل که به لایه‌ی HTTP وردپرس قلاب شده و هر ارسال به درگاه را ثبت می‌کند — شماره‌ی پوشانده، نام الگو، وضعیت درگاه و شناسه‌ی پیام — بی‌آنکه هیچ کد فرستنده‌ای مجبور باشد یادش بماند لاگ بنویسد.
  • یک تب مدیریتی که هر ۵۵ الگوی ثبت‌شده را با متن ثبت‌شده، دلیل ارسال و میزان استفاده نشان می‌دهد و آن‌ها را در چهار وضعیتی دسته‌بندی می‌کند که از بیرون عین هم‌اند: سیم‌کشی‌شده و با لاگ اثبات‌شده؛ فرستاده‌شده اما نامرئی برای لاگ، چون افزونه‌ی فاکتور مستقیم SDK درگاه را صدا می‌زند و صفرِ آنجا یعنی «نمی‌بینم» نه «نرفت»؛ ثبت‌شده اما بی‌سیم، که هیچ کدی نمی‌فرستدشان؛ و ثبت‌شده اما تأییدنشده، که با ۴۲۴ بی‌صدا شکست می‌خورند.
  • بازیابی سبد و سفارش ناموفق روی Action Scheduler به‌جای wp-cron، پشت رویداد ثبت پاپ‌آپ، محدودیت نرخ، بررسی خرید و قاعده‌ی کارت هدیه؛ پوشش سفارش‌های معلق‌به‌ناموفق و یک جاروی معلق‌ها؛ و رویداد «پیامک رفت» که به صف هشدار تلفن فروشگاه می‌رسد، چون «پیامک رفت» یک واقعیت است و «سبد رها شد» یک حدس.
  • کوپن تولد که هفت روز جلوتر صادر و با الگو فرستاده می‌شود، با وضعیت ارسال روی خود کوپن و دکمه‌ی ارسال مجدد در نمای تولدهای اپ اپراتور؛ کوپن پاداش بعد از خرید که یک‌بار به ازای هر سفارش روی وضعیت پرداخت‌شده یا تحویل‌شده می‌رود و موفقیت و شکستش روی سفارش ثبت می‌شود.
  • یک صفحه‌ی پیامک برای میز تعمیرات که نام هر الگو در آن قابل ویرایش است و دکمه‌ی آزمایش فقط به خود اپراتوری که فشارش می‌دهد پیام واقعی می‌فرستد، پس تغییر نام الگو دیپلوی نمی‌خواهد — به‌علاوه‌ی یک کلید اصلی که همه‌ی پیام‌های تعمیرات را می‌بندد و هیچ‌چیز دیگر را نه.

رویکرد فنی

  • لاگ ممیزی عمداً منفعل است. به لایه‌ی HTTP قلاب می‌شود نه به فرستنده‌ها، پس مسیر پیامک جدید همان روزی که منتشر می‌شود لاگ دارد و کسی لازم نیست یادش بماند. تنها نقطه‌ی کور — افزونه‌ای که توابع HTTP وردپرس را دور می‌زند — روی تب مدیریتی با اسم نوشته شده، نه زیر فرش.
  • قواعد توکن روی درگاه زنده اندازه گرفته شد، نه از مستندات: نیم‌فاصله در هر جایگاهی رد می‌شود و کل پیام را دور می‌ریزد؛ فاصله در توکن‌های ساده ممنوع و در توکن‌های فاصله‌دار مجاز است؛ توکن فاصله‌دار سی کاراکتر فارسی می‌فرستد و در سی‌ویک شکست می‌خورد؛ لینک چهل‌وهشت کاراکتری در توکن ساده جا می‌شود. تشخیص قبلی طول لینک را مقصر شکست بازیابی و اعلان موجودی دانسته بود. علت واقعی یک نیم‌فاصله داخل نام محصول بود و نام الگویی که هیچ‌وقت در پنل وجود نداشت.
  • کدهای وضعیت خاموش بالا می‌آیند. مسیر ارسال وضعیت درگاه را روی کوپن یا سفارش ثبت می‌کند، نمای اپراتور نشانش می‌دهد و نمای تولد می‌تواند دوباره بفرستد؛ ۴۲۴ حالا چیزی است که یک آدم می‌بیند، نه پیامکی که فروشگاه فرض می‌کند رفته.
  • گارد سرعت ادمین که درخواست‌های کند را حفاظت می‌کند، حالت امنی داشت که HTTP خروجی را برای cron و admin-ajax بدون شلیک قلاب HTTP قطع می‌کرد — پس ارسال‌های cron بدون هیچ خط لاگی شکست می‌خوردند. میزبان درگاه حالا در هر زمینه‌ای به‌عنوان حیاتی‌برای‌کسب‌وکار هاردکد شده؛ درسش که به‌عنوان قاعده نگه داشته شده: ارسال ناموفق بدون هیچ لاگی، پیش از آنکه معنی درگاه بدهد، معنی قطع میانی می‌دهد.
  • نام‌ها پیکربندی‌اند. نام الگو در درگاه قابل تغییر نیست و ویرایش متن تأییدشده آن را به بازبینی برمی‌گرداند، پس خانواده‌ی تعمیرات نام‌ها را موقع ارسال از یک option از راه فیلتر می‌خواند؛ ثابتِ داخل کد فقط کلید است.

نتیجه و شواهد

هر پیام تراکنشی فروشگاه حالا از یک کلید، یک تابع ارسال و یک لاگ می‌گذرد. الگوی بازیابی از زمان تغییر نام هر اجرای زمان‌بندی‌شده را تمیز رد کرده؛ ده‌دوازده کوپن تولد که زیر حالت امن گارد شکست خورده بودند بعد از اصلاح دوباره فرستاده شدند؛ الگوی موجود‌شدن کالا سیم‌کشی‌شده و با لاگ اثبات‌شده است، با نام محصول در توکن فاصله‌دار و لینک در توکن ساده. چهار وضعیت تب مدیریتی همان چیزی است که به یک بازبین نشان می‌دهم: صداقت این پلتفرم در این است که فرق «رفت»، «رفت اما من نمی‌بینم»، «سیم‌کشی نشده» و «تأیید نشده» را نشان می‌دهد، نه یک چراغ سبز. هیچ عدد نرخ تحویلی ادعا نمی‌شود؛ نتیجه‌ی هر شماره در درگاه چیزی نیست که فروشگاه کنترل کند.

اهمیت برای کارفرما

برای یک فروشگاه ایرانی، پیامک الگویی همان کانال تراکنشی است — کد ورود، یادآوری سبد، کوپن، «ساعتتان آماده است». وقتی دو سیستم می‌توانند هفته‌ها مرده باشند و همه‌ی داشبوردها سبز، هزینه‌اش یک گزارش باگ نیست؛ مشتری‌هایی است که هیچ‌وقت خبر نگرفتند. پلتفرمی که هر ارسال را لاگ می‌کند، هر وضعیت خاموش را بالا می‌آورد و وضعیت هر الگو را نشان می‌دهد، این کانال را از چیزی که فروشگاه امیدوار است کار کند، به چیزی که می‌تواند چک کند تبدیل می‌کند.

خلاصه-اجرایی-پروژه

خلاصه اجرایی خوانا

خلاصه_اجرایی {
  پروژه: "پلتفرم پیامک تراکنشی"
  وضعیت: در حال کار روی یک فروشگاه زنده‌ی WooCommerce
  درگاه: یک کلید، یک تابع ارسال؛ مسیر افزونه‌ی غیرفعال بازنشسته
  ممیزی: قلاب منفعل HTTP -> شماره‌ی پوشانده، الگو، وضعیت، شناسه‌ی پیام
  الگوها: ۵۵ ثبت‌شده -> اثبات‌شده | نامرئی برای لاگ | بی‌سیم | تأییدنشده(۴۲۴)
  اندازه‌گیری: نیم‌فاصله همه‌جا رد می‌شود؛ توکن فاصله‌دار در ۳۰ حرف فارسی می‌بُرد
  بازیابی: Action Scheduler، پشت پاپ‌آپ + محدودیت نرخ + خرید + کارت هدیه
  مناسبت: کوپن تولد ۷ روز جلوتر، وضعیت روی کوپن، ارسال مجدد در /op/
  پاداش: کوپن یک‌بار به ازای سفارش روی پرداخت/تحویل، نتیجه روی سفارش
  تعمیرات: نام الگو قابل ویرایش + دکمه‌ی آزمایش، تغییر نام بدون دیپلوی
  قاعده: «شکست بدون خط لاگ» => قطع میانی، نه درگاه
  نمایش عمومی: github.com/shiny-a2/mu-plugins-showcase
}

ارزش حرفه‌ای این پروژه

چهار وضعیت تب مدیریتی خودِ طراحی است. هرکسی می‌تواند یک تابع ارسال بنویسد؛ کار این بود که بپذیریم «رفت» سه دوست دروغین دارد و به اپراتور صفحه‌ای بدهیم که از هم جدایشان کند. این از اندازه‌گیری درگاه درآمد نه اعتماد به مستنداتش، و از لاگی که فرستنده‌ها نمی‌توانند فراموشش کنند.

ماجرای گارد همان نوع باگی است که در سیستم‌های پروداکشن انتظارش را دارم: نه کد پیامک، بلکه یک محافظ همسایه که کارش را در زمینه‌ی اشتباه انجام می‌دهد. پیدا کردنش یعنی نبودنِ خط لاگ را شاهد بگیریم، که حالا برای هر یکپارچه‌سازی بعدی که از cron اجرا می‌شود به‌عنوان قاعده نوشته شده.