В последние годы компания Gusto, известная своими решениями в сфере бухгалтерии, льгот и управления персоналом, сделала значительный шаг вперед в совершенствовании архитектуры своего программного обеспечения через развитие методов модульности и инструментов для их поддержки. В 2024 году текущий статус этих инструментов отражает накопленный опыт, уникальные подходы к структурированию кода и решения реальных проблем, которые возникают при масштабировании сложных сервисов. В этой статье подробно рассматривается эволюция модульности в Gusto, особенности их инструментальной базы в настоящее время, а также ключевые вызовы и достижения, которые задают вектор дальнейшего развития. Начало пути Gusto с модульностью связано с внедрением инструмента packwerk, появившимся на рынке осенью 2020 года. Уже спустя несколько недель после его релиза команда инженеров Gusto начала создавать первые пакеты — минимальные единицы модульной структуры — ориентируясь на более чистую и удобную архитектуру кода.
Интенсивная работа позволила в первые полгода перенести подавляющую часть крупного кодового массива компании в пакеты, количество которых колебалось примерно около двухсот. Это был лишь первый этап, когда модульность воспринималась скорее как новая форма организации, которая потенциально должна облегчить понимание и поддержку продукта. Следующий этап, называемый расширением, ознаменовался увеличением числа пакетов до более чем четырехсот за счет вовлечения различных инженерных команд и областей ответственности. Несмотря на значимость этого шага, сами пакеты содержали нередко нарушения границ, и их структура оставалась далекой от идеала. Тем не менее, наличие большого числа пакетов способствовало визуальному разграничению доменов, что помогало коллективам более наглядно понимать связи и зону ответственности в системе.
Такой эффект послужил базой для дальнейшей рефакторинговой работы. Нынешний, третий этап — это этап уточнения и совершенствования. Повсеместный рост числа пакетов внёс свои сложности: большое количество небольших, зачастую неоптимально связанных частей затрудняет ориентацию в системе и порой создает ощущение разбросанности архитектуры. Gusto столкнулась с дилеммой: с одной стороны, развитие пакетов как инструментов поддержки доменов важно для удержания порядка внутри команд, с другой — необходимо улучшить восприятие общей структуры кода. Эти противоречия подвигли команду к формулировке принципиального вопроса — «Что мы действительно считаем приложением?» Именно это дало толчок к пересмотру ключевых архитектурных решений.
Проведя внутренние обсуждения, Gusto выделила около двадцати основных приложений, существующих в их кодовой базе. Такое резкое упрощение — от четырёхсот до двадцати «приложений» — значительно повысило прозрачность системной архитектуры, сделав связь между технической структурой и пользовательскими продуктами и функциями более очевидной. Однако прямое сокращение количества пакетов было невозможно, так как инженерные команды вполне ценили возможность детальной проработки внутри каждого домена. Поэтому было принято оформить несколько новых инструментальных решений: слои, продукты-сервисы и вложенные пакеты. Слои, введённые в структуру, помогают разделить ответственность и направленность компонентов.
На верхнем уровне находится application harness — корневая точка Rails-приложения, обеспечивающая конфигурацию и инициализацию. Ниже размещаются слои с инструментами разработки, базовыми классами Rails и набором утилит, которые используются во многих сервисах. Такой слойный подход основан на принципе односторонней зависимости: нижележащие уровни не должны знать о верхних, что минимизирует связи и упрощает поддержку. Физическая организация кода адаптировалась под эти концепции: оказались выделены каталоги для каждого слоя с соответствующим размещением пакетов. Особенно важным является средний слой — product_services — в который вошло почти 80% кода и пакетов.
Здесь же была введена вложенность пакетов, позволяющая каждой продуктовой команде моделировать иерархию внутри своей зоны ответственности, сохраняя при этом внешнюю четкость структуры. Однако вложенность породила новый вопрос: как определить, что считать API, особенно в условиях, когда внутри product_services размещено множество пакетов? По мнению Gusto, продуктовые сервисы — аналог приложений, поэтому у их API существуют строгие ограничения. Внутренние пакеты при этом ведут себя как библиотеки, и для них нет универсальных правил ограничения API. Таким образом, возникла потребность в дифференциации и разграничении интерфейсов. Важным разработанным решением стала концепция выделения отдельного пакета _api в рамках каждого product_service.
Этот пакет содержит внешний интерфейс, через который взаимодействуют другие сервисы или компоненты системы. Внутренние пакеты имеют собственные механизмы приватности и зависимости, которые регулируются отдельными настройками и инструментами проверки, но их область действия ограничена рамками самого product_service. Такой подход снижает шум от потенциальных нарушений и позволяет каждой команде гибко настраивать структуру под свои нужды. Отдельно команда Gusto отмечает значимость переосмысления традиционных рекомендаций по границам API. Например, правило «никакого ActiveRecord на границе» может быть оправдано применительно к межприложенческому взаимодействию, однако внутри одного product_service такое ограничение ведет к излишним накладным расходам и снижению производительности из-за необходимости преобразовывать данные и выстраивать дополнительные уровни абстракции.
Возможность внутреннего непосредственного взаимодействия с моделями ActiveRecord улучшает эффективность и упрощает реализацию сложных функций, например, резолверов GraphQL. Текущий набор инструментов в Gusto предусматривает использование новых расширений для packwerk, а также переход на pks — более производительный и функционально продвинутый инструмент, написанный на языке Rust. Этот шаг обусловлен необходимостью ускорения анализа и проверки кода, а также расширения возможностей инструментов по обнаружению и контролю зависимостей. В отличие от packwerk, pks способен работать не только с загруженными Zeitwerk константами, но и напрямую анализировать все Ruby файлы, что существенно повышает качество обратной связи инженерным командам. С точки зрения бизнес-целей, развитие модульности в Gusto является не только техническим упражнением, но и фундаментальным элементом для поддержки масштабирования и внедрения новых продуктов и функций.
Улучшение модульной структуры кода обеспечивает более быструю адаптацию к изменяющимся требованиям рынка, позволяет создавать узнаваемые и понятные клиентские продукты, а также облегчает интеграцию и расширение экосистемы решений Gusto. Кроме того, активное обсуждение и обмен опытом вокруг модульности ведется в профессиональных сообществах, в частности на Slack-канале Ruby/Rails Modularity, где инженеры Gusto делятся практиками и получают обратную связь от коллег по индустрии. Такой обмен способствует коллективному развитию и улучшению подходов к архитектуре Ruby и Rails приложений вне зависимости от масштаба компании. Подводя итог, сегодня Gusto достигла значимых успехов в организации модульного кода, внедрив концепцию слоёв и продуктов-сервисов, реализовав инструментальную дифференциацию API и улучшая эффективность работы с кодом с помощью современных инструментов анализа. Вызовы, с которыми столкнулась компания, хорошо иллюстрируют сложность масштабирования крупной кодовой базы и необходимость балансировать между гибкостью, понятностью и бизнес-ориентированностью архитектуры.
В перспективе ожидается дальнейшее развитие этих идей, направленное на создание прочных внутренних API и ускоренное внедрение новых возможностей, что подчеркнет лидерство Gusto в области разработки надежных и удобных программных решений для бизнеса.