Какие архитектурные подходы в Dart (вне контекста Flutter UI) ты знаешь или применял?

«Какие архитектурные подходы в Dart (вне контекста Flutter UI) ты знаешь или применял?» — вопрос из категории Архитектура, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Dart как язык позволяет строить чистую, модульную архитектуру на уровне бизнес-логики и данных, независимо от Flutter. Вот ключевые подходы, которые я применял:

  • Слоистая архитектура (Layered Architecture): Это основа большинства моих проектов. Я четко разделяю код на слои:

    • Domain (Бизнес-слой): Содержит сущности (User, Product), абстрактные интерфейсы репозиториев (UserRepository) и Use Cases (или Interactors) — классы, инкапсулирующие конкретные бизнес-правила. Этот слой не зависит ни от чего внешнего.
    • Data (Слой данных): Реализует интерфейсы из Domain. Содержит конкретные реализации репозиториев, модели данных для API (UserDto), источники данных (локальная БД через sqflite/hive, сетевые клиенты через dio/http).
    • Presentation (Слой представления): Во Flutter это виджеты и state-менеджеры (BLoC, ViewModel), которые используют Use Cases из Domain слоя.
  • Принципы SOLID и Clean Architecture в Dart: Я активно применяю эти принципы для создания поддерживаемого кода. Например, Dependency Injection (внедрение зависимостей) достигается через передачу зависимостей в конструктор, что делает код тестируемым.

    // Domain слой: абстракция
    abstract class AuthRepository {
      Future<User> login(String email, String password);
    }
    
    // Use Case (также Domain слой)
    class LoginUseCase {
      final AuthRepository _repository;
      LoginUseCase(this._repository); // DI через конструктор
    
      Future<User> execute(String email, String password) {
        return _repository.login(email, password);
      }
    }
    
    // Data слой: конкретная реализация
    class AuthRepositoryImpl implements AuthRepository {
      final ApiClient _client;
      AuthRepositoryImpl(this._client);
    
      @override
      Future<User> login(String email, String password) async {
        final dto = await _client.post('/login', data: {'email': email});
        return User.fromDto(dto);
      }
    }
  • Функциональное программирование: Dart поддерживает функции первого класса, Future, Stream и операторы высшего порядка (map, where, fold). Я использую эти возможности для создания чистых функций, обработки асинхронных потоков данных и написания декларативного, предсказуемого кода, особенно в комбинации с async/await.

Такой подход позволяет писать бизнес-логику, которую можно легко тестировать изолированно (юнит-тесты для Use Cases) и повторно использовать, например, в CLI-утилите на Dart, если это потребуется.