در ابتدا لازم است به عنوان یک مهندس عمران، مرزبندی خود را با معماری به صورت کلی و معماری نرم افزار به صورت خاص، مشخص کنم. اگر در دانشکده فنی مهندسی تردد کرده باشید، احتمالا با دانشجویان عمران برخورد داشتهاید و اگر از دانشکده هنر و معماری گذر کرده باشید، حقیقتی واضح برای شما مشخص خواهد شد. عمران و معماری از وابستهترین رشتهها به یکدیگر هستند با اینحال، هر دو طیف به طرز مشخصی، رویکرد خوشایندی به هم ندارند. به بیان دیگر، هر دو در وظایف هم دخالت میکنند و یکدیگر را قبول ندارند، اما در پس ذهن هم میدانند که بدون حضور طرف مقابل، خروجی کار کیفیت لازم را ندارد.
اما من به عنوان تحصیل کرده عمران، حاضرم گواهی دهم، معماری مناسب، باعث بهبود خروجیها چه در حوزه ساختمان و چه حوزه نرمافزار میشود.
معمار نرم افزار کیست و چه میکند؟
زمانی که از تولید نرم افزار صحبت میکنیم و نه استفاده از نرمافزارها با متن باز (opensource) وجود نقش معمار نرمافزار یا software architecture خیلی برجسته میشود. این نقش بر کیفیت پیادهسازی نرم افزار نظارت میکند و بهترین راهکارها برای پیشبرد نرمافزار ارائه میکند. اصولا در پروژههای فریلنسی یا در تیمهای نرمافزار با تعداد برنامهنویس محدود، فلسفه حضور معمار نرمافزار دائمی زیر سوال است و در بهترین شرایط ممکن است که کارفرما پروژه از مشاوره یک معمار نرمافزار، به صورت پاره وقت بهره بگیرد
حوزههای شناختی معمار نرم افزار عبارتند از:
-
شناخت ساختمان داده و الگوریتم
-
شناخت انواع پایگاههای داده و نحوه بهینه ذخیره سازی داده
-
تسلط بر راهکارهای ارائه شده در مسائل مختلف نرمافزاری (معمولا این موضوع با تجربه بالا در فعالیتهای نرمافزاری کسب میشود)
-
شناخت بهینه ترین design patterns
-
تشخیص نقاط مشکلزای نرمافزاری و بهینه کردن آن
-
توانایی ارائه سرخط و مسیر رشد به برنامهنویسان تازهکار یا سطح پایین
در پروژههای به روز دنیا نقش کاملتری در این حوزه وجود دارد به نام solution architecture که علاوه بر موارد گفته شده، به بحثهای استقرار و نگهداشت سیستم میبایست مسلط باشد و حتی راهکارهایی برای مسايل مختلف تجاری در حوزه نرمافزار، بدون نیاز به تولید نرمافزار ارائه دهد
معماری نرم افزار و ساختمان داده
ساختمان داده روشی برای بهینه سازی نگهداشت داده در انواع نرم افزار است. بحث تئوری ساختمان داده به شرح زیر است:
- آرایه (Array): یک مجموعهی متوالی از عناصر با اندیسهای شمارا که به راحتی به آنها دسترسی دارید.
- لیست مرتب (Linked List): مجموعهای از عناصر که به یکدیگر ارتباط دارند و هر عنصر به عنصر بعدی اشاره دارد.
- صف (Queue): یک ساختمان داده مبتنی بر اصول "اول وارد، اول خروج" (FIFO) که برای مدیریت دادهها به ترتیب استفاده میشود.
- پشته (Stack): یک ساختمان داده مبتنی بر اصول "آخرین وارد، آخرین خروج" (LIFO) که برای مدیریت دادهها به ترتیب استفاده میشود.
- گراف (Graph): یک ساختمان داده با گرهها و روابط بین آنها که برای مدلسازی ارتباطات پیچیده میان دادهها استفاده میشود.
- درخت (Tree): یک ساختمان داده با گرهها که به شکل سلسلهمراتبی به یکدیگر متصلند و برای جستجو و سازماندهی دادهها به کار میرود.
- Heap: در علوم کامپیوتر، "heap" به عنوان یکی از ساختمانهای داده اساسی شناخته میشود. Heap یک نوع از ساختمان داده درختی است که به عنوان یک درخت دودویی (Binary Heap) معمولاً پیادهسازی میشود. در این درخت، هر گره (یا عنصر) دارای یک ارزش (معمولاً یک عدد صحیح) است و از دو زیرگره به نام گرههای چپ و راست بهره میبرد.
هنر اصلی معمار نرم افزار بهرهگیری از نوع صحیح ساختمان داده، به فراخور مقیاس پروژه و حجم داده کوتاه مدت و میان مدت است
معماری نرم افزار و طراحی الگوریتم
الگوریتم مجموعهای از عملیات و مراحل است که هر نرم افزار کامپیوتری برای انجام، وظیفه خود به آن نیاز دارد. فرض بفرمایید که شما دارای یک فروشگاه اینترنتی هستید که در آن مشتریان سفارش خود را ثبت میکنند و برای ارسال این سفارشات شما دارای دو پیک موتوری هستید، اینکه با چه تقدم و تاخری و چه ترتیبی پیکها شروع به رساندن بستهها کنند، در قالب الگوریتم ارسال بستهها، در هر نرم افزار تعریف میشود. دقت بفرمایید که برای ارسال بستهها شما صرفا دو پیک دارید و یک روز زمان، بنابراین تعیین مقاصد هر پیک و میزان زمانی که آن پیک میتواند بستهها را برساند، از اولویتهای بهینه سازی الگوریتم شما هستند.
در هر الگوریتم موراد زیر باید جز ارکان اساسی تعریف الگوریتم باشند:
-
وضوح و دقت: الگوریتم باید به صورت دقیق و واضح تعریف شود تا اجرای آن توسط کامپیوتر یا سیستم مشخص و بدون ابهام باشد.
-
ورودی و خروجی: الگوریتم باید ورودیهای مورد نیاز را تعیین کند و مشخص کند که چگونه از این ورودیها به خروجیهای مورد نظر برسیم.
-
محدودیت زمانی: الگوریتم باید در زمان متناهی تا خروجی برسد. به عبارت دیگر، الگوریتمها باید در زمان معقول حل مسائل باشند.
-
قابلیت تکرار: الگوریتم باید قابلیت تکرار و اجرا تکراری داشته باشد تا بتوان از آن برای حل مسائل مشابه استفاده کرد.
-
قابلیت اجراپذیری: الگوریتم باید توسط کامپیوتر یا سیستم معمولی اجراپذیر باشد.
-
محدودیت حافظه: الگوریتم باید به گونهای طراحی شود که مصرف حافظه معقول داشته باشد.
-
کامپوزیتیون و تجزیهپذیری: الگوریتمها میتوانند به صورت ترکیبی از زیرالگوریتمهای دیگر باشند و باید قابلیت تجزیهپذیری داشته باشند.
-
کارایی: اگر یک الگوریتم در مقایسه با الگوریتمهای دیگر بهبود کارایی داشته باشد، به عنوان یک الگوریتم بهینه شناخته میشود.
معماری نرم افزار و design patterns
یکی از بحثهای مهم نرم افزاری که به زبان برنامه نویسی مرتبط نیست و بین تمامی زبانهای شیءگرا (object oriented) مشترک است، بحث استفاده از design pattern هاست. به طور کلی هدف از ایجاد هر design pattern ساختار دادن به کد برنامه و امکان استفاده مجدد یا به بیان دیگر جلوگیری از بازنویسی کد است. یک معمار نرم افزار با سابقه، قطعا به برنامه نویسان کم تجربهتر، دانشی در این زمینه منتقل خواهد کرد و ساختار را به طور تنظیم خواهد کرد که از تکرار کدها با عملیات یکسان حتی المقدور جلوگیری کند. درک روشن این بحث نیاز به دانش نرم افزاری دارد با اینحال برای مثال میتوان بخش پرداخت یک سیستم فروشگاهی را در نظر گرفت. این بخش قادر خواهد بود، هم برای ثبت سفارش جدید استفاده شود و هم برای واریز الباقی سفارشات دارای مغایرت. بنابراین اگر معمار نرم افزار دارای دید درستی به سیستمهای تجارت الکترونیک باشد، میتواند با پیشبینی این موضوع از نوشتن، دو بخش پرداخت مجزا یکی برای ثبت سفارش و یکی برای پرداخت الباقی رفم مغایرت سفارش جلوگیری نماید.
به صورت کلی design pattern ها به چند دسته زیر تقسیم میشوند:
-
پترنهای ساختاری (Structural Patterns): این پترنها به ترکیب کلاسها و شیءها به منظور ایجاد یک ساختار مفهومی در نرمافزار میپردازند. مثالها: Singleton، Adapter، Bridge.
-
پترنهای رفتاری (Behavioral Patterns): این پترنها به تعامل و همکاری بین اشیاء و کلاسها در طول زمان میپردازند. مثالها: Observer، Strategy، Command.
-
پترنهای سازماندهی (Creational Patterns): این پترنها به فرآیند ایجاد شیءها و کلاسها میپردازند و کمک میکنند تا ایجاد آنها بهینهتر و کنترلپذیرتر باشد. مثالها: Factory Method، Abstract Factory، Singleton.
معمار نرم افزار و مسئولیتهای مدیریتی
یکی از رسالتهای مهم معمار نرم افزار، جلوگیری از زیاده روی، در موضوعات طراحی نرم افزار است. به عنوان مثال زمانی که تیم فنی درخواست پیاده سازی مدل خاص از تکنولوژی هنوز باثبات (stable) نشده را دارد، معمار نرم افزار وظیفه دارد تا با قانع کردن تیم فنی، جلوی این زیاده روی را بگیرد.
بحث over design: بحثی است که در آن شما باید از طراحی برای چند برابر نیاز سیستم نرم افزاری جلوگیری کنید. به عنوان مثال زمانی که شما برای بازدید روزانه ۲۰۰۰ نفر سیستم نرم افزاری طراحی میکنید باید رفتار متفاوتی داشته باشید تا زمانی که برای بازدید در ثانیه ۴۰۰٫۰۰۰ نفر. اگر شما در ابتدای راه اندازی کسب و کار هستید و مشخص نیست تا چه میزان، مخاطب خواهید داشت، سرعت تحویل قابلیتها (feature) خیلی مهمتر است تا قابلیت مقیاس پذیری (scale).
در یکی از پروژههای بزرگ کشور که به صورت تخصصی در حوزه حمل و نقل فعالیت دارد، به منظور توسعه تیم فنی و موقعیت شغلی سرپرست فنی، برای کسب و کار فروشگاهی، دعوت به همکاری شدم، مدیر بخش که با اصرار خاصی، سعی در جذب من داشت، راجع به انواع معماریهای مورد استفاده در پروژه، در حال سخنرانی بود. بنده نیز همش به این فکر بودم که چرا باید یک پروژه که مخاطب نه چندان زیادی هم ندارد، تا به این میزان، پیچیدگی معمارانه داشته باشد. در انتهای صحبتهای مدیر، همین موضوع را مورد پرسش قرار دادم. پاسخ مدیر کوتاه بود و تاثیرگذار!!! اگر طراحی ساده میکردیم که دیگر پروژه درست حسابی نبودیم.
رسالت دیگر معمار نرم افزار، بهینه کردن سیستم در حال کار است. کسب و کاری را تصور کنید که در ثانیه نزدیک ۱۰۰۰ سفارش در آن ثبت میشود، یکی از چالشهای این کسب و کار، بحث نگهداشت و حصول اطمینان از پاسخگویی دائمی سیستم است. به عبارت دیگر باید مطمئن باشیم تا بخشهای جدیدی که برنامه نویسان اضافه میکنند، خللی در کارکرد، بخشهایی که در حال کار هستند، ایجاد نکند و حتی برای یک ثانیه، کارکرد سیستم را متوقف نکند.
رسالت بعدی معمار نرم افزار، پیش بینی برای راحتی کار برنامه نویسهاست. تصور کنید که در یک پروژه با متن باز (open source) مشغول فعالیت هستید، هرکسی از هر جای دنیا امکان مشاهده پروژه شما و اعمال تغییرات در آن را دارد. اما اگر شمای معمار نرم افزار، مکانیزم درستی، برای نحوه تعامل تیم که در اکثر موارد، دارای زبان گفتاری یکسان نیستند، را نداشته باشید، هرگز امکان رشد پروژه، بدون دخالت خودتان، وجود ندارد.
انواع معماریهای نرم افزاری
به لحاظ دسته بندی، بر مبنای اجزای قابل تفکیک در هر نرم افزار، دو معماری زیر تعریف میشوند:
- معماری مونولیتیک monolithic: معماری که در آن یک نرم افزار واحد، همه بخشهای سیستم را به پیش میبرد و رفتار نرم افزار به این شکل است که یا همه سیستم به درستی مشغول به کار هستند یا هیچ کدام از اجزا سیستم قابل استفاده نیستند
- معماری میکروسرویس microservice: در این معماری هر بخش سیستم، به صورت تفکیک شده، قابلیت فعالیت دارد و همه بخشها با داشتن ارتباط با یکدیگر فرآیند کلی را، پیش میبرند
معماری مونولیتیک
معماری سنتی نرم افزار است و از دیرباز، برای پروژههای مختلف در حال استفاده بوده است. در این معماری، کاربر نهایی از طریق درگاه واحد، با نرم افزار شما، ارتباط میگیرد. برنامه نویسان منبع کد واحد (source code) را توسعه میدهند. در صورتی که نرم افزار دارای ساختار بر مبنای ماژول باشد، همه ماژول در زمان انتشار به صورت یکپارچه، کامپایل میشوند.
از آنجا که این نرم افزار به صورت یکپارچه منتشر میشود، نیاز به ارتباط بیرون نرم افزاری، بین ماژولها نیست و علاوه بر این در صورت از کار افتادن یک بخش از نرم افزار، احتمال زیاد، سایر بخشها قادر به کار نخواهند بود
معماری میکروسرویس
در این معماری، هر بخش سیستم در قالب مجزا توسعه داده میشود، هر بخش پایگاه داده (database) خود را دارد، فرآیندهای کامپایل پروژه به ازای هر سرویس به صورت جداگانه انجام میشود. برنامه نویسها به ازای هر سرویس کد منبع (source code) خود را دارند. میتوانند به صورت جداگانه برنامه خود را توسعه دهند و برای هر سرویس نیز از زبان دلخواه خود استفاده کنند.
معایب معماری میکروسرویس
- یکی از مهمترین معایب معماری میکروسرویس، طراحی این معماری برای پروژهها با سایز متوسط و بزرگ میباشد، به بیان دیگر برای یک پروژه کوچک یا MVP استفاده از معماری میکروسرویس به هیچ وجه توصیه نمیشود.
- نگهداشت پروژه در معماری، میکروسرویس به مراتب دشوارتر از نگهداری پروژه مونولیتیک میباشد. در واقع به ازای هر سرویس شما نیاز به استقرار و نگهداری زیرساختی دارید.
- زمان توسعه پروژههای میکروسرویس در مقایسه با مونولیتیک بیشتر است.
- برای استفاده از معماری میکروسرویس، علاوه بر دانش برنامه نویسی، نیازمند به دانستن دانش، میکروسرویس و زیرساخت میباشید.
- برای ارتباط بین سرویسها نیاز به درک از مفاهیم پروتوکل و شبکه وجود دارد و در مواردی استفاده از broker ها نظیر rabbitMQ و Kafka الزامی است.
مزایای معماری میکروسرویس
- امکان استفاده از چندین زبان برنامه نویسی به ازای هر سرویس
- امکان مقیاس پذیر کردن سیستم و راه اندازی سرویسها با قابلیت auto scale
- امکان استفاده از چندین پایگاه داده به فراخور کاربری تعریف شده در هر سرویس
- امکان راه اندازی load balancer به منظور افزایش تعداد پاسخ دهی همزمان سیستم
- امکان به روز رسانی سرویس خاص بدون بروز کردن سایر سرویسها
- امکان ایجاد پایپ لاین نگهداشت اختصاصی به ازای هر سرویس
فرهنگ میکروسرویس
برنامه نویسانی که از گذشته با معماری مونولیتیک کار میکردند، برای ورود به فضای میکروسرویس، نیاز دارند تا مفاهیم زیر را بیاموزند:
- استراتژی تفکیک سرویسها: در هر کسب و کاری نیاز است تا بدانیم core service (بخش اصلی کسب و کار) چه مواردی را شامل میشود و چه بخشهایی به سادگی قابلیت تبدیل به میکروسرویس را دارند، معمولا بخشهایی نظیر ثبت نام، احراز هویت و ثبت سفارش، جز بخشهای core service در نظر گرفته میشود و سایر بخشها به فراخور کسب و کار قابلیت تبدیل به میکروسرویس را دارند. معماری میکروسرویسی توصیه میشود که حدود ۴۰ درصد یا کمتر از کسب و کار شما در core service و سایر بخشها به صورت میکروسرویس باشند. لازم به ذکر است که این مدل تفکیک برای کسب و کارهای تجارت الکترونیک توصیه میشود و ممکن است در سایر کسب و کارها درصدهای متفاوتی توصیه شوند.
- نحوه تعامل بین سرویسی: برنامه نویسان معمولا با مدل restful و webservice آشنایی دارند، خبر خوب این است که بین سرویسهای میکروسرویس، امکان استفاده از این پروتوکلها وجود دارد، اما روشهای نوینتری نیز ایجاد شده است، به عنوان مثال شما امکان تبادل داده بر مبنای brokerهای نظیر rabbitMQ و kafka را دارید و همچنین امکان استفاده از سرویسهای جدیدتری نظیر gRPC و graphql نیز، در دسترس شما هستند. توصیه همیشگی من به کسانی که در فضای میکروسرویس کار میکنند، این است که یک شبکه داخلی برای ارتباط بین سرویسها در نظر بگیرید، شبکهای که از بیرون قابل دسترس نیست و تنها سرویسها میتوانند هم را ببینند.
- شناخت api gateway و استفاده از آن: فرآیند authentication و authorization بین سرویسها یکی از نقاط ضعف همیشگی پروژههای میکروسرویس است. این موضوع میتواند به وسیله ابزارهایی مثل kong یا krakend به نحو مناسبی مدیریت شود.
- راهاندازی سیستم مانیتورینگ بین سرویسی: زمانی که شما قصد مدیریت یک پروژه با چندین میکروسرویس را دارید، باید اطمینان حاصل کنید که سرویسها، همدیگر را به درستی میبینند و در هر لحظه در صورت اختلال در این فرآیند، اقدامات لازم برای رفع اختلال را انجام دهید. در سناریویی که سرویس شما قابلیت auto scale را داشته باشد، باید اقدامات لازم برای service discovery و orchastration صورت پذیرد. (این موضوع در پستی در آینده به تفضیل بررسی، خواهد شد)
- وجود قرارداد بین سرویسی (contract): برای جلوگیری از اختلال بین سرویسی، لازم است تا ورودیهای هر سرویس و خروجی آن به صورت مستند، نگهداری شود. علاوه بر نگهداری مستند ورودی و خروجی، به ازای هر آپدیت، نسخه مستند را بروز نمایید و نسخ قبلی را نیز داشته باشید، تا در سناریوی خطاهای بین سرویسی، رهگیری خطا راحت تر باشد و متوجه شوید، کدام سرویس، بروز نشده است
دیدگاهها
پیوند ثابتافزودن دیدگاه جدید