Skip to content

فصل ۸ — الگوهای استفادهٔ مجدد

خلاصهٔ تحلیلی — متن حاضر مفاهیم فصل را با بیان مستقل و تحلیلی توضیح می‌دهد.

مسئله و زمینه

استفادهٔ مجدد در مونولیت معمولاً یک import است، اما در معماری توزیع‌شده نوعی coupling می‌سازد. این coupling می‌تواند در زمان build، در زمان اجرا یا در لایهٔ زیرساخت باشد و هر شکل آن هزینه‌ای متفاوت برای تغییر، latency، مقیاس و خرابی دارد. بنابراین شعارهایی مانند «هرگز تکرار نکن» و «هیچ‌چیز را به اشتراک نگذار» پاسخ معماری نیستند.

معیار بنیادی فصل چنین است: abstraction فقط نامزد reuse را آشکار می‌کند؛ ارزش عملی reuse به نرخ تغییر پایین و قابل پیش‌بینی آن وابسته است. منطق دامنه‌ای که سریع تحول می‌یابد، هدف بدی برای coupling مشترک است؛ قابلیت فنی پایدار، غالباً هدف بهتری است.

مفاهیم و الگوهای محوری

۱) تکثیر کد

کپی‌کردن کد در هر repository، وابستگی مستقیم میان سرویس‌ها را حذف و bounded context را حفظ می‌کند. این الگو فقط برای قطعه‌های بسیار کوچک، ایستا و کم‌خطر—مانند marker یا utility یک‌باره—قابل دفاع است. نقطهٔ ضعف روشن است: defect یا تغییر لازم باید در همهٔ کپی‌ها یافته، اعمال و منتشر شود و drift به‌تدریج معناها را واگرا می‌کند.

۲) shared library

کتابخانهٔ خارجی در زمان compile به سرویس متصل می‌شود. نسخه‌بندی می‌تواند adoption تغییر را تدریجی و backward-compatible کند؛ در نتیجه، یک سرویس نیازمند تغییر فوری لازم نیست همهٔ مصرف‌کنندگان را با خود deploy کند.

اما granularity کتابخانه trade-off دارد: library بزرگ dependency را ساده می‌کند ولی تغییر کوچک را به retest و migration گسترده تبدیل می‌کند؛ libraryهای ریز blast radius تغییر را محدود می‌کنند ولی ماتریس dependency را پیچیده و نگهداری را دشوار می‌سازند. فصل معمولاً libraryهای کوچک و functionally partitioned را بر یک SharedStuff عظیم ترجیح می‌دهد.

نسخه‌بندی خود محصول جانبی نیست: سیاست deprecation، اطلاع‌رسانی تغییر، قرارداد سازگاری و پرهیز از وابستگی LATEST لازم‌اند. سیاست جهانی ساده‌تر است، ولی library پرنوسان را به churn اجباری سرویس‌ها تبدیل می‌کند؛ سیاست اختصاصی دقیق‌تر اما نیازمند governance است.

۳) shared service

در این الگو قابلیت مشترک به سرویس مستقلی تبدیل و در runtime مصرف می‌شود؛ پس محیط‌های polyglot و کد پرنوسان می‌توانند بدون بازساخت همهٔ سرویس‌ها تغییر را دریافت کنند. بهای این چابکی، تغییر runtime با ریسک گسترده‌تر است: endpoint یا semantics جدید می‌تواند مصرف‌کننده‌ها را در production بشکند.

همچنین هر فراخوانی هزینهٔ شبکه و امنیت دارد، shared service باید هم‌پای مصرف‌کننده‌ها scale شود و unavailable شدن آن زنجیره‌ای از سرویس‌ها را از کار می‌اندازد. version کردن API، به‌خصوص با protocolهای متفاوت مانند REST، messaging و gRPC، از version کردن library دشوارتر است.

۴) sidecar و service mesh

Sidecar منطق domain را از قابلیت‌های عملیاتی cross-cutting جدا می‌کند: logging، monitoring، discovery، authentication، authorization و circuit breaker. اگر همهٔ سرویس‌ها sidecar سازگار داشته باشند، mesh یک سطح کنترل و مشاهده‌پذیری یکنواخت می‌سازد.

این یک reuse متعامد است: domainها از یکدیگر جدا می‌مانند، اما concern عملیاتی از روی همهٔ domainها عبور می‌کند. Sidecar محل مناسب Customer یا Address مشترک نیست؛ این کار coupling دامنه‌ای را پنهان و sidecar را به مونولیت بزرگ‌تری تبدیل می‌کند. هزینه‌ها شامل پیاده‌سازی یا سازگارسازی برای هر platform، اندازه و پیچیدگی sidecar، و تعیین مالکیت آن است.

موازنه‌ها

  • تکثیر، استقلال را با دشواری اصلاح مبادله می‌کند.
  • library، latency و dependency runtime را حذف می‌کند اما compatibility و lifecycle نسخه‌ها را به تیم‌ها تحمیل می‌کند.
  • shared service تغییر را متمرکز می‌کند، اما performance، availability و scale را متمرکز می‌سازد.
  • mesh consistency عملیاتی را بالا می‌برد، ولی نباید به کانال مشترک منطق کسب‌وکار تبدیل شود.

تمرکز همهٔ اطلاعات «مشتری» در یک سرویس enterprise نیز مثال هشداردهنده‌ای است: abstraction ظاهری دارد، اما مدل را برای همهٔ domainها بیش از حد پیچیده و هر تغییر را سراسری می‌کند. platform با API و contract خوب فقط وقتی مفید است که API مرزی پایدارتر از implementation داشته باشد.

راهنمای عملی

  1. ابتدا ماهیت کد را تعیین کنید: domain یا infrastructure، میزان مصرف، زبان‌ها و نرخ تغییر تاریخی آن.
  2. برای کد پایدار در محیط نسبتاً همگن، library نسخه‌دار و کوچک را بررسی کنید.
  3. shared service را برای قابلیت‌های پرنوسان و چندزبانه انتخاب کنید، اما latency، replica، failure handling و compatibility runtime را از ابتدا طراحی کنید.
  4. concernهای عملیاتی فراگیر را به sidecar/mesh بسپارید و با fitness function حضور و سازگاری آن را کنترل کنید.
  5. پیش از reuse، بپرسید «کدام تیم‌ها مجبور می‌شوند با یک تغییر هماهنگ شوند؟»؛ پاسخ از تعداد خطوط مشترک مهم‌تر است.

نکات کلیدی فصل

  • reuse فضیلت ذاتی نیست؛ coupling حاصل از آن باید ارزشش را ثابت کند.
  • نرخ تغییر پایین شرط مهم reuse پایدار است.
  • domain reuse پرنوسان معماری را شکننده می‌کند؛ operational reuse کاندید مناسب‌تری برای mesh است.
  • نسخه‌بندی، deprecation و communication بخشی از خودِ design کتابخانه‌اند.

یادداشت‌های مترجم

  • sidecar، service mesh، runtime، compile-time و polyglot به‌صورت لاتین باقی مانده‌اند.
  • «استفادهٔ مجدد» در این فصل هم reuse کد و هم reuse قابلیت را پوشش می‌دهد؛ این دو در معماری توزیع‌شده یکسان نیستند.