برای مشاهده نتایج کلید Enter و برای خروج کلید Esc را بفشارید.

هواپیمای ملخی کوچکی که روی آسمان به سوپرجت تبدیل می‌شود (قسمت دوم)

پاییز ۹۵ بود که باسلام روی اینترنت شروع به فعالیت کرد. آن زمان باسلام تیم نرم‌افزار نداشت و وبسایتش با یک فریم‌ورک قدیمی به صورت برون‌سپاری شده ساخته شده بود. در قسمت اول این نوشته توضیح دادیم که پس از تشکیل تیم داخلی توسعه نرم‌افزار باسلام و معماری یک راهکار نرم‌افزاری جدید مطابق با نیازهای باسلام، در توسعه آن دچار مشکل شدیم، چرا که عمده وقت تیم روی پشتیبانی و توسعه فریمورک قبلی صرف می‌شد و نمی‌توانستیم وقت خوبی روی فریمورک جدید بگذاریم و آن را آماده کنیم. (اگر نرم‌افزار را به خودرو تشبیه کنیم، موتور و شاسی و سیستم تعلیق همان فریمورک هستند)

هدف اولیه تیم این بود که نرم‌افزار جدید باسلام را مبتنی بر فریمورک لاراول (Laravel) که معماری سرویس‌گرا را در آن تعبیه کرده‌ایم بنویسیم و در یک شب دیتاها را از ساختار قدیمی به ساختار جدید دیتابیس نگاشت کنیم و صبح که کاربران می‌آیند با سایت جدید روبه‌رو شوند. (نگاشت به معنای کپی داده‌ها از یک ساختار ذخیره‌سازی به یک ساختار ذخیره‌سازی متفاوت است.) اما این هدف هر روز دست‌نیافتنی‌تر می‌شد،‌ چون هرچه زمان می‌گذشت تعداد کاربران بیشتر می‌شد و ما مجبور می‌شدیم که دائما فریمورک قدیمی را بیشتر پشتیبانی کنیم و ارتقا بدهیم و به همان اندازه که روی این کار وقت می‌گذاریم از توسعه روی فریمورک جدید بازبمانیم.

توسعه فریمورک جدید به کندی پیش می‌رفت و منابع ما داشت روی سیستمی که قرار است به زودی کنار گذاشته شود صرف می‌شد. کم کم داشتیم از این روند احساس نگرانی می‌کردیم تا این که روزی استراتژی تیم فنی تغییر کرد. قرار شد سایت قدیمی باسلام مدت بیشتری زنده بماند و راهکار‌ نرم‌افزاری جدید هم به جای یک شبه متولد شدن، آهسته آهسته و جزء به جزء به دنیا بیاید. یعنی فریمورک جدید به صورت موازی در کنار فریمورک قدیمی شروع به کار کند و هر مسئولیتی را که فریمورک جدید به عهده می‌گیرد، فریمورک قدیمی از آن خلع شود و به این ترتیب در یک فرآیند نامحسوس و بلندمدت سیستم نرم‌افزاری جدید، عهده‌دار همه مسئولیت‌ها شود و اثری از سیستم قدیمی باقی نماند.

در این نوشته علاوه بر این که ابعاد فنی این مهاجرت را به زبان غیرفنی می‌نویسم، قدری توضیحات فنی جزئی را هم برای توسعه‌دهنده‌های نرم‌افزار اضافه می‌کنم.

کار کردن دو فریم‌ورک به صورت موازی

بالا آوردن دو فریمورک وب دقیقا در کنار هم می‌تواند مشکلات فنی ایجاد کند. برای مثال ممکن است تنظیمات وب سرور مخل کارکرد یکی از فریمورک‌ها باشد. یا اصلا فایل‌های اساسی دو فریمورک یا نسخه زیان برنامه‌نویسی و افزونه‌هایش متفاوت باشند و با همدیگر ناسازگاری داشته باشند. در باسلام فایل .htaccess هر فریمورک متفاوت است و روترهای هر یک از نرم‌افزارها تفاوت‌های اساسی‌ای دارد که امکان ادغام موقت دو فریمورک و اجرای تمام درخواست‌ها از یک مبدا را تقریبا غیرممکن می‌کرد. به همین دلیل یک قدم جلوتر رفتیم و فریمورک جدید را در یک مسیر مجزا روی وبسرور روی دامین n.basalam.ir لانچ کردیم. خوشبختانه فریمورک قدیمی با php7 که برای فریمورک جدید ضروری بود، مشکلی بر هم نزد و از این بابت راحت بودیم.

و اما طراحی ساختار جداول دیتابیس هر یک از فریمورک‌ها کاملا مجزا بود. البته هر دو دیتابیس مبتنی بر PostgreSQL بودند و ما جداول جدید را در یک ‌Schemaی جدید در همان دیتابیس سابق ایجاد کردیم و فریمورک جدید و ساختار دیتابیس جدید بدون مشکل در کنار هم شروع به کار کردند. در به روزرسانی دیتابیس به آخرین نسخه نیز طبیعتا مشکلی وجود نداشت.

پس تا اینجای کار، یک فریمورک جدید داریم و یک دیتابیس جدید که هر دو در یک سرور در کنار یکدیگر کار می‌کنند. اما بگذارید کمی به عقب برگردم و از مراحل ابتدایی معماری و پیاده‌سازی فریمورک جدید بنویسیم. بعد دوباره به روند اجرای موازی دو فریمورک در کنار یکدیگر می‌پردازیم.

دیوارکوب‌های سفالی

قصه از کجا شروع شده بود؟

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

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

کاری که مدیر فنی به کمک مشاور فنی و همفکری با همه بچه‌های تیم در ابتدا انجام می‌دهند شامل انتخاب تکنولوژی‌های زیرساختی مثل دیتابیس، زبان برنامه‌نویسی، فریمورک سمت سرور، معماری‌ها و طراحی‌های افزون بر فریمورک سمت سرور، فریمورک و ابزارهای سمت رابط کاربری، سیستم‌های کشینگ و چنین چیزهایی است. در مرحله بعد، بازتعریف دقیق ساختار موجودیت‌های سیستم و روابط آن‌ها با یکدیگر انجام می‌شود و در مرحله نهایی ابزارهای نرم‌افزاری میانی مثل ابزار تولید فرم، ابزار تولید دیتاگرید (لیست‌های گزارش‌گیری)، ابزار آپلود فایل و دیگر ابزارهای عمومی این چنینی انجام می‌شود.

همه این مراحل تئوری که انجام شد، به مرحله راه‌اندازی و کانفیگ (تنظیم) ابزارها و زیرساخت‌های از پیش‌آماده‌شده (مثل زبان برنامه‌نویسی، دیتابیس و فریمورک) می‌رسیم. بعد به صورت همزمان پیاده‌سازی مدل‌های دیتابیس و پیاده‌سازی و تست معماری طراحی شده برای فریمورک سمت سرور، آغاز می‌شود. تا اینجای کار هنوز در مراحل پیش از رونمایی هستیم و چیزی برای نمایش به عموم وجود ندارد. این مرحله هم مرحله دشوار و نسبتا زمان‌بری است.

معماری سرویس‌گرا در فریمورک جدید

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

بیایید سرویس را به یک ریموت کنترل یا ماشین حساب تشبیه کنیم تا مفهوم سرویس واضح‌تر شود. ماشین حساب را فرض کنید. ماشین حساب، یک دستگاه مستقل است که کارش به طور مشخص انجام محاسبات ریاضی است و به تنهایی و مستقلا می‌تواند با گرفتن ورودی و پردازش آن‌ها، خروجی‌هایی را ارائه کند. درون ماشین حساب یک مدار الکترونیکی، نرم‌افزار و منبع تغذیه وجود دارد اما کاربر برایش مهم نیست که آن داخل چه خبر است. او از طریق رابط کاربری (کلیدها) ۲ را ضرب در ۲ می کند و ۴ را تحویل می‌گیرد.

سرویس‌های نرم‌افزاری هم همین‌طور هستند. داخلشان کدهای پیچیده و زیادی وجود دارد ولی برنامه‌نویس از طریق یک رابط کاربری نرم‌افزاری (‌api) بدون اهمیت به این که داخل سرویس چه می‌گذرد ورودی‌ای را تحویل می‌دهد و خروجی‌ای را تحویل می‌گیرد. این سرویس هم به صورت مستقل می‌تواند هرجایی که برنامه‌نویس بخواهد استفاده شود، همانطور که ماشین حساب را می‌شود توی خیابان، فروشگاه، مدرسه، هواپیما و زیردریایی استفاده کرد.

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

  • بررسی دسترسی کاربر به صفحه
  • حذف دسترسی کاربر از صفحه
  • انتصاب کاربر به یک سمت
  • انفصال کاربر از یک سمت
  • درخواست دسترسی به یک صفحه

برای مثال برنامه نویس وقتی می‌خواهد از این سرویس استفاده کند به سادگی با ارائه پارامترهای لازم به سرویس پاسخ مناسب را دریافت می‌کند:

AuthorizationService::accessToPage($userId, $pageId);
AuthorizationService::applyToRole($userId, [“role” => “vendorManagement”, “vendorId” => $vendorId]);

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

مفهوم سرویس‌گرایی در سیستم عامل لینوکس نمود خیلی خوبی دارد. در سیستم عامل ویندوز غالبا نرم‌افزارها از سرویس‌های سوار شده روی سیستم عامل تشکیل نشده‌اند و هر نرم‌افزاری مجبور است مجموعه کارهایی را که انجام می‌دهد خودش به تنهایی به عهده بگیرد. یعنی مثلا اگر دو نرم‌افزار دارید که هر دو یکی از وظایف‌شان فشرده کردن تصاویر است، هر کدام به طور مستقل این کار را انجام می‌دهند. اما در لینوکس این کار به شکل دیگری است. شما می‌توانید نرم‌افزارهای متعددی داشته باشید که همگی با استفاده از یک سرویس فشرده سازی تصویر که در کل سیستم عامل قابل استفاده است این کار را انجام بدهند. وقتی این سرویس آپدیت می‌شود، انگار که بخشی از تمام نرم‌افزارهایی که از این سرویس استفاده می‌کرده‌اند آپدیت شده است. معماری سرویس‌گرا مزایای زیادی دارد، درگیر نکردن برنامه‌نویس با منطق داخلی سرویس، تمرکز بالای سرویس‌ها روی انجام کار مشخصی که دارند، استقلال از بخش‌های دیگر و توسعه‌پذیری منعطف از جمله مزایای پررنگ آن هستند.

نقشه مهاجرت

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

تصور می‌کردم این نوشته بخش پایانی «هواپیمای ملخی‌» باشد و به قسمت سومی نیاز نداشته باشیم، اما نوشته طولانی شد و باید ادامه آن را که کاملا فنی خواهد بود در پست دیگری منتشر کنیم. ان شا الله به زودی در قسمت بعدی این نوشته به صورت کاملا فنی جزئیات این نقشه مهاجرت را خواهیم نوشت. اگر تا اینجای کار نظری دارید دوست داریم بدانیم. حتما در قسمت سوم بر اساس نظر شما می‌توانیم اطلاعات بهتری ارائه کنیم.