В 2024 году компания Gusto, известная своими инновационными решениями в области управления зарплатами, льготами и кадровыми процессами, стоит на переднем крае модульности и реорганизации программных продуктов. Специалисты компании активно развивают инструменты для более качественной и структурированной работы с кодовой базой, что позволяет не только повысить качество архитектуры, но и улучшить производительность команды и скорость разработки новых функций. История развития модульности и нынешний подход к организации кода в Gusto показывают, как можно адаптировать современные практики под сложные бизнес-задачи и неоднородные проекты на Ruby on Rails. Модульность в программировании — один из ключевых факторов, который позволяет крупным программным системам оставаться гибкими, понятными и масштабируемыми. Для Gusto, чья платформа охватывает множество продуктов и функций, включая расчет заработных плат, управление льготами и HR-процессы, создание ясной и управляемой структуры проекта имеет принципиальное значение.
За последние несколько лет компания прошла три основные этапа в адаптации инструментария модульности, начиная с первых попыток разделения проекта на пакеты, вплоть до глубокой реструктуризации и внедрения слоев с четкой иерархией. Первая фаза, известная как этап принятия, началась вскоре после появления инструмента packwerk, разработанного Shopify. Gusto быстро внедрил упаковки, позволяющие разбить большой монолитный код на отдельные модули. За несколько месяцев было создано чуть меньше 200 пакетов, что позволило отделить ответственные за разные области функционала части системы. Несмотря на относительную незрелость и многочисленные нарушения правил модульности, нововведение продемонстрировало ценность визуального разделения доменов и подготовило почву для дальнейшей работы.
Следующий этап можно назвать фазой расширения. Количество пакетов выросло в два раза — до более чем четырёх сотен, что стало свидетельством активной вовлечённости инженерных команд в процесс модульности. Помимо количественного роста, появилось понимание необходимости улучшать качество, увеличивать соблюдение границ между модулями и выстраивать логику взаимодействия. Однако с увеличением количества пакетов возникла новая проблема: сложность понимания структуры кода. Многие инженеры отмечали, что из-за обилия мелких компонентов стало трудно получить целостную картину продукта и разобраться, как различные области взаимосвязаны.
Это привело к началу текущего этапа — фазы уточнения. Главной задачей стало разрешение конфликта между желанием развивать внутреннюю структуру, сохраняя детализацию, и стремлением к более простому и интуитивно понятному отображению архитектуры. В качестве решения была поставлена цель сократить количество верхнеуровневых приложений с около четырёх сотен до примерно двадцати. Такой масштаб уже значительно упрощал бы понимание продукта и соотносился с ключевыми бизнес-доменами и видимыми клиентам функциями. Резко сокращать количество пакетов не представлялось возможным, так как команды ценили свои внутренние структуры и не хотели терять гибкость.
Поэтому была разработана концепция с использованием слоев, «продуктовых сервисов» и вложенных пакетов. Эта идея значительно повысила систематизацию. В основе систематизации лежит выделение нескольких слоев, которые взаимодействуют по определённому порядку. Слой, играющий роль приложения, расположен в центре и называется «продуктовым сервисом». Вокруг него располагаются другие слои, такие как «приложенческий каркас» — корневое приложение Rails, инструменты для разработки, с помощью которых настраиваются среды и данные вне продакшна, базовые классы Rails и наборы утилит, общих для большинства сервисов.
Каждый нижележащий слой не зависит от вышележащего. Такая иерархия позволяет поддерживать жесткие границы между уровнями и предотвращать признаки тесной связанности, негативно влияющей на масштабируемость и сопровождение. Физически это было реализовано перемещением пакетов в соответствующие папки для каждого слоя, а также использованием расширения к packwerk — layer protection, которое заставляло инструменты автоматически отслеживать направление зависимостей. В результате основная часть кода (примерно 80%) и пакетов оказалась в слое продуктовых сервисов. Для удобства дальнейшей работы продуктовые сервисы стали отдельными вложенными папками в этом слое, где размещаются сами приложения и их внутренние подразделения.
Важно отметить, что хотя продуктовые сервисы играют роль приложений, пакеты внутри них больше ведут себя как библиотеки. Это вынудило задуматься о том, что же считать выдержанной границей API. Разграничение между API приложений и внутренними API пакетов внутри приложения имеет принципиальные отличия. Внешний API приложения должен быть более строго ограничен и четко документирован, а внутренние взаимодействия между пакетами — более открыты и гибки, чтобы обеспечить эффективность работы внутри единой применении без лишних накладных расходов. Одно из распространенных правил касалось запрета использования ActiveRecord на границах между модулями, что для внешних API приложений логично и оправдано.
Однако внутри продуктового сервиса это правило часто существенно снижает производительность и вызывает избыточную сложность. В случае сложных операций, например, при формировании данных для GraphQL резолверов, прямое взаимодействие с моделями ActiveRecord внутри пакетов обеспечивает гораздо более эффективную и оптимальную работу. Если следовать правилу «без ActiveRecord на границе» жестко, приходится создавать дополнительные слои, репозитории и трансформации, что негативно сказывается на производительности. На сегодня в Gusto для разрешения этой проблемы создается специальный пакет с суффиксом _api, который фиксирует внешний интерфейс продуктового сервиса и содержит все публичные API, предназначенные для взаимодействия с другими сервисами и пакетами. Остальные пакеты внутри сервисов остаются закрытыми и могут свободно использовать внутренние средства и границы API на свое усмотрение.
Правила доступа и ограничения приватности реализуются при помощи инструментов типа folder visibility packwerk-extension, позволяющих контролировать и отделять внутренние API от публичных. Это обеспечивает четкий механизм разграничения зон ответственности и предотвращает случайные нарушения границ модульности. Помимо архитектурных новаций, важно отметить эволюцию инструментов, обеспечивающих эти процессы. Первоначально Gusto активно использовал packwerk, предложенный Shopify, благодаря удобству и возможностям контроля зависимостей и приватности модулей. Однако со временем стало понятно, что packwerk ограничен по скорости и функционалу, особенно в крупных и сложных проектах.
В 2024 году Gusto полностью перешёл на pks — утилиту, написанную на Rust, которая сохраняет совместимость с ключевыми функциями packwerk, но значительно превосходит его по производительности и глубине анализа кода. Pks позволяет более полно анализировать Ruby-код и выявлять зависимости по всем константам, даже тем, которые не загружаются через привычные механизмы, что расширяет возможности контроля и рефакторинга. Таким образом, инструментарий компании стоит на прочной технологической основе, обеспечивающей быстрые и глубокие обратные связи для разработчиков. Важным аспектом является и культура, связанная с модульностью, которую строит команда Gusto. Архитектура и инструменты — лишь часть успеха, вокруг них формируется мышление инженеров, ориентированное на создание чистых границ, разумных API, удобного разделения ответственности и ответственности команд за свои зоны.
Фокус смещается с хаотичного создания десятков и сотен мелких пакетов к целевому созданию понятных сервисов, отражающих бизнес-направления и продукты. В конечном итоге это увеличивает скорость разработки процессов, уменьшает количество ошибок и помогает легче вводить новые функции и интеграции. Помимо внутренней пользы, Gusto активно участвует в обсуждениях и развитии сообщества Ruby и Rails, делясь своими наработками и принимая участие в Slack-каналах, посвящённых модульности. Создание открытых и доступных публикаций и проведение исследований помогает распространять передовой опыт массово, повышая качество сетевого сообщества профессионалов. В целом, взглянув на процесс становления и современные достижения Gusto в области модульности, можно выделить важные выводы для всех, кто работает с крупными Ruby on Rails приложениями.