← Назад к вопросам

Какие виды архитектур предпочитаешь использовать на своих проектах?

1.2 Junior🔥 141 комментариев
#Архитектура Flutter

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Архитектуры 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 и помнить про тестируемость

Выбор архитектуры должен соответствовать масштабу проекта и команде разработчиков. Переинженерить на маленьких проектах вредно, а недостаточная архитектура на больших проектах создает техдолг.

Какие виды архитектур предпочитаешь использовать на своих проектах? | PrepBro