در قسمت اول از این مجموعه درباره میکروسرویسها، مزایا و معایب آنها و چگونگی تاثیرگذاریشان بر دنیای توسعه نرم افزار صحبت کردیم. با اینکه توسعه میکروسرویسها پیچیدگیهای زیادی به همراه دارد، اما تاثیرات شگرفی بر حل پیچیدگیهای موجود در توسعه نرم افزارهای بزرگ داشته که باعث شده به عنوان یکی از محبوبترین روشهای توسعه نرم افزار طی چند سال اخیر به حساب بیاید. در این قسمت و چند قسمت آینده در مورد برخی پیچیدگیها و راه حلهای آنها و الگوهای توسعه میکروسرویسها صحبت خواهیم کرد.
هنگامی که میکروسرویسها خود را توسعه میدهید یک مسئله مهم پیش روی شما قرار دارد. چگونه clientهای شما با میکروسرویسهای شما تعامل خواهند کرد؟ هنگامی که از روش Monolithic برای توسعه نرمافزارهای خود استفاده میکنید نهایتا یک Endpoint خواهید داشت و در صورت نیاز همین یک Endpoint برای تقسیم بار روی چند سرور نصب میشود و به کمک یک Load balancer بار روی این سرورها توزیع میشود. اما زمانیکه میکروسرویس توسعه میدهید مجموعهای از endpointها خواهید داشت که باید بتوانید با آنها تعامل کنید. در این قسمت قصد داریم راجع به تاثیرات تعداد زیادی endpoint داشتن و نحوه حل کردن این مشکل صحبت کنیم.
1. مقدمه:
فرض کنید شما در حال توسعه اپلیکیشن برای یک فروشگاه آنلاین هستید. احتمالا صفحه ای خواهید داشت که جزئیات محصولات را نمایش خواهد داد. بیایید نگاهی به صفحه محصولات دیجی کالا بیاندازیم. در این صفحه به راحتی میتوان چندین بخش را تشخیص داد. بخش اصلی هدف صفحه است که همان نمایش اطلاعات محصول است. اما در این صفحه چند قسمت دیگر نیز به چشم میخورد. مثل: فروشندگان، نقد و بررسی، نظرات کاربران، پرسش و پاسخ، محصولات پیشنهادی و ... اگر کمی ریزبینباشید احتمالا متوجه بخشهایی که من جا انداختهام هم شدهاید مثل اطلاعات سبد خرید.
حالا فرض کنید که نیاز داریم یک موبایل اپلیکیشن هم برای فروشگاه آنلاین خود طراحی کنیم و در نتیجه در موبایل اپلیکیشن خود نیز به صفحهای برای نمایش جزئیات کالا نیاز داریم. ( هر چند نمایش این حجم داده در یک صفحه موبایل بسیار زیاد است اما برای سادگی مثال شما به روی خودتان نیاورید (-: )
هنگامی که از روش Monolith استفاده میکنیم موبایل اپ ما تمام این دادهها را با یک درخواست ساده مانند زیر به دست خواهد آورد:
Get api.digikala.ir/product/111
در صورتی که برنامه ما تک نسخهای باشد که به سادگی اجرا خواهد شد و نتیجه را باز خواهد گردانید و در صورتی که نرم افزار ما پشت load balancer قرار داشته باشد درخواست توسط load balancer مسیریابی شده و به بهترین نسخه اجرایی نرم افزار تحویل داده خواهد شد و بعد از عبور از لایههای مختلف نرم افزار به دیتابیس خواهد رسید و با اجرای کوئری روی چند جدول دیتابیس نتیجه دلخواه آماده شده و در اختیار ما قرار خواهد گرفت.
در طرف مقابل اگر از روش میکروسرویس استفاده کرده باشیم دادههایی که به آنها در صفحه جزئیات محصول نیاز داریم در اختیار چندین سرویس مختلف قرار دارد. در اینجا به چند نمونه از این میکروسرویسهای احتمالی اشاره خواهیم کرد
حالا نیاز داریم تصمیم بگیریم اپلیکیشن موبایل ما چگونه به این سرویسها دسترسی خواهد داشت. در ادامه به بررسی گزینههای موجود خواهیم پرداخت.
2. تعامل مستقیم Clientها با میکروسرویسها:
به صورت تئوری میتوانیم متصور شویم که هر Client در صورت نیاز به صورت مستقیم با Microserviceها تعامل خواهد کرد. هر میکروسرویس Endpoint اختصاصی خود را دارد و به راحتی میتوان با آن تعامل کرد مثل:
با کمی دقت متوجه خواهیم شد که برای اینکه یک صفحه ساده ایجاد شود اپلیکیشن ما نیاز دارد که به چندین endpoint متفاوت درخواست ارسال کند. متاسفانه هر چند این روش در نگاه اول راه حل بدیهی و ساده ای به نظر میرسد اما مشکلات فراوانی به همراه خواهد داشت. بزرگترین مشکل تعداد زیاد درخواستهایی است که باید برای ساخت یک صفحه ارسال شود و با بالارفتن تعداد درخواست کار ساخت و مدیریت صفحه نیز به شدت سخت و پیچیده خواهد شد. در یک اپلیکیشن ساده مانند مثال بالا به سادگی نیاز به ارسال بیش از 10 درخواست برای ساخت صفحه داریم. شرکت آمازون در مطلبی مرتبط توضیح داده بود که چگونه برای نمایش صفحه محصولات خود نیاز به استفاده از قریب به صدها سرویس داشته است.
مشکل بعدی مربوط به استفاده از پروتوکلهای متفاوت برای ارائه API می شود. با توجه به اینکه هر میکروسرویسی می تواند به طور اختصاصی و با توجه به نیازهای خود APIهای خود را ارائه دهد ممکن است یک سرویس از Thrift استفاده و کند و دیگری از AMQP و کاملا محتمل است که در این بین از پروتوکلهایی استفاده شود که خیلی web friendly نیستند.
سومین مشکل در این روش مربوط به Refactor کردن میکروسرویسها میشود. در طول زمان ممکن است متوجه اشتباهاتی در طراحی خود بشویم و نیاز داشته باشیم که دو یا چند میکروسرویس را با هم ادغام کنیم یا برعکس یک سرویس را به چندین سرویس تبدیل کنیم. در حالتی که Clientها به طور مستقیم با سرویسهای ما تعامل کنند احتمالا این تغییرات تاثیر منفی فراوانی رو Clientهای ما خواهد گذاشت و کار Refactoring بسیار پرخطر و پر ریسک خواهد شد.( به هر حال اگر در چنین شرایطی تصمیم به Refactor گرفتید به طور اکید توصیه میکنم تا زمانی که آب ها از آسیاب بیوفته یه جایی خودتونو مخفی کنید.)
به خاطر تمامی دلایل بالا و بسیاری دلایل دیگری که شاید با کمی بررسی به آنها خواهید رسید استفاده از این روش مناسب نیست و باید به فکر جایگزینی برای این روش باشیم.
3. حل مشکلات با استفاده از API Gateway:
روش بهتری برای تعامل clientها با میکروسرویسها وجود دارد که آن را با نام API Gateway میشناسیم. در این روش تنها نقطه ورود برنامه ما یک Gateway است و راه ارتباطی Clientها با microserviceها همین Gateway است. در صورتی که با الگوی facade آشنایی داشته باشید API Gateway عملکردی شبیه به این الگو دارد. در این الگو به جای اینکه برای انجام یک کار با چندین API مختلف تعامل کنیم به سادگی با یک API تعامل میکنیم و پیچیدگیها و معماری داخلی ما از چشم استفاده کننده پنهان میماند. صدا زدن چندین سرویس مختلف و ترکیب نتنیجه و بازگرداندن نتیجه نهایی مواردی است که باید داخل API Gateway ما کپسوله شود و استفاده کننده نهایی به سادگی و با صدا زدن یک API نتیجه دلخواه خود را دریافت کند.
تمامی درخواستهای کاربران به API Gateway تحویل داده می شود و در API Gateway مسیریابی به هر سرویس، تعامل با پروتوکلهای مختلف و ترکیب نتیجه به دست آمده از هر میکروسرویس انجام می شود.
یکی دیگر از کارهای خوبی که در این زمینه میتوان انجام داد پیاده سازی Gatewayهای تخصصی برای هر Client است. مسلما تمام دادههایی که در صفحه مانیتور نمایش داده میشود مناسب نمایش در صفحه یک گوشی موبایل نیست. پس بهتر است به ازای هر Client یک Gatewayتخصصی داشته باشیم که صرفا دادههای آن Client را فراهم کند.
3.1. مزایا و معایب استفاده از API Gateway:
مثل هر کار دیگری استفاده از API Gateway هم مزایا و معایب خاص خود را دارد که ابتدا به مزایای آن میپردازیم. بزرگترین مزیت آن از بین بردن معایب روش دسترسی مستقیم است. عدم وابستگی به معماری داخلی سیستم ما باعث میشود کار Refactoring سادهتر قابل اجرا باشد و دیگر برای ترکیب یا تجزیه سرویسهای مختلف دغدغههای قبل را نداشته باشیم( پیدا کردن جایی تا زمانی که آبها از آسیاب بیوفتد). ارائه API تخصصی برای هر Client باعث افزایش بهرهوری و بهبود خروجیها و در یک کلام UX بهتر میشود. کاهش تعداد درخواستهای ارسالی از Client هم مورد بعدی است که بهرهوری کار را بالاتر میبرد.
در کنار این مزایا اما چند ایراد نیز میتوان به استفاده از این روش گرفت. بزرگترین ایراد این روش اضافه شدن یک ماژول بزرگ به سیستم است که باید همیشه سرحال و آنلاین باشد و در صورتی که عملکرد درستی ارائه نکند کل سیستم با مشکل مواجه خواهد شد. با توجه به اینکه تعامل با هرکدام از میکروسرویسها باید در API Gateway پیاده سازی شود و به ازای هر Clientهم نیاز داریم که پیاده سازی اختصاصی داشته باشیم این احتمال وجود دارد که همین API Gateway به سدی برای تیم توسعه تبدیل شود. زمانی که یک سرویس به روز میشود clientها باید منتظر بمانند تا این به روزرسانی در Gateway ارائه شود. به همین دلیل باید توسعه API Gateway ما طوری باشد که به سادگی قابل تغییر و به روزرسانی باشد.
با وجود تمامی این مشکلات اما نکات مثبت استفاده از این الگو به قدری زیاد است که نمیتوان به سادگی از این الگو چشم پوشی کرد.
3.2. پیاده سازی API Gateway:
حال که با مزایا و معایب API Gateway آشنا شدیم به بررسی چند نکته در رابطه با پیاده سازی آن خواهیم پرداخت.
3.2.1. مسئله بهرهوری و مقیاس پذیری:
احتمالا تعداد انگشتشماری شرکت در دنیا مانند Netflix وجود دارند که نیاز دارند روزانه به میلیونها و میلیاردها درخواست پاسخ بدهند و از دسترس خارج شدن آنها حتی برای چند لحظه غیر قابل قبول باشد. با این حال با توجه به شرایطی که بررسی شد،بهره وری بالا و قابلیت مقیاس پذیری از نیازهای اولیه هر API Gateway است. ابزارها و زبانهای مختلفی وجود دارد که میتواند این ویژگیها را در اختیار شما قرار دهد که برای مثلا میتوان به .net core و Node.js اشاره کرد.
3.2.2. استفاده از مدل برنامه نویسی Reactive:
در بعضی موارد میتوان به سادگی درخواستهای ورودی را به یک مسیر جدید ارسال کرد و نتیجه را دریافت کرد و به کاربر بازگرداند. در بعضی موارد هم ممکن است یک درخواست ورودی به چندین سرویس ارجاع داده شود و در نهایت نتیجه تمامی این درخواستها با هم ترکیب شود و به کاربر بازگردانده شود. در بعضی موارد هم ممکن است یک درخواست نیاز باشد از چند سرویس استفاده کند اما ترتیب و توالی استفاده از این سرویسها اهمیت داشته باشد. برای مثال زمانی که قرار است به کاربری کالا پیشنهاد داده شود ابتدا لازم است تنظیمات و علائق کاربر از سرویس کاربران دریافت شده و سپس با اطلاعات دریافتی کالاهای پیشنهادی پیدا شده و در اختیار کاربر قرار بگیرد.
با توجه به شرایط توضیح داده شده احتمالا استفاده از روشهای معمول برنامه نویسی خیلی زود ما را وارد جهنمی از کدهای پیچیده و غیرقابل خواندن و تغییر میکند. پس بهتر است به روشی توسعه خود را انجام دهیم که مناسب شرایط باشد. در این شرایط به نظر میرسد Reactive Programming راهکار بهینهتری نسبت به روش معمول برنامه نویسی باشد.
3.3.3. راهکارهای تعامل:
در یک API Gateway نیاز است با سرویسهای مختلف تعامل انجام شود و اصطلاحا Inter-Process Communication انجام شود. سرویسهای مختلف ممکن است راهکارهای متفاوتی برای تعامل در اختیار ما قرار دهند. ممکن است از روشهای Async مثل AMQP یا روشهای sync مثل HTTP و Thrift استفاده شود. به هر حال بدون توجه به روشهای تعامل API Gateway مورد نظر ما باید بتواند با تمامی این روشها ارتباط برقرار کند.
3.3.4. یافتن آدرس سرویسها:
وقتی Gateway را پیاده سازی میکنیم باید به این موضوع فکر کنیم که نیاز داریم آدرس سرویس های مختلف را پیدا کنیم. در روشهای قدیمی احتمالا نرم افزارهای ما به راحتی روی یک سرور نصب میشوند و دانستن آدرس آنها کار سختی نخواهد بود. اما در روشهای جدید و استفاده از سرویسهای ابری آدرس دیگر به سادگی و ثابت به دست نمیآید پس باید قبل از هر مسئلهای به یافتن آدرس میکروسرویسها بیاندیشیم. در قسمتهای بعد به طور مفصل راجع به این مشکل و راه حل آن صحبت خواهیم کرد.
3.3.5. مدیریت خطاها:
مشکل دیگری که هنگام توسعه یک API Gateway باید به آن بیاندیشیم partial failure است. یعنی زمانی که یک درخواست کلی میآید و بخشی از درخواست قابل پاسخ گویی نیست. مثلا در سیستم فروشگاه و صفحه جزئیات فروشگاه یکی از سرویسها در دسترس نباشد. مثلا سرویس پیشنهاد کالا در دسترس نباشد. API Gateway باید این قابلیت را داشته باشد که در این شرایط به جای اینکه کل درخواست را لغو کند،دادههای بخشهای صحیح را به دست آورد و برای بخشهای مشکل دار خطا را مدیریت کند. برای مثال دادههای کش شده داشته باشد که در این شرایط جایگزین دادههای آنلاین شود. یا مثلا در صورتی که سرویس پیشنهاد کالا قطع باشد 10 کالای پرفروش را بازگرداند.
4. جمع بندی:
در این قسمت راجع به یکی از الگوهای پرکاربرد و شرایط و نیازمندیهای توسعه آن صحبت کردیم. پیاده سازی یک API Gateway خالی از لطف نیست و میتواند به درک بهتر چگونگی پیاده سازی این الگو کمک کند. با این حال برای پروژههای عملیاتی با توجه به شرایط و نیازهای پروژه استفاده از ابزارهای آماده برای این کار مانند Kong، aws api gateway یا Ocelot گزینههای بهتری است.
ادامه مطلب :