В современной индустрии разработки программного обеспечения модульность становится краеугольным камнем архитектурных решений, улучшая управляемость, удобство в поддержке и масштабируемость продуктов. Компания Gusto, специализирующаяся на предоставлении облачных сервисов для управления заработной платой, льготами и HR-процессами, активно развивает и совершенствует свои подходы к модульности с помощью специализированных инструментов и методологий. В 2024 году Gusto продвигается значительными шагами вперёд в этой области, применяя инновации, ориентированные на конкретные бизнес-задачи и особенности своей инфраструктуры. Обширное понимание внутренней организации кода и взаимодействия между его частями является центральной темой в развитии эффективных инструментов, обеспечивающих модульность. Путь компании к реализации модульности начался в 2020 году с принятия open-source решения Packwerk, разработанного Shopify для организации пакетов и разграничения зависимостей между ними.
Первоначально в Gusto было создано почти двести пакетов, позволяющих разнести большой монотонный кодовый массив на более мелкие, логически обособленные части. Это дало возможность визуально и структурно отделить разные домены и области функциональности продукта. Однако с ростом числа пакетов до четырёхсот возникла новая проблема — слишком большое разнообразие и количество пакетов усложняло понимание общей структуры проекта, порождало путаницу и увеличивало барьеры для новых и существующих инженеров. Осознав это, команда Gusto перешла к фазе усовершенствования модульной структуры, пытаясь найти баланс между удобством понимания системы и необходимостью продолжать масштабирование. Ключевым моментом стало понимание и разграничение понятий «приложения» и «пакеты».
Было выявлено, что несмотря на сотни пакетов, внутри проекта можно выделить всего около двадцати приложений, которые можно отождествить с бизнес-продуктами и пользовательскими функциями. Это существенно упрощает картину целостной системы и облегчает навигацию по её компонентам. Но объединить сотни пакетов в двадцать приложений напрямую оказалось невозможным — инженеры ценили возможность гибкого структурирования внутри своих областей ответственности и не хотели отказываться от этого уровня детализации. Для решения возникшей дилеммы была разработана комплексная стратегия, включающая разделение кода на слои, создание понятия продуктовых сервисов и внедрение вложенных пакетов внутри них. Появление концепции слоёв решило вопрос взаимодействия групп пакетов, несущих разные функции.
Были выделены четыре основных слоя, включая корневое приложение (Rails harness), вспомогательные инструменты для разработки, базовые классы Rails и утилиты, предоставляющие общие функции всем сервисам. Взаимодействие между этими слоями регулируется направлением зависимости — функциональные нижние слои не должны знать о верхних, что обеспечивает чистоту архитектуры, упрощает сопровождение и повышает устойчивость к изменениям. Внутренний слой продуктовых сервисов, содержащий около 80% всего кода и пакетов, стал местом вложенной модульности. Теперь команды могут структурировать внутреннюю архитектуру каждого продуктового сервиса, создавая пакеты-вложения, которые отражают конкретные бизнес-домены или функциональные подсистемы. Такая вложенность обеспечивает двойной уровень модульности: снаружи видно приложение как цельный сервис, а внутри — гибко управляемые независимые компоненты.
Наряду с перестройкой структуры возник вопрос о том, каким должен быть интерфейс API в рамках такой системы. В отличие от библиотек, где API могут быть относительно свободны, приложения (продуктовые сервисы) предъявляют более жёсткие требования к разграничению и безопасности. В Gusto приняли твёрдую позицию отделения внешнего API продуктового сервиса от внутренних интерфейсов пакетов. Для этого введён специальный пакет «_api», служащий единой точкой доступа к функционалу сервиса извне. В то же время остальные вложенные пакеты остаются внутренними и управляются с помощью механизмов приватности, что позволяет минимизировать утечки и ошибочный доступ извне.
Данная моделировка API имеет непосредственные последствия для архитектурных решений и производительности приложения. Одним из распространённых в сообществе заблуждений является правило «избегать ActiveRecord на границах модулей», которое подходит для разделенных приложений, но не всегда эффективно внутри одного продукта. В случае с Gusto, при построении сложных GraphQL запросов внутри сервиса, использование возможностей ActiveRecord непосредственно внутри пакетов значительно упрощает и оптимизирует обработку данных, исключая лишние уровни абстракции и затраты на преобразования модели. Технологический стек также подвергся значительным изменениям. Важным шагом стало решение отказаться от оригинального Packwerk в пользу собственного инструмента под названием pks, реализованного на Rust.
Этот инструмент предлагает значительно более высокую скорость парсинга и анализ расширенного спектра констант, что делает отчёты о нарушениях и зависимости более полными и точными. Помимо этого, pks поддерживает гибкие настройки игнорирования правил, что даёт командам больше свободы в проведении постепенной модульной реорганизации без лишнего шума в результатах. В итоге, современное состояние модульных инструментов Gusto — это продуманная многослойная система, ориентированная на бизнес-потребности и технические реалии. В основе лежит идея баланса между разбиением кода для удобства разработки и поддержания, и необходимостью сохранять управляемую структуру, понятную на уровне всего приложения. Благодаря вложенной модульности и строгому разграничению API становится возможным развивать продуктовые сервисы как независимые, самодостаточные единицы, что открывает перспективы для дальнейшего масштабирования и интеграции новых возможностей.