Upload
-
View
187
Download
0
Embed Size (px)
DESCRIPTION
Проблемы, которые возникают в Rails приложении при создании корпоративного приложения.
Citation preview
Корпоративное приложение на Rails. Отчет после года разработки
Андрей Колешко @ka8725
Что представляет из себя наше приложение
• Полно бизнес логики
• Работа с деньгами клиентов
• Общение со сторонними системами
• Очереди обработки данных (sidekiq)
• Асинхронность
• Поддержка плагинов через Rails engines
• Звездолет
Приблизительное состояние кода в проекте
Быть или не быть?
• Посмотрим с какими проблемами мы столкнулись и как с ними боролись
• Какие проблемы не решены
• Попробуем сделать выводы, можно ли использовать Rails в коммерческих приложениях
Модульность
• Ruby modules
• Rails engines
Ruby modules• Многие гемы просто не готовы к тому, что мы будем использовать Ruby модули
• DataGrid - не было возможности изменить шаблоны для client и admin области
• Draper - для работы декоратора в пространстве имен приходится писать магические строчки
• InheritedResources - поиск ресурса (AR модельки) в модуле не был реализован
• Rails - polymorphic routes генерировали неправильные helper-методы
Rails Engines• Нельзя использовать разные версии gems в ядре и плагине
• В Ruby нет интерфейсов. Нельзя заставить плагины реализовать все методы, которые нам нужны
• Тестирование Rails Engines в контексте ядра - болезненный процесс
• rake railties:install:migrations постоянно генерирует новые файлы
• Установленный Rails Engine может поломать все приложение. У него есть доступ ко всему
Имеем дело со всеми известными проблемами модульного приложения
Как мы преодолеваем проблемы сейчас
• Pull request’ы в гемы, которые не готовы к использованию Ruby модулей
• Для плагинов предоставляем миксины
• Общение с плагинами стараемся выстраивать через самописный Pub-Sub, основанный на ActiveSupport::Notifications-
• Тестируются плагины вручную-
• Собственный регистратор плагинов
Пример Pub-Sub# Core Publisher.broadcast_event('user.created', {user_id: 1}) !# Plugin Subscriber.subscribe('user.created') do |event| payload = event.payload puts payload[:user_id] end
http://goo.gl/QJxujy
Особенности нашего Pub-Sub
• Однопоточный
• Нет обратной связи. Сообщения однонаправленные
Собственный регистратор плагинов
• Дает простую установку копированием плагина в нужную папку
• Контролируемый процесс подключения плагина к системе
• Плагин - Rails engine
Преимущества Rails engines как модулей
• В плагинах можно изменить все. View, Routes, Controllers, Models, Helpers…
• Быстро работает (нет сторонних вызовов)
ActiveRecord• Избегаем nested_attributes
• Используем FormObjects, ServiceObjects, QueryObjects, PolicyObjects
• В моделе остается только отображение данных и самые необходимые валидации, ассоциации
• Избегаем Observers и Callbacks
Исключение для использования callbacks
• Отправка сообщения в очередь для фоновой обработки
• Не изменяет состояние объектов
Observers
• Правила использования не отличаются от callbacks
• Ведут к более запутанному коду чем callbacks
• Лучше никогда не использовать
Завязались окончательно?
Возможноcть развязать узлы в тестах
!
• Отключаем все колбэки в моделях
• Включаем колбэки в тестах в тех местах, где их вызов необходим
• Не используйте этот подход в новых приложениях!
Возможность оставить тесты рабочими
class ActiveRecord::Base cattr_accessor :skip_callbacks-end !class User < ActiveRecord::Base after_create :send_invitation, unless: :skip_callbacks def send_invitation puts 'hello' end end !ActiveRecord::Base.skip_callbacks = true User.create # => ActiveRecord::Base.skip_callbacks = false User.create # => 'hello'
Слой View• Проблема с длинными именами helper-методов
• Использование instance переменных (@var) во view
• Рендеринг таблиц
• fields_for и nested_attributes
• Фарш AngularJS
Длинные имена helper-методов
plugin_exchange_client_account_application_contact_contact_distribution_group_path(parent.account, parent.core_application, parent, resource) !
Всего 141 символ!
Решение# Controllers class SomeController < ApplicationController helper_method :submit_path helper_method :cancel_path ! private ! def submit_path any_size_of_helper_method_you_want_submit_path( arg1, arg2, … ) end ! def cancel_path any_size_of_helper_method_you_want_cancel_path( arg1, arg2, … ) end !end
Решение
# Views != form_tag submit_path do = link_to 'Cancel', cancel_path
Преимущества решения
• “Чистый”, читабельный и более надежный код во view
• HAML вам сважет спасибо !
PS. Не используйте HAML! Есть более удачный шаблонизатор - SLIM.
Helper-методы вместо @var
• Ошибка рендеринга более адекватная (undefined method my_helper_method вместо undefined_method “…” for nil class)
Рендеринг таблиц• DataGrid-
• Сортировка и фильтрация из коробки
• Беспроблемная интеграция с пагинаторами и полнотектовыми движками (через свои scopes)
• Возможность изменять шаблоны таблицы
• view занимают всего 3 (!!!) строчки кода
• и другое - https://github.com/bogdan/datagrid
fields_for и nested_attributes
• В FormObjects сложно обрабатывать магические хеши nested_attributes
• В большинстве случаев необходимо использовать свой primary key для поиска записи на обновление в базе. А не id, который зашит в rails хардкором
• Огромные имена переменных ассоциаций
fields_for и nested_attributes
- form_object.locations.each do |location|
= f.fields_for 'locations[]', location, include_id: false, index: nil do |ff|
# include_id: false - предотвращает генерацию hidden field c id, т.к. нас это не устраивает
# index: nil - не включать index в название сгенерированного аттрибута
Преимущества данного подхода
• Имена параметров чистые и понятные
• Возможность передавать на сервер свой primary ключ для поиска модели на обновление
• Проще обработка в FormObjects
Фарш AngularJS• Т.к. состояние объектов хранится на сервере необходимо вручную вызывать ng-init
• В каждый input необходимо передать ng-model атрибут
• Если в ng-init забыт атрибут, который связан с ng-model, то получим некорректное отображение объекта на форме
• Что делать при успешном сохранении формы? Reload страницы? - Теряем flash сообщения
Разработка на Rails быстрая?
• Только для небольших приложений
• Огромное количество кода препятствует быстрой разработке
Rails way Enterprise way
Выводы• Rails отлично подходит для создания прототипов-
• К модульности и огромным приложениям Rails не готов
• В сложных приложениях приходится отказываться от многих магических палочек Rails-
• Это ведет к увелечению собственного кода
• Код требует ухода. Знание шаблонов проектирования, принципов проектирования классов (SOLID) здесь просто необходимо
Что в Ruby хорошо?
• Писать DSL
• Сам язык очень выразительный
• Код приятно писать и читать
• И на этом все :(
Решились писать Enterprise на Rails?
• Нужен ли Вам Rails? Может, достаточно взять Sinatra + Grape, если у вас будет SPA (Single Page Application)?
• Тщательно обрабатывайте требования. Выясняйте Use Cases-
• На основании UseCases стройте доменную модель (набор классов и их взаимодействия).
• Не привязывайтесь к ActiveRecord. ActiveRecord - только для представления данных!
• Тщательно выбирайте гемы. Подумайте 100500 раз прежде чем выбрать InheritedResources, например.
• Следуйте принципам SOLID, не нарушайте закон Деметры
Литература1. http://goo.gl/GyqjXg - 7 steps to get started with Clean
Architecture in Ruby
2. http://goo.gl/g0VA1 - 7 Patterns to Refactor Fat ActiveRecord Models
3. http://goo.gl/lz1fEX - Ruby Midwest 2011 - Keynote: Architecture the Lost Years by Robert Martin
4. http://goo.gl/MP6L7Z - High-Low Testing
5. http://goo.gl/aI3kdc - Growing Rails Applications in Practice
6. http://goo.gl/9KsmM - Принципы проектирования классов (S.O.L.I.D.)
На правах рекламы
!
Основы Rake
Простые примеры применения в реальном мире
Сравнение с конкурентом - Thor