” در معماری Feature-based، کدها و ساختار پروژه رو بر اساس قابلیتهای مختلف برنامه کنار هم قرار میدیم. تو این نوشته با یک مثال ساده میبینیم این روش چطور ساختار پروژههای بزرگتر رو مرتبتر میکنه. “
توی نوشته قبلی درباره این صحبت کردم که اصلاً چه زمانی یک پروژه به معماری مشخص نیاز داره. اینکه قرار نیست از روز اول برای یه پروژه کوچیک، دهها قانون و فولدر و لایه مختلف درست کنیم و خودمون رو درگیر معماری و ساختارش کنیم.
اما وقتی به این نتیجه رسیدیم که پروژه داره به جایی میرسه که باید ساختار مشخصی داشته باشه، یه سؤال دیگه به وجود میاد:
خب، حالا کدها رو چطور کنار هم بچینیم؟
یکی از روشهایی که توی پروژههای بزرگ میبینیم، Feature Architecture یا معماری بر اساس Feature هست.
به جای اینکه پروژه رو بر اساس نوع فایل تقسیم کنیم، بر اساس کاری که اون بخش از برنامه انجام میده تقسیمش میکنیم.
همین.
فرض کنیم داریم یه فروشگاه اینترنتی میسازیم.
احتمالاً اوایل پروژه یه ساختار ساده شبیه این داریم:
components/
pages/
hooks/
services/
utils/
types/این ساختار برای شروع کاملاً مناسبه.
مثلاً کامپوننتها میرن داخل components، درخواستهای API داخل services و Hookها هم داخل hooks.
مشکل معمولاً زمانی ایجاد میشه که پروژه بزرگتر میشه.
فرض کن برای بخش سبد خرید چند فایل داریم:
components/Cart.tsx
components/CartItem.tsx
hooks/useCart.ts
services/cart.ts
types/cart.tsحالا چند ماه بعد میخوای یه تغییر توی سبد خرید بدی.
باید بدونی کدهای مربوط به Cart کجاست و بین چند فولدر مختلف دنبالش بگردی.
حالا بخشهای دیگه پروژه هم همین وضعیت رو دارن.
محصولات، احراز هویت، پرداخت، پروفایل، سفارشها و...
در نتیجه کمکم فولدرهایی مثل components و utils تبدیل میشن به یه انبار بزرگ از فایلهای مختلف.
اسم فایلها مشخصه، ولی ساختار پروژه دیگه لزوماً بهت نمیگه هر فایل متعلق به کدوم بخش از برنامه است.
در معماری Feature-based، اول از خود برنامه میپرسیم:
کاربر چه کارهایی میتونه انجام بده؟
مثلاً توی فروشگاه ما:
وارد حسابش بشه
محصول ببینه
محصول به سبد خرید اضافه کنه
سفارش ثبت کنه
پروفایلش رو ویرایش کنه
هر کدوم از اینها میتونه یک Feature باشه.
پس به جای این:
components/
hooks/
services/
types/میتونیم ساختار رو این شکلی کنیم:
features/
├── auth/
├── products/
├── cart/
├── checkout/
└── profile/حالا وقتی وارد cart میشیم، چیزهایی که مربوط به سبد خرید هستن رو همونجا داریم:
cart/
├── components/
├── hooks/
├── services/
└── types/مثلاً:
cart/
├── components/
│ ├── Cart.tsx
│ └── CartItem.tsx
├── hooks/
│ └── useCart.ts
├── services/
│ └── cart.ts
└── types/
└── cart.tsاین بار اگه بخوام روی سبد خرید کار کنم، تقریباً میدونم باید کجا رو نگاه کنم.
چیزی که قبلاً در چند فولدر پخش شده بود، حالا کنار هم قرار گرفته.
اینجا یه نکته مهم وجود داره.
Feature لزوماً به معنی یک صفحه نیست.
مثلاً products میتونه یک Feature باشه، ولی ممکنه چند صفحه مختلف ازش استفاده کنن.
یا auth فقط صفحه ورود نیست. میتونه ورود، ثبت نام، بازیابی رمز عبور و چیزهای مرتبط با احراز هویت کاربر رو شامل بشه.
پس بهتره Feature رو اینطور ببینیم:
یک بخش مشخص از برنامه که یک کار یا مجموعهای از کارهای مرتبط با هم رو انجام میده.
این تعریف کمک میکنه خیلی درگیر اسمگذاری نشیم.
قرار نیست بشینیم برای هر کامپوننت یه Feature بسازیم.
البته قرار نیست با استفاده از معماری Feature-based، همه فایلهای پروژه رو ببریم داخل Featureها.
یه سری چیزها هستن که در بخشهای مختلف برنامه استفاده میشن و وابسته به یک Feature مشخص نیستن. مثلاً یک Button، Modal یا Input ممکنه در چندین قسمت پروژه استفاده بشه.
برای این موارد همچنان میتونیم پوشههای مشترک خودمون رو داشته باشیم:
components/
├── Button.tsx
├── Modal.tsx
└── Input.tsxیا مثلاً:
shared/
├── components/
├── hooks/
└── utils/اسم این بخش خیلی مهم نیست. چیزی که مهمه اینه که کدهای مربوط به یک Feature رو از کدهایی که در کل برنامه استفاده میشن جدا کنیم.
مثلاً:
features/
├── auth/
├── cart/
└── products/
shared/
├── components/
├── utils/
└── hooks/حالا CartItem داخل cart قرار میگیره، چون مخصوص همون بخشه.
ولی Button داخل shared قرار میگیره، چون بخشهای مختلف برنامه میتونن ازش استفاده کنن.
مهمترین مزیتش اینه که وقتی وارد یک Feature میشیم، بخش زیادی از چیزهایی که برای کار کردن روی اون قسمت لازم داریم همونجاست.
فرض کن میخوای Checkout رو یسری تغییرات بدی.
در ساختار معمولی شاید مجبور باشی بین این فولدرها دنبال فایلهای مربوط به Checkout بگردی:
components/
hooks/
services/
types/
utils/اما در ساختار Feature-based، احتمالاً بیشتر چیزهایی که لازم داری اینجاست:
features/
└── checkout/
├── components/
├── hooks/
├── services/
├── types/
└── utils/این فقط باعث مرتبتر شدن فولدرها نمیشه.
یه مزیت دیگه هم داره:
ذهن آدم راحتتر میتونه پروژه رو بخشبندی کنه.
به جای اینکه پروژه رو به عنوان صدها فایل مختلف ببینی، میتونی بگی:
«این پروژه چند بخش اصلی داره؛ Auth، Products، Cart و Checkout.»
و بعد داخل هر بخش، جزئیات خودش رو میبینی.
یه نکته دیگه هم هست. وقتی میگیم هر Feature کدهای خودش رو داشته باشه، منظورمون این نیست که برای هر چیزی چندتا پوشه درست کنیم.
مثلاً اگه cart فقط سه چهار فایل داره، همین ساختار ساده کافیه:
cart/
├── Cart.tsx
├── CartItem.tsxلازم نیست از همون اول اینها رو هم بسازیم:
components/
hooks/
services/
types/
utils/ولی وقتی Feature بزرگتر شد و تعداد فایلهاش بالا رفت، اون موقع میتونیم کمکم ساختارش رو جدا و بهتر کنیم:
cart/
├── components/
├── hooks/
└── services/یعنی قرار نیست ساختار پروژه رو از روز اول برای چیزی که هنوز وجود نداره آماده کنیم.
هر وقت یک Feature بزرگتر و شلوغتر شد، همون موقع میتونیم مرتبترش کنیم.
این مدل رو شاید بیشتر در پروژههای Front-end و مخصوصاً React ببینیم، ولی ایدهاش به یک فریمورک خاص وابسته نیست.
در Laravel یا Vue یا حتی یک پروژه ساده JavaScript هم میشه همین طرز فکر رو داشت.
مثلاً در Laravel میتونیم بخشهای مختلف برنامه رو جدا کنیم و چیزهایی که مربوط به یک قابلیت خاص هستن، تا جای ممکن کنار هم نگه داریم (پکیج nwidart/laravel-modules یه نگاهی بندازید).
اسم فولدرها و جزئیات پیادهسازی ممکنه فرق کنه، ولی ایده یکیه:
کد رو بر اساس چیزی که میسازه و کاری که انجام میده سازماندهی کن، نه فقط بر اساس پسوند یا نوع فایل.
اگر الان یک پروژه داری که ساختار معمولی داره، لازم نیست فردا صبح کل پروژه رو جابهجا کنی.
اول فقط Featureهای اصلی رو پیدا کن.
مثلاً:
Auth
Products
Cart
Orders
Profileبعد ببین هر کدوم چه فایلهایی دارن.
احتمالاً متوجه میشی بخشی از فایلهایی که الان داخل components یا services هستن، در واقع متعلق به یک Feature مشخص هستن.
همونها رو میتونی کمکم منتقل کنی.
و چیزهایی که واقعاً بین بخشهای مختلف مشترک هستن، بیرون از Featureها باقی میمونن.
قرار نیست همه پروژهها رو با معماری Feature-based بسازیم. برای یه پروژه کوچیک که چند صفحه و چندتا قابلیت ساده داره، همون ساختار معمولی میتونه کاملاً جواب بده و نیازی نیست خودمون رو درگیر این چیزها کنیم.
ولی وقتی پروژه بزرگتر میشه، تعداد فایلها بالا میره و بخشهای مختلف برنامه بیشتر میشن، پیدا کردن کدهای مربوط به هر بخش هم سختتر میشه. اینجاست که جدا کردن پروژه بر اساس Featureها میتونه کمک زیادی بکنه.
چیزی که مهمه اینه که ساختار پروژه متناسب با اندازه و پیچیدگی خودش باشه..
نکته ای، سوالی یا ... داشتید توی قسمت دیدگاه ها بنویسید :)
تا نوشته بعدی بدرود ❤️👋
دیدگاه و یا پرسش خود را برای ما ارسال کنید.
هنوز دیدگاه یا پرسشی ایجاد نشده است :/
تجربهها، دیدگاهها و نکات الهامبخشی که با شما به اشتراک میگذاریم.