Бизнес логика в Rails приложениях
Для многих не секрет, что Ruby on Rails - это фрэймворк, основанный на паттерне Model-View-Controller (MVC
), где
- M (Model) - представляет собой данные, их зависимости и бизнес логику
- V (View) - отвечает за предоставление информации
- C (Controller) - получает на вход данные и преобразует их для дальнейшего использования в
Model
илиView
.
И не смотря на то, чтоRails
позволяет решать огромное количество задач, данный паттерн становится нежизнеспособным в больших проектах, особенно в проектах с быстро меняющейся бизнес логикой. Решением данной проблемы может стать внедрение еще одного слоя, который вынесет бизнес логику из моделей.
Бизнес логика
В нашей команде для выделения слоя бизнес логики мы начали использовать гем active_interaction. По сути он стал для нас обработчиком форм. Гем позволяет объявлять поля с необходимым типом данных, с возможностью задавать значения по-умолчанию, валидировать эти поля, используя знакомый всем механизм валидации изActive Model
. Обработка данных формы происходит в методеexecute
, который в лучших традицияхUnix
, должен выполнять одну единственную задачу и делать это хорошо.
Поработав какое-то время с этим гемом решили поделиться своим опытом с другими разработчиками.
Плюсы
Начнем с общего перечисления положительных сторон:
- код стал более красивым и читаемым
- модели и контроллеры стали "тоньше" за счет удаления из них бизнес логики
- работа с
View
не изменилась, так как интеракторы хорошо мимикрируют под модели и рельсовые хелперы с ними прекрасно работают - вынос большинства валидаций, в том числе собственных, позволило более гибко менять бизнес логику
- сама бизнес логика стала легче тестироваться, так как теперь необходимо тестировать один интерактор, а не подстраивать условия для модели для выполнения данной логики
Отдельно я бы хотел остановиться на валидации. В реальном приложении часто случается так что разные пользователи (админ или гражданин) имеют разное поведение при реализации CRUD
для одной и той же сущности. Например администратор может устанавливать параметры учетных записей пользователя "на прямую", а гражданину может потребоваться специальный бизнес процесс для наполнения своего профиля. В этом случае очень неудобно держать валидации в модели - они обрастают условными операторами и сильно усложняют понимание. В случае с интеракторами такого рода логика будет реализована в разных классах: для администраторов и для граждан, что очень сильно облегчает тестирование.
Минусы
- количество файлов резко увеличилось, так как для каждого действия
CRUD
нужно писать свой интерактор. И возможно еще под каждую роль пользователя тоже. - разделение объявление полей и валидации (сначала идет проверка на наличие данных необходимого типа, потом
Active Model
валидации) приводит к поэтапным валидациям, что не очень нравится бизнесу
Однако эти минусы неприятны только дляMVP
разработки, когда надо очень быстро выкатить рабочее решение. А вот уже начиная со среднесрочной перспективы много файлов уже можно считать плюсом, поскольку объем логики не меняется и в противном случае будет сконцентрирован в одном огромном файле.
Как это выглядит
Мы группируем интеракторы по предметной области и часто выделяем в ней базовые сущности:
Это не обязательный подход, но часто это позволяет реализоватьDRY
и в результате получать весьма простые сущности:
Так выглядит сильно "похудевшая" модель, которая теперь отвечает только за целостность структуры данных:
Завершает цепочку примеров контроллер:
Заключение
Вынос бизнес логики из моделей оказалось верным решением, а гем active_interaction очень в этом помог.
Построенный на основе ActiveModel
, ActiveInteraction
превосходно подходит для выноса бизнес-логики из моделей, поскольку:
- Обеспечивает бесшовное использование привычных инструментов
ActiveModel
(::Dirty
,::Validations
,::Callbacks
и пр), а также совместимость с другими компонентами и инструментамиRails
и пр. - Повышает гибкость и эффективность использования этих инструментов за счёт обеспечиваемой
ActiveInteraction
типизации интерфейсов, возможности каскадного использования сервисных объектов (compose
) и пр.