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

Какие знаешь принципы чистой архитектуры?

2.0 Middle🔥 121 комментариев
#Архитектура Flutter#ООП и паттерны

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

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

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

Какие знаешь принципы чистой архитектуры

Чистая архитектура (Clean Architecture) — это методология проектирования приложений, предложенная Робертом Мартином. Она разделяет приложение на слои с чёткими границами ответственности.

Основная концепция

┌────────────────────────────────┐
│  Presentation (UI)              │
├────────────────────────────────┤
│  Application (Use Cases)        │
├────────────────────────────────┤
│  Domain (Business Logic)        │
├────────────────────────────────┤
│  Infrastructure (External APIs) │
└────────────────────────────────┘

Зависимости только внутрь! →
Presentation → Application → Domain
Infrastructure → Domain

Четыре основных слоя

1. Domain Layer — Центр (бизнес-логика)

Самый независимый слой. Ничего не знает о внешних сервисах.

// domain/entities/user.dart
class User {
  final String id;
  final String name;
  final String email;
  
  User({
    required this.id,
    required this.name,
    required this.email,
  });
  
  // Бизнес-логика (валидация)
  bool isValidEmail() => email.contains('@');
  bool isAdult() => true; // Пример бизнес-логики
}

// domain/repositories/user_repository.dart
abstract class UserRepository {
  Future<User?> getUserById(String id);
  Future<void> saveUser(User user);
  Future<void> deleteUser(String id);
}

// domain/usecases/get_user_usecase.dart
class GetUserUseCase {
  final UserRepository repository;
  
  GetUserUseCase(this.repository);
  
  Future<User?> call(String id) {
    return repository.getUserById(id);
  }
}

2. Application Layer — Бизнес-правила

Координирует Domain. Использует Use Cases.

// application/services/user_service.dart
class UserService {
  final UserRepository repository;
  final EmailValidator emailValidator;
  
  UserService(this.repository, this.emailValidator);
  
  Future<void> registerUser(String name, String email) async {
    // Применяем бизнес-правила
    if (!emailValidator.isValid(email)) {
      throw InvalidEmailException();
    }
    
    final user = User(
      id: generateId(),
      name: name,
      email: email,
    );
    
    await repository.saveUser(user);
  }
}

// application/dto/user_dto.dart
class UserDTO {
  final String id;
  final String name;
  final String email;
  
  UserDTO.fromEntity(User user)
    : id = user.id,
      name = user.name,
      email = user.email;
}

3. Infrastructure Layer — Реализация деталей

Работа с БД, API, файловой системой.

// infrastructure/repositories/user_repository_impl.dart
class UserRepositoryImpl implements UserRepository {
  final UserLocalDataSource localDataSource;
  final UserRemoteDataSource remoteDataSource;
  
  UserRepositoryImpl(this.localDataSource, this.remoteDataSource);
  
  @override
  Future<User?> getUserById(String id) async {
    try {
      // Пытаемся получить с сервера
      final user = await remoteDataSource.getUserById(id);
      // Кешируем локально
      await localDataSource.saveUser(user);
      return user;
    } catch (e) {
      // Если сервер не доступен, берём из кеша
      return await localDataSource.getUserById(id);
    }
  }
  
  @override
  Future<void> saveUser(User user) async {
    await localDataSource.saveUser(user);
    // Синхронизируем с сервером
    try {
      await remoteDataSource.saveUser(user);
    } catch (e) {
      // Синхронизируем позже
      await _markForSync(user.id);
    }
  }
}

// infrastructure/datasources/user_local_datasource.dart
abstract class UserLocalDataSource {
  Future<User?> getUserById(String id);
  Future<void> saveUser(User user);
}

class UserLocalDataSourceImpl implements UserLocalDataSource {
  final HiveDatabase hive;
  
  UserLocalDataSourceImpl(this.hive);
  
  @override
  Future<User?> getUserById(String id) async {
    return hive.users.get(id);
  }
  
  @override
  Future<void> saveUser(User user) async {
    await hive.users.put(user.id, user);
  }
}

// infrastructure/datasources/user_remote_datasource.dart
abstract class UserRemoteDataSource {
  Future<User> getUserById(String id);
  Future<void> saveUser(User user);
}

class UserRemoteDataSourceImpl implements UserRemoteDataSource {
  final Dio dio;
  
  UserRemoteDataSourceImpl(this.dio);
  
  @override
  Future<User> getUserById(String id) async {
    final response = await dio.get('/users/$id');
    return User.fromJson(response.data);
  }
  
  @override
  Future<void> saveUser(User user) async {
    await dio.post('/users', data: user.toJson());
  }
}

4. Presentation Layer — UI/Controllers

BLoC, GetX, Riverpod. Слушает события, обновляет UI.

// presentation/bloc/user_bloc.dart
class UserBloc extends Bloc<UserEvent, UserState> {
  final GetUserUseCase getUserUseCase;
  
  UserBloc(this.getUserUseCase) : super(UserInitial()) {
    on<FetchUserEvent>(_onFetchUser);
  }
  
  Future<void> _onFetchUser(
    FetchUserEvent event,
    Emitter<UserState> emit,
  ) async {
    emit(UserLoading());
    
    try {
      final user = await getUserUseCase(event.userId);
      if (user != null) {
        emit(UserLoaded(user));
      } else {
        emit(UserNotFound());
      }
    } catch (e) {
      emit(UserError(e.toString()));
    }
  }
}

// presentation/pages/user_page.dart
class UserPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<UserBloc, UserState>(
      builder: (context, state) {
        if (state is UserLoading) {
          return Center(child: CircularProgressIndicator());
        }
        
        if (state is UserLoaded) {
          return UserDetailView(user: state.user);
        }
        
        if (state is UserError) {
          return Center(child: Text('Ошибка: ${state.message}'));
        }
        
        return SizedBox();
      },
    );
  }
}

Правила зависимостей (Dependency Rules)

// ❌ ПЛОХО — нарушение правила
class UserPage extends StatelessWidget {
  // Presentation имеет доступ к DataSource!
  final dio = Dio();
  
  @override
  Widget build(BuildContext context) {
    // Прямой запрос к API из UI — это плохо
    dio.get('/users/123');
    return Container();
  }
}

// ✅ ХОРОШО — правильная зависимость
class UserPage extends StatelessWidget {
  final UserBloc userBloc; // Зависимость от BLoC
  
  UserPage({required this.userBloc});
  
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<UserBloc, UserState>(
      bloc: userBloc,
      builder: (context, state) => ...,
    );
  }
}

Структура проекта

project/
├── lib/
│   ├── domain/
│   │   ├── entities/
│   │   │   └── user.dart
│   │   ├── repositories/
│   │   │   └── user_repository.dart
│   │   └── usecases/
│   │       └── get_user_usecase.dart
│   │
│   ├── application/
│   │   ├── services/
│   │   │   └── user_service.dart
│   │   ├── dto/
│   │   │   └── user_dto.dart
│   │   └── mappers/
│   │       └── user_mapper.dart
│   │
│   ├── infrastructure/
│   │   ├── repositories/
│   │   │   └── user_repository_impl.dart
│   │   ├── datasources/
│   │   │   ├── user_local_datasource.dart
│   │   │   └── user_remote_datasource.dart
│   │   └── models/
│   │       └── user_model.dart
│   │
│   └── presentation/
│       ├── bloc/
│       │   ├── user_bloc.dart
│       │   ├── user_event.dart
│       │   └── user_state.dart
│       ├── pages/
│       │   └── user_page.dart
│       └── widgets/
│           └── user_tile.dart

Ключевые принципы

  1. Независимость от Framework — Domain не знает о Flutter
  2. Тестируемость — каждый слой можно тестировать отдельно
  3. Направление зависимостей — всегда внутрь
  4. Инверсия управления — dependency injection
  5. Разделение ответственности — каждый класс = одна задача

Пример правильного потока данных

UI (Flutter Widget)
  ↓
BLoC (User Management)
  ↓
Use Case (GetUserUseCase)
  ↓
Repository Interface
  ↓
Repository Implementation (Выбор источника данных)
  ↓
Data Sources (API / Database)
  ↓
External Services (REST API / SQLite)

Преимущества Clean Architecture

  • Масштабируемость — легко добавлять новые функции
  • Тестируемость — юнит тесты без мок-фреймворков
  • Переиспользуемость — Domain слой используется в мобил, веб, десктоп
  • Независимость — замена БД не требует изменения бизнес-логики
  • Команда — чёткие границы, разработчики не конфликтуют

Чистая архитектура — это не панацея, но золотой стандарт для серьёзных приложений.

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