در بخش اول، محدودیتهای API سفارش را نوشتیم: بار، بودجه، توان تیم و نیازهای داده. حالا فرض کنیم این بررسی هنوز دلیل روشنی برای چند استقرار مستقل نشان نداده است. انتخاب برنامه یکپارچه میتواند منطقی باشد، اما یک سؤال باقی میماند: چطور جلوی تبدیل آن به مجموعهای از وابستگیهای درهم را بگیریم؟
پاسخ این بخش، معماری یکپارچه ماژولار یا Modular Monolith است. سفارش، موجودی و پرداخت را در یک برنامه نگه میداریم، ولی مسئولیت و مسیر ارتباطشان را روشن میکنیم. آشنایی با API و دیتابیس کافی است. خروجی، یک نقشه مالکیت و چند قاعده وابستگی است؛ نه صرفاً پوشههایی با نامهای متفاوت.
مرز کد با مرز استقرار یکی نیست
یک ماژول میتواند مسئولیت مشخص داشته باشد و از طریق قرارداد کوچک با بخش دیگر صحبت کند، بدون اینکه برای هر تماس درخواست شبکه بفرستد. در برنامه یکپارچه، بخشها معمولاً با هم ساخته و منتشر میشوند. این موضوع درباره کیفیت مرزبندی کد حکم قطعی نمیدهد؛ یک استقرار میتواند ساختار روشن یا ساختار آشفته داشته باشد.
در میکروسرویس، مرز استقرار و ارتباط شبکه هم وارد مسئله میشود. حالا باید تأخیر، قطع ارتباط، نسخه قرارداد و سازگاری عملیات بین سرویسها را مدیریت کنیم. این هزینه ممکن است در برابر استقلال لازم ارزش داشته باشد، اما نباید صرفاً برای مرتبکردن نام پوشهها پرداخت شود.
سه مسئولیت در سامانه نمونه
ماژول سفارش، وضعیت سفارش و اقلام آن را میشناسد. ماژول موجودی، مقدار قابل تخصیص و قواعد رزرو کالا را نگه میدارد. ماژول پرداخت، تلاش پرداخت و نتیجه معتبر آن را ثبت میکند. ممکن است همه از یک دیتابیس رابطهای استفاده کنند، اما این به معنی مجازبودن نوشتن مستقیم هر جدول از هر نقطه نیست.
| ماژول | مالک چه تصمیمی است؟ | چه چیزی را در اختیار دیگران میگذارد؟ |
|---|---|---|
| سفارش | چرخه عمر و قواعد سفارش | ثبت سفارش و مشاهده وضعیت |
| موجودی | تخصیص و آزادسازی ظرفیت کالا | عملیات رزرو و نتیجه آن |
| پرداخت | ثبت و اعتبارسنجی نتیجه پرداخت | وضعیت معتبر پرداخت |
این تقسیمبندی نمونه آموزشی است. در محصول واقعی ممکن است دامنه متفاوت باشد. معیار، وجود قواعد و مسئولیت مستقل است، نه اینکه هر اسم در نیازمندی الزاماً به یک ماژول تبدیل شود.
ثبت سفارش از کدام مرز عبور میکند؟
یک لایه هماهنگکننده میتواند درخواست را دریافت کند، اعتبار ورودی را بسنجد و عملیات سفارش و رزرو موجودی را به ترتیب مناسب اجرا کند. خود ماژول سفارش نباید در هر بخش دلخواه، جزئیات جدول موجودی را دستکاری کند. اگر سیاست رزرو عوض شد، باید محل تصمیم آن معلوم باشد.
برای مثال، بهجای کمکردن مستقیم یک ستون از چند کنترلر، عملیات «رزرو موجودی برای سفارش» تعریف میکنیم. این نام یک نیاز واقعی را بیان میکند. قرارداد باید نتیجه ناکافیبودن موجودی را هم مشخص کند؛ فقط مسیر موفق برای طراحی مرز کافی نیست.
مالکیت داده از محل جدول مهمتر است
قرارگرفتن همه جدولها در یک دیتابیس، امکان تراکنش مشترک را فراهم میکند؛ ولی اگر هر ماژول قواعد ماژول دیگر را دور بزند، مرزبندی کد ضعیف میشود. میتوان قواعدی برای دسترسی تعریف کرد: تغییر موجودی از مسیر مشخص انجام شود و مصرفکننده به ساختار داخلی جدول وابسته نشود.
این قاعده مطلق نیست که هیچ کوئری مشترکی مجاز نباشد. گزارشگیری ممکن است نیازهای متفاوتی داشته باشد و باید آگاهانه طراحی شود. مهم این است که وابستگی خواندن برای گزارش با مالکیت تغییر قواعد مخلوط نشود. هر استثنا باید دلیل و محدوده روشنی داشته باشد.
تراکنش سادهتر میماند، اما رایگان نیست
وقتی ثبت سفارش و رزرو موجودی در همان دیتابیس انجام میشوند، میتوان عملیات وابسته را در تراکنش مناسب نگه داشت. این مزیت به معنی حذف مشکل رقابت همزمان نیست؛ همچنان باید قفل یا شرط معتبرسازی متناسب با دیتابیس و الگوی دسترسی انتخاب شود.
در بخش تراکنش و موجودی، این موضوع را با سناریوی آخرین کالا باز میکنیم. فعلاً مرز تراکنش را با مرز ماژول اشتباه نگیریم: دو ماژول ممکن است در یک عملیات هماهنگ شرکت کنند. اگر بعدها جدا شوند، همان تضمین محلی را نمیتوان بدون طراحی جایگزین روی شبکه فرض کرد.
ماژول مشترک را به انبار همهچیز تبدیل نکنیم
پوشهای با نام مشترک یا ابزارها خیلی زود میتواند محل قواعدی شود که صاحب ندارند. تابع محاسبه مبلغ بازپرداخت فقط چون دو بخش به آن نیاز دارند، الزاماً ابزار عمومی نیست. باید پرسید کدام مفهوم مالک این قاعده است و آیا هر دو مصرفکننده واقعاً یک معنا از آن دارند.
اشتراک برای نوع داده یا کمکتابع واقعاً عمومی میتواند مفید باشد، اما دامنه را بیدلیل در یک لایه مرکزی متمرکز نکنیم. اگر هر تغییر کوچک در مشترک، همه ماژولها را تحت تأثیر قرار میدهد، باید وابستگیها را دوباره بررسی کنیم.
وابستگی را قابل دیدن کنیم
برای شروع، یک نقشه ساده از importها و قراردادهای عمومی کافی است. مشخص کنید کدام بخش اجازه وابستگی به کدام بخش را دارد. چرخهای که سفارش به پرداخت و پرداخت دوباره به جزئیات سفارش وابسته است، میتواند نشانه مخلوطشدن مسئولیتها باشد.
راهحل همیشه افزودن یک واسط نیست. گاهی یک شناسه یا نتیجه کوچک کافی است و نیازی به عبور کل مدل نداریم. گاهی هم هماهنگی باید از داخل دو ماژول بیرون بیاید و در مسیر کاربردی بالاتری قرار بگیرد. هدف، فهم جریان تغییر است؛ تعداد رابطها معیار موفقیت نیست.
تستها هم باید مرز را منعکس کنند
قواعد محلی هر ماژول را میتوان با آزمون محدود بررسی کرد. در کنار آن، مسیر ثبت سفارش به آزمون یکپارچه نیاز دارد تا قرارداد واقعی سفارش و موجودی دیده شود. اگر همه ارتباطها با mock جایگزین شوند، ممکن است تغییر قرارداد را هیچ آزمونی نگیرد.
آزمون مهم نمونه این است: با موجودی کافی، سفارش و تخصیص سازگار ثبت شوند؛ با موجودی ناکافی، نتیجه نیمهکاره باقی نماند. شرایط همزمانی هم باید در آزمون متناسب با دیتابیس واقعی بررسی شوند. این مقاله طرح آزمون را پیشنهاد میکند و ادعای اجرای آن روی سامانه واقعی ندارد.
چه زمانی جداسازی سرویس را بررسی کنیم؟
اگر یک بخش واقعاً به چرخه انتشار مستقل، مدل بار متفاوت یا مالکیت تیم جدا نیاز دارد، میتوان جداسازی را بررسی کرد. بهتر است این نیاز با شواهد بیان شود. جمله «شاید روزی خیلی بزرگ شویم» برای توجیه هزینه عملیات مستقل کافی نیست.
مرز ماژولی روشن میتواند تحقیق درباره جداسازی را آسانتر کند، اما آن را خودکار و بدون هزینه نمیکند. تغییر دسترسی محلی به شبکه، شکستهای تازه ایجاد میکند. قرارداد، مهاجرت داده، پایش و شیوه هماهنگی تراکنشها باید دوباره طراحی شوند.
نقشه مالکیت را به کار روزمره وصل کنیم
یک صفحه بنویسید که مالک قواعد، مسیرهای عمومی و استثناهای مجاز را نشان دهد. هنگام بازبینی تغییر، بپرسید آیا قاعده در ماژول درست قرار گرفته و آیا مصرفکننده به جزئیات داخلی تازهای وابسته شده است. این سؤالها از حفظ ساختار پوشهها بهتنهایی مؤثرترند.
برای تمرین، قابلیت لغو سفارش را روی نقشه اضافه کنید. سفارش چه چیزی را تغییر میدهد، موجودی چه زمانی آزاد میشود و نتیجه پرداخت از کجا میآید؟ بخش بعد به انتخاب دیتابیس بر اساس الگوی دسترسی میپردازد؛ همین خواندنها و نوشتنها مبنای تصمیم خواهند بود.
منابع تکمیلی
- Monolith First، نوشته مارتین فاولر؛ دیدگاهی درباره شروع ساده و هزینه شناخت زودهنگام مرزها.
- مستندات تراکنش Django؛ نمونهای از تضمین اتمیک در مرز دیتابیس محلی، نه بین سرویسهای مستقل.
