Workers архитектуры Clean Swift

  • Tutorial
Привет, читатель!

Ранее мы разобрали как устроен VIP цикл и как совершать переходы между с ценами с передачей данных. Теперь нужно разобраться как разгрузить наш Interactor от переизбытка логики и вынести ее часть для повторного использования другими сценами. И в этом нам помогут Worker’ы. Тема достаточно скромная по объему, но важная для упоминания.



Теория


Worker’ы — это вспомогательные классы/структуры (не путать с сервисами или хелперами) задача которых взять на себя часть бизнес логики Interactor’a. Если методы в Interactor’е разрастаются, значит пора выносить объемную логику в Worker. В Clean Swift их используют для работы с логикой хранения данных, с сетью, с отдельными слоями приложения и так далее. Другими словами — все объемное, низкоуровневое и не относящееся к бизнес логике приложения.

Worker’ы делятся на два основных типа:

  1. Локальные, которые используются только внутри сцены
  2. Глобальные, которые доступны для любой сцены

Локальные Worker’ы размещаются непосредственно внутри сцены и именуются в ее честь — SceneNameWorker. Глобальные Worker’ы размещаются в корневой директории Workers и именуются под свою тематику. Так же локальные Worker’ы могут выступать как “декораторы над глобальными”, с расширенной логикой для нужд сцены.

В случае, если используется разделение проекта на слои (Presentation, Business Logic, Persistency, Network Logic), то роль соединяющего моста между слоем Presentation и Business Logic можно отдать Worker’ам. Таким образом мы разгрузим Interactor, получим более предсказуемое поведение и удобство в повторном использовании.

Практика


Работу Worker’ов мы рассмотрим на примере работы с сетью. У нас будет два экрана — на первом отображается список пользователей, а на втором список постов этого пользователя. Все данные будут браться по API. Эту задачу мы разобьем на три Worker’a, два локальный и один глобальный, который будет выступать точкой входа для двух других. Реализацию самих методов в статье я скрою, а кто захочет опробовать на практике, то в конце статьи будет ссылка на готовый проект.



Данная структура проекта не является эталонной для работы с сетью и никак не отображает того, как с ней нужно работать в Clean Swift. Все это сделано лишь для наглядного примера роли локальных и глобальных Worker’ов.

Для начала создадим глобальный WorkerNetworkWorker. Разместим его в директории Workers, на уровне с директорией Scenes. В примере ниже, есть метод sendRequest(to:params:completion), который будет являться общим для локальных Worker’ов. Он выполняет рутинную задачу — формирует ссылку из параметров, отправляет запрос и пересылает результат в completion.

struct NetworkWorker {

  // MARK: - Private Properties
  private let session = URLSession.shared

  // MARK: - Public Methods
  /// Глобальный Worker. Отправка запроса к API
  ///
  /// - Parameters:
  ///   - to: Ссылка, на которую делать запрос
  ///   - params: Список параметров ссылки
  ///   - completion: Возвращает данные или ошибку
  func sendRequest(to: URL, params: [String: String], completion: @escaping (Data?, Error?) -> Void) {
    // ...
  }
}

Для первой сцены нам необходимо получить по API список всех пользователей. Для этого мы создаем локальный HomeWorker, который будет конфигурировать в себе параметры для загрузки пользователей и вызывать sendRequest(to:params:completion) в NetworkWorker с этими параметрами. Теперь в Interactor’е сцены нам нужно вызвать fetchUsers(completion:) и отправить полученные данные на обработку в Presenter.

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

struct HomeWorker {

  // MARK: - Private Properties
  private let networkWorker = NetworkWorker()
  private let usersURL = URL(string: "https://jsonplaceholder.typicode.com/users")

  // MARK: - Public Methods
  /// Локальный Worker. Запрос к API на загрузку списка пользователей
  ///
  /// - Parameter complete: Возвращает загруженный список пользователей
  func fetchUsers(_ complete: @escaping ([User]?) -> Void) {
    // ...
  }
}

На странице с постами пользователя мы создаем PostsWorker, но только с методом fetchPosts(userId:completed:). В него мы передаем ID пользователя, посты которого нужно загрузить. В методе формируем параметры и вызываем sendRequest(to:params:completion) в NetworkWorker’е. И по той же схеме, что и ранее, вызываем метод fetchPosts(userId:completed:) в Interactor’е сцены, передавая полученные данные в Presenter.

struct PostsWorker {

  // MARK: - Private Properties
  private let networkWorker = NetworkWorker()
  private let postsURL = URL(string: "https://jsonplaceholder.typicode.com/posts")

  // MARK: - Public Methods
  /// Запрос к API на загрузку списка постов пользователя
  ///
  /// - Parameters:
  ///   - userId: ID пользователя, посты которого нужно загрузить
  ///   - completed: Возвращает список загруженных постов
  func fetchPosts(userId: Int, _ completed: @escaping ([Post]?) -> Void) {
    // ...
  }
}

Теперь вся реализация у нас вынесена в отдельные файлы, которые можно повторно использовать, при этом не нагружая бизнес логику в Interactor’e.

Заключение


Хоть Worker’ы очень просты и не раскрывают никаких потаенных знаний об архитектуре, все же их использование важно в Clean Swift. При написании Worker’ов стоит не забывать про протоколы, структурные паттерны и DI. Иначе у вас быстро образуется бардак из Worker’ов, куда по кусочкам выносилось все, что только можно было вынести.

Вот и все. Спасибо, что дочитали до конца, ниже есть ссылка на полный проект.

Серия статей


  1. Общее представление об архитектуре Clean Swift
  2. Router и Data Passing в архитектуре Clean Swift
  3. Workers архитектуры Clean Swift (вы здесь)
  4. Тестирование приложения на архитектуре Clean Swift
  5. Пример простого интернет-магазина на архитектуре Clean Swift

Ссылка на проект
Помощь в написании статьи: Bastien
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

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

    0
    Мне кажется, что будут проблемы с пониманием неймингов, если называть Worker'ами всю бизнес логику вне интеракторов. В Сlean swift Worker преподносится так, что он связан с сущностью модели и не завязан на конкретный модуль.

    Я бы не стал называть класс, работающий с сетью Worker, делать зависимости воркеров друг на друга и если хочется сделать Worker для модуля, то назвать его как-то по-другому, мб Facade.

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое