سامانهٔ ثبت تخلف صنفی، ساختهشده در ۲۶ ساعت
از مخزن خالی تا سامانهٔ عمومی مستقر، با گردشکار تأیید عضو، دفتر رویداد فقطافزودنی و ۱٬۳۶۰ اعلان آزمون، در حدود ۲۶ ساعت.
فعالان یک صنف بارها از سوی طرفهای تجاری تکراری متضرر میشدند و هرکس جداگانه و دیر متوجه میشد. خواسته یک فهرست عمومی و قابلجستوجو از تخلفهای گزارششده بود که هرکس بدون حساب کاربری بخواند و فقط اعضای تأییدشده در آن بنویسند — با آنقدر پاسخگویی تنیده در مسیر نوشتن که به ابزار تخریب تبدیل نشود.
آنچه یک سامانهٔ عمومی اتهام باید درست انجام دهد
محصول یک صفحه است: فهرست قابلجستوجوی شرکتهایی که اعضای یک صنف برایشان تخلف گزارش کردهاند. هرکس بدون حساب کاربری میخواند. فقط اعضای تأییدشدهٔ مدیر مینویسند.
جذابیت مهندسی در خودِ فهرست نیست. در این است که ثبت عمومی اتهام یک ابزار حقوقی و اخلاقی است و تقریباً هر میانبُر راحتی در ساختش، بدترش میکند.
«اگر کسی اسم شرکت من را در این فهرست بگذارد و درست نباشد، چه اتفاقی میافتد؟»
این پرسش باید پیش از نوشتهشدن اولین سطر جواب داشته باشد. اینجا جواب چهار بخش دارد: گزارشدهنده پیش از نوشتن تأیید میشود، نام و شرکت گزارشدهنده عمومی کنار گزارش دیده میشود، گزارش منجمد است پس تاریخچهاش بیسروصدا بازنویسی نمیشود، و مسیر حذف بهشکل یک وضعیت در پایگاهداده وجود دارد. آخری فقط نیمهساخته است، که هم اینجا و هم در فهرست محدودیتها نوشته شده تا کسی خودش کشفش نکند.
جستوجو ILIKE است، و همین تصمیم است
جایگزینها پیش از نوشتن کد، در سند برنامه نوشته و رد شدند:
| گزینه | چرا نه |
|---|---|
تماممتن با tsvector / tsquery |
به پیکربندی جستوجوی متن و دیکشنری نیاز دارد؛ ریشهیابی فارسی آنقدر ضعیف است که در ازای یک ایندکس، یک مهاجرت و یک فایل پیکربندی نتیجهٔ بدتری میدهد |
شباهت سهنویسهای با pg_trgm |
برای تحمل غلط تایپی روی فهرستهای بزرگ است — مسئلهای که این سامانه هنوز ندارد و حل زودهنگامش یک مهاجرت ایندکس GIN هزینه دارد |
تطبیق زیررشته با ILIKE |
انتخابشده |
ارقام فارسی و لاتین از یک تابع مشترک در مسیر خواندن و نوشتن میگذرند، پس شرکتی که با ارقام فارسی ثبت شده با جستوجوی لاتین پیدا میشود، و یک آزمون سرتاسری هر دو جهت را ادعا میکند.
در این محصول هیچ مدلی، هیچ برداری، هیچ انبارهٔ برداری و هیچ تابع رتبهبندیای نیست. روی سامانهای که خروجیاش میتواند به توان تجاری یک شرکت آسیب بزند، تطبیقی که خواننده نتواند با چشم راستیآزمایی کند، بهبود نیست.
روایت هوش مصنوعی اینجا کاملاً دربارهٔ نحوهٔ ساخت است — و روایت خوبی هم هست؛ دقیقاً برای همین لازم نیست به ادعای محصول باد شود.
۲۶ ساعت، ۳۹ کامیت، از مخزن خالی
| خط کد · فایل | ۵۲٬۸۱۲ · ۲۸۶ |
| نسبت کد آزمون به کد تولیدی (بکاند) | حدود ۳٫۲ به ۱ |
| نقطهٔ پایانی · جدول · مهاجرت · enum | ۱۵ · ۸ · ۶ · ۸ |
| مسیر · کامپوننت · برچسب فارسی | ۹ · ۱۷ · ۱۸۲ |
| اعلان آزمون | ۷۰۷ بکاند + ۶۵۳ فرانتاند |
| کامیت · زمان سپریشده | ۳۹ · حدود ۲۶ ساعت |
| واحد کار برنامهریزیشدهٔ سبز | ۱۵ از ۱۵، در حدود ۸ ساعت و ۲۰ دقیقه |
پانزده برنامه در هفت موج مرتبشده بر اساس وابستگی اجرا شدند؛ هر وظیفهٔ بکاند روی پایگاهدادهٔ ایزولهٔ خودش، تا کارهای همزمان در هم نروند، و پس از هر موج یک بررسی یکپارچگی روی درخت ادغامشده.
سامانهٔ طراحی پیش از وجود هر خط کد اپلیکیشن ساخته و تأیید شد و کار صفحهها را مشروط به خودش کرد. برچسبهای فارسی اپلیکیشن عیناً از ماکاپهای طراحی کپی شدهاند و آزمونهای مرورگری همان رشتههای دقیق را میگیرند — پس اپلیکیشن و طراحی نمیتوانند از هم فاصله بگیرند بدون آنکه آزمونی قرمز شود.
دو تکه از کار امنیت که ارزش نامبردن دارند
تشخیص بازپخش توکن تازهسازی، با ابطال کل خانواده. توکنها بهشکل هش و کلیدخوردهٔ شناسه ذخیره میشوند و در هر تازهسازی میچرخند. ارائهٔ دوبارهٔ توکنی که قبلاً چرخیده، بهجای اشتباه، سرقت فرض میشود و کل خانوادهٔ توکن آن کاربر باطل میشود.
بخش جالب، ایرادی است که بازبینی گرفت: خودِ ابطال، توسط تراکنشی که ۴۰۱ را برمیگرداند برگشت میخورد. سامانه بازپخش را درست تشخیص میداد، خانواده را درست باطل میکرد، و بعد همهٔ آن را هنگام برگرداندن خطا درست از بین میبرد — یعنی تشخیص کار میکرد و اثری نداشت. این دقیقاً همان جنس ایرادی است که با خوشحالی منتشر میشود، از آزمون دستی رد میشود، و فقط وقتی پیدا میشود که کسی بهجای منطق، مرزهای تراکنش را بخواند.
مجوزدهی کاربر زنده را میخواند، نه ادعای توکن را. نگهبانها بهجای اعتماد به نقش و وضعیت داخل توکن، رکورد کاربر را دوباره بارگذاری میکنند؛ پس تعلیق یک عضو در درخواست بعدیاش اثر میکند، نه هر وقت که توکنش منقضی شد.
آنچه تمام نشده
سه شکاف باز، بیرودربایستی
TLS وصل نیست در مسیر استقرار کانتینری؛ پشته در لبه روی HTTP ساده گوش میدهد و انتظار پایاندهندهای جلوی خودش را دارد.
صف حذف طراحی شده و ساخته نشده. یک وضعیت «ردشده» در پایگاهداده هست و هیچ رابطی آن را نمیراند. روی سامانهٔ عمومی ثبت اتهام، این مهمترین شکاف سامانه است.
محدودکنندهٔ نرخ در حافظهٔ پروسه است، که روی یک نمونه درست است و روی دو نمونه غلط.
هر سه در مستندات خود مخزن ثبتاند. نیاوردنشان در یک پروندهکاری عمومی، انتخابِ پنهانکردن چیزی است که تیم خودش نوشته.
تصمیمها
جستوجو تطبیق زیررشتهای با تاشدن ارقام است. نه بیشتر.
گزینههای دیگر: جستوجوی تماممتن PostgreSQL با tsvector و tsquery، شباهت سهنویسهای با pg_trgm، جستوجوی معنایی روی ایندکس برداری
جستوجوی تماممتن به پیکربندی زبان و دیکشنری نیاز دارد و ریشهیابی فارسی آنقدر ضعیف است که در ازای یک ایندکس، یک مهاجرت و یک فایل پیکربندی، نتیجهٔ بدتری تحویل میدهد. شباهت سهنویسهای برای تحمل غلط تایپی روی فهرستهای بزرگ ساخته شده؛ مسئلهای که این سامانه هنوز ندارد و حل زودهنگامش یک مهاجرت ایندکس GIN هزینه دارد. عضوی که نام یک شرکت را جستوجو میکند، سطرهایی را میخواهد که همان چیزی را دارند که تایپ کرده، در شکلی که با خواندن قابل راستیآزمایی باشد.
ارقام فارسی و لاتین در هر دو مسیر خواندن و نوشتن از یک تابع مشترک عبور میکنند، پس پرسوجو با هر دو شکل رقم به یک شرکت میرسد و یک آزمون سرتاسری هر دو جهت را ادعا میکند.
هیچ هوش مصنوعیای در محصول نیست. اصلاً.
گزینههای دیگر: تطبیق معنایی نام شرکتها، طبقهبندی خودکار متن گزارش، دستیار گفتوگو روی سامانه
این یک سامانهٔ عمومی ثبت اتهام است. هر تطبیقی که خواننده میبیند باید با چشم قابل بررسی باشد و هر پیامدی باید به عضوی نامدار که آن را ثبت کرده برسد. تابع رتبهبندیای که کسی نتواند توضیحش بدهد ابزار درستی برای این کار نیست، و مدلی که میان دو شرکت پیوندی استنتاج کند، در عمل اتهام تولید میکند. هوش مصنوعی در این پروژه تماماً در نحوهٔ ساخت است و ادعای خلافش هم نادرست بود و هم روایت بهتر را هدر میداد.
گزارش بلافاصله منتشر میشود؛ دروازهٔ تأیید پیش از انتشار وجود ندارد.
گزینههای دیگر: بازبینی هر گزارش پیش از نمایش عمومی
سامانهای که به اندازهٔ یک صف بازبینی از واقعیت عقب باشد، دقیقاً در لحظهای که به کار میآید بیفایده است؛ و مدل پاسخگویی اینجا دروازهٔ عضویتِ تأییدشده در سمت نوشتن است، نه بازبین در سمت خواندن. قاعدهٔ رؤیتپذیری در یک گزارهٔ واحد زندگی میکند — «قابل نمایش اگر حذفنشده و ردنشده باشد»، عمداً نه «اگر تأییدشده باشد» — پس معکوسکردن سیاست یک تغییر تکخطی است نه بازبینی همهٔ پرسوجوها. هزینهٔ متناظرش صف حذف است که طراحی شده و ساخته نشده، و پایینتر همانطور که هست فهرست شده.
سامانه فقطافزودنی است. ویرایش وجود ندارد.
گزینههای دیگر: اجازهٔ اصلاح گزارش توسط ثبتکننده
گزارش، از جمله نوع تخلفش، پس از ثبت منجمد میشود. اصلاح یعنی یک ثبت تازه که کنار ثبت اصلی مینشیند. ویرایش محتوای تاریخی اجازه میداد تاریخچهٔ سامانه بیسروصدا بازنویسی شود، و روی یک سند عمومی اتهام، همین مهمترین حالت شکست است.
ایمیل از طریق outbox تراکنشی روی PostgreSQL، نه یک واسط پیام.
گزینههای دیگر: Redis با صف کار، ارسال درونخطی ایمیل در خودِ درخواست
سطر اعلان در همان تراکنشی نوشته میشود که تغییر وضعیتِ توصیفشده را ثبت میکند، پس ایمیل نه میتواند برای تغییری که برگشت خورده فرستاده شود و نه برای تغییری که ثبت شده جا بیفتد. یک واسط پیام دقیقاً همان شکاف را برمیگرداند و به سامانهای که حجم ایمیلش روزی چند پیام است، یک مؤلفهٔ عملیاتی اضافه میکند. توزیعکننده سطرها را با SKIP LOCKED برمیدارد، رندر را بیرون از قفل انجام میدهد، با شمارندهٔ تلاش دوباره امتحان میکند و در سقف تلاش با صدای بلند به نامههای مرده میفرستد.
ایمیل بازنشانی گذرواژه عمداً از outbox استفاده نمیکند: ماندگارکردن پیام یعنی ماندگارکردن توکن یکبارمصرف، که همان خاصیتی را از بین میبرد که هشکردن توکن برای آن وجود دارد.
محدودیتها
- TLS در مسیر استقرار کانتینری وصل نشده است. پشتهٔ compose در لبه روی HTTP ساده گوش میدهد و انتظار یک پایاندهندهٔ TLS جلوی خودش را دارد. این در مستندات استقرار بهعنوان درز باز ثبت شده و اولین چیزی است که باید بسته شود.
- صف حذف و بازبینی با جزئیات طراحی شده و ساخته نشده است. امروز رابطی برای حذف وجود ندارد؛ فقط وضعیت «ردشده» در پایگاهداده هست. روی سامانهٔ عمومی ثبت اتهام، این مهمترین شکاف سامانه است و ترجیح میدهیم خودمان بگوییم تا شما کشفش کنید.
- محدودکنندهٔ نرخ در حافظهٔ پروسه نگهداری میشود، پس روی یک نمونه درست است و بهمحض دو نمونه غلط. جابهجایی به یک انبارهٔ مشترک یک درز مستندشده است، نه کاری انجامشده.
- شناسهٔ ثبت هر شرکت، متن آزادی است که عضو گزارشدهنده تایپ میکند. این شناسه نرمال و برای یکتاسازی شرکتها استفاده میشود و هرگز در برابر هیچ مرجع ثبتی اعتبارسنجی نمیشود. سامانه نمیداند شرکتی وجود دارد یا نه.
- همهٔ دادهها عضومحورند. نه واردکنندهای هست، نه خزندهای، نه خوراک دادهٔ بیرونی و نه محتوای پیشبارگذاریشده. سامانه فقط همان چیزی را میداند که اعضایش به آن گفتهاند.
- شواهد فقط پیوند است. مدل داده، شواهد فایلی را با نوع محتوا و اندازه در خود دارد و انبارهٔ ذخیرهسازی پشت آن وصل نشده است.
- جستوجو زیررشته پیدا میکند. غلط تایپی را اصلاح نمیکند، ریشهیابی نمیکند، بر اساس ارتباط رتبه نمیدهد، و شرکتی را که نامش جور دیگری وارد شده پیدا نخواهد کرد.