یکی از داغترین مباحث در معماری نرمافزارهای امروزی، مفهموم “میکرو سرویس”ها هستند. در این مقاله ابتدا مقدمهای از علت شکلگیری این معماری و چالشهای موجود در ساختارهای نرمافزارهای فعلی ارائه خواهد شد. در ادامه راهکارها و ساختارهای مورد نیازبرای طراحی و معماری یک میکرو سرویس موفق بررسی خواهند شد.
معماری یکپارچه (Monolothic)
بسیاری از نرمافزارهای تجاری موجود به منظور تسهیل نیازهای تجاری متعددی طراحی شدهاند و عموما دارای ساختارهای و ویژگیهای متعددی هستند که مجموع این قابلیتها باعث شکل گیری یک نرمافزار جامع یکپارچه شده است. برای نمونه ERPها، CRMها و بسیاری نرمافزارهای مشابه به عنوان نرمافزارهای یکپارچه با صدها ویژگی کاربردی شناخته میشوند. در این نرمافزارها استقرار، عیبیابی، مقیاسپذیری و ارتقاء، از بزرگترین کابوسهای تیم نرمافزاری محسوب میشوند!
برخی از مهمترین شناسههای نرمافزارهای توسعه داده شده با معماری یکپارچه (Monolothic) میتوان به موارد زیر اشاره کرد:
- نرمافزارهای یکپارچه به عنوان یک مجموعه یکتا طراحی، توسعه و پیادهسازی میشوند.
- این نرمافزارها عموما پیچیده هستند; به همین دلیل گاهی توسعه و پشتیبانی این سرویسها به کابوسی برای تیم توسعه و پشتیبانی تبدیل میشود.
- متودولوژیهای توسعه و تحویل (آپدیت اتوماتیک) مطابق با متد توسعه نرمافزاری چابک به سختی در این دسته نرمافزارها قابل پیادهسازی هستند.
- در صورتی که بخشی از نرمافزار به روز شود، باید کل سرویس دوباره به روز رسانی شود.
- توسعهپذیری نیز در این نرمافزارها با مشکل روبرو خواهد شد. بخصوص برای حالتهایی که نیازمندیها در بخشهای مختلف کد فرق دارد. به طور مثال بخشی از کد نیاز به حافظه بیشتری دارد ولی بخشی از کد (یک سرویس دیگر در همان نرمافزار) نیاز به پردازشگر قویتر دارد.
- یک سرویس ناپایدار یا خطا در قسمتی از برنامه ممکن از کل نرمافزار را با مشکل مواجه کند.
- عدم انعطافپذیری در توسعه تکنولوژیها: در این نرمافزارها عموما بسیار دشوار است تا از تکنولوژیها و فریمورکهای جدید استفاده کرد. چرا که تمامی ساختارها توسط یک سری فریمورکها و ساختارهای برنامهنویسی خاص نوشته شدهاند و در طول توسعه نیز تمامی آنها باید رعایت شوند.
همانطور که در بالا اشاره شد، اگرچه این معماری هنوز خیلی پرطرفدار است و بسیاری از نرمافزارهای بزرگ در حال استفاده از این معماری هستند، ولی مسلما این پایان راه نیست و همین باعث شکلگیری معماری میکرو سرویسها شده است که در ادامه بررسی خواهند شد.
معماری میکرو سرویس (MicroService)
پایه و اساس معماری سرویسهای میکرو سرویس، توسعه یک نرمافزار واحد از مجموعهای از سرویسهای کوچک و مستقل است که هر کدام از این سرویسها دارای پردازشهای خاص خود باشند و به صورت مستقل توسعه و پیادهسازی شده باشند.
به طور کلی در این معماری، تمامی سرویسهای موجود در یک نرمافزار یکپارچه و بزرگ به مجموعهای از سرویسهای مستقل و کوچکتر تقسیم میشوند. به عنوان مثال شکلهای زیر را در نظر بگیرید که یک نرمافزار یکپارچه دارای سه قسمت اموال، حملونقل و فروشگاه (شکل سمت راست) به چهار سرویس مستقل تقسیم شده است (شکل سمت چپ). نکته جالب در این تغییر معماری میتوان به اضافه شدن سرویس Accounting اشاره کرد.
این سرویس که وظیفه احراز اصالت و مدیریت کاربران و … را بر عهده دارد به صورت یک سرویس مجزا تعریف شده است. چرا که در حالت یکپارچه به دلیل اینکه تمامی سرویسها در کنار یکدیگر قرار داشتهاند، تمامی فرآیندهای احراز اصالت و مدیریت حسابهای کاربری داخل همان نرمافزار و در کنار کدهای سرویسهای موجود استفاده شده است، ولی در حالت میکرو سرویس حتما باید به صورت یک سرویس مستقل پیاده شود.
طراحی میکرو سرویس
برای اجرای این طراحی دو پیشفرض وجود دارد:
- زیرساخت فعلی نرمافزار شما به صورت یکپارچه پیادهسازی شده است و میخواهید این ساختار فعلی را به حالت میکرو سرویس تغییر دهید.
- به طور کامل میخواهید نرمافزار خود را از ابتدا با این طراحی پیادهسازی کنید.
در هر صورت هر کدام از دو مدل که مورد نظر شما باشد، قبل از آن باید یک سری مسائل در نظر گرفته شوند:
- اصطلاح میکرو کمی گمراهکننده است. چرا که بسیاری از توسعهدهندگان فکر میکنند که در این طراحی باید تا جای ممکن سرویسهای خود را کوچک کنند.
- در طراحی SOA مبتنی بر سرویس، عموما سرویسها با دید یکپارچه طراحی میشوند و دارای چندین قابلیت و کارایی هستند. به همین دلیل استفاده از ساختارهای این مدلی مسلما خیلی با هدف میکرو سرویس سازگار نیست و هدف از میکرو سرویس ارائه سرویسهای مستقل کوچک با تمرکز بر هدفی خاص هست.
به طور کلی در زمان طراحی این مدل سیستم باید موارد زیر در نظر گرفته شوند:
- اصل مسئولیت واحد (Single Responsibility Principle): تمرکز بر یک منطق و محدوده خاص برای هر میکرو سرویس، به چابک بودن توسعه و استقرار سرویسها بسیار کمک میکند.
- در طول طراحی باید ابعاد و محدودیتهای موجود در هر سرویس شناسایی شده و با هدفهای تجاری مورد نظر تطبیق داده شوند. (مفهموم Domain Driven Design)
- مطمئن شویم که طراحی این میکرو سرویسها از چابک بودن توسعه و استقرار نرمافزار نکاهند.
- نباید ابعاد میکرو سرویس کوچک شوند. بلکه باید به نحوی کامل توسعه داده شود که تمامی نیازهای مربوط به بخش تجاری و مورد نیاز کاربران را تامین کند. به همین دلیل است که نباید مفهوم میکرو استفاده شده در این طراحیها با کوچک کردن سرویس یکی در نظر گرفته شود!
- تا جای ممکن باید سادگی در توسعه رعایت شود و سعی شود تا قابلیتها و ساختارهای قابل درک و کوچکی داشته باشد. چرا که هدف از میکرو سرویسها بهبود توسعه و استقرار و جلوگیری از پیچیدگی در توسعه میکروها است.
- بهتر است که ابتدا از یک سرویس بزرگ و یکپارچه کار شروع شود و سعی شود تا سرویسهای موجود در آن جدا شده و به سرویسهای کوچکتر تبدیل شوند.
تبادل پیام در میکرو سرویس
در نرمافزارهای یکپارچه تمامی عملکردهای مورد نیاز برنامه از طریق پروسهها و توابع مختلف صورت میگیرند و از طریق فراخوانی توابع یا متدهای ارائه شده آن زبان برنامهنویسی خواهند بود. اما در میکرو سرویسها به طور کلی ارتباط بین سرویسها و تبادل پیامها به یکی از دو طریق زیر خواهد بود:
پیام همزمان
در تبادل پیام همزمان (که کاربران پیامی برای سرور ارسال نموده و منتظر جواب آن خواهند بود)، یکی از بهترین راهکارهای موجود REST است. REST یک راهکار تبادل پیام ساده مبتنی بر درخواستهای HTTP است که ساختارهای شفاف و استانداردی را در اختیار توسعهدهندگان قرار میدهد. به همین دلیل امروزه بسیاری از سرویسهای پیادهسازیشده در این معماری از HTTP نیز استفاده میکنند تا سایر سرویسها برای دسترسی به منابعشان از طریق درخواستهای HTTP اقدام کنند.
پیام ناهمزمان
در بر خی از سناریوهای موجود در این معماری نرمافزاری، نیاز است یک ساختار تبادل پیام ناهمزمان پیادهسازی شود. به عنوان مثال کاربران کاربران انتظار دریافت هیچ پاسخی از سمت سرور بلافاصله پس از ارسال ندارند. به عنوان مثال کاربری که لاگین کرده و نیاز است تا یک ایمیل فعالسازی برای وی ارسال شود. این ارسال ایمیل میتواند توسط سرویسی مجزا اجرا شود. در این سناریوها، پروتکلهای پیامرسانی ناهمزمان مثل AMQP، STOMP یا MQTT بسیار مورد استفاده قرار میگیرند.
ادغام میکرو سرویسها (ارتباط بین سرویسها و پروسسهای داخلی)
به طور قطع یکی از مهمترین مباحث در طراحی یک معماری میکرو سرویس موفق، بررسی و پیادهسازی ارتباط بین سرویسهای موجود است. به دلیل اینکه اکثر میکرو سرویسها از پرتوکل HTTP و نوع دیتای JSON استفاده میکنند، به همین دلیل ادغام آنها به دلیل سادگی این ساختارها راحتتر خواهد بود.
راه دیگر ارتباط بین سرویسها از طریق یک bus یا درگاه با کمترین میزان routing است تا هدایت درخواست به سرویسهای داخلی را انجام دهد. به همین مدلهای مختلفی برای ادغام میکرو سرویسها ارائه شده است که در ادامه به صورت مختصر بررسی خواهند شد.
ارتباط point-to-point (فراخوانی سرویسها به صورت مستقیم)
در این مدل ارتباط، هر کدام از سرویسها به صورت مستقیم با هم در ارتباط هستند. هر میکرو سرویس APIهای خود را ارائه کرده و در اختیار سایر سرویسها قرار میدهد. حال هر کدام از سرویسها در صورت نیاز از این API ها استفاده کرده و اطلاعات مورد نیاز را از سرویس مورد نظر دریافت میکنند. به عنوان مثال شکل زیر این معماری را نشان میدهد.
همانطور که مشاهده میکنید در صورتی که کاربر از اپ موبایل بخواهد اطلاعات فروشگاه را دریافت کند، درخواست خود را به میکرو سرویس فروشگاه ارسال میکند. حال سرویس فروشگاه در صورت نیاز برای دریافت اطلاعات حساب شخصی یا خریدهای وی به سرویس مربوطه یک درخواست HTTP ارسال میکند و اطلاعات مربوطه را دریافت کرده و از این اطلاعات در سرویس فعلی استفاده کرده و جواب مورد نظر را برای کاربر ارسال میکند.
استفاده از API-Gateway
در این مدل تمامی درخواستهای ورودی به کل مجموعه از طریق یک درگاه صورت میگیرد. به این صورت که کاربران کلاینت که میتوانند کاربران اپ موبایل، وب یا هر سرویس دیگری باشند تنها به این درگاه ارسال خواهد شد و این درگاه درخواستها را به سرویس مربوطه هدایت میکند. شکل زیر این فرآیند را نشان میدهد. همانطور که مشاهده میکنید تمامی درخواستها فقط به این درگاه ارسال شده و این درگاه است که تصمیم میگیرد که این درخواست به کدام سرویس داخلی هدایت شود. البته کار درگاهها خیلی فراتر از اینها است که همگی در مقالهای دیگر مفصل بررسی خواهند شد.
از جمله این قابلیتها میتوان به کنترل تعداد درخواستها از سمت کاربران برای جلوگیری از حملات و اسپمها، احراز اصالت، load balancing و … اشاره کرد. به عبارت کلی در این مدل پیادهسازی درگاه APIهای شما یکی از مهمترین قسمتهای سیستم است که بهتر است توجه بیشتری به آن شود.
ارتباط با استفاده از بروکر پیامها
یک راه دیگر در پیادهسازی ارتباطات ادغام سرویسها با سناریوهای استفاده از کانالهای پیامی غیرهمزمان است. به عنوان مثال درخواستها به صورت یک طرفه ارسال شوند و با استفاده از مکانیزم publish-subscribe بر روی صفها و تاپیکهای مختلف پخش شوند. سپس میکرو سرویسهایی که مصرف کننده آن پیام هستند به تاپیک مربوطه subscribe شده و از این اطلاعات استفاده میکنند و دوباره در صورت نیاز اطلاعات را انتشار میدهند که این چرخه میتواند بین میکرو سرویسها جریان داشته باشد.
یکی از مزیتهای این مدل استقلال کامل سرویس publisher و subscriber است که بروکر میانی این اطلاعات را بافر کرده و در صورتی که سرویس شلوغ بود منتظر میماند تا آن سرویس آماده شده و اطلاعات را در اختیار سرویس قرار دهد. شکل زیر این مدل را بهتر توضیح میدهد:
مدیریت داده غیر متمرکز
در معماری یکپارچه تمامی اطلاعات دیتابیس نرمافزار در یک پایگاه داده و متمرکز ذخیره میشوند. در حالی که در میکرو سرویسها هر سرویس پایگاه داده خاص خود را دارد. چرا که مطابق با نوع عملیاتی که هر سرویس قرار است انجام دهد ممکن است ساختار داده متفاوتی برای آن سرویس مناسب باشد. به طور مثال نیاز است تا سرویس از پایگاه داده mongodb استفاده کند در حالی که سرویس دارای ساختار و قواعد sql است و پیادهسازی آن سرویس با استفاده از پایگاه دادهای مثل mysql راحتتر است.
علاوه بر این، دلیل اصلی جدا کردن پایگاه داده در میکرو سرویسها این است که اگر همه سرویسها از یک دیتابیس مشترک استفاده کنند به هم وابسته هستند که این اولین شرط شکلگیری یک میکرو سرویس موفق یعنی استقلال پایگاه داده را نقض خواهد کرد. به این صورت که تغییر در یک سرویس باعث تغییر در پایگاه داده و در صورتی که مشترک باشد سایر سرویسها نیز باید این تغییرات را اعمال کنند. شکل زیر تفاوت این دو مدل را نشان میدهد:
سرویس رجیستری و سرویس کشف
در این معماری تعداد میکرو سرویسها عموما زیاد است. ضمن اینکه موقعیت آنها ممکن است به طور پیوسته تغییر کند که این ناشی از خاصیت چابک و سریع بودن توسعه و استقرار در این مدل توسعه نرمافزاری است. به همین دلیل است تا بتوان به راحتی موقعیت هر سرویس را شناسایی کرد. راهکار استفاده از سرویس رجیستری است. این سرویس نام هر سرویس و موقعیت آن را نگهداری میکند. که نام سرویس در ابتدای کار ثبت میشود و در هر تغییر موقعیت جدید به آن گزارش خواهد شد. حال هر کدام از سرویسهای موجود به راحتی میتوانند از طریق این سرویس رجیستری سرویس مورد نظر خود را کشف کنند.
ضمن اینکه برای پیدا کردین سرویسهای موجود و موقعیت آنها باید مکانیزمی برای کشف نیز وجود داشته باشد. که عموما دو مکانیزم کشف سرویس وجود دارد. یکی از طرف کلاینتها و دیگری از سمت سرویسها.
Deployment
شاید یکی از سختترین و حساسترین قسمتهای پیادهسازی یک میکرو سرویس این قسمت است و دارای نیازهای زیر است:
- امکان استقرار یا برداشتن یک میکرو سرویس مستقل از سایرین (اختلالی در عملکری سایر سرویسها بوجود نیاید)
- قابل توسعه تا هر مقیاسی باشد. به این صورت که هر سرویس را باید بتوان تا هر اندازهای متناسب با نیازی که به آن وجود دارد توسعه داد و نباید محدودیتی وجود داشته باشد)
- پیادهسازی و توسعه سریع میکرو سرویسها
- ایجاد مشکل در یک سرویس نباید باعث اختلال در مابقی سرویسها شود
که برای این منظور تکنولوژیهای Docker و Kubernetes بسیار به موفق بودن عملیات استقرار و دیپلوی کمک خواهند کرد. که هر کدام از این مباحث به طور مفصل در مقالههای دیگری ارائه خواهند شد.
امنیت
به طور قطع امنیت پارامتری است که وجود آن شاید به چشم نیاید، ولی قطعا نبود و نقص آن کل معماری را زیر سوال خواهد برد. به همین دلیل باید توجه ویژهای به آن داشت. یک راهکار خسته کننده این است که امنیت در سطح هر میکرو سرویس رعایت شده و تمامی عملیات امنیتی زیر نظر مدیریت یک سرویس متمرکز در کنار این سرویسها باشد که باید با آن سرویس هماهنگ باشد. ولی راهکار بهتر استفاده از روشهای استاندارد API-Security ارائه شده مثل استفاده از OAuth2 و OpenID Connect است. اما قبل از توضیح این مدل نیاز است تا این دو تکنولوژی کمی بررسی شوند.
- OAuth2: یک پروتکل مجوز دسترسی است. مشتری با سرور احراز اصالت هویت خود را تایید نموده و یک توکن بخصوص تحت عنوان Access Token دریافت میکند. این توکن دسترسی هیچ اطلاعاتی در مورد کاربر یا مشتری ارائه نمیدهد. بلکه تنها از طریق سرور مجوزدهی مورد تایید است و در صورت ارائه به آن سرور اطلاعات کاربر مربوطه را در صورت صحت ارائه میدهد. این مدل تحت عنوان by-reference token نیز شناخته میشوند چرا که در صورتی که توکن افشا شود باز هم هیچ اطلاعاتی را در اختیار سایرین قرار نمیدهد و بهتر از در شبکههای عمومی مورد استفاده قرار بگیرد.
- OpenID Connect: که رفتاری مشابه با OAuth دارد ولی در ازای Access Token سرور احراز اصالت یک ID Token در اختیار قرار میدهد که شامل اطلاعات کاربر است. این مدل که عموما با استفاده از JWT پیادهسازی شده و با استفاده از سرور احراز اصالت تایید میشود تا صحت ارتباط بین سرور و مشتری مورد تایید قرار بگیرد. این توکن که تحت عنوان By-value-token شناخته میشوند دارای اطلاعات کاربر هستند و به همین دلیل بهتر است که تنها در شبکههای داخلی استفاده شوند برای ارتباط بین سرویسهای لوکال.
شکل زیر نحوه استفاده از این استانداردهای امنیتی را نشان میدهد.
با توجه به شکل بالا، میتوان مهمترین قسمتها در پیادهسازی امنیت در میکرو سرویسها را به موارد زیر تقسیم کرد:
- تمامی احراز اصالتها با OAuth و OpenID connect انجام خواهند شد. و در صورتی که کاربری دسترسیهای لازم را داشته باشد، میکرو سرویسها اطلاعات مورد نظر را در اختیار آنها قرار میدهند.
- از مدل پیادهسازی با API-Gateway باید استفاده شود. به این صورت که تنها یک نقطه ورود برای دسترسی کلاینتها به تمامی سرویسها تنها از طریق API Gateway وجود خواهد داشت.
- کاربران به سرور احراز اصالت وصل شده و یک Access Token دریافت میکنند (یک توکن by-reference-token). سپس این توکن را در تمامی درخواستهای خود برای API Gateway ارسال میکنند.
- تبدیل توکنها در سطح درگاه صورت خواهد گرفت. به این صورت که درگاه API-Gateway این توکن Access Token ارائه شده توسط کلاینتها را از درخواستها استخراج میکند و آن را برای سرور احراز اصالت ارسال میکند تا کد jwt (by-value-token) را دریافت کند.
- سپس API Gateway این توکن را همراه با درخواست خود به سرویسهای داخلی ارسال میکند.
- این jwt شامل اطلاعات مورد نیاز کاربر مثل نشست کاربر و غیره است. در صورتی که همه سرویسها (یا حداقل سرویسهایی که به احراز اصالت نیاز دارند) این مکانیزم JWT را متوجه بشوند، از این طریق موفق شدهاید تا اطلاعات کاربران درخواست کننده را بین سرویسها انتقال دهید.
- در سطح هر میکرو سرویس میتوان یک ماژول ساده که این کد jwt را پردازش میکند قرار داد که کار خیلی پیچیدهای نیست.
تراکنشها
اگر با تراکنشهای بانکی یا فرایندهایی که به هم وابسته هستند کار کرده باشید آگاه هستید که اگر در طول فرآیند اتفاقی رخ دهد که تراکنش ناموفق باشد (منظور از تراکنش مجموعه فرآیندها هست نه لزوما تراکنش بانکی!) کل فرآیند باید به حالت اول برگردد. این مفهموم که تحت عنوان Transactions شناخته میشود یکی از پیچیدهترین مقولهها در پیادهسازی میکرو سرویسها است. ولی لزوما به این معنی نیست که قابل پیادهسازی نیست:)
ایده میکرو سرویسها این است که سرویس داده شده کاملا مستقل و بر اساس اصل مسئولیت واحد است. به همین در صورتی که انجام تراکنشهای توزیع شده بین چند میکرو سرویس وجود داشته باشد، نشان دهنده نقض این قضیه است. ولی با این حال اگر حالتی به وجود آمد که این شرایط را شکل داد، باید مکانیزمی وجود داشته باشد تا fail شدن در یک قسمت از تراکنش در یک میکرو سرویس تمامی سرویسهای بالاسر و مرتبط را از طریق کانالی (استفاده از یک کانال پیامی غیرهمزمان) مطلع کند تا آنها نیز به حالت اولیه بازگردند.
کنترل خطاها
با توجه به اینکه در معماری میکرو سرویس تعداد سرویسهای کوچک و مستقل افزایش مییابد، احتمال خطا هنوز در هر سرویس وجود دارد. به همین دلیل نباید خطا و fail شدن یک سرویس سایر سرویسها را تحت تاثیر قرار دهد. ضمن اینکه ممکن است هر لحظه اتفاقی برای یک سرویس بیفتد نیاز است تا از دسترس خارج شدن سرویسها شناسایی شده و راهکاری برای حل آنها وجود داشته باشد. که راهکارهای متفاوتی برای این منظور وجود دارند که میتوان به Circuit Breaker، Bulkhead و Timeout اشاره کرد. که عموما بهترین راهکار استفاده از API Gateway و پیادهسازی مکانیزمها در آن بخش است. که امروزه درگاههای بسیاری قویای مثل Kong پیادهسازی شدهاند که قابلیت Fault Tolerant را به صورت پیشفرض در خود پیادهسازی کرده و قابل تنظیم هستند.
سخن پایانی
در آخر سوالی که مطرح میشود این است که آیا از میکرو سرویسها استفاده شود یا نه. و جواب این سوال کاملا وابسته به ساختار نرمافزاری، تیم توسعه شما، امکانات موجود، ابعداد پروژه و … است. و باید دقت شود گاهی پیادهسازی نادرست میکرو سرویسها ممکن است حتی به نابودی پروژه شما ختم شود!!!
چرا که اگرچه این معماری دارای مزیتهای بسیاری زیادی است ولی اگر هر کدام از بخشهای ارائه شده به درستی پیادهسازی نشوند و یا تکنولوژی و علم کافی برای پیادهسازی آن وجود نداشته باشد، میتواند نتیجهای عکس داشته باشد. ولی اگر این معماری به درستی پیادهسازی شود اگرچه در شروع کار پروژه وقت بیشتری باید برای پیادهسازی و استقرار سرویسها گذاشته شود، ولی توسعه سرویس میتواند فوقالعاده سریعتر، چابکتر و مقیاسپذیرتر است. امروزه بسیاری از شرکتهای بزرگ مثل Netflix به سمت این معماری حرکت کردهاند. پس آیا نمیتوان یک آینده فرا موفق برای این معماری نرمافزاری تصور کرد؟