آنچه گذشت
پاییز ۹۵ بود که باسلام روی اینترنت شروع به فعالیت کرد. آن زمان باسلام تیم نرمافزار نداشت و وبسایتش با یک فریمورک قدیمی به صورت برونسپاریشده ساخته شده بود. در قسمت اول این نوشته توضیح دادیم که پس از تشکیل تیم داخلی توسعه نرمافزار باسلام و معماری یک راهکار نرمافزاری جدید مطابق با نیازهای باسلام، در توسعه آن دچار مشکل شدیم. عمده وقت تیم روی پشتیبانی و توسعه فریمورک قبلی صرف میشد و نمیتوانستیم وقت خوبی روی فریمورک جدید بگذاریم و آن را آماده کنیم (اگر نرمافزار را به خودرو تشبیه کنیم، موتور، شاسی و سیستم تعلیق همان فریمورک هستند).
هدف اولیه تیم این بود که نرمافزار جدید باسلام را مبتنی بر فریمورک لاراول (Laravel) که معماری سرویسگرا را در آن تعبیه کردهایم بنویسیم و در یک شب دیتاها را از ساختار قدیمی به ساختار جدید دیتابیس نگاشت کنیم و صبح که کاربران میآیند با سایت جدید روبهرو شوند (نگاشت به معنای کپی دادهها از یک ساختار ذخیرهسازی به یک ساختار ذخیرهسازی متفاوت است). اما این هدف هر روز دستنیافتنیتر میشد؛ هرچه زمان میگذشت تعداد کاربران بیشتر میشد و ما مجبور میشدیم که دائماً فریمورک قدیمی را بیشتر پشتیبانی کنیم و ارتقا بدهیم و به همان اندازه که روی این کار وقت میگذاریم از توسعه روی فریمورک جدید بازبمانیم.
فهرست:
توسعه فریمورک جدید به کندی پیش میرفت و منابع ما روی سیستمی که قرار بود به زودی کنار گذاشته شود صرف میشد. کمکم داشتیم از این روند احساس نگرانی میکردیم تا این که روزی استراتژی تیم فنی تغییر کرد. قرار شد سایت قدیمی باسلام مدت بیشتری زنده بماند و راهکار نرمافزاری جدید هم به جای یک شبه متولد شدن، آهستهآهسته و جزء به جزء به دنیا بیاید. یعنی فریمورک جدید به صورت موازی در کنار فریمورک قدیمی شروع به کار کند و هر مسئولیتی را که فریمورک جدید به عهده میگیرد، فریمورک قدیمی از آن خلع شود. به این ترتیب در یک فرآیند نامحسوس و بلندمدت، سیستم نرمافزاری جدید، عهدهدار همه مسئولیتها خواهد شد و اثری از سیستم قدیمی باقی نمیماند.
در این نوشته علاوه بر این که ابعاد فنی این مهاجرت را به زبان غیرفنی مینویسم، قدری توضیحات فنی جزئی را هم برای توسعهدهندههای نرمافزار اضافه میکنم.
دو فریمورک موازی
بالا آوردن دو فریمورک وب دقیقاً در کنار هم، میتواند مشکلات فنی ایجاد کند. برای مثال ممکن است تنظیمات وب سرور مخل کارکرد یکی از فریمورکها باشد؛ یا اصلا فایلهای اساسی دو فریمورک، یا نسخه زیان برنامهنویسی و افزونههایش متفاوت و ناسازگار باشند. در باسلام فایل .htaccess هر فریمورک متفاوت است و روترهای هر یک از نرمافزارها تفاوتهای اساسیای دارد که امکان ادغام موقت دو فریمورک و اجرای تمام درخواستها از یک مبدأ را تقریباً غیرممکن میکند. به همین دلیل یک قدم جلوتر رفتیم و فریمورک جدید را در یک مسیر مجزا روی وبسرور روی دامین n.basalam.ir لانچ کردیم. خوشبختانه فریمورک قدیمی با php7 که برای فریمورک جدید ضروری بود، مشکلی بر هم نزد و از این بابت راحت بودیم.
و اما طراحی ساختار جداول دیتابیس برای هر فریمورک کاملاً مجزا بود. البته هر دو دیتابیس مبتنی بر PostgreSQL بودند و ما جداول جدید را در یک Schemaی جدید در همان دیتابیس سابق ایجاد کردیم و فریمورک جدید و ساختار دیتابیس جدید بدون مشکل در کنار هم شروع به کار کردند. در بهروزرسانی دیتابیس به آخرین نسخه نیز طبیعتاً مشکلی وجود نداشت.
پس تا اینجای کار، یک فریمورک جدید داریم و یک دیتابیس جدید که هر دو در یک سرور در کنار یکدیگر کار میکنند. اما بگذارید کمی به عقب برگردم و از مراحل ابتدایی معماری و پیادهسازی فریمورک جدید بنویسیم. بعد دوباره به روند اجرای موازی دو فریمورک در کنار یکدیگر میپردازیم.
قصه از کجا شروع شده بود؟
میگویند سختترین قسمت یک کار، آغاز آن است. راهاندازی یک پروژه نرمافزاری در یک استارتاپ در حال رشد، کاملاً تابع همین قاعده است. یک جنبه از این دشواری به پیچیدگیهای مهندسی و دشواری انتخابها و تصمیم برمیگردد. جنبه دیگر آن مربوط به پاسخگو بودن به دیگر اعضای استارتاپ است. اعضا انتظار دارند بدانند که شما دقیقاً دارید چه کار میکنید و چه موقع درخواستهای فنیشان را میسازید.
وقتی در حال معماری سیستم جدید بودیم هیچکدام از اعضای استارتاپ نمیدانستند ما دقیقاً مشغول چه کاری هستیم. معماری هم چیز ملموس و مشهودی نیست که بشود به غیر کدنویسها نشانش داد. اگر میخواستیم نشان بدهیم باید یک سری تصمیمها و طراحیهای کاملاً فنی را که اکثرش برای غیرفنیها قابل درک نیست توضیح میدادیم. به دلیل کمبود وقت، ما این کار را در اولویت نگذاشتیم، ولی امروز که به گذشته نگاه میکنیم میبینیم این گزارش، هرچند برای بچهها نامفهوم و غیرقابل درک بود، اما میتوانست مفید باشد و آنها را از ابهام و بیاطلاعی و نگرانی بیرون بیاورد و باید این گزارش را میدادیم.
کاری که مدیر فنی به کمک مشاور فنی و همفکری با همه بچههای تیم در ابتدا انجام میدهند شامل انتخاب تکنولوژیهای زیرساختی مثل دیتابیس، زبان برنامهنویسی، فریمورک سمت سرور، معماریها و طراحیهای افزون بر فریمورک سمت سرور، فریمورک و ابزارهای سمت رابط کاربری، سیستمهای کشینگ و چنین چیزهایی است. در مرحله بعد، بازتعریف دقیق ساختار موجودیتهای سیستم و روابط آنها با یکدیگر انجام میشود و در مرحله نهایی ابزارهای نرمافزاری میانی مثل ابزار تولید فرم، ابزار تولید دیتاگرید (لیستهای گزارشگیری)، ابزار آپلود فایل و دیگر ابزارهای عمومی این چنینی انجام میشود.
همه این مراحل تئوری که انجام شد، به مرحله راهاندازی و کانفیگ (تنظیم) ابزارها و زیرساختهای ازپیشآمادهشده (مثل زبان برنامهنویسی، دیتابیس و فریمورک) میرسیم. بعد به صورت همزمان پیادهسازی مدلهای دیتابیس و پیادهسازی و تست معماری طراحی شده برای فریمورک سمت سرور، آغاز میشود. تا اینجای کار هنوز در مراحل پیش از رونمایی هستیم و چیزی برای نمایش به عموم وجود ندارد. این مرحله هم مرحله دشوار و نسبتاً زمانبری است.
معماری سرویسگرا در فریمورک جدید
بعد از این که معماری فریمورک انجام شد، امکان توسعه بخشهای کاربردی تازه شروع میشود. در فریمورک جدید باسلام معماری سرویسگرا پیادهسازی شد. در این نوع معماری، توابع و کلاسهای عمومی به قوت خود برقرارند، اما از ترکیب همین کلاسها و توابع با کدهای اختصاصی که به منظور انجام یک یا چند وظیفه خاص توسعه داده شدهاند، سرویسهای مستقلی تولید میشود.
بیایید سرویس را به یک ریموت کنترل، یا ماشینحساب تشبیه کنیم، تا مفهوم سرویس واضحتر شود. ماشینحساب را فرض کنید. ماشینحساب، یک دستگاه مستقل است که کارش به طور مشخص انجام محاسبات ریاضی است. ماشینحساب به تنهایی و مستقلاً میتواند با گرفتن ورودی و پردازش آنها، خروجیهایی را ارائه کند. درون ماشینحساب یک مدار الکترونیکی، نرمافزار و منبع تغذیه وجود دارد اما کاربر برایش مهم نیست که آن داخل چه خبر است. او از طریق رابط کاربری (کلیدها) ۲ را ضرب در ۲ می کند و ۴ را تحویل میگیرد.
سرویسهای نرمافزاری هم همینطور هستند. داخلشان کدهای پیچیده و زیادی وجود دارد، ولی برنامهنویس از طریق یک رابط کاربری نرمافزاری (api) بدون اهمیت به این که داخل سرویس چه میگذرد ورودیای را تحویل میدهد و خروجیای را تحویل میگیرد. این سرویس هم به صورت مستقل میتواند هرجایی که برنامهنویس بخواهد استفاده شود، همانطور که ماشین حساب را میشود توی خیابان، فروشگاه، مدرسه، هواپیما و زیردریایی استفاده کرد.
برای مثال میتوانیم سرویسی داشته باشیم که وظیفهاش مدیریت سطح دسترسی کاربران است. این سرویس پیچیدگیهای داخلی خاص خودش را دارد، ولی طراح سرویس، رابط کاربری آن رابه صورت کاملاً سادهای میسازد و برنامهنویسهای دیگر بدون اینکه لازم باشد از داخل سرویس سر در بیاورند میتوانند نیازهایشان را برطرف کنند. این سرویس مثلاً چنین وظایفی را انجام میدهد:
- بررسی دسترسی کاربر به صفحه
- حذف دسترسی کاربر از صفحه
- انتصاب کاربر به یک سمت
- انفصال کاربر از یک سمت
- درخواست دسترسی به یک صفحه
برای مثال برنامه نویس وقتی میخواهد از این سرویس استفاده کند به سادگی با ارائه پارامترهای لازم به سرویس پاسخ مناسب را دریافت میکند:
AuthorizationService::accessToPage($userId, $pageId);
AuthorizationService::applyToRole($userId, [“role” => “vendorManagement”, “vendorId” => $vendorId]);
در شبهکد فوق، در خط اول، برنامه نویس دسترسی یک کاربر به یک صفحه را بررسی می کند. در خط دوم هم یک کاربر را به سِمت مدیریت یک غرفه درمیآورد. در پشت این دستورات ساده، منطق پیچیدهایست، اما به این شکل امکان استفاده از آن فراهم شده است. چنین دستوری در کل فریمورک قابلیت استفاده شدن دارد؛ بدون ذرهای رنج و دردسر. حسن بزرگی که سرویسها دارند این است که وقتی منطق داخلیشان تغییر کند، هیچ جای سیستم به هم نمیریزد؛ چرا که رابط کاربری آنها تغییری نکرده است. همچنین وقتی که باگی در عملکردشان کشف میشود، با حل شدن آن مورد، در تمام سیستم آن باگ رفع میشود.
مفهوم سرویسگرایی در سیستم عامل لینوکس، نمود خیلی خوبی دارد. در سیستم عامل ویندوز غالباً نرمافزارها از سرویسهای سوارشده روی سیستم عامل تشکیل نشدهاند و هر نرمافزاری مجبور است مجموعه کارهایی را که انجام میدهد خودش به تنهایی به عهده بگیرد. یعنی مثلاً اگر دو نرمافزار دارید که هر دو یکی از وظایفشان فشرده کردن تصاویر است، هر کدام به طور مستقل این کار را انجام میدهند. اما در لینوکس این کار به شکل دیگری است. شما میتوانید نرمافزارهای متعددی داشته باشید که همگی با استفاده از یک سرویس فشردهسازی تصویر که در کل سیستم عامل قابل استفاده است این کار را انجام بدهند. وقتی این سرویس آپدیت میشود، انگار که بخشی از تمام نرمافزارهایی که از این سرویس استفاده میکردهاند آپدیت شده است. معماری سرویسگرا مزایای زیادی دارد، درگیر نکردن برنامهنویس با منطق داخلی سرویس، تمرکز بالای سرویسها روی انجام کار مشخصی که دارند، استقلال از بخشهای دیگر و توسعهپذیری منعطف از جمله مزایای پررنگ آن هستند.
نقشه مهاجرت
توضیح دادیم که بعد از انتخاب تکنولوژیهای زیرساختی، معماری فریمورک، نصب و کانفیگ ابزارها و خلاصه شکل دادن فونداسیون ساختمان نرمافزار، امکان توسعه بخشهای کاربردی فراهم میشود. توسعه بخشهای کاربردی روی فریمورک جدید باسلام به طوری که به صورت موازی و همزمان با فریمورک قدیمی کار کند نیازمند مهندسی یک نقشه جامع بود که به دلیل پیچیدگی اجرا، سطح استرس و فشار توسعه را بسیار بالاتر میبرد. این نقشه مهندسی جامع که از این به بعد از آن با عنوان «نقشه مهاجرت» یاد میکنیم، تجربه فنی جالب و حساسی بود که وارد آن شدیم و با موفقیت اجرا شد. همین الآن که این نوشته منتشر شده است، باسلام روی دو فریمورک کاملاً متفاوت کار میکند که به صورت موازی و همزمان در حال سرویس دادن به کاربراناند.
تصور میکردم این نوشته بخش پایانی «هواپیمای ملخی» باشد و به قسمت سومی نیاز نداشته باشیم، اما نوشته طولانی شد و باید ادامه آن را که کاملاً فنی خواهد بود در پست دیگری منتشر کنیم. ان شاء الله به زودی در قسمت بعدی این نوشته به صورت کاملاً فنی جزئیات این نقشه مهاجرت را خواهیم نوشت. اگر تا اینجای کار نظری دارید دوست داریم بدانیم. حتماً در قسمت سوم بر اساس نظر شما میتوانیم اطلاعات بهتری ارائه کنیم.
این عالیه که تجربه های فنی تون رو می نویسید