در توسعه نرم افزار، ایده اصلی معماری میکروسرویس بر تجزیه عملکردی مبتنی است. به بیان ساده تر به جای توسعه یک برنامه بزرگ، آن را در قالب سرویس های جداگانه توسعه داده و با هم ادغام می کنیم. در صورت درک اولیه این مفهوم، ممکنه با ابهامات متعددی روبرو بشیم از جمله اینکه تعریف از سرویس چیست؟ اندازه یک سرویس چه قدر است؟ تعامل بین آنها چگونه انجام می شود؟ و ...
برای پاسخ به این ابهامات لازمه که ابتدا با معماری نرم افزار آشنا بشیم. Chris Richardson در کتاب Microservices Patterns به این صورت تعریف مورد علاقه خودش رو از معماری نرم افزار بیان می کنه:
معماری نرم افزار مجموعه ای از ساختارهای مورد نیاز برای موجودیتی است که شامل عناصر نرم افزاری، روابط بین آنها و ویژگی های هرکدام می باشد.
از این تعریف می شود به خوبی برداشت کرد که یک نرم افزار می تواند از چندین بخش مستقل (سرویس) و روابط بین آنها تشکیل بشود. امروزه تجزیه پذیری یکی از مهمترین فاکتورهایی است که در توسعه نرم افزار در نظر گرفته می شود که به دلایل زیر اهمیت زیادی دارد:
برای پاسخ به این ابهام ابتدا باید منظور خودمون رو از اندازه مشخص کنیم. آیا تعداد خط های کد می تواند معیار صحیحی برای اندازه باشه؟ به نظر نمی رسه که بتونه معیار درستی باشه در نظر بگیرید یک پیاده سازی ساده اگر برای Java تعداد 25 خط کد نیاز داشته باشه در Clojure به سادگی در 10 خط انجام میشه.
سم نیومن در کتاب Building Microservices نقل قول جالبی از James Lewis بیان کرده:
یک میکروسرویس باید اندازه کَله من باشه
در نگاه اول شاید این دیدگاه یکم گنگ و بی معنی باشه اما در حقیقت بیانگر این نکته هست که اندازه یک میکروسرویس باید به قدری باشه که به خوبی قابل فهم باشه. به عبارت دیگر پیچیدگی مهمترین معیار برای سنجش اندازه یک میکروسرویس هست. این ویژگی به شدت به تیم شما بستگی داره که تا چه قدر بتونن با پیچیدگی ها کنار بیان. به عبارت دیگه برای تیمی با توسعه دهندگان حرفه ای تر، میتوان پیچیدگی بیشتری برای یک میکروسرویس تصور کرد.
هدف نهایی تهیه واسط برنامه نویسی نرم افزار (API) می باشد. به گونه ای که سایر سرویس ها با استفاده از آن بتوانند تعامل مناسبی با سرویس کنونی برقرار کنند. در ادامه برخی از مهمترین رویکرد هایی که برای تعامل بین سرویس ها استفاده می شود خدمتتون بیان میکنیم.
انتقال بازنمودی حالت (Representational State Transfer) نوعی معماری نرم افزار است که مجموعه اصول و قواعدی را برای بهره وری از یک سرویس خاص در بستر وب فراهم می کند.
داده، اهمیت خاصی در این سبک معماری دارد. میتوانید داده را به عنوان موجودیتی تصور کنید که سرویس به خوبی از آن آگاه هست و اطلاعات آن ذخیره می کند (مانند مشتری). سرویس RESTful، حالت های مختلفی از یک داده را بر حسب درخواست دریافتی از سایر سرویس ها تولید کرده و با فرمت معینی (مثلا JSON) بر میگرداند. این ویژگی عملا امکان تجزیه پذیری نرم افزار را فراهم کرده و به سرویس ها اجازه می دهد که به خوبی زبان یکدیگر را بفهمند.
بعضا دیده می شود که REST و HTTP به جای یکدیگر به کار برده می شوند. دقت کنید که HTTP در حقیقت یک پروتکل از لایه کاربرد شبکه است که امکان تبادل داده میان کلاینت و سرور را میسر می کند. در صورتی که REST مجموعه اصول و قواعدی را بیان می کند که برای معماری نرم افزار به کار برده می شود. یک سیستم RESTful، برای افزایش انعطاف پذیری از قواعد خاص HTTP شامل GET, POST, DELETE استفاده می کند. یادآوری این نکته مهم است که استفاده از پروتکل HTTP برای یک سیستم RESTful لازم نیست.
یکی از ایراداتی که به REST گرفته شد تعداد حالت های محدود برای نمایش داده است. برای توضیح بیشتر به مورد زیر توجه کنید:
یک نرم افزار تلفن همراهی را تصور کنید که قصد دارد در یک صفحه، نمای کلی از آخرین سفارشات مشتری را نشان دهد. صفحه شامل پنج سفارش آخر مشتری است که فقط فیلد های خاص از هر سفارش را نشان می دهد. در حالت عادی برای بازیابی اطلاعات مورد نیاز، باید درخواست های متعددی به سرور زده بشود که در واقع باعث کاهش کارایی می شود.
روش GraphQL این امکان را فراهم می کند که با استفاده از یک تک کوئری خاص، تمام اطلاعات مورد نیاز از سرور را به دست بیاورید بدون این که مجبور باشید چندین بار به روش های مختلف به سرور درخواست بزنید. با وجود این که GraphQL برای مصرف کننده یا کلاینت دست بازتری برای واکشی داده قرار می دهد اما میتواند موجب بار بیشتری در سمت سرور بشود.
این روش به طور ویژه برای کاربردهای آسنکرون استفاده می شود. قبل از آن مثال زیر را برای درک تفاوت بین فراخوانی سنکرون و آسنکرون در نظر بگیرید:
Response registerUser(User user) { Response response = userService.register(user); messageSender.send(response.getMessage()); return response; }
واضح است که در این مثال ابتدا کاربری را در سامانه ثبت نام کرده سپس پیامی را (از طریق ایمیل، پیامک و ...) برای او ارسال می کنیم و در نهایت نتیجه نهایی را برای کاربر برمیگردانیم. نکته مهم در این مثال ارسال پیام است. اگر بخواهیم فراخوانی send از messageSender را به صورت سنکرون در نظر بگیریم در این صورت باید تا پایان فرایند ارسال پیام به کاربر منتظر مانده و سپس نتیجه را به او بازگشت دهیم. آیا برای برنامه سمت کاربر بهینه است که تا پایان ارسال پیام منتظر بماند تا در نهایت نتیجه را دریافت کند؟
در مدل آسنکرون این مشکل برطرف میشود و با فراخوانی send دیگر تا پایان آن منتظر نمی مانیم و نتیجه را بلافاصله به برنامه سمت کاربر برمیگردانیم.
برای پیاده سازی فراخوانی آسنکرون معمولا به این صورت عمل می شود که هر سرویس پیام (Message) را برای دیگر سرویس ها می فرستد. پیام یک مفهوم عمومی است که میتواند شامل درخواست، پاسخ یا رویدادی باشد که میان سرویس ها رد و بدل می شود. این پیام توسط فرستنده به کارگزار پیام (Message broker) ارسال میشود که در نهایت گیرنده آنها را از کارگزار دریافت می کند. کارگزاران برای مدیریت پیام ها ممکنه که از topic، queue و یا هردو استفاده کنند.
صف queue: به طور ویژه برای مکالمه نقطه به نقطه استفاده میشود. تولید کنند پیام را در صف قرار داده و مصرف کننده آن را از صف می خواند. دقت کنید که با هر بار خواندن عملا آن پیام از آن صف برداشته می شود بنابراین اگر همزمان چندین سرویس بخواهند از یک صف مصرف کنند، به همه پیام ها دسترسی نخواهند داشت.
تاپیک topic: امکان اشتراک گذاری پیام میان چندین سرویس را فراهم می کند. هر مصرف کننده در حقیقت یک کپی از پیام داخل تاپیک را دریافت میکند. این مفهوم به طور گسترده ای در Apache Kafka استفاده می شود.
ممکنه تا حالا از خودتون پرسیده باشین که اصلا کارگزار پیام چه مزیتی داره؟ چرا پیام ما مستقیم برای سرویس هدف ارسال نمی شه؟ برای رفع این ابهامات توجهتون رو به مزایای این کارگزاران جلب می کنیم:
در خصوص ارتباط از طریق کارگزاران پیام، یکی از مهمترین مفاهیمی که مطرح هست الگوهای ادغام سازمانی (Enterprise integration patterns) هست که بیانگر مجموعه ای از الگوهای طراحی است که برای ادغام نرم افزار های موجود در یک سیستم تجاری استفاده می شود. این الگوها بر رویکرد ارتباط از طریق ارسال پیام، میانِ سرویس ها اشاره می کنند. در قسمت بعد سعی میکنیم بیشتر آن را بررسی کنیم.
منابع:
Microservices Patterns by Chris Richardson
Building Microservices, 2nd Edition by Sam Newman