Аннотация
Добавление новой функциональности в большое унаследованное Rails-приложение зачастую дорого, если это возможно. Но что, если вместо добавления нового поведения в приложение Rails мы реализовали его как одностраничное приложение в JavaScript? В этой статье я покажу несколько полезных приемов для этого.
Добавление поведения в устаревшие приложения Rails
Предположим, у нас есть приложение для продажи книг, и нам нужно внедрить новый рабочий процесс для наших клиентов. Например, клиенты должны иметь возможность покупать книги без необходимости регистрации. Рабочий процесс довольно сложен, и, следовательно, потребует изменения примерно дюжины вариантов использования и, возможно, создания нескольких новых. Это повлияет на несколько моделей, контроллеров и множество частичных представлений. Смена последних особенно рискованна, потому что они не очень хорошо проверены.
Все эти изменения могут занять месяцы, и вероятность того, что другой рабочий процесс будет затронут, довольно высока.
Вместо этого создаем одностраничное приложение
Вместо того, чтобы вносить такие масштабные изменения в приложение Rails, где все взаимосвязано и границ нет, замените его часть одностраничным приложением. Сначала это одностраничное приложение делегирует большую часть своей работы бэкэнду. Это, однако, очень хорошее место для реализации новых рабочих процессов и вариантов использования. Таким образом, со временем, по мере появления новых требований, одностраничное приложение будет их включать, сводя к минимуму изменения в серверной части. Со временем вся логика координации будет перенесена в одностраничное приложение, а на сервер останутся поставщики услуг и репозитории.
Зачем создавать одностраничное приложение, а не просто переписывать модуль в Ruby? Зачем выбирать JavaScript? Потому что это позволяет новые типы взаимодействия с пользователем. Это означает, что вы не просто переписываете часть своего приложения в целях удобства сопровождения, вы делаете это для создания некоторой ценности для бизнеса. В этом случае лучший пользовательский опыт. И вдобавок ко всему, вы получаете больше поддерживаемого кода. Сказав это, не все требует интерактивного пользовательского интерфейса. Очевидно, что перестройка таких модулей как одностраничных приложений не будет лучшей идеей.
Два фронтенда
Поскольку создание одностраничного приложения не является однодневной задачей, в течение некоторого времени у вас будут сосуществовать два интерфейса. Те, кому понадобится новый рабочий процесс, будут использовать одностраничное приложение, а все остальные будут использовать старый пользовательский интерфейс. Только после того, как все функции будут перенесены в одностраничное приложение, вы сможете отключить старый интерфейс. Через некоторое время, когда все успокоится, его можно удалить.
Рефакторинг Backend
Заставить бэкэнд обслуживать два разных интерфейса делается следующим образом.
Шаг 1. Перенос поведения с контроллеров на сервисы
Как я уже упоминал, одностраничное приложение сначала собирается делегировать большую часть своей работы бэкэнду. В результате мы должны повторно использовать поведение, реализованное «устаревшими» контроллерами. Самый простой способ сделать это — извлечь это поведение из сервисных объектов.
class OrdersController < ApplicationController def create order_attrs = sanitize_attributes params[:order] order = Order.new(order_attrs.merge(user: current_user)) #... if order.save transaction = PaymentProcessor.create_payment order flash[:notice] = "Transaction Id: #{transaction}" redirect_to order_path(order) else @order = order render 'new' end end end
Извлечь все поведение в OrderService можно следующим образом.
class OrdersController < ApplicationController def create order_attrs = sanitize_attributes params[:order] OrderService.create self, current_user, order_attrs end def order_creation_succeeded order, transaction flash[:notice] = "Transaction Id: #{transaction}" redirect_to order_path(order) end def order_creation_failed order @order = order render 'new' end end module OrderService def self.create listener, user, order_attrs Order.new(order_attrs.merge(user: current_user)) if order.save transaction = PaymentProcessor.create_payment order listener.order_creation_succeeded order, transaction else listener.order_creation_failed order end end end
Как видите, у контроллера все еще есть некоторые обязанности. Во-первых, он должен подготовить данные. Во-вторых, он играет роль слушателя, который получает уведомление от вызываемой службы сценариев использования.
Шаг 2. Создание новых контроллеров
Теперь, имея OrderService, мы можем реализовать контроллер, который будет обслуживать наше одностраничное приложение.
#new controller class SPA::OrdersController < ApplicationController def create order_attrs = preprocess_attributs params[:order] OrderService.create self, current_user, order_attrs end def order_creation_succeeded order, transaction render status: 200, json: order, serializer: OrderSerializer end def order_creation_failed order render status: 422, json: order, serializer: OrderSerializer end end
Так как одностраничное приложение может потребовать небольшой настройки сервиса сценариев использования, шаги 1 и 2 часто выполняются одновременно.
Следующая диаграмма показывает отношения между контроллерами и сервисом.
Шаг 3. Переключение между старым и новым интерфейсом
В течение некоторого времени одностраничное приложение не будет иметь всех функциональных возможностей, реализованных старым интерфейсом. Поэтому нам нужно перенаправить клиентов на правильный интерфейс в зависимости от их рабочего процесса. Еще одна вещь, которая может оказаться весьма полезной, — это возможность полностью отключить одностраничное приложение на случай, если оно начнет плохо себя вести.
Завершение
Один из способов иметь дело с унаследованными приложениями Rails — это окружить их одностраничными приложениями, которые будут включать новые рабочие процессы и варианты использования. Я показал, как сделать это постепенно, не нарушая существующие рабочие процессы.
Я не говорил о вещах, связанных с JavaScript, из-за большого разнообразия платформ, которые затрудняют предоставление общих советов.
Учить больше
-
Подробнее о сервисах сценариев использования и пассивных контроллерах читайте в моей статье «Шестиугольная архитектура для разработчиков Rails».
-
Кент Бек рассказывает о параллельных реализациях в своей знаменитой презентации по адаптивному дизайну.