Какие виды архитектур предпочитаешь использовать на своих проектах?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Архитектуры Flutter проектов
Мой выбор архитектуры зависит от размера и сложности проекта. Рассмотрю наиболее эффективные подходы, которые я применяю в реальных проектах.
1. Clean Architecture (мой основной выбор)
Это классическая многослойная архитектура, которая обеспечивает максимальную тестируемость и масштабируемость.
Структура слоев:
lib/
├── presentation/
│ ├── pages/
│ │ └── home_page.dart
│ ├── widgets/
│ │ └── user_card.dart
│ └── controllers/
│ └── home_controller.dart
├── application/
│ ├── use_cases/
│ │ └── get_users_use_case.dart
│ └── services/
│ └── auth_service.dart
├── domain/
│ ├── entities/
│ │ └── user.dart
│ ├── repositories/
│ │ └── user_repository.dart
│ └── value_objects/
│ └── email.dart
└── infrastructure/
├── repositories/
│ └── user_repository_impl.dart
├── datasources/
│ ├── remote/
│ │ └── user_api.dart
│ └── local/
│ └── user_db.dart
└── models/
└── user_model.dart
Преимущества:
- Полная независимость бизнес-логики от UI
- Легко тестировать каждый слой отдельно
- Простая замена реализации (например, API на mock)
- Масштабируется на большие проекты
Пример реализации:
// Domain: Entity
class User {
final String id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
}
// Domain: Repository interface
abstract class UserRepository {
Future<List<User>> getAllUsers();
Future<User> getUserById(String id);
}
// Application: Use Case
class GetAllUsersUseCase {
final UserRepository repository;
GetAllUsersUseCase(this.repository);
Future<List<User>> call() async {
return await repository.getAllUsers();
}
}
// Infrastructure: Repository implementation
class UserRepositoryImpl implements UserRepository {
final UserApi api;
UserRepositoryImpl(this.api);
@override
Future<List<User>> getAllUsers() async {
final models = await api.fetchUsers();
return models.map((m) => m.toDomain()).toList();
}
@override
Future<User> getUserById(String id) async {
final model = await api.fetchUserById(id);
return model.toDomain();
}
}
// Presentation: Controller
class HomeController extends ChangeNotifier {
final GetAllUsersUseCase useCase;
List<User> _users = [];
bool _isLoading = false;
HomeController(this.useCase);
Future<void> loadUsers() async {
_isLoading = true;
notifyListeners();
try {
_users = await useCase();
} catch (e) {
print('Error: $e');
}
_isLoading = false;
notifyListeners();
}
}
// Presentation: Widget
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late HomeController controller;
@override
void initState() {
super.initState();
controller = context.read<HomeController>();
controller.loadUsers();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Users')),
body: Center(
child: Text('Users list'),
),
);
}
}
2. BLoC Architecture
Для сложных проектов с реактивным потоком данных предпочитаю BLoC (Business Logic Component).
// Event
class LoadUsersEvent extends UserEvent {}
class DeleteUserEvent extends UserEvent {
final String userId;
DeleteUserEvent(this.userId);
}
// State
abstract class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
final List<User> users;
UserLoaded(this.users);
}
class UserError extends UserState {
final String message;
UserError(this.message);
}
// BLoC
class UserBloc extends Bloc<UserEvent, UserState> {
final GetAllUsersUseCase getAllUsersUseCase;
final DeleteUserUseCase deleteUserUseCase;
UserBloc(this.getAllUsersUseCase, this.deleteUserUseCase)
: super(UserInitial()) {
on<LoadUsersEvent>(_onLoadUsers);
on<DeleteUserEvent>(_onDeleteUser);
}
Future<void> _onLoadUsers(
LoadUsersEvent event,
Emitter<UserState> emit,
) async {
emit(UserLoading());
try {
final users = await getAllUsersUseCase();
emit(UserLoaded(users));
} catch (e) {
emit(UserError(e.toString()));
}
}
Future<void> _onDeleteUser(
DeleteUserEvent event,
Emitter<UserState> emit,
) async {
if (state is UserLoaded) {
final currentUsers = (state as UserLoaded).users;
emit(UserLoading());
try {
await deleteUserUseCase(event.userId);
final updatedUsers = currentUsers
.where((u) => u.id != event.userId)
.toList();
emit(UserLoaded(updatedUsers));
} catch (e) {
emit(UserError(e.toString()));
}
}
}
}
// Presentation
class UserPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<UserBloc, UserState>(
builder: (context, state) {
if (state is UserLoading) {
return Center(child: CircularProgressIndicator());
} else if (state is UserLoaded) {
return ListView.builder(
itemCount: state.users.length,
itemBuilder: (context, index) {
final user = state.users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
context.read<UserBloc>().add(
DeleteUserEvent(user.id),
);
},
),
);
},
);
} else if (state is UserError) {
return Center(child: Text('Error: ${state.message}'));
}
return Center(child: Text('Initial'));
},
);
}
}
3. MVVM (Model-View-ViewModel)
Для средних проектов, где нужна простота и скорость разработки:
// Model
class User {
final String id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
}
// ViewModel
class UserViewModel extends ChangeNotifier {
final UserRepository repository;
List<User> users = [];
bool isLoading = false;
UserViewModel(this.repository);
Future<void> loadUsers() async {
isLoading = true;
notifyListeners();
try {
users = await repository.getUsers();
} catch (e) {
print('Error: $e');
}
isLoading = false;
notifyListeners();
}
}
// View
class UserView extends StatefulWidget {
@override
_UserViewState createState() => _UserViewState();
}
class _UserViewState extends State<UserView> {
@override
void initState() {
super.initState();
context.read<UserViewModel>().loadUsers();
}
@override
Widget build(BuildContext context) {
return Consumer<UserViewModel>(
builder: (context, viewModel, _) {
if (viewModel.isLoading) {
return Center(child: CircularProgressIndicator());
}
return ListView(
children: viewModel.users
.map((user) => ListTile(title: Text(user.name)))
.toList(),
);
},
);
}
}
4. Выбор архитектуры по типу проекта
Маленький проект (MVP, прототип):
- MVVM с Provider
- Простая структура папок
- Быстрая разработка
Средний проект (3-5 разработчиков):
- Clean Architecture
- BLoC для сложной логики
- Четкое разделение ответственности
Большой проект (команда 5+):
- Clean Architecture
- BLoC pattern
- Repository pattern
- Dependency injection (GetIt)
- Unit и widget тесты для каждого слоя
5. Мой StackExchange Architecture
Для реальных проектов использую гибридный подход:
Domain Layer (независимая от фреймворка)
↓
Application Layer (use cases, сервисы)
↓
Presentation Layer (BLoC/ChangeNotifier + UI)
↓
Infrastructure Layer (API, БД, файловая система)
Основные правила:
- Зависимости идут снизу вверх (от Domain к Presentation)
- Domain слой не знает про Flutter/BLoC/UI
- Каждый слой полностью тестируем
- Используется Dependency Injection
Итого
Рекомендации по архитектуре:
- Маленькие проекты → MVVM
- Средние проекты → Clean Architecture + BLoC
- Большие проекты → Clean Architecture + BLoC + DI
- Всегда следовать принципам SOLID и помнить про тестируемость
Выбор архитектуры должен соответствовать масштабу проекта и команде разработчиков. Переинженерить на маленьких проектах вредно, а недостаточная архитектура на больших проектах создает техдолг.