معماری میکروسرویس: چه زمانی مرزها را بشکنیم؟

نرم‌افزار · · زمان مطالعه: ۵ دقیقه

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

معماری میکروسرویس (microservices architecture) در بسیاری از گفت‌وگوهای فنی به‌عنوان «مرحلهٔ بعدی بلوغ» هر نرم‌افزار سازمانی معرفی می‌شود؛ اما در عمل این معماری یک تصمیم مهندسی با هزینهٔ مشخص است، نه نشانهٔ پیشرفت. اگر تیم شما برای انتشار یک تغییر کوچک باید چند روز با تیم‌های دیگر هماهنگ شود، میکروسرویس آن درد را از بین نمی‌برد؛ فقط جای آن را عوض می‌کند. این نوشته سراغ یک پرسش عملی می‌رود: از کجا بفهمیم وقت شکستن یک سیستم یکپارچه رسیده است و از کجا بفهمیم هنوز نه؟

میکروسرویس در یک جمله

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

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

معاملهٔ اصلی: چه می‌خرید و چه می‌پردازید

هر تصمیم معماری یک معامله است. در میکروسرویس، صورت حساب دو ستون دارد.

آنچه به دست می‌آورید:

  • استقلال در انتشار: یک تیم می‌تواند بدون هماهنگی با بقیه، سرویس خودش را منتشر کند.
  • مالکیت روشن: وقتی سرویسی خراب می‌شود، دقیقاً معلوم است چه کسی پاسخگوست.
  • مقیاس‌پذیری نقطه‌ای (targeted scaling): فقط بخشی که بار سنگین دارد بزرگ می‌شود، نه کل سامانه.
  • مرز خطا: از کار افتادن بخش کم‌اهمیت، لزوماً کل سامانه را زمین نمی‌زند.

آنچه می‌پردازید:

  • فراخوانی شبکه‌ای: هر فراخوانی تابع درون‌فرایندی به یک سفر روی شبکه تبدیل می‌شود؛ با تأخیر، قطعی، تلاش مجدد و احتمال تکرار عملیات.
  • سازگاری داده: تراکنش‌های متمرکز جای خود را به سازگاری نهایی (eventual consistency) و الگوهای جبران می‌دهند.
  • مشاهده‌پذیری (observability): لاگ متمرکز، سنجه و ردگیری توزیع‌شده از «خوب است داشته باشیم» به «بدون آن نمی‌شود کار کرد» ارتقا پیدا می‌کند.
  • هزینهٔ عملیات: هر سرویس یعنی یک خط لولهٔ استقرار، یک پیکربندی، یک نسخه و یک سطح خطای تازه.

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

مرز را از سرعت تغییر بردارید، نه از فهرست جدول‌ها

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

مثال روشن: در یک فروشگاه اینترنتی، منطق قیمت‌گذاری و تخفیف تقریباً هر هفته تغییر می‌کند، در حالی که کاتالوگ محصول ماهی یک بار دست می‌خورد. اگر هر دو در یک فرایند باشند، هر تغییر قیمت‌گذاری یک انتشار کل سیستم را قفل می‌کند. اینجا شکستن مرز توجیه دارد، چون دو ریتم تغییر متفاوت را از هم جدا می‌کند. در مقابل، اگر همهٔ بخش‌های سامانه با یک ریتم و توسط یک تیم تغییر می‌کنند، شکستن فقط یک لایهٔ شبکه به کار اضافه کرده است.

سه آزمون پیش از شکستن

پیش از هر بازطراحی، این سه پرسش را صادقانه پاسخ دهید:

  1. آزمون تغییر: آیا بخشی از سیستم داریم که چند برابر بقیه تغییر می‌کند و هر تغییرش کل انتشار را گروگان می‌گیرد؟
  2. آزمون تیم: آیا بیش از یک تیم داریم که باید مستقل کار کند و بتواند واقعاً مالک یک سرویس باشد؟ اگر یک تیم کوچک همه‌چیز را نگه می‌دارد، استقلال استقرار صرفه‌ای ندارد.
  3. آزمون بار: آیا الگوی مصرف منابع بخش‌ها واقعاً متفاوت است؛ مثلاً یک بخش پردازش سنگین یا حافظه‌بر است و بخش دیگر سبک و پرترافیک؟

اگر پاسخ هر سه «نه» است، پاسخ صادقانه یک سیستم یکپارچهٔ خوش‌ساخت است. مرز اشتباه از نداشتن مرز گران‌تر تمام می‌شود، چون هر تغییر بعدی را به یک مذاکرهٔ شبکه‌ای تبدیل می‌کند.

مونولیت ماژولار: نقطهٔ شروع معقول

بیشتر سامانه‌هایی که امروز به‌درستی به سرویس‌های مستقل شکسته شده‌اند، از یک مونولیت ماژولار (modular monolith) عبور کرده‌اند: یک برنامهٔ واحد، اما با مرزهای ماژولی سخت‌گیرانه که هر ماژول فقط از طریق یک واسط مشخص با بقیه حرف می‌زند و هیچ‌کس مستقیم به جدول ماژول دیگر دست نمی‌زند. چنین ساختاری ارزان‌تر اداره می‌شود، آزمون‌پذیری بالایی دارد و استخراج ماژول دردناک‌ترین بخش را بعداً ممکن می‌کند؛ همان‌جایی که داده نشان داده واقعاً گلوگاه است.

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

حداقل‌های عملیاتی پیش از اولین سرویس

اگر تصمیم گرفتید مرزها را بشکنید، این‌ها را به‌عنوان پیش‌نیاز ببینید، نه کارهای بعدی:

  • خط لولهٔ استقرار خودکار برای هر سرویس، با امکان انتشار مستقل و بازگشت سریع
  • لاگ متمرکز با شناسهٔ درخواست مشترک و ردگیری توزیع‌شده (distributed tracing)
  • سنجه‌ها و هشدار بر پایهٔ تجربهٔ کاربر، نه فقط مصرف پردازنده
  • بررسی سلامت (health check)، محدودسازی نرخ و مدارشکن (circuit breaker) برای فراخوانی‌های بین‌سرویسی
  • آزمون قرارداد (contract test) تا تغییر یک سرویس، دیگری را بی‌صدا نشکند
  • محیط یکسان برای توسعه و تولید و پیکربندی خارج از کد

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

جمع‌بندی

میکروسرویس ابزار مقیاس‌دهی به سازمان است، نه ابزار مقیاس‌دهی به کد. زمانی درست است که چند تیم، چند ریتم تغییر و چند الگوی بار واقعاً متفاوت دارید و می‌توانید هزینهٔ مشاهده‌پذیری و استقرار مستقل را دائمی بپردازید. اگر این شرط‌ها برقرار نیست، یک سیستم یکپارچه با مرزهای ماژولار شفاف، انتخاب حرفه‌ای‌تری است: ساده‌تر، ارزان‌تر و آماده برای شکستن در روزی که داده‌ها واقعاً آن را طلب کنند.

← بازگشت به همه مقالات