· آموزش فلاتر - ۱۸ دقیقه
بررسی ساختار پروژههای فلاتر - مقایسه روشهای ویژگیمحور و لایهمحور
در ساخت اپلیکیشنهای بزرگ Flutter، انتخاب ساختاری مناسب برای پروژه مهم است تا تیم بتواند ویژگیها را به شیوهای منظم اضافه کند. این مقاله دو رویکرد «ویژگیمحور» و «لایهمحور» را بررسی میکند، مزایا و معایب آنها را توضیح میدهد و یک راهنمای گام به گام ارائه میدهد.
مقدمه و اهمیت ساختار پروژه در Flutter
مقدمه و اهمیت ساختار پروژه در Flutterساختاردهی مناسب یک پروژه Flutter از جمله عواملی است که نقش بسزایی در موفقیت و عملکرد یک اپلیکیشن دارد. این ساختار نه تنها بر نگهداری و توسعه آسان پروژه تاثیر میگذارد، بلکه بر مقیاسپذیری و توانایی تیم توسعه برای اضافه کردن ویژگیهای جدید به اپلیکیشن نیز تاثیرگذار است. با گذشت زمان و رشد پروژه، داشتن یک ساختار مناسب و پیروی از یک قرارداد واضح میتواند به توسعهدهندگان کمک کند تا بدون ایجاد پیچیدگیهای اضافی، به راحتی تغییرات لازم را اعمال کنند.
ساختار پروژه در Flutter، معمولا حول محور اجزای UI، مدیریت State، مدلهای داده و سرویسهای خارجی شکل میگیرد. تعادل برقرار کردن بین این لایهها و ویژگیهای اپلیکیشن، چالشی است که اغلب توسعهدهندگان با آن مواجه میشوند. در اینجا دو رویکرد اصلی در طراحی ساختار پروژهها وجود دارد: Feature-first (ویژگیمحور) و Layer-first (لایهمحور). هر یک از این رویکردها مزایا و معایب خاص خود را دارد و انتخاب صحیح بین آنها میتواند موفقیت پروژه را تعیین کند.
در این مقاله، قصد داریم هر دو رویکرد Feature-first و Layer-first را با جزئیات بررسی کنیم تا شما بتوانید با توجه به نیازهای پروژه خود، ساختار مناسبی را انتخاب کنید. و در نهایت رویکردی را پیشنهاد میدهیم که برخی از پروژههای پیچیده میتوانند از آن بهرهمند شوند.
روش Layer-first
روش Layer-firstدر رویکرد لایهمحور، پروژه را بر اساس لایههای مختلف مانند Domain، Data و Presentation سازماندهی میکند. در این ساختار، فایلهای مشابه از نظر نوع و عملکرد (مانند فایلهای مربوط به دادهها، رابط کاربری یا مدیریت State) در پوشههای مربوط به همان لایه قرار میگیرند. این روش از اصول مهندسی نرمافزار کلاسیک الهام گرفته شده و بر تفکیک وظایف و مسئولیتها در لایههای مختلف تاکید دارد.
و اگر بخواهیم feature3 را اضافه کنیم، باید یک پوشه feature3 در داخل هر لایه اضافه کنیم و این روند را تکرار کنیم:
‣ lib
‣ src
‣ presentation
‣ feature1
‣ feature2
‣ feature3 <--
‣ domain
‣ feature1
‣ feature2
‣ feature3 <--
‣ data
‣ feature1
‣ feature2
‣ feature3 <--
مزایای رویکرد Layer-first
مزایای رویکرد Layer-first- مناسب برای پروژههای کوچک تا متوسط: برای اپلیکیشنهای کوچک که ویژگیهای زیادی ندارند، این روش ساده و منطقی است و به توسعهدهندگان کمک میکند تا کد خود را بدون پیچیدگی نگهداری کنند.
- سازماندهی تیمی بهتر: اگر تیم توسعهدهندگان بر اساس تخصص در لایههای مختلف (مثلاً فرانتاند یا بکاند) تقسیم شده باشد، این روش به تیمها اجازه میدهد تا بر روی تخصص خود تمرکز کنند.
معایب رویکرد Layer-first
معایب رویکرد Layer-firstوابستگی زیاد بین ویژگیها: به دلیل اینکه فایلهای مربوط به یک ویژگی در لایههای مختلف پراکنده شدهاند، ایجاد تغییرات در یک ویژگی ممکن است به لایههای دیگر هم تاثیر بگذارد و احتمال بروز باگهای ناخواسته افزایش یابد. و یا اگر تصمیم بگیریم که میخواهیم یک ویژگی را حذف کنیم، فراموش کردن برخی فایلها بسیار بدیهی است، زیرا همه آنها بر اساس لایه سازماندهی شدهاند.
کاهش مقیاسپذیری: به مرور زمان و با افزایش ویژگیهای جدید، مقیاسپذیری پروژه دشوارتر میشود. فایلهای مربوط به هر ویژگی در لایههای مختلف پراکنده هستند و با رشد پروژه، مدیریت این لایهها پیچیدهتر شده و زمان جستجو و تغییرات در کد افزایش مییابد.
سرعت کمتر توسعه ویژگیها: در رویکرد Layer-first، اگرچه کدها بر اساس وظیفهشان سازماندهی شدهاند، اما تفکیک ویژگیها دشوارتر است. به همین دلیل، نگهداری و تغییرات ممکن است به دلیل وابستگیهای متقابل بین لایهها پیچیدهتر شود. توسعهدهندگانی که تازه به پروژه ملحق میشوند ممکن است زمان بیشتری برای فهمیدن ساختار و روابط بین لایهها نیاز داشته باشند.
روش Feature-first
روش Feature-firstرویکرد ویژگیمحور یکی از رایجترین روشهای ساختاردهی پروژهها در Flutter است که تمرکز اصلی آن بر ویژگیها و عملکردهای اپلیکیشن است. در این روش، هر ویژگی (Feature) به صورت مستقل در یک پوشه جداگانه قرار میگیرد و تمام لایهها، فایلها و کدهای مرتبط با آن ویژگی در همان پوشه قرار داده میشوند. به عبارت دیگر، پروژه بر اساس ویژگیها تقسیم میشود نه بر اساس نقش هر فایل.
ساختار Feature-first با استفاده از نمونه قبل:
‣ lib
‣ src
‣ features
‣ feature1
‣ presentation
‣ domain
‣ data
‣ feature2
‣ presentation
‣ domain
‣ data
مزایای رویکرد Feature-first
مزایای رویکرد Feature-first- استقلال ویژگیها: هر ویژگی به صورت کاملاً مستقل پیادهسازی میشود، به این معنی که توسعه، نگهداری یا حتی حذف یک ویژگی بدون تاثیر بر سایر بخشها امکانپذیر است. توسعهدهندگان میتوانند به راحتی کدهای مرتبط با یک ویژگی خاص را پیدا کنند و تغییرات لازم را بدون نیاز به جستجو در لایههای مختلف اعمال کنند.
- مقیاسپذیری: این روش برای پروژههایی که در حال رشد و توسعه مداوم هستند، بسیار مناسب است. با افزایش ویژگیها، هر ویژگی به صورت مستقل اضافه میشود و این امر به توسعهدهندگان کمک میکند تا بدون تغییر در ساختار اصلی پروژه، ویژگیهای جدیدی را به راحتی پیادهسازی کنند. از آنجا که هر ویژگی در یک پوشه جداگانه قرار دارد، مدیریت و نگهداری آنها سادهتر است و پیچیدگی اضافی به وجود نمیآید.
- همکاری تیمی بهتر: رویکرد Feature-first مناسب تیمهایی است که اعضا به صورت مستقل روی ویژگیهای مختلف کار میکنند. به دلیل جداسازی ویژگیها، هر تیم یا توسعهدهنده میتواند بدون نگرانی از تاثیرگذاری بر بخشهای دیگر پروژه، روی بخش خود کار کند. این امر به تسریع فرآیند توسعه و کاهش تداخل بین تیمها کمک میکند.
- تست متمرکز: آزمایش و تست کردن هر ویژگی به طور مستقل انجام میشود که منجر به کاهش پیچیدگی در تست کلی پروژه میشود.
معایب رویکرد Feature-first
معایب رویکرد Feature-first- تکرار کد: به دلیل جداسازی کامل لایهها بر اساس ویژگیها، ممکن است برخی از منطقها یا سرویسهای مشترک در ویژگیهای مختلف تکرار شوند، که میتواند منجر به افزایش حجم کد و پیچیدگی شود.
- اضافهکاری برای پروژههای کوچک: برای اپلیکیشنهای کوچک یا ساده، استفاده از این رویکرد میتواند منجر به پیچیدگی غیرضروری و افزایش تعداد فایلها و پوشهها شود.
رویکرد پیشنهادی
رویکرد پیشنهادیبا توجه به بررسیها و مقایسهای که بین دو رویکرد کردیم، به نظر میرسد رویکرد Feature-first انتخاب برتری است.
با این حال، در دنیای واقعی، همه چیز چندان آسان نیست!
تکلیف کدهای مشترک
تکلیف کدهای مشترکهنگام ساخت برنامههای واقعی، متوجه خواهید شد که کد شما همیشه بهطور منظم در پوشههای خاصی که در نظر گرفتهاید قرار نمی گیرد.
اگر دو یا چند ویژگی جداگانه نیاز به اشتراکگذاری برخی ویجتها یا کلاسهای مدل داشته باشند، چه کار کنیم؟
در این موارد، به راحتی میتوان پوشههایی به عنوان shared
یا common
یا utils
را ایجاد کنید.
اما خود این پوشهها چگونه باید سازماندهی شوند؟ و چگونه میتوانید از تبدیل شدن آنها به محل دفن انواع فایلها جلوگیری کنید؟
اگر برنامه شما 20 ویژگی دارد و کدی دارد که فقط باید توسط دو مورد از آنها به اشتراک گذاشته شود، آیا واقعاً باید به یک پوشه shared
در سطح بالا تعلق داشته باشد؟
اگر بین 5 ویژگی به اشتراک گذاشته شود چطور؟ یا 10؟
در این سناریو، پاسخ درست یا غلطی وجود ندارد و شما باید در هر مورد از بهترین قضاوت خود استفاده کنید.
جدا از این، یک اشتباه بسیار رایج وجود دارد که باید از آن اجتناب کنیم.
Feature-first در مورد رابط کاربری نیست!
Feature-first در مورد رابط کاربری نیست!وقتی روی رابط کاربری تمرکز میکنیم، احتمالا یک ویژگی را به عنوان یک صفحه واحد در برنامه در نظر میگیریم. اما این کار باعث بوجود آمدن یک ساختار نامتعادل میشود. بهتر است مثال بزنیم.
ساختار زیر را برای یک اپ فروشگاه در نظر بگیرید:
‣ lib
‣ src
‣ features
‣ account
‣ admin
‣ checkout
‣ leave_review_page
‣ orders_list
‣ product_page
‣ products_list
‣ shopping_cart
‣ sign_in
تمام ویژگیهای بالا صفحات واقعی در اپ فروشگاه را نمایش میدهند.
اما وقتی نوبت به قرار دادن لایههای presentation، domain و data درون آنها میرسد، با چالشی روبهرو میشویم و آن اینکه برخی مدلها و ریپازیتوریها توسط چندین صفحه به اشتراک گذاشته میشوند (مانند product_page
و product_list
).
در نتیجه، احتمالا مجبور میشویم پوشههای سطحبالا برای سرویسها، مدلها و ریپازیتوریها ایجاد کنیم:
‣ lib
‣ src
‣ features
‣ account
‣ admin
‣ checkout
‣ leave_review_page
‣ orders_list
‣ product_page
‣ domain
‣ product.dart <-- اینجا؟
...
‣ products_list
‣ domain
‣ product.dart <-- اینجا
...
‣ shopping_cart
‣ sign_in
‣ models <-- آیا اینجا درست هست؟
‣ repositories <-- آیا اینجا درست هست؟
‣ services <-- آیا اینجا درست هست؟
آیا لزومی دارد مثلا مدل product
که در product_page
و product_list
استفاده میشود، در لایه اشتراکی اپ قرار داده شود؟
تکلیف چیست؟ اینجا میبایستی یک قدم به عقب برگردیم و مشخص کنیم کهیک ویژگی
چیست؟
یک ویژگی چیست؟
یک ویژگی چیست؟یک ویژگی درباره آنچه کاربر میبیند نیست، بلکه درباره آنچه کاربر انجام میدهد است:
- احراز هویت
- مدیریت سبد خرید
- تسویه حساب
- مشاهده تمام سفارشات گذشته
- ثبت نظر
به عبارت دیگر، یک ویژگی یک نیاز عملکردی است که به کاربر کمک می کند یک کار معین را تکمیل کند.
و با گرفتن نکاتی از طراحی مبتنی بر دامنه (Domain-Drivern-Design or DDD)، میتوانیم ساختار پروژه را حول لایه دامنه سازماندهی کنیم و product_page
و product_list
را زیر مجموعهی product
ببینیم.
در نتیجه ساختار ما به این صورت خواهد شد:
‣ lib
‣ src
‣ features
‣ address
...
‣ authentication
...
‣ cart
...
‣ checkout
...
‣ orders
...
‣ products
‣ data
‣ domain
‣ product.dart
‣ presentation
‣ admin
‣ product_screen
‣ products_list
‣ reviews
البته توجه داشته باشید که با این رویکرد همچنان امکان وابستگی کد درون یک ویژگی خاص به کد از یک ویژگی متفاوت وجود دارد. به عنوان مثال:
- صفحه محصول لیستی از نظرات را نمایش میدهد
- صفحه سفارشات برخی اطلاعات محصول را نمایش میدهد
- جریان تسویه حساب نیاز دارد که کاربر ابتدا احراز هویت کند
اما ما با فایلهای بسیار کمتری که در تمام ویژگیها به اشتراک گذاشته میشوند مواجه میشویم، و کل ساختار بسیار متعادلتر است.
چگونه Feature-first را به درستی انجام دهیم؟
چگونه Feature-first را به درستی انجام دهیم؟به طور خلاصه، رویکرد ویژگیمحور به ما اجازه میدهد پروژه خود را حول الزامات عملکردی برنامهمان ساختار دهیم.
با رعایت این نکات میتوانید از رویکرد ویژگیمحور به طور صحیح استفاده کنید.
- از لایه دامنه شروع کنید و کلاسهای مدل و منطق کسب و کار را شناسایی کنید.
- یک پوشه برای هر مدل (یا گروهی از مدلها) که به هم تعلق دارند ایجاد کنید.
- درون آن پوشه، زیرپوشههای presentation، domain، data را در صورت نیاز ایجاد کنید.
- درون هر زیرپوشه، تمام فایلهای مورد نیاز خود را اضافه کنید.
حالا یک نمونه ساختار کلی پیشنهادی را ببینید:
‣ lib
‣ src
‣ common_widgets
‣ constants
‣ exceptions
‣ features
‣ address
...
‣ authentication
‣ domain
user.dart
‣ presentation
‣ account
‣ login
...
‣ cart
...
‣ checkout
...
‣ orders
...
‣ products
‣ data
‣ domain
‣ product.dart
‣ presentation
‣ admin
‣ product_screen
‣ products_list
‣ reviews
‣ localization
‣ routing
‣ utils
بدون اینکه حتی داخل پوشههایی مانند common_widgets، constants، exceptions، localization، routing و utils را نگاه کنیم، میتوانیم حدس بزنیم که همه آنها حاوی کدی هستند که واقعا در بین ویژگیها به اشتراک گذاشته میشود، یا دلیل خوبی برای متمرکز شدن دارند (مانند زبان اپ و ناوبری).
و این پوشهها همه حاوی کد نسبتا کمی هستند.
نتیجهگیری
نتیجهگیریساختاردهی مناسب در پروژههای Flutter، یکی از عوامل کلیدی موفقیت در توسعه و نگهداری اپلیکیشنها است. دو رویکرد اصلی یعنی Feature-first و Layer-first، هر کدام مزایا و معایب خاص خود را دارند و انتخاب بین آنها بستگی به نیازهای پروژه و تیم توسعه دارد.
در رویکرد Feature-first، تمرکز بر جداسازی ویژگیها و ماژولار کردن پروژه است. این روش برای پروژههای بزرگ و پیچیده که ویژگیهای متعددی دارند بسیار مناسب است و به تیمهای توسعه اجازه میدهد به صورت مستقل روی هر ویژگی کار کنند. این رویکرد باعث بهبود مقیاسپذیری و تسهیل نگهداری پروژه میشود.
از سوی دیگر، رویکرد Layer-first با سازماندهی پروژه بر اساس لایهها (مانند دادهها، منطق برنامه و رابط کاربری) به جداسازی وظایف و استفاده مجدد از کدها کمک میکند. این روش برای پروژههای کوچک تا متوسط که نیاز به تفکیک وظایف دارند مناسب است، اما با رشد پروژه، ممکن است مدیریت آن پیچیدهتر شود و توسعه ویژگیهای جدید زمانبر باشد.
در انتخاب ساختار مناسب برای پروژه خود، باید به اندازه پروژه، تعداد ویژگیها و همکاری تیمی توجه کنید. انتخاب صحیح ساختار میتواند بهرهوری تیم را افزایش دهد و از بروز مشکلاتی همچون پیچیدگی زیاد و نگهداری دشوار جلوگیری کند.