404 lines
10 KiB
Markdown
404 lines
10 KiB
Markdown
# Feature-First Architecture
|
|
|
|
Organize code by features instead of layers for better maintainability and team scalability.
|
|
|
|
## Overview
|
|
|
|
**Feature-first** organizes code by business features, with each feature containing all layers needed for that feature.
|
|
|
|
```
|
|
lib/
|
|
├── features/
|
|
│ ├── auth/
|
|
│ │ ├── data/
|
|
│ │ │ ├── repositories/
|
|
│ │ │ └── services/
|
|
│ │ ├── domain/
|
|
│ │ │ ├── models/
|
|
│ │ │ └── use-cases/
|
|
│ │ └── presentation/
|
|
│ │ ├── views/
|
|
│ │ └── viewmodels/
|
|
│ ├── todos/
|
|
│ │ ├── data/
|
|
│ │ ├── domain/
|
|
│ │ └── presentation/
|
|
│ └── settings/
|
|
│ ├── data/
|
|
│ ├── domain/
|
|
│ └── presentation/
|
|
├── shared/
|
|
│ ├── core/
|
|
│ │ ├── constants/
|
|
│ │ ├── theme/
|
|
│ │ └── utils/
|
|
│ ├── data/
|
|
│ │ ├── models/
|
|
│ │ └── services/
|
|
│ └── ui/
|
|
│ ├── widgets/
|
|
│ └── components/
|
|
└── main.dart
|
|
```
|
|
|
|
## Feature-First vs Layer-First
|
|
|
|
### Layer-First (Traditional)
|
|
|
|
```
|
|
lib/
|
|
├── data/
|
|
│ ├── repositories/
|
|
│ ├── services/
|
|
│ └── models/
|
|
├── domain/
|
|
│ ├── use-cases/
|
|
│ └── entities/
|
|
├── presentation/
|
|
│ ├── views/
|
|
│ └── viewmodels/
|
|
└── shared/
|
|
```
|
|
|
|
**When to use:**
|
|
- Small to medium apps
|
|
- Few features (<10)
|
|
- Solo developers or small teams
|
|
- Simple business logic
|
|
|
|
**Pros:**
|
|
- Clear separation by layer
|
|
- Easy to find components by type
|
|
- Less nesting
|
|
|
|
**Cons:**
|
|
- Harder to delete features
|
|
- Changes often span multiple folders
|
|
- Tangled dependencies between layers
|
|
|
|
### Feature-First (Recommended for teams)
|
|
|
|
```
|
|
lib/
|
|
├── features/
|
|
│ ├── auth/
|
|
│ │ ├── data/
|
|
│ │ ├── domain/
|
|
│ │ └── presentation/
|
|
│ └── todos/
|
|
│ ├── data/
|
|
│ ├── domain/
|
|
│ └── presentation/
|
|
└── shared/
|
|
```
|
|
|
|
**When to use:**
|
|
- Medium to large apps
|
|
- Many features (10+)
|
|
- Team development (2+ developers)
|
|
- Complex business logic
|
|
- Frequently adding/removing features
|
|
|
|
**Pros:**
|
|
- Features are self-contained units
|
|
- Easy to add/remove entire features
|
|
- Clear feature boundaries
|
|
- Reduced merge conflicts
|
|
- Teams work independently on features
|
|
|
|
**Cons:**
|
|
- More nesting in folder structure
|
|
- Shared code requires careful organization
|
|
- Can duplicate similar code across features
|
|
|
|
## Feature Structure
|
|
|
|
Each feature contains all layers for that feature:
|
|
|
|
```
|
|
features/
|
|
└── todos/
|
|
├── data/
|
|
│ ├── repositories/
|
|
│ │ └── todo_repository.dart
|
|
│ └── services/
|
|
│ └── todo_api_service.dart
|
|
├── domain/
|
|
│ ├── models/
|
|
│ │ └── todo.dart
|
|
│ └── use-cases/
|
|
│ └── fetch_todos.dart
|
|
│ └── create_todo.dart
|
|
│ └── delete_todo.dart
|
|
└── presentation/
|
|
├── views/
|
|
│ └── todo_list_view.dart
|
|
└── viewmodels/
|
|
└── todo_list_viewmodel.dart
|
|
```
|
|
|
|
### Naming Conventions
|
|
|
|
**Feature folders:** Plural, lowercase, underscore-separated
|
|
- `auth/`, `todos/`, `user_profile/`
|
|
|
|
**Component folders:** Plural, lowercase
|
|
- `data/`, `domain/`, `presentation/`
|
|
- `repositories/`, `services/`, `models/`
|
|
- `views/`, `viewmodels/`
|
|
|
|
**Files:** snake_case for regular files, camelCase for classes
|
|
- `todo_repository.dart` → `class TodoRepository`
|
|
- `todo_list_view.dart` → `class TodoListView`
|
|
- `fetch_todos.dart` → `class FetchTodos`
|
|
|
|
## Shared Code
|
|
|
|
Code used across multiple features goes in `shared/`:
|
|
|
|
```
|
|
shared/
|
|
├── core/
|
|
│ ├── constants/
|
|
│ │ └── app_constants.dart
|
|
│ ├── theme/
|
|
│ │ ├── app_colors.dart
|
|
│ │ └── app_text_styles.dart
|
|
│ └── utils/
|
|
│ ├── extensions.dart
|
|
│ ├── validators.dart
|
|
│ └── formatters.dart
|
|
├── data/
|
|
│ ├── models/
|
|
│ │ └── common_models.dart
|
|
│ └── services/
|
|
│ ├── network_service.dart
|
|
│ ├── local_storage_service.dart
|
|
│ └── analytics_service.dart
|
|
└── ui/
|
|
├── widgets/
|
|
│ ├── app_scaffold.dart
|
|
│ ├── loading_indicator.dart
|
|
│ └── error_widget.dart
|
|
└── components/
|
|
├── app_button.dart
|
|
├── app_text_field.dart
|
|
└── app_card.dart
|
|
```
|
|
|
|
### What goes in `shared/core/`
|
|
|
|
- **Constants**: App-wide constants (API URLs, timeouts)
|
|
- **Theme**: Colors, text styles, themes
|
|
- **Utils**: Extensions, validators, formatters used across features
|
|
|
|
### What goes in `shared/data/`
|
|
|
|
- **Models**: Domain models used by multiple features (User, etc.)
|
|
- **Services**: Platform services (network, storage, analytics)
|
|
|
|
### What goes in `shared/ui/`
|
|
|
|
- **Widgets**: Reusable UI components (scaffolds, indicators)
|
|
- **Components**: Reusable UI elements (buttons, cards, inputs)
|
|
|
|
## Dependency Rules
|
|
|
|
### Within a Feature
|
|
|
|
```
|
|
presentation/ -> domain/ -> data/
|
|
presentation/ -> data/ // simple flows can skip domain
|
|
presentation/ -> shared/ui/
|
|
data/ -> shared/data/
|
|
```
|
|
|
|
- **Presentation ViewModels** can use Domain, Data for simple flows, and
|
|
Shared/UI. Views should still talk to ViewModels, not Services.
|
|
- **Domain layer** can use feature data abstractions and shared pure utilities.
|
|
- **Data layer** can use Shared/Data services or models.
|
|
- **Domain layer is optional**. Add use-cases only for complex, reused, or
|
|
multi-repository business logic.
|
|
|
|
### Between Features
|
|
|
|
**Features should NOT import each other's implementation files.**
|
|
|
|
If Feature A needs Feature B's functionality:
|
|
1. Move common logic to `shared/`
|
|
2. Use dependency injection with a stable interface
|
|
3. Consider if features should be merged
|
|
|
|
```dart
|
|
// ❌ BAD: Feature depending on another feature
|
|
import 'package:my_app/features/auth/data/repositories/auth_repository.dart';
|
|
|
|
// ✅ GOOD: Using shared service
|
|
import 'package:my_app/shared/data/services/auth_service.dart';
|
|
|
|
// ✅ GOOD: Depending on a stable shared interface provided through DI
|
|
import 'package:my_app/shared/data/auth_session.dart';
|
|
```
|
|
|
|
## When to Add a New Feature
|
|
|
|
1. **Create feature folder:**
|
|
```
|
|
features/new_feature/
|
|
├── data/
|
|
└── presentation/
|
|
```
|
|
|
|
2. **Add subfolders based on complexity:**
|
|
- Simple: Skip `domain/`, use only `data/` and `presentation/`
|
|
- Complex: Add `domain/` for use-cases
|
|
|
|
3. **Implement bottom-up:**
|
|
- Add models in `data/` or `domain/models/`
|
|
- Add services in `data/services/`
|
|
- Add repositories in `data/repositories/`
|
|
- Add viewmodels in `presentation/viewmodels/`
|
|
- Add views in `presentation/views/`
|
|
|
|
4. **Add exports:**
|
|
```dart
|
|
// features/new_feature/new_feature.dart
|
|
export 'data/repositories/new_feature_repository.dart';
|
|
export 'presentation/views/new_feature_view.dart';
|
|
export 'presentation/viewmodels/new_feature_viewmodel.dart';
|
|
```
|
|
|
|
5. **Register in DI container** (main.dart)
|
|
|
|
## Best Practices
|
|
|
|
### 1. Barrel Files
|
|
|
|
Export feature contents from barrel file:
|
|
|
|
```dart
|
|
// features/todos/todos.dart
|
|
library todos;
|
|
|
|
export 'data/repositories/todo_repository.dart';
|
|
export 'domain/models/todo.dart';
|
|
export 'domain/use-cases/fetch_todos.dart';
|
|
export 'presentation/views/todo_list_view.dart';
|
|
export 'presentation/viewmodels/todo_list_viewmodel.dart';
|
|
```
|
|
|
|
Usage:
|
|
```dart
|
|
import 'package:my_app/features/todos/todos.dart';
|
|
|
|
// Now have access to Todo, TodoRepository, etc.
|
|
```
|
|
|
|
### 2. Lazy Loading
|
|
|
|
For large apps, use lazy loading:
|
|
|
|
```dart
|
|
// In router
|
|
GoRoute(
|
|
path: '/todos',
|
|
builder: (context, state) => const LazyLoad(
|
|
builder: () => const TodosView(),
|
|
),
|
|
);
|
|
```
|
|
|
|
### 3. Feature Scoping
|
|
|
|
Keep feature dependencies minimal:
|
|
- Use `shared/` for common code
|
|
- Avoid imports from another feature's `data/` or `presentation/`
|
|
- When needed, use dependency inversion through an interface
|
|
|
|
### 4. Testing
|
|
|
|
Each feature is testable independently:
|
|
|
|
```
|
|
features/
|
|
└── todos/
|
|
├── data/
|
|
│ └── repositories/
|
|
│ └── todo_repository_test.dart
|
|
├── domain/
|
|
│ └── use-cases/
|
|
│ └── fetch_todos_test.dart
|
|
└── presentation/
|
|
└── viewmodels/
|
|
└── todo_list_viewmodel_test.dart
|
|
```
|
|
|
|
## Migration from Layer-First
|
|
|
|
Gradual migration strategy:
|
|
|
|
1. **Start with new features:**
|
|
- Add new features using feature-first
|
|
- Keep old code in layer-first
|
|
|
|
2. **Migrate one feature at a time:**
|
|
```
|
|
// Before (layer-first)
|
|
data/
|
|
├── todo_repository.dart
|
|
└── user_repository.dart
|
|
presentation/
|
|
├── todo_view.dart
|
|
└── user_view.dart
|
|
|
|
// After (feature-first)
|
|
features/
|
|
├── todos/
|
|
│ ├── data/repositories/todo_repository.dart
|
|
│ └── presentation/views/todo_view.dart
|
|
└── users/
|
|
├── data/repositories/user_repository.dart
|
|
└── presentation/views/user_view.dart
|
|
```
|
|
|
|
3. **Move shared code to `shared/`:**
|
|
- Identify cross-feature code
|
|
- Move to appropriate `shared/` folder
|
|
- Update imports
|
|
|
|
4. **Update barrel files** for clean imports
|
|
|
|
## Example: Complete Feature
|
|
|
|
```
|
|
features/
|
|
└── todos/
|
|
├── todos.dart // Barrel file
|
|
├── data/
|
|
│ ├── models/
|
|
│ │ └── todo_model.dart // DTOs from API
|
|
│ ├── repositories/
|
|
│ │ └── todo_repository.dart // SSOT for todos
|
|
│ └── services/
|
|
│ └── todo_api_service.dart // API calls
|
|
├── domain/
|
|
│ ├── models/
|
|
│ │ └── todo.dart // Domain entity
|
|
│ └── use-cases/
|
|
│ ├── fetch_todos.dart
|
|
│ ├── create_todo.dart
|
|
│ └── update_todo.dart
|
|
│ └── delete_todo.dart
|
|
└── presentation/
|
|
├── viewmodels/
|
|
│ └── todo_list_viewmodel.dart
|
|
└── views/
|
|
└── todo_list_view.dart
|
|
```
|
|
|
|
This structure provides:
|
|
- Complete feature encapsulation
|
|
- Clear layer separation within feature
|
|
- Easy to add/remove entire feature
|
|
- Minimal dependencies on other features
|