امیر کولیوند
فهرست
مهندسی نرم‌افزار

معماری میکرو یا مایکرو، سرویس‌گرایی بی انتها

میز چوبی، دفتر یادداشت و گلدان در کنار پنجره‌ای رو به کوهستان
فهرست مطالب

شهری که هر اتاقش شهرداری دارد

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

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

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

دیوار را به مرز شبکه تبدیل نکنیم

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

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

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

آزمون تغییر نشانی مشتری

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

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

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

نامه رسیده است؛ رسیدِ نامه نه

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

این همان هزینه‌ای است که در نقشه‌ی تمیز سرویس‌ها معمولاً دیده نمی‌شود: میان «انجام شد» و «انجام نشد»، وضعیت «هنوز نمی‌دانیم» هم وجود دارد. اگر درخواست را بی‌قید تکرار کنیم، ممکن است عملیات دوباره اجرا شود؛ اگر تکرار نکنیم، ممکن است یک عملیات انجام‌نشده برای همیشه معطل بماند.

const request = {
  idempotencyKey: "checkout-order-42",
  orderId: "order-42",
};
// Persist and enforce this key in the receiving service.

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

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

هر ساختمان مستقل، ساکن مسئول می‌خواهد

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

از سوی دیگر، تیم‌هایی با مسئولیت‌های متفاوت، چرخه‌های انتشار مستقل یا نیازهای عملیاتی متمایز ممکن است از جداسازی سود ببرند. در این حالت، مرز شبکه پاسخی به یک نیاز مشخص است؛ مثلاً بخش گزارش‌گیری باید جداگانه منابع مصرف کند یا تیم پرداخت باید بدون درگیر کردن تغییرهای سفارش، نسخه‌ی تازه‌ای منتشر کند.

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

پیش از تأسیس شهرداری بعدی

برای جداسازی بعدی، یک یادداشت کوتاه کافی است؛ به شرط آنکه به پرسش‌های دشوار پاسخ دهد:

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

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

شهر را با تعداد تابلوهایش نسنجیم

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

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

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