Операция
Определение
Операция представлена отдельным объектом, который получает на вход сервисы в качестве зависимостей.
Операция не предоставляет доступа к своему состоянию и не имеет каких-либо методов в интерфейсе, кроме метода launch
. Метод launch
принимает на вход параметры, описывающие контекст запуска (переменные и/или дополнительные контекстные зависимости, а также замыкание onFinish
, если операция асинхронная. Таким образом, операция похожа на обычную функцию: принимает параметры на вход и возвращает результат.
Операция обладает собственным жизненным циклом, который разделяется на два этапа: инициализацию и выполнение. Инициализация осуществляется в фабрике операций, а выполнение – в точке обращения к фабрике. Операция может работать в синхронном и асинхронном режимах. Асинхронный режим означает, что операция может оставаться в памяти, даже если вызывающая область видимости уже удалена.
Асинхронная операция обязательно завершается вызовом коллбека/замыкания onFinish
, переданного ей при запуске. Так гарантируется однозначность выполнения операции и обеспечивается механизм ее удаления в асинхронном режиме. Ссылку на асинхронную операцию нужно где-то хранить, чтобы после выхода из скоупа запущенная асинхронная операция не была удалена. В контексте iOS удобно использовать искусственный retain-cycle: при запуске launch
операция создает сильную ссылку сама на себя, а при вызове onFinish
освобождает ссылку.
Как правило, каждый экран приложения работает в контексте некоторой операции, т.е. использует вью-модель, порожденную операцией. Вместе с тем операция может выполнять задачи, вообще не связанные с UI, например, обработка данных или инициализации ключевых сервисов.
Обычно операция выполняется в рамках некоторого сценария, но также возможны случаи, когда ключевой сервис запускает одиночную операцию, не прибегая к созданию отдельного сценария.
Графическое представление
Операция описывается диаграммой сообщений.
В ходе инициализации операция получает на вход ключевые сервисы. Интерфейсы сервисов, как ключевых, так и контекстных, представлены на диаграмме сообщений прямоугольниками объектов и соотвествующими линиями жизни. Время жизни контекстного сервиса отмечается линией существования (полоса-прямоугольник поверх линии жизни). Время жизни ключевого сервиса явным образом не обозначается, т.к. оно по умолчанию привыешат время жизни всей операции.
В ходе выполнения операции внутри нее создаются контекстные сервисы а также формируются контекстные связи между сервисами. Например, создается вью-модель, ей на вход передается локальное хранилище (жесткая связь), а также выполняется привязка вью-модели к роутеру (контекстная связь).
После запуска операции начинается обмен сообщениями между сервисами, представленный на диаграмме сообщений стрелками.
Разделение операции
Предыдущая диаграмма описывает AuthOp
– операцию входа в приложение по номеру телефона. Операция отвечает как за отправку номера телефона, так и за отправку смс-кода подтверждения, при этом данные вводятся на двух различных экранах. Оба экрана/запроса объединены в рамках одной операции, поскольку всегда выступают в неразрывной цепочке. Неделимость – это отличительный признак операции. Как только возникает необходимость вставить в середину цепочки другую операцию или переиспользовать часть операции в другом контексте, большая операция разбивается на две и более отдельных операции. Разберем пример.
После ввода номера телефона сервис генерирует пользовательское соглашение на индивидуальных условиях. Согласно бизнес-требованиям соглашение должно:
- показываться первый раз до процедуры смс-подтверждения (пользователь должен принять условия до запроса кода), что разбивает операцию входа;
- показываться в любой другой произвольный момент при смене условий соглашения, что означает переиспользуемость процедуры показа и принятия соглашения.
Таким образом, процедура входа, изначально представленная единственной операцией AuthOp
, теперь представлена тремя отдельными операциями: PhoneOp
, TermsOfUseOp
и CodeOp
.
Последовательность из этих операций объединяется в рамках единого сценария AuthScenario
. Сценарий с этого момента отвечает за процедуру входа, поэтому ее удобно называть «сценарий входа».
Обмен данными между операциями
PhoneOp
формирует и использует PhoneViewModel
, передавая ей LocalStorage
и AuthAPI
в виде жестких связей — зависимостей. Итогом выполнения операции будет наличие токена авторизации в LocalStorage
.
AuthAPI
и LocalStorage
можно инкапсулировать в сервисе-обертке PhoneService
. PhoneViewModel
будет взаимодействовать только с PhoneService
, а PhoneOp
будет отражать исключительно бизнес-логику обработки номера телефона, не беспокоясь о технической логике, связанной с обработкой и хранением токена авторизации.Операция CodeOp
(равно как и TermsOfUseOp
) извлекает токен из LocalStorage
. Так реализуется обмен данными между CodeOp
и PhoneOp
через ключевой сервис LocalStorage
. У операций нет императивного интерфейса, поэтому общий ключевой сервис – это единственный способ обмена данными между операциями, выполняющимися одновременно. В случае цепочки операций есть альтернатива: операция может возвращать данные в onFinish
, а сценарий, в рамках которого эта операция выполняется, передаст эти данные на вход следующей операции в метод launch
. Например, операция PhoneOp
может не сохранять в LocalStorage
полученный от AuthAPI
токен авторизации, а вернуть его в AuthScenario
. AuthScenario
передаст токен сначала в TermsOfUseOp
, а затем в CodeOp
.
Таким образом, в ходе выполнения AuthScenario
токен хранится внутри сценария. Сохранение токена в LocalStorage
для последующего использования может быть выполнено через отдельную операцию — последнюю в цепочке. Также AuthScenario
может вернуть токен вовне уже через свой onFinish
(см. обмен данными между сценариями).