وبهوک برای همگامسازی داده ساخته نشده
خلاصهٔ کاملتر
نویسنده میگه سه بار، تو سه شرکت و برای سه ارائهدهندهٔ مختلف، دقیقاً همون سیستم رو ساخته؛ سیستمی که هیچوقت اسم نداره و تو رودمپ هم نمیاد. حقیقتِ داده دربارهٔ مشتریهای خودت تو دیتابیس یکی دیگهست: کاربرها تو identity provider، اشتراکها تو Stripe، بانس ایمیلها جای دیگه. محصولت به یه کپی محلی از این داده نیاز داره، پس وبهوکها رو subscribe میکنی و یه نسخه نگه میداری. بار اول فکر میکرده کار یه بعدازظهره: یه روت که JSON رو پارس کنه و یه رکورد رو آپدیت کنه.
اون بعدازظهر شد یه هفته. اول تأیید امضا اومد، چون یه اندپوینت باز که دیتابیس رو تغییر میده یه حفرهست. بعد جدول dedup، چون تحویلها دوبار میرسن و مستندات با خوشحالی اسمش رو at-least-once میذارن. بعد بافر، چون گاهی membership.created قبل از user.created میرسه. بعد ایمپورت اولیه، چون وبهوک فقط از لحظهٔ اشتراک به بعد رو بهت میگه، و اون ایمپورت با رویدادهای زنده مسابقه میذاشت. آخر سر هم کرون تطبیق ساعت ۳ صبح.
به گفتهٔ نویسنده اون کرون در واقع یه اعترافنامهست: به کپیای که ساختم اعتماد ندارم و راهی ندارم بفهمم کِی غلطه، پس هر شب از صفر بازسازیش میکنم. یه بار یه مشتری ماهها قبل کنسل کرده بود و دیتابیس هنوز میگفت active؛ رویداد حذف اشتراک بین Stripe و اونها بخار شده بود و هیچجا قابل تشخیص نبود، چون لاگ نمیتونه درخواستی رو ثبت کنه که اصلاً نرسیده. نبودِ داده، حالتِ شکستِ بیصداست.
تحلیلش اینه که این ایراد هیچ ارائهدهندهای نیست، ایراد ماهیت خودِ وبهوکه. وبهوک برای «وقتی اتفاقی افتاد، کاری بکن» عالیه، ولی برای «کپی داده رو درست نگه دار» دقیقاً همون چیزهایی رو کم داره که لازمه: ترتیب، کامل بودن، بوتاسترپ و راه تأیید. نویسنده اینو یه بهینهٔ محلی میدونه: یه صنعت کامل — از Svix و Hookdeck تا EventBridge و SQS و کانکتورهای Fivetran — روی کف این درّه ساخته شده. تونل محلی مثل stripe listen هم فقط برای دور زدنِ جهتِ تحویلِ خودِ وبهوکه.
پیشنهادش برعکس کردن جهت پیکانه: بهجای اینکه ارائهدهنده بهت خبر بده، تو ازش بپرسی از آخرین باری که چک کردی چه چیز جدیدی داره. یعنی هر مجموعه یه URL داشته باشه که یه لاگ تغییرات مرتب و کرسر-محور از رویدادهای full-state برگردونه:
{"cursor":"01J9XR2M","operation":"upsert","object":{"id":"cus_123","plan":"pro"}}
{"cursor":"01J9XR2N","operation":"delete","object_id":"cus_099"}بدون کرسر بخونی از اول میخونی، که همون بوتاسترپته؛ با کرسر بخونی از همونجا ادامه میدی و کل وضعیت sync تو همون یه کرسره. اونوقت dedup لازم نیست چون هر رویداد state کامل رو داره و اعمالش یه upsert کورِ کلیدخورده به idه، بافر ترتیب حذف میشه، حذفها بهشکل tombstone تو لاگ میمونن و اندپوینت و امضا و تونل اصلاً بهوجود نمیان. نویسنده این ایده رو بهعنوان پیشنویس یه پروتکل به اسم SCROLL منتشر کرده و میگه دنبال مخالفته، نه تأیید.
نکات کلیدی:
- وبهوک برای تریگر کردن یه side effect ساخته شده، نه برای درست نگه داشتن کپی دادهٔ یه ارائهدهنده
- پشتهٔ همیشگی این کار — امضا، dedup، بافر ترتیب، ایمپورت اولیه، کرون تطبیق — نشونهٔ گیر افتادن تو یه بهینهٔ محلیه
- خطرناکترین حالت، حذفِ گمشدهست: هیچ لاگی نمیتونه درخواستی رو ثبت کنه که هرگز نرسیده
- لاگ مرتب همیشه سمت ارائهدهنده وجود داره؛ Stripe با /v1/events و WorkOS با Events API دارن همین رو بیرون میدن
- یه فید مرتب و کرسر-محور، بوتاسترپ و resume و tombstone رو یکجا حل میکنه و replica میتونه با checksum خودش رو تأیید کنه
- پیشنویس پروتکل با اسم SCROLL منتشر شده و میشه با یه shim روی وبهوکهای موجود شبیهسازیش کرد




