جمعه, 10 مهر 1405

معماری Feature-based

...

” در معماری 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 رو ببین

در معماری 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 دقیقاً یعنی چی؟

اینجا یه نکته مهم وجود داره.

Feature لزوماً به معنی یک صفحه نیست.

مثلاً products می‌تونه یک Feature باشه، ولی ممکنه چند صفحه مختلف ازش استفاده کنن.

یا auth فقط صفحه ورود نیست. می‌تونه ورود، ثبت نام، بازیابی رمز عبور و چیزهای مرتبط با احراز هویت کاربر رو شامل بشه.

پس بهتره Feature رو این‌طور ببینیم:

یک بخش مشخص از برنامه که یک کار یا مجموعه‌ای از کارهای مرتبط با هم رو انجام میده.

این تعریف کمک می‌کنه خیلی درگیر اسم‌گذاری نشیم.

قرار نیست بشینیم برای هر کامپوننت یه 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 بزرگ‌تر و شلوغ‌تر شد، همون موقع می‌تونیم مرتب‌ترش کنیم.

معماری Feature-based فقط برای React نیست

این مدل رو شاید بیشتر در پروژه‌های Front-end و مخصوصاً React ببینیم، ولی ایده‌اش به یک فریم‌ورک خاص وابسته نیست.

در Laravel یا Vue یا حتی یک پروژه ساده JavaScript هم میشه همین طرز فکر رو داشت.

مثلاً در Laravel می‌تونیم بخش‌های مختلف برنامه رو جدا کنیم و چیزهایی که مربوط به یک قابلیت خاص هستن، تا جای ممکن کنار هم نگه داریم (پکیج nwidart/laravel-modules یه نگاهی بندازید).

اسم فولدرها و جزئیات پیاده‌سازی ممکنه فرق کنه، ولی ایده یکیه:

کد رو بر اساس چیزی که می‌سازه و کاری که انجام میده سازمان‌دهی کن، نه فقط بر اساس پسوند یا نوع فایل.

از کجا شروع کنیم؟

اگر الان یک پروژه داری که ساختار معمولی داره، لازم نیست فردا صبح کل پروژه رو جابه‌جا کنی.

اول فقط Featureهای اصلی رو پیدا کن.

مثلاً:

Auth
Products
Cart
Orders
Profile

بعد ببین هر کدوم چه فایل‌هایی دارن.

احتمالاً متوجه میشی بخشی از فایل‌هایی که الان داخل components یا services هستن، در واقع متعلق به یک Feature مشخص هستن.

همون‌ها رو می‌تونی کم‌کم منتقل کنی.

و چیزهایی که واقعاً بین بخش‌های مختلف مشترک هستن، بیرون از Featureها باقی می‌مونن.

حرف آخر

قرار نیست همه پروژه‌ها رو با معماری Feature-based بسازیم. برای یه پروژه کوچیک که چند صفحه و چندتا قابلیت ساده داره، همون ساختار معمولی می‌تونه کاملاً جواب بده و نیازی نیست خودمون رو درگیر این چیزها کنیم.

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

چیزی که مهمه اینه که ساختار پروژه متناسب با اندازه و پیچیدگی خودش باشه..

نکته ای، سوالی یا ... داشتید توی قسمت دیدگاه ها بنویسید :)

تا نوشته بعدی بدرود ❤️👋

ارسال دیدگاه

دیدگاه و یا پرسش خود را برای ما ارسال کنید.

وارد شوید

برای ارسال دیدگاه یا پرسش خود ابتدا وارد سایت شوید

ورود یا ثبت نام

دیدگاه کاربران

هنوز دیدگاه یا پرسشی ایجاد نشده است :/

تازه‌ترین نوشته‌ها

دیدن همه

تجربه‌ها، دیدگاه‌ها و نکات الهام‌بخشی که با شما به اشتراک می‌گذاریم.