Приветствую, меня зовут Руслан, и я фаундер стартапа Глабикс. В этой статье я расскажу о нашем фреймворке Ree и его уникальной механике Link & Import для Ruby. Мы долгое время разрабатывали веб-приложения, используя подход Domain-Driven Design (DDD), что привело нас к созданию Ree — инструмента, который помогает выделять Bounded Contexts и описывать бизнес-правила через высокоуровневые функции.
В этой статье хочу познакомить сообщество с фреймворком Ree на примере используемой в нем link и import механики.
Link-механика в Ree
Многие из вас знакомы с Ruby через фреймворк Ruby on Rails. Одним из важных его компонентов является система автоматической загрузки модулей и классов ActiveSupport::Dependencies
, основанная на соглашениях об именах и путях файлов. По мере роста проекта появляется необходимость детального управления зависимостями и этого соглашения становится недостаточным. Мы столкнулись с этой проблемой восемь лет назад и начали разрабатывать различные решения, что в конечном итоге привело к созданию Ree.
В отличие от ActiveSupport::Dependencies
, Ree использует подход Tree Shaking. Это позволяет не только отказаться от инструментов вроде Zeitwerk, но и существенно ускорить запуск приложений и тестов благодаря гранулированной загрузке. В качестве примера рассмотрим класс CreateOrganization
.
class Accounting::CreateOrganization
include Ree::FnDSL
fn :create_organization do
link :deliver_welcome_email
link :grant_promo_access
link :organizations_dao, import: -> { Organization & OrganizationUser.as(OrgUser) }
link :persist_org
link :transaction
end
contract Integer, String => Organization
def call(access_user_id, name)
org = transaction do
org = Organization.build(name: name)
org.org_users.build(user_id: access_user_id)
persist_org(org)
end
deliver_welcome_email(org.id)
grant_promo_access(org.id)
org
end
end
В Ree любой класс может быть объявлен функцией через fn
. Композиция функций осуществляется посредством link-механики. Импорт констант из других функций, модулей или классов происходит через import
. Здесь отмечу, что обвязка link & import полностью автоматизирована lsp плагином ruby-lsp-ree и не требует дополнительных усилий от разработчиков.
Особенности Import-механики в Ree
Импорт констант осуществляется через лямбда-функцию, которая выполняется в контексте специального объекта Ree::ImportDsl
. Этот объект собирает информацию об импортируемых константах и добавляет их скоуп родительского класса. Таким образом, Ree не только композирует функции, но и лаконично управляет импортом констант.
Поддерживаются следующие возможности:
Импорт констант из других функций.
Импорт констант из модулей и классов, которые не являются функциями.
Импорт нескольких констант через оператор
&.
Импорт вложенных констант
Foo::BAR.
Aliasing импортируемых констант посредством
.as(ALIAS_NAME).
Совместно с линками импорты обеспечивают прозрачность и контроль над зависимостями.
Преимущества и недостатки
Преимущества:
Прозрачность кода: видно, из каких пакетов и модулей добавляются функции и константы.
Tree shaking. Загружаются только необходимые зависимости.
Лаконичность вызовов в методах: упрощение синтаксиса бизнес-операций.
Нет зависимости от расположения файлов, упрощение рефакторинга.
Недостатки:
Link & Import по своей сути являются обвязкой, увеличивающей количество строк кода.
Двойная нотация имен CreateOrganization и :create_organization
Небольшой оверхед на вызов линкуемых функций
Чтобы устранить время на работу с обвязкой, мы разработали плагин ruby-lsp-ree. Он полностью забирает на себя управление обвязкой: автоматическое добавление и удаление линков и импортов.
Заключение
В будущих статьях я планирую подробнее рассказать о самом фреймворке Ree и его расширениях, а также показать примеры использования на реальных проектах с DDD подходом. Все, кто заинтересовался и хочет поучаствовать в развитии Ree, пишите мне в личку.