الگو های طراحی میکروسرویس برای Node.js

آفلاین
user-avatar
عرفان حشمتی
16 مرداد 1400, خواندن در 16 دقیقه

پس از مدتی طولانی کار با Node.js به این نتیجه رسیده‌ام که هیچ ابزار بهتری برای استفاده هنگام نوشتن میکروسرویس وجود ندارد. البته این نظر من است که می‌تواند کاملا مغرضانه برای جاوااسکریپت به عنوان یک زبان تلقی شود. اما حتی بدون اینکه من نظرم را می‌گفتم، شرط می‌بندم که نمی‌توانستید بگویید Node.js ابزاری عالی برای ساخت میکروسرویس نیست.

به طوری که زمان توسعه بسیار سریع، تعداد زیادی از فریمورک‌های موجود و ورودی و خروجی ناهمزمان را برای عملکرد فوق العاده هنگام برخورد با بسیاری از درخواست‌های همزمان فراهم می‌کند.

گرچه Node.js به خودی خود ابزاری عالی است، اما با این وجود در نهایت از آن استفاده خواهید کرد. با توجه به این نکته در اینجا چند الگوی طراحی مفید و جالب برای میکروسرویس آورده شده است. ایده این است که ایجاد یک سرویس واحد برای همه پروژه‌های خود را متوقف کنید و بسته به نوع کارتان روش‌های زیادی برای تقسیم آنها وجود دارد. بنابراین بیایید نگاهی مختصر به آنها بیندازیم.

الگوی Aggregator

ایده اصلی این الگو ایجاد یک سرویس مبتنی بر سایر سرویس‌های جزئی و انفرادی است. یک سرویس aggregator خدماتی است که API عمومی را که توسط کلاینت استفاده می‌شود، ارائه می‌دهد.

این فقط منطق مورد نیاز برای استفاده از همه سرویس‌های دیگر را خواهد داشت که به نوبه خود خدماتی با بالاترین بار منطق تجاری هستند.

به تصویر بالا نگاهی بیندازید. این شماتیک سعی می‌کند یک میکروسرویس بیمه خودرو را به تصویر بکشد. کلاینت فقط به API اصلی اهمیت می‌دهد، برای همه اهداف تنها میکروسرویس موجود است و جایی است که تمام برنامه‌های کلاینت به آن اشاره می‌کنند. با این حال در پشت پرده می‌بینید که چندین API منفرد وجود دارد که با هم کار می‌کنند و توسط aggregator ترتیب بندی شده‌اند.

این روش مزایای مختلفی را به همراه دارد:

  1. رشد معماری برای کلاینت نهایی آسان‌تر و شفاف‌تر است. علاوه بر آن API هرگز تغییر نخواهد کرد و در صورت تغییر فقط یک URL برای تنظیم وجود دارد. با این حال در پس زمینه می‌توانید هر دو مورد را به همان اندازه که می‌خواهید اضافه یا حذف کنید. در حقیقت اگر API رو به جلو را تغییر ندهید، می‌توانید معماری داخلی را تا آنجا که می‌خواهید تنظیم کنید بدون اینکه روی کلاینت تأثیر بگذارید.
  2. امنیت ساده‌تر است. تمرکز اصلی تلاش‌های امنیتی مبتنی بر API شما باید رابط برنامه نویسی API باشد. بقیه آنها می‌توانند در یک شبکه مجازی خصوصی (VPN) اتفاق بیافتند که فقط توسط aggregator قابل دسترسی است. این ارتباط بین API را بسیار ساده‌تر می‌کند.
  3. می‌توانید این میکروسرویس‌ها را به صورت جداگانه مقیاس بندی کنید. همچنین با اندازه گیری میزان استفاده از منابع می‌توانید تشخیص دهید که بیشتر از همه تحت تأثیر بار فعلی هستند و نسخه‌های بیشتری از آن ایجاد کنید. سپس همه آنها را به یک تعدیل کننده بسپارید و اطمینان حاصل کنید که همه افرادی که با آن سرویس ارتباط برقرار می‌کنند متعادل هستند. تا زمانی که خدمات خود را بدون وضعیت نگه دارید (مانند اینکه برای مثال از REST استفاده می‌کنید)، مکانیک ارتباطات تحت تأثیر قرار نمی‌گیرد.

میکروسرویس‌های کوچکی از این دست می‌توانند به عنوان مثال در اطراف Restify ساخته شوند، که هر آنچه برای ایجاد یک میکروسرویس REST نیاز دارید را فراهم می‌کند. این بسیار عالی عمل می‌کند و API آن بسیار شبیه به Express است، همچنین یک فریمورک بسیار شناخته شده و آسان برای استفاده است که می‌تواند برای ایجاد این سرویس‌ها مورد استفاده قرار گیرد.

زنجیره مسئولیت

این رویکرد بسیار شبیه به الگوی قبلی است. شما اساسا معماری مبتنی بر میکروسرویس پیچیده را در پشت یک سرویس ساده پنهان کرده‌اید که از تمرکز بر روی جمع آووری منطق مراقبت می‌کند.

تفاوت اصلی در اینجا این است که منطق کسب و کارتان به دلایلی شما را ملزم می‌کند که چندین سرویس را بهم پیوند دهید. این بدان معناست که برای انجام تعامل API کلاینت، درخواست باید از Main API به Service 1 سپس از Service 1 به Service 2 و بعد از Service 2 به Service 3 برسد، در نهایت برای ارائه پاسخ واقعی از Service 3 به Service 4 پیش می‌رود که به نوبه خود کل خدمات را مرحله به مرحله تا API اصلی برمی‌گرداند.

توضیح آن طولانی است اما اجرای آن طولانی‌تر است. این نوع تعامل توصیه نمی‌شود مگر اینکه کانال ارتباطی را به کانال سریعتری تغییر دهید. شما می‌توانید از REST over HTTP برای تعامل Client-Main API استفاده کرده و سپس برای برقراری ارتباط بین سرویسها به سوکت‌ها سویچ کنید. به این ترتیب تأخیر افزوده شده از HTTP به هر درخواست اضافه نمی‌شود. چرا که سوکت‌ها از قبل کانال‌های ارتباطی بین میکروسرویس‌های داخلی را باز و فعال می‌کنند.

این روش مزایای مشابه روش قبلی را دارد، هرچند یک نقص بزرگ وجود دارد. هرچه زنجیره را طولانی کنید، تأخیر بیشتری در پاسخ خواهد داشت. به همین دلیل اطمینان از استفاده از پروتکل ارتباطی مناسب برای موفقیت این الگو بسیار مهم است.

اگر به این فکر می‌کنید که چگونه می‌توانید از طریق سوکت‌ها ارتباط بین سرویس‌ها را مدیریت کنید، به Socket.io نگاهی بیندازید. این کتابخانه واقعی برای مدیریت سوکت‌ها در Node.js ساخته شده است.

پیام ناهمزمان

یک الگوی جالب برای بهبود مکانیک زنجیره مسئولیت، ناهمگام ساختن آن است.

من شخصا عاشق این الگو هستم. دستیابی به یک معماری مبتنی بر میکروسرویس برای ایجاد رویدادهای غیرهمزمان می‌تواند انعطاف پذیری و بهبود عملکرد زیادی به شما بدهد.

اما به این آسانی نیست، زیرا ارتباطات می‌تواند کمی پیچیده باشد و مشکلات رفع اشکال در اطراف آن را به همراه دارد. هرچند در اینجا جریان داده مشخصی از Service 1 به Service 2 وجود ندارد.

یک راه حل عالی برای آن داشتن یک شناسه رویداد است که کلاینت هنگام ارسال درخواست اولیه خود ایجاد می‌کند و سپس به هر رویدادی که از آن ناشی می‌شود منتشر می‌گردد. به این ترتیب می‌توانید با استفاده از آن شناسه، گزارش‌های دریافتی را فیلتر کرده و هر پیامی را که از درخواست اصلی ایجاد شده درک کنید.

توجه داشته باشید که نمودار بالا تعامل مستقیم کلاینت با صف پیام را نشان می‌دهد. اگر یک رابط کاربری ساده ارائه دهید یا کلاینت شما داخلی باشد و توسط تیم توسعه دهنده مدیریت شود، این می‌تواند یک راه حل عالی در نظر گرفته شود. با این حال اگر این یک کلاینت عمومی است که هر کسی می‌تواند آن را کدنویسی کند، ممکن است بخواهید یک کتابخانه SDK برای او فراهم کنید تا بتواند با شما ارتباط برقرار کند. اختصار و ساده سازی ارتباط به شما کمک می‌کند تا گردش کار را ایمن کرده و تجربه توسعه بهتری را برای هر کسی که سعی در استفاده از آنها دارد فراهم کند.

با این وجود گزینه دیگر ارائه یک سرویس درگاه مانند است که با کلاینت تعامل داشته باشد. به این ترتیب کلاینت فقط نگران درگاه است و بقیه مراحل برای آن شفاف می‌باشد. با این حال این موضوع مورد نیاز کلاینت برای آگاه بودن از ماهیت غیرهمزمان ارتباطات را نفی نمی‌کند. شما هنوز هم باید راهی برای اشتراک رویدادها و ارسال موارد جدید به صف ارائه دهید. همچنین توجه داشته باشید که این از طریق درگاه انجام می‌شود، اما هنوز هم باید اتفاق بیفتد.

مزایای اصلی چنین رویکردی:

  1. زمان پاسخ بسیار سریعتر است. ارتباطات با اشتراک اولیه رویدادها باز می‌شود و سپس هر درخواست بعدی از بین رفته و فراموش می‌گردد. این بدان معناست که برای بستن اتصال و ارائه پاسخ به کاربر لازم نیست منتظر بماند تا کل منطق کسب و کار پایان یابد. داده‌ها به محض آماده شدن ارائه می‌شوند و این روشی است که رابط کاربری در مواجهه با آن نیاز به رسیدگی و مدیریت آن دارد.
  2. به دلیل مشکل پایداری، از دست دادن اطلاعات دشوارتر است. اگر یکی از میکروسرویس‌ها برای معماری‌های دیگر خراب شود، درخواست‌های فعال فعلی ناموفق شده و باعث از بین رفتن داده‌ها می شوند. با این حال تا زمانی که صف پیام بیدار بماند، این روش اطمینان حاصل می‌کند که داده‌ها نه تنها ذخیره می‌شوند، بلکه پس از بازیابی میکروسرویس‌های خراب، می‌توان درخواست‌های معلق را به پایان رساند.
  3. مقیاس گذاری بسیار ساده‌تر است. داشتن چندین نسخه از یک میکروسرویس دیگر به تعدیل کننده بار احتیاج ندارد. اکنون هر سرویس به یک مصرف کننده / تولید کننده منفرد تبدیل می‌شود و به طور مستقل با رویدادها برخورد می‌کند.

گزینه‌های مناسب برای صف‌های پیام که با Node.j بسیار عالی کار می‌کنند Redis و Kafka هستند. مورد اول دارای ویژگی‌هایی مانند Pub / Sub، اعلان‌های Key-space و حتی استریم‌ها است که آن را به یک صف پیام عالی و کارآمد تبدیل می‌کند. پیشنهاد من این خواهد بود:

  • اگر ترافیک زیادی دارید و انتظار دارید در هر دقیقه صدها هزار رویداد ایجاد کنید، Kafka گزینه بهتری خواهد بود.
  • در غیر این صورت از Redis بهره بگیرید، چرا که تنظیم و نگهداری آن بسیار سریعتر است.

مطمئنا اگر از یک اکوسیستم مبتنی بر ابر استفاده می‌کنید، حتما راه حل‌های بومی ابری مانند AWS SQS را نیز بررسی کنید که به خوبی کار می‌کنند و می‌توانند به طور خودکار مقیاس بندی شوند. 

قطع کننده مدار

آیا تا به حال به دلیل یک سرویس شخص ثالث بسیار ناپایداری که استفاده می‌کنید خدمات شما از کار افتاده است؟

اگر بتوانید به نحوی زمان وقوع آن را تشخیص داده و سپس منطق داخلی خود را به صورت پویا به روز کنید، چه می‌شود؟ عالی خواهد بود، اینطور نیست؟

الگوی قطع کننده مدار دقیقا همین کار را انجام می‌دهد. به این صورت که راهی را برای شما فراهم می‌کند تا وابستگی ناموفق را شناسایی کرده و جریان داده را از رفتن به آنجا متوقف کنید و بتوانید باعث جلوگیری از به وجود آمدن تأخیر و یک تجربه کاربری وحشتناک شوید.

توجه داشته باشید شما می‌توانید از این الگوی داخلی برای سرویس‌های خود نیز استفاده کنید اما اگر سرویسهایتان خراب هستند، احتمالا می‌خواهید سعی کنید آنها را برطرف کنید. با این وجود کار بسیار کمی وجود دارد که باید با سرویس‌های شخص ثالثی که در حال خراب شدن هستند انجام دهید، اینطور نیست؟

اساسا آنچه شما در اینجا می‌خواهید این است که یک سرویس پروکسی برای API شخص ثالث خود داشته باشید. به این ترتیب پروکسی می‌تواند دو کار انجام دهد:

  1. ارتباطات را از سرویس‌های خود به API شخص ثالث هدایت کنید.
  2. ردیابی درخواست‌های ناموفق. اگر این تعداد در یک بازه زمانی از پیش تعیین شده از یک آستانه عبور کرد، برای مدتی ارسال درخواست‌ها را متوقف کنید.

هنگامی که حالت خطا ظاهر می‌شود، بسته به نیاز منطق داخلی خود مجبور خواهید بود آن را مدیریت کنید. من یک پاسخ پیش فرض را در نمودار اضافه کردم، به این صورت که شما می‌توانید نوعی داده پیش فرض ارائه دهید که باعث می‌شود جریان داده حتی با اطلاعات اولیه ادامه یابد. اما می‌توانید فعلا آن ویژگی را غیرفعال کنید یا سرویس ثانویه‌ای را که همان اطلاعات را ارائه می‌دهد، قرار دهید.

گزینه‌ها به اندازه منطق تجارت مرتبط با آنها متنوع است، بنابراین شما باید خود تصمیم بگیرید که بهترین اقدام چیست. این الگو فقط توانایی فهمیدن زمان وقوع را برای شما فراهم می‌کند، آنچه پس از آن انجام می‌دهید کاملا به خودتان بستگی دارد.

یک معماری داخلی بهتر برای میکروسرویس شما

با اینکه در سطح معماری کلان الگوهای فوق برای ایجاد معماری‌های بسیار توسعه پذیر و بسیار کارآمد عالی هستند، اما ساختار داخلی میکروسرویس‌های شما نیز باید مورد توجه قرار گیرد.

به همین دلیل است که می‌خواهم به سرعت ایده اجزای مشترک میکروسرویس‌ها را پوشش دهم.

تمام الگوهای ذکر شده در اینجا به شما نشان می‌دهد که چگونه یک سرویس یکپارچه را به چندین سرویس کوچک تبدیل کنید. همه ما مزایای آن را می‌دانیم: این اقدام کار با چندین تیم را ساده می‌کند، تمدید یا به روزرسانی یک سرویس بر دیگران تاثیر نمی‌گذارد (مانند یک سرویس یکپارچه) و مقیاس گذاری سرویس‌های فردی نیز سریعتر و آسان‌تر است.

با این حال اگر اقدامات احتیاطی لازم را انجام ندهید، داشتن تیم‌های زیادی که به طور موازی در چندین میکروسرویس کار می‌کنند، می‌تواند یک چالش واقعی باشد. بنابراین لازم است که:

  • کد را تکرار نکنید و سعی کنید تلاش برای کدنویسی بین تیم‌های مختلف را تا حد ممکن یکنواخت نگه دارید. همچنین آنها را مجبور نکنید که چرخ را دوباره اختراع کنند.
  • اطمینان حاصل کنید که همه تیم‌ها به یک روش کار می‌کنند. همچنین استانداردهای یکسان را در تیم‌های مختلف حفظ کنید تا بتوانید از توانایی افراد در تیم‌های مختلف استفاده کنید.
  • یک مکانیزم ساده برای استفاده از کد دیگران داشته باشید. مطابق با نکته اول، استفاده از کد تیم دیگر برای ساده سازی اقدامات توسعه و کاهش زمان باید بسیار ساده باشد.
  • اطمینان حاصل کنید که کشف آنچه دیگران روی آن کار می‌کنند آسان است. وقتی چندین تیم به طور موازی کار می‌کنند، اشتراک گذاری کار آنها با یکدیگر برای کمک به استفاده مجدد بی اهمیت نیست. بعضی اوقات تیم‌ها به همین دلیل نمی‌دانند که دیگران در حال انجام چه کاری هستند و همان فرایند را دوباره اجرا می‌کنند.

اگر به این نکات توجه نکنید، به دلیل چندین بار اجرای مجدد راه حل‌ها (مانند ورود به سیستم کتابخانه‌ها، اعتبارسنجی‌های مشترک و موارد دیگر) و مشکل در اشتراک دانش بین تیم‌ها با افزایش زمان توسعه مواجه می‌شوید و در نهایت منجر به افزایش زمان انتقال افراد از تیمی به تیم دیگر خواهد شد.

چگونه می‌توانید برای این امر برنامه ریزی کنید؟

پیشنهاد من این است که این موضوع را از روز اول در ذهن داشته باشید و تمام تلاش شما باید در جهت تهیه ابزار لازم برای توسعه دهندگان برای فعال کردن این امر باشد.

من شخصا دوست دارم که از گیت هاب برای متمرکز کردن همه چیز استفاده کنم، این ابزار امکانات زیر را برایتان فراهم می‌کند:

  • می‌توانید یک محیط توسعه کاری واحد را برای به اشتراک گذاشتن بین همه تیم‌ها تعریف کنید.
  • می‌توانید ماژول‌های مشترک را از طریق نصب استاندارد و همچنین وارد کردن کد منبع کامل به اشتراک بگذارید. هر دو حالت به شما امکان دسترسی به ماژول را مانند هر نصب مبتنی بر npm می‌دهد، اما مورد دوم به شما امکان می‌دهد به کد منبع دسترسی پیدا کنید در صورتی که بخواهید آن را گسترش دهید و دوباره به ریپازیتوری مرکزی اکسپورت کنید. این یک پیروزی بزرگ نسبت به سایر مدیریت کننده‌های پکیج Node.js است که فقط به شما امکان نصب ماژول را می‌دهند.
  • می‌توانید بفهمید که تیم‌های دیگر در حال کار بر روی چه مواردی هستند و همچنین می‌توانید از ماژول‌های آنها از طریق بازار مرکزی استفاده کنید که می‌تواند عمومی یا خصوصی باشد.
  • ایجاد و استقرار میکروسرویس‌ها. هنگام استفاده از گیت هاب شما تصمیم می‌گیرید که کدام یک از اجزای مستقل در میکروسرویس‌ها به اشتراک گذاشته شده و مورد استفاده قرار بگیرند.

از این طریق می‌توانید مفاهیمی مانند "linter"، "package manager"، "bundler" و سایر موارد را انتزاع کنید و فقط بر روند واقعی کار تمرکز کنید. به جای بررسی اینکه از npm، yarn یا pnpm استفاده کنید، فقط نگران نحوه کار با آن باشید. به علاوه این چندگانگی بین اعضای تیم که کدام یک از این ابزارها را می‌دانند از بین رفته و نحوه کار همه تیم‌ها را استاندارد می‌کند.

مسلما می‌توانید همین کار را از طریق مجموعه‌ای از استانداردهای کاملا مشخص و ابزارهای فردی انجام دهید. این کاملا ممکن است و من خودم دیده‌ام که بعضی‌ها این کار را می‌کنند. با این حال اگر یک ابزار واحد می‌تواند این کار را برای شما انجام دهد، چرا شما تلاش اضافی برای نوشتن و تعیین همه این استانداردها را انجام دهید؟

الگوهای میکروسرویس مورد علاقه شما چیست؟ آیا آنها در اینجا پوشش داده شده‌اند؟

در مورد معماری داخلی میکروسرویس‌ها چطور؟ چگونه وظایف چندین تیم را کنترل می‌کنید تا مطمئن شوید کتابخانه‌های مشترک و یکسانی ایجاد نمی‌کنند؟

منبع

چه امتیازی به این مقاله می دید؟
خیلی بد
بد
متوسط
خوب
عالی

دیدگاه‌ها و پرسش‌ها

برای ارسال دیدگاه لازم است، ابتدا وارد سایت شوید.

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

آفلاین
user-avatar
عرفان حشمتی @heshmati74
مهندس معماری سیستم های کامپیوتری، طراح و توسعه دهنده وب سایت
دنبال کردن

گفتگو‌ برنامه نویسان

بخشی برای حل مشکلات برنامه‌نویسی و مباحث پیرامون آن وارد شو