skills/flutter-architecture/references/feature-first.md

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