Статьи

Использование JavaScript для спасения устаревших Rails-приложений

Аннотация

Добавление новой функциональности в большое унаследованное Rails-приложение зачастую дорого, если это возможно. Но что, если вместо добавления нового поведения в приложение Rails мы реализовали его как одностраничное приложение в JavaScript? В этой статье я покажу несколько полезных приемов для этого.

Добавление поведения в устаревшие приложения Rails

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

изменения

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

Вместо этого создаем одностраничное приложение

Вместо того, чтобы вносить такие масштабные изменения в приложение Rails, где все взаимосвязано и границ нет, замените его часть одностраничным приложением. Сначала это одностраничное приложение делегирует большую часть своей работы бэкэнду. Это, однако, очень хорошее место для реализации новых рабочих процессов и вариантов использования. Таким образом, со временем, по мере появления новых требований, одностраничное приложение будет их включать, сводя к минимуму изменения в серверной части. Со временем вся логика координации будет перенесена в одностраничное приложение, а на сервер останутся поставщики услуг и репозитории.

Добавление одностраничных приложений

Зачем создавать одностраничное приложение, а не просто переписывать модуль в Ruby? Зачем выбирать JavaScript? Потому что это позволяет новые типы взаимодействия с пользователем. Это означает, что вы не просто переписываете часть своего приложения в целях удобства сопровождения, вы делаете это для создания некоторой ценности для бизнеса. В этом случае лучший пользовательский опыт. И вдобавок ко всему, вы получаете больше поддерживаемого кода. Сказав это, не все требует интерактивного пользовательского интерфейса. Очевидно, что перестройка таких модулей как одностраничных приложений не будет лучшей идеей.

Два фронтенда

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

2 фронтенда

Рефакторинг 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, из-за большого разнообразия платформ, которые затрудняют предоставление общих советов.

Учить больше