Операция
Определение
Операция представлена отдельным объектом, который получает на вход сервисы в качестве зависимостей.
Операция не предоставляет доступа к своему состоянию и не имеет каких-либо методов в интерфейсе, кроме метода 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 (см. обмен данными между сценариями).