دفترکل روی جدول انبار قدیمی، بدون بازنویسی سیستم
خلاصهٔ کاملتر
نویسنده تو یه مارکتپلیس B2B به اسم MaestroRumble با یه مشکل قدیمی روبهرو بوده: موجودی هر کالا فقط تو یه ستون عدد صحیح به اسم stocks.qty نگهداری میشد و حدود نوزده فایل مختلف — از کرونها و کنترلرها تا helpers.php — مستقیم میخوندنش، حسابوکتاب میکردن و دوباره مینوشتنش. هیچ ردی از تغییر نمیموند، پس وقتی انبار و تأمینکننده سر یه عدد اختلاف داشتن، هیچ منبع سومی برای داوری نبود.
کل بازطراحی روی یه معادله سواره: جمع دلتای همهٔ رکوردهای stock_movements باید دقیقاً با stocks.qty برابر باشه. نویسنده میگه عمداً سراغ event sourcing نرفته، چون اونجا وضعیت فعلی از روی رویدادها بازسازی میشه و بازنویسی نوزده نقطهٔ فراخوانی تو مسیر خرید شدنی نبود. بهجاش stocks.qty منبع حقیقت مونده و یه دفترکل کنارش نشسته که همیشه باید باهاش بخونه.
فاز اول همهٔ کمکردنها رو از یه در واحد رد کرده: کلاس StockMovementService. هر نوشتن داخل یه تراکنش با lockForUpdate() انجام میشه تا دو خرید همزمان روی آخرین واحد پشت سر هم اجرا بشن، و رکورد حرکت دقیقاً تو همون تراکنش ثبت میشه — وگرنه اگه ریکوئست بین دو نوشتن بمیره، دفترکل دروغ میگه. یه تغییر رفتار عمدی هم داره: بهجای صفر کردن بیصدای موجودی موقع فروش بیش از حد، حالا استثنا پرتاب میشه.
$stock = Stock::where('id', $stockId)->lockForUpdate()->firstOrFail();
if ($quantity > $stock->qty) {
throw new InsufficientStockException($stockId, $quantity, (int) $stock->qty);
}
$stock->qty -= $quantity;
$stock->save();فاز دوم اضافهشدنها (برگشت انبار، انتقال، خرید) رو هم از همون در رد کرده و ستون quantity_after رو آورده که موجودیِ بعد از هر حرکت رو نگه میداره؛ به گفتهٔ نویسنده این ستون تکراری نیست، چون جمع دلتاها ممکنه درست باشه ولی ترتیب رکوردها خراب باشه. فاز سوم هم گذشته رو «لنگر» میندازه: برای هر موجودی که جمع حرکتهاش با ستون نمیخونه، فقط یه رکورد به اندازهٔ اختلاف نوشته میشه، نه به اندازهٔ کل مقدار.
همین جزئیات باعث میشه دستور بکفیل idempotent باشه و چند بار اجرا کردنش چیزی رو خراب نکنه. کنارش یه دستور stock:reconcile هر شب ساعت ۲:۳۰ هم جمع دلتاها و هم quantity_after آخرین رکورد رو با ستون مقایسه میکنه و با خروجی غیرصفر شکست میخوره، ولی عمداً هیچی رو تعمیر نمیکنه؛ نویسنده معتقده کرونی که بیصدا اصلاح کنه، دیگه نمیتونه بهت بگه یکی داره از کنار در رد میشه. آخرش هم از پاکسازیای میگه که کامل طراحی کرد ولی نفرستاد: تبدیل ستونهای اعشاری t_qty و f_qty به عدد صحیح روی دیتای زنده، چون ریسک از دست رفتن برگشتناپذیر داده به تمیزی کد نمیارزید.
نکات کلیدی:
- اول یه نویسندهٔ واحد جلوی ستون بذار؛ قاعدهای که هر کسی بتونه دورش بزنه، قاعده نیست
- تغییر مقدار و ثبت تاریخچه باید تو یک تراکنش نوشته بشن
- گذشته رو بازسازی نکن، فقط با یه رکورد افتتاحیه لنگر بنداز
- دلتای بکفیل رو برابر اختلاف بگیر تا اجرای دوباره امن بمونه
- reconcile باید فقط گزارش بده، نه اینکه خودش درست کنه
- بعضی پاکسازیها ارزش ریسک روی دیتای زنده رو ندارن




