Приветствую, меня зовут Руслан, и я фаундер стартапа Глабикс. В этой статье я расскажу о нашем фреймворке 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, пишите мне в личку.

Комментарии (0)