معماری Vertical Slice: منطق مشترک دقیقاً کجا باید قرار بگیرد؟ 🚀
معماری Vertical Slice Architecture (VSA) وقتی برای اولین بار با آن روبهرو میشوید شبیه یک نسیم تازه است.
دیگر برای افزودن یک فیلد مجبور نیستید بین هفت لایه جابهجا شوید. پروژههای متعدد را از داخل Solution حذف میکنید. احساس آزادی میکنید.
اما وقتی شروع به پیادهسازی قابلیتهای پیچیدهتر میکنید، ترکها شروع به نمایان شدن میکنند. ⚠️
یک Slice برای CreateOrder میسازید. سپس UpdateOrder. بعد GetOrder.
ناگهان متوجه تکرارها میشوید:
• منطق اعتبارسنجی آدرس در سه مکان تکرار شده است.
• الگوریتم قیمتگذاری هم در Cart نیاز است و هم در Checkout.
• احساس میکنید باید یک Common project یا یک SharedServices folder بسازید.
این لحظه، بحرانیترین نقطه در مسیر پذیرش VSA است.
اگر اشتباه انتخاب کنید، همان coupling که قصد داشتید از آن فرار کنید را دوباره برمیگردانید. 🔄
اگر درست انتخاب کنید، استقلالی را حفظ میکنید که VSA را ارزشمند کرده است.
در ادامه توضیح میدهم که چطور من با shared code در Vertical Slice Architecture برخورد میکنم.
گاردریلها در برابر جادهٔ باز 🛣
برای اینکه بفهمیم چرا این موضوع سخت است، باید به چیزی که پشت سر گذاشتهایم نگاه کنیم.Clean Architecture گاردریلهای سختگیرانه ارائه میکند.
کاملاً مشخص میکند که هر کدی دقیقاً کجا زندگی میکند:
• Entities در Domain
• Interfaces در Application
• Implementations در Infrastructure
امن است. جلوی خطاها را میگیرد.
اما همچنین جلوی میانبرهای ضروری را نیز میگیرد.
در مقابل، Vertical Slice Architecture گاردریلها را حذف میکند.
این معماری میگوید:
"کد را بر اساس قابلیتها سازماندهی کن، نه بر اساس دغدغههای تکنیکی."
این کار سرعت و انعطافپذیری به شما میدهد،
اما بار انضباط معماری را بر دوش خودتان میگذارد.
پس چه باید کرد؟ 🤔
تله: کشوی آشفتگی به نام "Common" 🗃
سادهترین مسیر این است که یک پروژه یا فولدر به نامهای Shared, Common, یا Utils بسازید.
این کار تقریباً همیشه یک اشتباه است. ❌
فرض کنید پروژهای دارید به نام Common.Services همراه با یک کلاس OrderCalculationService.
این کلاس:
• یک متد برای جمع Cart دارد (مورد استفادهی Cart)
• یک متد برای درآمد تاریخی دارد (مورد استفادهی Reporting)
• یک Helper برای فرمتکردن فاکتور دارد (مورد استفادهی Invoices)
سه concern کاملاً بیربط.
سه نرخ تغییر متفاوت.
و یک کلاس که همهٔ اینها را به یکدیگر couple کرده است. 🕸
پروژهٔ Common دیر یا زود تبدیل میشود به یک junk drawer،
محلی برای هر چیزی که حوصلهٔ نامگذاری یا جایگذاری درستش را ندارید.
نتیجه؟
یک شبکهٔ پیچیده از وابستگیها که در آن قابلیتهای مستقل، فقط چون یک Helper مشترک استفاده میکنند، به هم گره میخورند.
در واقع coupling که قصد داشتید از آن فرار کنید دوباره بازمیگردد. 🔁
چارچوب تصمیمگیری 🧭
وقتی به یک موقعیت بالقوهٔ اشتراکگذاری (Sharing) میرسم، سه سؤال از خودم میپرسم:
1️⃣ آیا این موضوع Infrastructural است یا Domain؟
موارد Infrastructure مثل database contexts، logging، HTTP clients تقریباً همیشه باید Shared باشند.
اما مفاهیم Domain نیاز به بررسی دقیقتری دارند.
2️⃣ این کانسپت چقدر پایدار است؟
اگر سالی یک بار تغییر میکند → Shared کردن مناسب است.
اگر همراه با هر Feature Request تغییر میکند → محلی نگهش دارید (Local).
3️⃣ آیا از «Rule of Three» عبور کردهام؟
یک بار Duplicate کردن مشکلی ندارد.
دو بار هم قابل تحمل است.
اما سه بار تکرار باید برای شما زنگ خطر باشد.
تا قبل از رسیدن به سه، Abstraction انجام ندهید.
ما اینها را با Refactor کردن حل میکنیم. بیایید مثالها را ببینیم. 🔍
سه سطح اشتراکگذاری
بهجای اینکه فقط دو گزینهٔ «Shared» یا «Not Shared» داشته باشید، در سه سطح فکر کنید.