Операция

Определение

👉
Операция — это неделимая последовательность сообщений между сервисами. Сообщение — это обращение к методу того или иного сервиса.

Операция представлена отдельным объектом, который получает на вход сервисы в качестве зависимостей.

💡
Основное отличие операции от сервиса в том, что операция не имеет императивного интерфейса.

Операция не предоставляет доступа к своему состоянию и не имеет каких-либо методов в интерфейсе, кроме метода launch. Метод launch принимает на вход параметры, описывающие контекст запуска (переменные и/или дополнительные контекстные зависимости, а также замыкание onFinish, если операция асинхронная. Таким образом, операция похожа на обычную функцию: принимает параметры на вход и возвращает результат.

Операция обладает собственным жизненным циклом, который разделяется на два этапа: инициализацию и выполнение. Инициализация осуществляется в фабрике операций, а выполнение – в точке обращения к фабрике. Операция может работать в синхронном и асинхронном режимах. Асинхронный режим означает, что операция может оставаться в памяти, даже если вызывающая область видимости уже удалена.

Асинхронная операция обязательно завершается вызовом коллбека/замыкания onFinish, переданного ей при запуске. Так гарантируется однозначность выполнения операции и обеспечивается механизм ее удаления в асинхронном режиме. Ссылку на асинхронную операцию нужно где-то хранить, чтобы после выхода из скоупа запущенная асинхронная операция не была удалена. В контексте iOS удобно использовать искусственный retain-cycle: при запуске launch операция создает сильную ссылку сама на себя, а при вызове onFinish освобождает ссылку.

Как правило, каждый экран приложения работает в контексте некоторой операции, т.е. использует вью-модель, порожденную операцией. Вместе с тем операция может выполнять задачи, вообще не связанные с UI, например, обработка данных или инициализации ключевых сервисов.

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

Графическое представление

Операция описывается диаграммой сообщений.

👉
Диаграмма сообщений — это диаграмма последовательности, стрелками которой являются сообщения.

В ходе инициализации операция получает на вход ключевые сервисы. Интерфейсы сервисов, как ключевых, так и контекстных, представлены на диаграмме сообщений прямоугольниками объектов и соотвествующими линиями жизни. Время жизни контекстного сервиса отмечается линией существования (полоса-прямоугольник поверх линии жизни). Время жизни ключевого сервиса явным образом не обозначается, т.к. оно по умолчанию привыешат время жизни всей операции.

В ходе выполнения операции внутри нее создаются контекстные сервисы а также формируются контекстные связи между сервисами. Например, создается вью-модель, ей на вход передается локальное хранилище (жесткая связь), а также выполняется привязка вью-модели к роутеру (контекстная связь).

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

Диаграмма сообщений для AuthOp – операции для входа по номеру телефона.
Диаграмма сообщений для AuthOp – операции для входа по номеру телефона.

Разделение операции

Предыдущая диаграмма описывает AuthOp – операцию входа в приложение по номеру телефона. Операция отвечает как за отправку номера телефона, так и за отправку смс-кода подтверждения, при этом данные вводятся на двух различных экранах. Оба экрана/запроса объединены в рамках одной операции, поскольку всегда выступают в неразрывной цепочке. Неделимость – это отличительный признак операции. Как только возникает необходимость вставить в середину цепочки другую операцию или переиспользовать часть операции в другом контексте, большая операция разбивается на две и более отдельных операции. Разберем пример.

После ввода номера телефона сервис генерирует пользовательское соглашение на индивидуальных условиях. Согласно бизнес-требованиям соглашение должно:

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

Таким образом, процедура входа, изначально представленная единственной операцией AuthOp, теперь представлена тремя отдельными операциями: PhoneOp, TermsOfUseOp и CodeOp.

Диаграмма сообщений для PhoneOp – операции для отправки номера телефона.
Диаграмма сообщений для PhoneOp – операции для отправки номера телефона.
Диаграмма сообщений для TermsOfUseOp – операции для получения и принятия соглашения.
Диаграмма сообщений для TermsOfUseOp – операции для получения и принятия соглашения.
Диаграмма сообщений для CodeOp – операции для отправки смс-кода подтверждения.
Диаграмма сообщений для CodeOp – операции для отправки смс-кода подтверждения.

Последовательность из этих операций объединяется в рамках единого сценария AuthScenario. Сценарий с этого момента отвечает за процедуру входа, поэтому ее удобно называть «сценарий входа».

Обмен данными между операциями

PhoneOp формирует и использует PhoneViewModel, передавая ей LocalStorage и AuthAPI в виде жестких связей — зависимостей. Итогом выполнения операции будет наличие токена авторизации в LocalStorage.

Диаграмма классов для PhoneViewModel.
Диаграмма классов для PhoneViewModel.
👉
Взаимодействие между AuthAPI и LocalStorage можно инкапсулировать в сервисе-обертке PhoneService. PhoneViewModel будет взаимодействовать только с PhoneService, а PhoneOpбудет отражать исключительно бизнес-логику обработки номера телефона, не беспокоясь о технической логике, связанной с обработкой и хранением токена авторизации.

Операция CodeOp (равно как и TermsOfUseOp) извлекает токен из LocalStorage. Так реализуется обмен данными между CodeOp и PhoneOp через ключевой сервис LocalStorage. У операций нет императивного интерфейса, поэтому общий ключевой сервис – это единственный способ обмена данными между операциями, выполняющимися одновременно. В случае цепочки операций есть альтернатива: операция может возвращать данные в onFinish, а сценарий, в рамках которого эта операция выполняется, передаст эти данные на вход следующей операции в метод launch. Например, операция PhoneOp может не сохранять в LocalStorage полученный от AuthAPI токен авторизации, а вернуть его в AuthScenario. AuthScenario передаст токен сначала в TermsOfUseOp, а затем в CodeOp.

Диаграмма сообщений для «чистой» PhoneOp – токен возвращается в onFinish.
Диаграмма сообщений для «чистой» PhoneOp – токен возвращается в onFinish.
Диаграмма сообщений для параметризированной CodeOp – токен передается в launch.
Диаграмма сообщений для параметризированной CodeOp – токен передается в launch.
💡
Использование «чистых» операций без сайд-эффектов позволяет легко их откатывать и/или перезапускать. Например, ошибку операции можно обработать в сценарии, после чего просто перезапустить операцию заново. В этом случае не придется беспокоиться об очистке состояний ключевых сервисов, задействованных в предыдущей попытке, поскольку состояние сервисов не менялось.

Таким образом, в ходе выполнения AuthScenario токен хранится внутри сценария. Сохранение токена в LocalStorage для последующего использования может быть выполнено через отдельную операцию — последнюю в цепочке. Также AuthScenario может вернуть токен вовне уже через свой onFinish (см. обмен данными между сценариями).