dotNet full-stack web developer
سوال مصاحبهای که طرز فکر من در مورد طراحی سیستم را تغییر داد.
حدود شش یا هفت سال پیش، برای یک نقش mid-level در .NET مصاحبهای داشتم. یکی از سوالات از آن زمان تا به حال با من مانده است:
کاربر روی دکمهای در رابط کاربری کلیک میکند تا یک گزارش اکسل یا PDF تولید کند. تولید گزارش حدود پنج دقیقه طول میکشد (زمان میتواند دلخواه باشد). کاربر باید منتظر بماند تا تمام شود. چگونه این جریان را بهینه میکنید؟
در آن زمان، من روی چیزی که بهتر میدانستم تمرکز کردم: Performance.
شروع به فکر کردن در مورد چگونگی سریعتر کردن تولید گزارش کردم. شاید میتوانستم کوئریهای SQL را بهینه کنم، تبدیل دادهها را کاهش دهم یا بخشهایی از نتیجه را ذخیره کنم. اگر میتوانستم فرآیند را از پنج دقیقه به یک دقیقه کاهش دهم، این یک برد بزرگ به نظر میرسید. اما حتی اگر آن را پنج برابر سریعتر میکردم، کاربر هنوز باید منتظر میماند. اگر مرورگر از کار میافتاد، همه چیز را از دست میداد. اگر شبکه قطع میشد، فرآیند متوقف میشد. اگر تب را میبستند، تمام پیشرفت از بین میرفت. در واقع اصلاً مشکل عملکرد نبود، بلکه یک مشکل طراحی بود.
چیزی که آن موقع از دست دادم
با نگاهی به گذشته، متوجه میشوم که در طرز فکر «کد را سریعتر کنید» گیر افتاده بودم. نه اینکه مشکلی در این مورد وجود داشته باشد، بهینهسازی عملکرد یک مهارت ارزشمند است. چیزی که فوراً متوجه نشدم، مشکل بزرگتر بود. برنامه تمام این کارها را به صورت همزمان انجام میداد و کاربر را تا زمان اتمام کار گروگان نگه میداشت. در نهایت با چند اشاره از مصاحبهکننده، متوجه این موضوع شدم.

سوال بهتر این نبود که «چطور میتوانم این را سریعتر کنم؟»، بلکه این بود که «اصلاً چرا کاربر منتظر میماند؟» اگر تکمیل چیزی چند دقیقه (یا ساعت، روز) طول بکشد، نباید کاربر را مسدود کند. این کار باید در پسزمینه، خارج از جریان اصلی درخواست، در حالی که کاربر به کار خود ادامه میدهد، اتفاق بیفتد. با این حال، بهینهسازی خود کد را فراموش نکنید. پرسوجوهای پایگاه داده، پردازش دادهها و تولید فایل، همگی مهم هستند. شاید یک ایندکس از دست رفته، یک حلقه ناکارآمد یا یک کتابخانه بهتر برای ایجاد فایلهای اکسل وجود داشته باشد. اما این بهینهسازیها فقط بخشی از راهحل هستند، نه کل تصویر.
چگونه امروز آن را حل کنم
امروز، من هنوز با همان دکمه رابط کاربری شروع میکنم. کاربر روی "ایجاد گزارش" کلیک میکند، اما به جای انتظار، بکاند درخواست را میپذیرد، آن را در جایی ذخیره میکند (شاید به عنوان یک رکورد کار در پایگاه داده) و بلافاصله برمیگرداند. این جوهره ساخت APIهای ناهمزمان است. سپس کار توسط یک worker پسزمینه برداشته میشود. worker میتواند یک hosted service، یک Quartz job یا حتی یک تابع AWS Lambda باشد که توسط یکqueue message فعال میشود. worker کارهای سنگین را انجام میدهد: دریافت دادهها، ساخت فایل و آپلود آن در فضای ذخیرهسازی ابری.
پس از آماده شدن گزارش، worker وضعیت کار را به "تکمیل شده" بهروزرسانی میکند و به کاربر اطلاع میدهد. این میتواند یک ایمیل با لینک دانلود یا یک پیام SignalR در لحظه باشد که در برنامه نمایش داده میشود. لینک به گزارش ذخیره شده اشاره میکند که به طور ایمن از بکاند ارائه میشود.

حالا، کاربر منتظر یک درخواست HTTP طولانی مدت نیست. سرور اتصالات باز را برای چند دقیقه نگه نمیدارد. اگر چیزی با شکست مواجه شود، میتوان آن را به طور خودکار دوباره retry کرد. همچنین میتوانید در صورت نیاز پیشرفت را پیگیری کنید یا کار را لغو کنید. و اگر صد کاربر به طور همزمان درخواست گزارش کنند، سیستم میتواند بدون قفل شدن، scale کند. این تجربه سریعتر به نظر میرسد، حتی اگر زمان تولید گزارش واقعی تغییر نکرده باشد. زیرا در نهایت، کاربران به معیارهای عملکرد اهمیتی نمیدهند، بلکه به پاسخگویی اهمیت میدهند.
چرا هنوز از این سوال استفاده میکنم؟
چند سال بعد، من شروع به استفاده از همین سوال در مصاحبه با سایر توسعهدهندگان کردم. نه برای فریب دادن کسی، بلکه به این دلیل که طرز فکر افراد را آشکار میکند.
برخی از کاندیداها مستقیماً به سراغ بهینهسازی کد و کوئریها میروند، درست مثل من در آن زمان. این نشانه خوبی است که آنها راه خود را در تنظیم عملکرد میدانند. من میتوانم با سوالات فنی بیشتری در مورد الگوریتمها، ساختارهای داده یا بهینهسازی پایگاه داده ادامه دهم.
برخی دیگر لحظهای مکث میکنند و شروع به فکر کردن در مورد تجربه کاربری، پردازش پسزمینه و تحمل خطا میکنند. اینجاست که مکالمه واقعی شروع میشود: صفها، تلاشهای مجدد، اعلانها، اشتراکگذاری ایمن فایل و غیره. راههای زیادی وجود دارد که میتوانید این سناریو را به یک بحث گستردهتر طراحی سیستم تبدیل کنید. هیچ پاسخ درست واحدی وجود ندارد. اما تفاوت زیادی بین کسی که فقط روی کد تمرکز میکند و کسی که میتواند یک سیستم مقیاسپذیر طراحی کند، وجود دارد.
درس
وقتی برای اولین بار این سوال را شنیدم، به سریعتر کردن کد فکر کردم. حالا به بهتر کردن تجربه فکر میکنم. بهینهسازی یک پرسوجو یا حلقه میتواند کمک کند، اما انتظار، خرابی یا مقیاسپذیری را برطرف نمیکند. اگر بسیاری از کاربران یک گزارش را همزمان شروع کنند، یک طراحی sync به سرعت از کار میافتد.
یک جریان async، سیستم را پاسخگو و مقاوم نگه میدارد، صرف نظر از بار.
این تغییر از بهینهسازی توابع به طراحی سیستمهای مقیاسپذیر، تفاوت بین یک توسعهدهنده خوب و یک توسعهدهنده عالی است. اگر میخواهید عمیقتر به ساخت سیستمهای مقیاسپذیر بپردازید، Clean Architecture دقیقاً شما را در این مسیر راهنمایی میکند. یاد خواهید گرفت که چگونه برنامهها را ساختار (structure) دهید، نگرانیها (separate concerns) را از هم جدا کنید و سیستمهایی را طراحی کنید که بدون خرابی رشد کنند. امیدوارم مفید بوده باشد.
کانال من:
t.me/dotNetSchool
مطلبی دیگر از این انتشارات
چرا می خواهید پایگاه داده خواندن/نوشتن جداگانه داشته باشید؟
مطلبی دیگر از این انتشارات
آموزش طراحی سیستم: سه مفهوم سیستم های توزیع شده که باید بدانید
مطلبی دیگر از این انتشارات
ساخت APIهای وب مقیاس پذیر و ایمن با محدودیت نرخ در dotNET Core Web API با استفاده از AspNetCoreRateLimit