مقدمه:
اگر اهل توسعه نرم افزار و پیگیری اخبار روز دنیای توسعه نرم افزار باشید حتما اسم میکروسرویس به گوشتون خورده. تقریبا هیچ بلاگ و کتابی نیست که این روزها یکی دوتا پست یا فصل خودشو به این موضوع اختصاص نداده باشه. اغلب فریمورک ها و ابزارهای توسعه هم سعی میکنن برای توسعه میکروسرویس ها راه کارهایی ارائه بکنن و به کمک این راهکارها طرفداران بیشتری توی دنیای توسعه نرم افزار برای خودشون به دست بیارن. برای همین تصمیم گرفتم توی چند تا مطلب درباره این روش توسعه نرم افزار و بایدها و نبایدهاش و تکنیک های پیاده سازیش دانستههای خودمو با شما به اشتراک بذارم. بخش زیادی از مطالب این نوشتهها ترجمه مطالب آقای کریس ریچاردسون هست که بعضا با تجربههای شخصی خودم سعی کردم مطالب ساده تر و کامل تر بیان بشه. و اینکه قطعا به نظرم هیچ کاری توی دنیای نرم افزار بدون پیاده سازی و کد نویسی به درد نمیخوره برای همین توی این سری مطالب بعضا دست به کد میشیم و پیاده سازیهایی خواهیم داشت که برای این کار از C# و .NET Core استفاده میکنیم.
دنیا قبل از میکروسرویس:
فرض کنید شما برای توسعه یک نرم افزار دعوت به همکاری شدید. از شما خواسته میشه یک CMS خبری توسعه بدید که کارش بسیار ساده است. خبرنگارها امکان ثبت اخبار متنی دارن. دبیرها و سردبیرها امکان انتخاب و انتشار اخبار توی صفحات سایت دارن و در نهایت کاربران سایت امکان مشاهدهی مطالب سایت را دارن. از اونجا که شما یک توسعه دهنده حرفهای هستید احتمالا سراغ یک ابزار خوب و یک ساختار خوب و تمیز میرید مثلا معماری Hexagonal یا Onion برای توسعه انتخاب میکنید و در نهایت یک نرم افزار بسیار تمیز توسعه میدید. تو همچین شرایطی احتمالا منطق برنامه توی مرکز برنامه قرار گرفته و جاهایی که نیاز به ارتباط با زیرساخت و استفاده از ابزار دارید به جای وابسته شدن به تکنولوژی و زیرساخت از اینترفیسها استفاده میکنید و وابستگیها رو به خارج از دامنه اصلی برنامه هدایت میکنید خروجی مناسب برای برنامه خودتون طراحی و پیاده سازی میکنید.
هر چند در این روش از نظر منطقی برنامه ما کاملا ماژولار طراحی و پیاده سازی شده اما در واقع کل این ماژول ها کاملا وابسته به هم هستن و در قالب یک بسته نرم افزاری روی خروجی قرار میگیرن. اینکه این یک بسته نرم افزاری دقیقا چی هست بستگی به تکنولوژیهای مورد استفاده شما داره اما در محیط .NET شما یک یا چند اسمبلی دارید که در نهایت به عنوان خروجی برنامه شما شناخته خواهند شد و هر جایی نیاز به نرم افزار داشته باشید کل این اسمبلیها باید در کنار هم بسته بندی بشن و خدمات خودشونو ارائه بکنن.
توسعه برنامه به این روش بسیار فراگیر و مرسوم هست. البته این فراگیری دلیل خوبی داره و اون سادگی توی توسعه و نصب برنامه است. ابزارها و IDEها به سادگی این قابلیت را در اختیار شما قرار میدن که یک برنامههای خودتونو توسعه بدید و نصب و راه اندازی کنید. با یک نصب ساده قابلیت تست کل برنامه را دارید و برای راه اندازی نسخه جدید برنامه فقط کافیه بستههای نرم افزاری خودتونو کپی کنید و همه چیز آماده است. این روش توسعه همون چیزیه که در اصطلاح ما بهش Monolith میگیم.
پیش به سوی جهنم:
از قدیم گفتن: "هیچ ارزانی بی حکمت نیست" که اگه بخواهیم به زبان برنامه نویسی ترجمه کنیم میشه “هیچ سادگی بدون محدودیت نیست”. هر نرم افزار و پروژه ای در صورتی که موفق بشه قطعا تمایل به رشد داره، و موفقیت بیشتر پروژه موجب رشد بیشتر پروژه میشه. در نهایت بعد از مدتی پروژه کوچک و ساده ما تبدیل به هیولایی بزرگ و وحشتناک میشه. با گذشت زمان و تغییر نیازها و عملا افزایش درخواست ها باعث میشه که سورس کد ما بزرگ و بزرگ تر بشه و هر روز به کشنده تر از قبل بشه.
اما ممکنه به این فکر کنید که زیاد شدن حجم کدها در طول دوران توسعه نرم افزار و تغییرات اون چه مشکلی میتونه داشته باشه؟ در ادامه به چند مورد از این مشکلات خواهیم پرداخت.
با بزرگ و پیچیده شدن نرم افزار تیم توسعه شما با مشکلات فراوانی دست و پنجه نرم خواهد کرد. هر تصمیمی برای تغییر در برنامه یا ایجاد سریع یک ویژگی و ارائه اون احتمالا به بن بست خواهد رسید. بزرگترین مشکل این نرم افزارها پیچیدگی بیش از حد این برنامه هاست. معمولا نرم افزارها در طول سالیان اینقدر بزرگ و پیچیده میشن که درک دست و دقیق عملکرد اونها برای یک توسعه دهنده نرم افزار غیر ممکن میشه. در نتیجه این عدم توانایی شناخت درست و صحیح باعث میشه رفع خطاهای موجود یا اضافه کردن یک ویژگی جدید هم بسیار سخت و پیچیده باشه. نکته اصلی در این شرایط اینه که با گذشت زمان این مشکل به شکل نمایی بیشتر و بیشتر میشه. هرچی فهم کد سخت تر بشه احتمال اشتباه بیشتر احتمال پیدا کردن اشتباهها کمتر و توان رفع اونها هم کمتر میشه. در نهایت به سورس کدی خواهیم رسید که اصطلاحا به اون big ball of mud میگیم.
مشکل بعدی که سورس کد حجیم میتونه داشته باشه پایین اومدن سرعت توسعه نرم افزار هست. هر وقت تصمیم به بازکردن پروژه بگیرید دقایق زیادی طول خواهد کشید تا IDE شما باز بشه و آماده به کار باشه. در کنار این شرایط احتمالا این سیستم عظیم بهرهوری کل بستر شما رو هم پایین خواهد آورد.
سومین مشکلی احتمالا با داشتن یک سیستم بزرگ با اون مواجه خواهید شد عدم توانایی در انتشار بهبودها و توسعههای کوچک است. مسلما سیستمی که باز کردن اون چند دقیقه طول میکشه، Build شدنش بعضا 1 ساعت طول میکشه روال نصب راحتی هم نخواهد داشت. ضمن اینکه با توجه به اینکه در زمان نصب احتمالا سیستم از دسترس خارج خواهد بود و قاعدتا از دسترس خارج کردن یک سیستم عظیم به خاطر یک تغییر کوچک یا رفع باگ احتمالی جزئی مقرون به صرفه نیست و معمولا بالاجبار نصب نسخههای جدید با فاصلههای زمانی طولانی انجام خواهد شد.
عدم استفاده بهینه از منابع یکی دیگر از مشکلات اساسی توسعه نرم افزار به این شکل هست. در نرم افزارهای متفاوت نیازهای متفاوتی وجود دارد. ممکن است بخشی از برنامه مصرف RAM زیادی داشته باشه و بخش دیگه هم مصرف CPU اما در این روش امکان تخصیص یک منبع خاص به بخش خاصی که نیاز به اون منبع داره وجود نخواهد داشت. در نتیجه هنگامی که بخش با مصرف CPU زیاد ارتقا داده بشه این امکان در اختیار همه بخشها قرار خواهد گرفت و برای همه قسمت ها امکان استفاده از این منبع، بی رویه و ناصحیح وجود خواهد داشت.
پنجمین مشکلی که توسعه به روش Monolith به همراه داره انتشار مشکلات هست. کل برنامه در یک سیستم و در قالب یک پروسه اجرا خواهد شد. پس اگر ایرادی در هر یک از قسمتهای برنامه به وجود بیاد تمامی قسمتهای برنامه از کار خواهند افتاد و کل برنامه تا زمان رفع مشکل و نصب نسخه جدید از دسترس خارج خواهد بود. البته در این شرایط باید امیدوار بود که نصب نسخه جدید موجب ایجاد مشکلات جدید در سیستم نشه.
عدم توانایی در به کارگیری ابزارها و تکنولوژیهای جدید هم یکی دیگر از ایرادات این روش توسعه نرم افزار هست. در شرایطی که شما یک نرم افزار بزرگ و پیچیده را سالها توسعه دادید احتمالا وقتی با یک ابزار، تکنولوژی یا فریم ورک جدید مواجه بشید به جز حسرت امکانات موجود کار دیگه ای از دستتون بر نمیاد. معمولا امکان اینکه سالها تلاش تیم دور ریخته بشه نه پذیرفته میشه نه قابل اجرا هست. در حال حاضر در یکی از شرکتهای بزرگ درگیر مشاوره در یک پروژه ERP هستم که سالها پیش و با تکنولوژی سال 2005 توسعه داده شده. در این ابزار از .Net Framework 2 استفاده شده و برای توسعه وب هم از ASP.NET Web Form استفاده شده و اینقدر سیستم بزرگ و پیچیده شده که در طی این سالها کسی جرات دست زدن به سیستم و تغییر تکنولوژی را نداشته. در صورتی که خودتونو جای یکی از مدیران پروژه و شرکت بگذارید چقدر احتمال داره قبول کنید که ثمره 15 سال توسعه یک تیم برنامه نویسی دور ریخته بشه و یک پروژه جدید استارت زده بشه؟ به نظرتون در این 15 سال که یک تیم 15-20 نفره به طور میانگین درگیر این پروژه بوده چقدر هزینه تولید همچین ابزاری شده؟ آیا تغییر تکنولوژی با این شرایط امکان پذیر هست؟
خوب به نظر میرسه کم کم داریم به پایان دنیا نزدیک میشیم. اما واقعا راه حلی برای این مشکلات نیست؟
بهشت گمشده: میکروسرویس
بسیاری از شرکت ها بزرگ نرم افزاری دنیا مثل eBay, Amazon, Netflix و ... برای حل این مشکل به سراغ توسعه به روش میکروسرویس رفتن. این شرکتها تصمیم گرفتن که به جای داشتن هیولای غیرقابل کنترل تعداد زیادی مینی اپلیکیشن تولید کنن که به خیلی خوب با هم تبادل اطلاعات میکنن و هر کدون از این مینی اپلیکیشنها یک وظیفه خاص و دقیق را به انجام میرسونن.
در این روش هر سرویس مجموعه ای از وظایف مرتبط با هم را به طور کامل و بدون وابستگی به بخش دیگری به انجام میرساند. برای مثال در سیستم تولید محتوا و مدیریت خبر میتوان به مواردی چون مدیریت نظرات، مدیریت ثبت خبر و محتوا، مدیریت فایل، مدیریت انتشار در بستر وب و ... اشاره کرد. در این روش هر مینی اپلیکیشن از یک معماری تمیز مثل Hexagonal یا Onion به طور داخلی استفاده میکنه. هر مینی اپلیکیشن این توانایی را خواهد داشت که در صورت نیاز بخشی از خدمات و دادههای خودش رو برای سایر قسمت ها به صورت API در اختیار قرار بده. برای نصب و راه اندازی هم هر کدوم از این مینی اپلیکیشنها توانایی این را خواهند داشت که در یک VM جداگانه به کار خودشون ادامه بدن یا به عنوان Docker Image در اختیار تیم زیرساخت برای نصب و راه اندازی قرار بگیرن.
در این شرایط هر کدام از بخشهای عملیاتی برنامه به عنوان یک مینی اپلیکیشن توسعه داده شدن. مهم تر از اون حالا به جای یک وب اپلیکیشن بزرگ مجموعه ای از اپلیکیشنهای کوچک داریم که به طور دقیق و حساب شده ای برای انجام کار اصلی و رسیدن به هدف اصلی با هم تعامل و همکاری خواهند کرد. برای مثال در بخش تولید خبر این امکان برای این مینی اپلیکیشن فراهم هست که برای نمایش تصاویر از مینی اپلیکیشن مربوط به مدیریت فایلهای عکس کمک بگیره و از طریق APIهای ارائه شده توسط بخش مدیریت تصاویر به عکسهای ذخیره شده در سیستم دسترسی داشته باشه. برای ارتباط بین سرویسها راهکارهای مختلفی وجود داره و اگر با این روشها آشنا نیستید اصلا نگران نباشید. در قسمتهای بعد در مورد این روشها کامل صحبت خواهیم کرد.
خوب تا اینجا همه چیز به نظر خوب میاد اما احتمالا به این موضوع فکر بکنید که اگر تعداد مینیاپلیکیشنها زیاد بشه و هر برنامه هم API خودش را ارائه بده و برنامه ها نیاز داشته باشن با هم ارتباط برقرار کنن تعداد زیادی آدرس وجود داره که هر برنامه باید مدیریت کنه و این خودش شروع مشکلات میشه. اما خبر خوب اینکه این مشکل به کمک API Gateway حل میشه و در مورد جزئیات این بحث در قسمت آینده کامل صحبت خواهیم کرد.
مزایای میکروسرویسها:
گویا این روش توسعه جدید نرم افزار بسیاری از مشکلات روش Monolith را حل خواهد کرد. اما برای اینکه دقیق تر بدونیم به چه شکلی این مشکلات حل خواهند شد بیاید با هم نگاهی به برخی ویژگیهای مفید میکروسرویسها بندازیم.
احتمالا همه شما با روش تقسیم و غلبه برای حل کرد مشکلات آشنا هستید. به جای حل کردن یک مشکل بزرگ بهتره که اون مشکل به قطعات کوچک شکسته بشه و هر قطعه به طور جداگانه حل بشه و در نهایت جواب نهایی از مجموع جوابهای به دست آمده تشکیل خواهد شد. خوب همین فلسفه در مورد میکروسرویسها هم صدق میکنه. هیولای Monolith به تعداد زیادی مینی اپلیکیشن با قابلیت مدیریت و نگهداری بهتر شکسته می شه و حالا تعداد زیادی صورت مسئله جزئی برای حل کردن خواهیم داشت. قطعا با کوچک شدن برنامهها فهم و توسعه برنامه ها هم به شدت ساده تر از قبل خواهد شد.
اغلب شرکتهای نرم افزاری از تعدد stackهای توسعه نرم افزار وحشت دارن و معمولا محدودیتهای زیادی در انتخاب ابزارها و فریمورکهای توسعه نرم افزار وجود دارد با این حالا در شرایطی که تعداد زیادی مینی اپلیکیشن داریم به راحتی میتوانیم در هر کدام از این مینی اپلیکیشنها از ابزارهایی که دقیقا مناسب با نیاز آنها است استفاده کنیم. به راحتی اگر دادههای ما ساختار گراف داشته باشید به سراغ یک گراف دیتابیس میرویم. برای هر برنامه زبان توسعه خاص آن را انتخاب میکنیم و به راحتی به هر تکنولوژی و فریم ورکی سلام خواهیم کرد.
یکی دیگر از ویژکیهای توسعه میکروسرویس قابلیت نصب و انتشار مستقل هر کدام از این میکروسرویسها است. دیگر نیازی نیست برای رفع یک باگ کوچک در یک قسمت کوچک برنامه کل سیستم از دسترس خارج شود. بلکه به سادگی همان قسمتی که نیاز به رفع ایراد دارد در مدت کوتاهی راه اندازی مجدد خواهد شد.
اما استفاده بهینه و صحیح از منابع هم شاید یکی از مهم ترین ویژگیهای توسعه میکروسرویس باشد. در این روش هر مینی اپلیکیشن به اندازه نیاز خود منابع و امکانات دریافت خواهد کرد و در صورت نیاز میتوان منابع یک سرویس را افزایش داد یا یک سرویس خاص را در چند نسخه مختلف اجرا کرد.
خیلی هم خوب و خیلی هم عالی تا اینجای کار تمام معایب سیستمهای قدیمی از رفع شده و میتوانیم به راحتی به کار خود ادامه دهیم. اما طبق اصل Pay for Play قطعا به دست آوردن این همه مزیت بدون هزینه و ایراد نخواهد بود. پس در ادامه بعضی از این ایرادات را با هم بررسی خواهیم کرد.
معایب میکروسرویسها:
شاید بزرگترین ایراد توسعه به روش میکروسرویس نام این روش باشد. به طور جدی در این نام روی اندازه کوچک و یا بهتر بگویم بسیار کوچک این سرویسها تاکید شده است. اما این کوچک بودن به چه معناست؟ چقدر کوچک؟ اندازه یک مورچه به نسبت یک گربه بسیار کوچک است. اما همان گربه به نسبت یک فیل کوچک به حساب میآید. و در مجموع به اندازه کل کره زمین به نسبت کل کائنات کمی فکر کنید ببنید کدام یک از این مواردی که اسم بردیم کوچک به حساب میآید. پس تعیین مقیاس برای سرویسها یکی از مشکلات ما خواهد بود. هنگامی که به اندازه سرویسها فکر میکنید حتما این نکته را به یاد داشته باشید که کوچک بودن اندازه سرویسها وسیله ای برای رسیدن به هدفی بزرگ است نه یک هدف اصلی و بزرگ.
پیچیدگی برقراری ارتباط بین سرویسهای مختلف و سایر پیچیدگیهای فنی دیگری که هنگام توسعه یک سیستم توزیع شده با آن مواجه خواهیم شد بسیار بیشتر از زمانی است که یک نرم افزار Monolith توسعه میدهیم و قبل از انتخاب این روش توسعه، باید به این پیچیدگیها و راهکارهایی که برای برخورد با این پیچیدگیها داریم فکر کنیم. برای مثال برای استفاده از امکانات یک زیرسیستم دیگر در روش توسعه Monolith به راحتی از کلاسی نمونه سازی میکنیم و تابعی از آن کلاس را صدا می زنیم اما این کار را در یک سیستم توزیع شده نمیتوانیم انجام دهیم.
در سیستمهایی که به روش میکروسرویس توسعه میدهیم تاکید فراوانی بر توانایی عمکلرد هر سیستم به تنهایی وجود دارد و این به این معنا است که هر میکروسرویس دیتابیس اختصاصی خود و دادههای اختصاصی خود را خواهد داشت و این توزیع شدگی دادهها در چند دیتابیس و مدیریت نسخههای موجود از یک داده در دیتابیسهای مختلف میتواند مشکلات زیادی را برای تیم توسعه به وجود آورد. شاید این مشکل زمانی بیشتر به چشم بخورد که اتفاقی در سیستم بیوفتد که نیاز به تغییر در پایگاه داده در چند سرویس مختلف داشته باشد و نیاز داشته باشیم یک Transaction را بین چند سرویس مختلف مدیریت کنیم.
یکی دیگر از شمشیرهای دولبه در روش توسعه میکروسرویس نصب و راه اندازی آن است. شاید اگر به چند پاراگراف بالاتر برگردید مشاهده کنید که امکان نصب و راه اندازی جداگانه سرویسها را به عنوان یکی از برتریهای این روش توسعه نرم افزار شمردیم. اما با کمی دقت خواهید دید که همین مورد میتواند ایرادات زیادی را ایجاد کند. بعضا ممکن است یک سیستم بزرگ به بیش از 100 سرویس کوچک تقسیم کند و نصب و راه اندازی و تنظیم ارتباطات این 100 سرویس میتواند بسیار زمانگیر و پیچیده و پرخطا باشد.
برای بسیاری از این مشکلات راهکارهای عملیاتی زیادی تدارک دیده شده است که در ادامه این مطالب بررسی خواهیم کرد.
جمع بندی:
در این مطلب سعی کردیم با دو روش توسعه Monolith و میکروسرویس و مزایا و معایب هر کدام از این روشها آشنا شویم و با مزایا و معایب هرکدام از این روشها آشنا شدیم. به عنوان یک توسعه دهنده با نزدیک به 17 سال سابقه توسعه نرم افزارهای مختلف یک روحیه مشترک بین همه توسعه دهندهها سراغ دارم و این روحیه علاقه به استفاده از روشها و ابزارهای جدید بدون توجه به نیاز واقعی کار است. پس پیشنهاد میکنم به جای تصمیم به توسعه تمام برنامهها به روش میکروسرویس، قبل از انتخاب این روش، کاملا نیازهای اصلی سیستم را بررسی کنید و هر کدام از این روشها را که مناسب سناریوی شما است انتخاب کنید. هر چند میکروسریس بسیار مطرح و پر طرفدار است اما با یک بررسی سریع میتوانید نمونههای شکست خورده زیادی از سناریوهایی که برای توسعه میکروسرویس را انتخاب کرده اند پیدا کنید.
ادامه مطلب :