Сегодня нет недостатка в статьях о дебатах Монорепо и Мульти-репо, в то время как инструменты для монорепозитория на основе javascript стали немного более зрелыми, и этот вариант стал более осуществимым. Наша команда считает, что monorepo подходит, и мы изучили Yarn workspsace, что отлично - , если вы сможете заставить его работать.
Здесь мы хотели бы поделиться опытом нашей команды, проблемами, с которыми мы столкнулись.
- Контекст
- Проблемы I = архитектурные мысли, структура папок
- Проблемы II = больше тактической стороны, настройки базы / шуток / потока и т. Д.
Контекст
Невозможно обсуждать какую-либо архитектуру программного обеспечения без контекста.
Мы создаем чат-бота для японского коучинга - как MVP стартапа на ранней стадии. После создания прототипов для изучения как бизнес-задач, так и технических проблем, теперь с более твердым видением продукта и растущей командой инженеров, приходит время выбирать лучшие инструменты и структуру.
Технически мы были монорепозиторием с первого дня, но теперь мы разбиваем и переходим на небольшие, разделяемые модули, настраиваемые в рабочем пространстве Yarn - это далеко не идеально, но архитектура программного обеспечения в любом случае представляет собой повторяющийся процесс (я уже писал о переносе устаревшей кодовой базы на микросервисы ранее) . На этот раз цель состоит в том, чтобы за короткое время выполнить загрузку, сделать модули более разделенными, чтобы мы могли быстрее перебирать кодовую базу и инструменты каждого из них.
Что есть в наших системах:
- PWA на основе React, с mobx | основное приложение, веб-просмотры чат-бота
- React, статический сайт на основе Next.js | Блог, лендинги и другие SEO-страницы
- Бессерверный (функции Firebase) | Для веб-перехватчиков чат-ботов и конвейеров данных
- Чат-боты на основе узлов | диалог, facebook sdk и т. д.
- Узловые модули | адаптеры и процессоры конвейеров данных
- файл скриптов и локалей i18n
- (Мобильный модуль на базе React-Native в ближайшем будущем)
Наши инструменты
- Пряжа, jest, flow, eslint, babel7, сборник рассказов
- Лерна | На самом деле не обязательно, но введено из-за проблемы (см. Ниже)
- Firebase-tools | Инструменты командной строки для доступа (развертывания) к экосистемам Firebase.
- CircleCI, Cypress (WIP)
Проблемы и наш взгляд
Раздел I. Задачи высокого уровня
Задача: модульное построение без накладных расходов
Js-люди знают о достоинствах небольших, разделенных модулей, и наверняка об аду зависимостей и связанных с ними проблемах, связанных с node_modules.
корень проблем №1: поддерживать зависимости node_modules сложно
Когда вы фактически разбиваете свое приложение на модули с индивидуальной версией (package.json) и кодовой базой (git), проблемы часто умножаются и накладные расходы становятся существенными, особенно когда вы быстро двигаетесь и неизбежно вносите изменения в модули одновременно, или ваш код смешанный. транспиляционные требования.
Подход с использованием подмодулей git - одно из решений, но, вероятно, недостаточно простое или удобное для рабочего процесса, чтобы быть популярным среди разработчиков. »
Некоторые команды, с которыми я работал, просят разработчиков чаще публиковать публикации в частном npm, но не только медленнее работает npm, но и часто бывает трудно получить четкую версию выпуска и управление зависимостями для разных функций с помощью разных участников, в разных репозиториях это боль.
Yarn workspace и monorepo прекрасно решают вышеуказанные проблемы - ваши зависимости модулей управляются и связываются. Несомненно, еще одним большим преимуществом является совместное использование кода, и рекомендуется проводить рефакторинг, когда они становятся более заметными с помощью простого git diff.
Задача: правильная структура папок
Вероятно, наиболее полезно показать, что в итоге получится:
wordquest - docs - stories - wq-chat - wq-firebase-functions -- endpoints/ --- https/ --- pubsub/ - wq-lib-iso --- app/ --- domain/ - wq-lib-node - wq-locales - wq-ui-components --- article/ --- text/ - wq-ui-main -- src/ --- stores/ --- routes/ - wq-ui-site - .eslintrc-template.js
Структура папок важна по 2 причинам: 1) это замаскированная ваша архитектура, 2) к сожалению, большинство инструментов javascript ломаются при изменении пути к папке.
- Архитектурная часть
Большинство фреймворков имеют какое-то соглашение о структуре папок, и они существуют по какой-то причине, в то время как именно ваша архитектура формирует вашу структуру между модулями и различными фреймворками . (Не наоборот.)
Архитектуры не связаны (или не должны) быть связаны с фреймворками. Архитектуры не должны дополняться фреймворками. Фреймворки - это инструменты, которые нужно использовать, а не архитектуры, которым нужно соответствовать. Если ваша архитектура основана на фреймворках, то она не может быть основана на ваших сценариях использования.
- Screaming Architecture
Я не смогу закончить этот пост, если мне нужно будет вдаваться в подробности, но DDD, Hexagonal & CQRS являются нашими руководящими принципами. Для нас практическая установка находится где-то посередине между этими принципами и настройками фреймворка по умолчанию.
В результате для большинства модулей мы пытаемся разделить app/
, domain/
& adapters/ (infra).
Exception - это приложение response ui и веб-перехватчики firebase (организованные по конечным точкам), где мы рассматриваем оба модуля как само «приложение», и извлекается большая часть основной логики домена. в другие модули (wq-lib-sio
, как чистые функции, не зависящие от фреймворка).
В приложении мы предпочитаем группировать компоненты по функциям (вместо controllers/
или containers/
и т. Д.). Вы могли заметить stores/
для mobx в приложении React, где мы считаем разделение состояния и пользовательского интерфейса чрезвычайно полезным.
- часть инструментов
Корень проблем №2: неявное соглашение о загрузке конфигурации «вверх» делает конфигурацию №1 и инструментов еще более сложной.
Большинство инструментов js ищут корневую папку и пытаются использовать или объединить найденные конфигурации. Возьмем, к примеру, babel, у него есть варианты root, upward и upward-optional, которые часто опционально учитываются сторонними инструментами.
Короче говоря, в структуре папок nestd эффективная конфигурация обычно не та, которую вы настраиваете . Это сложно, когда вам нужно настроить для одного модуля, и беспорядок, когда вы пытаетесь использовать инструменты в нескольких модули работают вместе.
Затем пряжа по умолчанию - размещение папок модулей в параллельной иерархии кажется тривиальным, но оказывается чрезвычайно важным. Держите пакеты плоскими и сворачивайте конфигурацию в корневую папку.
Раньше мы помещали в наше основное приложение подпапку для наших функций firebase node.js, которая имеет разные конфигурации для babel, jest, flow и т. Д. Логически они изолированы, но все же возникали многочисленные проблемы, вызванные неявным разрешением пути.
Таким образом, в корне мы используем имена файлов шаблонов, которые загружаются и расширяются явно, например ,.eslintrc-template.js
модулями, а наш корень babel.config.js настраивает только babelrcRoots, но ничего больше. (По умолчанию yarn группирует модули в папке packages/
, но мы решили, что эту дополнительную иерархию можно пропустить.)
Задача: совместное использование компонентов пользовательского интерфейса и библиотек между приложениями React
wq-ui-components
содержит общие компоненты React для двух разных приложений. Мы считаем разработку этих изолированных компонентов с помощью Storybook прекрасным опытом для разработчиков.
Одна очевидная проблема - поддерживать разные версии зависимостей для каждого модуля. например Возможно, мы захотим обновить React и поэкспериментировать с Hook только на статическом сайте. (Совместное использование различных версий зависимых модулей одного и того же репо, т.е. отключение их в рабочем пространстве пряжи, - это другая история.)
Наше основное приложение загружается (и извлекается) с CRA - официальная поддержка монорепозитория еще впереди, в то время как другое приложение использует next.js.
Теперь вы, возможно, знаете, что это означает наличие множества различных сред бабеля (не забывайте о тестировании), и каждый пользовательский интерфейс имеет разные конфигурации веб-пакетов. Потребовалась некоторая работа, чтобы сделать все совместимым, в настройках, которые мы используем, есть `.babel.config.js` для каждого приложения пользовательского интерфейса, и мы заставляем их уважать конфигурацию .babelrc каждого модуля с помощью babelrcRoots .
Проблема: четко указывайте на изоморфизм
Вы когда-нибудь пробовали добавить сторонний модуль «js-sdk», написать несколько строк кода, а затем обнаружить, что только некоторые функции изоморфны? Мы считаем полезным явно разделять модули на lib-iso/ and lib-node/
Если взять в качестве примера firebase, необходимость использования в вашем приложении как firebase-js-sdk (изоморфный), так и firebase-admin (только для узла) может сбивать с толку. Кроме того, работа с опциями узла / браузера с помощью if / else может легко загрязнить нисходящий поток и стать беспорядком. Разделив их на модули с соответствующими оболочками среды, клиенты в пользовательском интерфейсе / узле просто должны импортировать правильный модуль, не беспокоясь об ошибках типа document is not defined
Раздел II. Дополнительные тактические задачи
Задача: сохранить пути импорта при разбиении на пакеты
Реорганизация существующих файлов в пакеты включает в себя большое количество перезаписей пути, что может привести к ошибкам без поддержки IDE. Js-refactor и Nuclide Flow Autocomplete могут помочь, если вы используете Atom.
Кроме того, рекомендуется в первую очередь упростить путь, например m odule-resolver может использовать псевдоним root для~/module1/abc
вместо ../../module1/abc.
Задача: развертывание функций Firebase (Google Cloud)
Это одна из основных задач для нас. Для быстрой загрузки мы сильно полагаемся на Firebase (хостинг / функции / Firestore). В идеале инструменты firebase ожидают простой firebase.json и развертывают ваш код на хостинге или функциях GCP.
На самом деле, очевидно, что некоторые проверки в GCP не подходят для структуры monorepo, поэтому нам нужно настроить конфигурацию nohoist из yarn для функций firebase, а firebase-admin получить некоторые команды для работы firebase-tools.
Firebase Functions имеет определенную среду выполнения nodejs, в которую должен быть перенесен наш код ES6. Сложности в том, что 1) это не только пакет функций, но и зависимые от него пакеты, 2) существует ограничение на размер того, что вы можете загрузить, и 3) похоже, что частичные модули node_modules не работают.
К сожалению, мы решили обойти это, используя подход частного npm. Изначально мы проводим оценку непрерывной поставки и не публикуем модули, поскольку у нас небольшая команда и мы не работаем над библиотекой.
В конечном итоге мы настроили Lerna для автоматического запуска версий при изменении зависимых модулей, наш поток CI публикует их в npm, а затем следуют задания по развертыванию функций firebase. Однако никакая другая часть нашего рабочего процесса не зависит от таких версий.
Задача: запустить Jest
Мы используем одну установку с корневым доступом. Функция jest - projects, появившаяся в версии 20, упрощает задачу. Конфигурация отдельного модуля также заслуживает уважения и может быть настроена, поэтому jsdom легко использовать для тестирования компонентов пользовательского интерфейса, а узел - для всего остального.
Затем мы можем запустить тесты или наблюдатель из корневого каталога. Самое большое преимущество monorepo shine - вы заметите, если ваши изменения в одном модуле нарушат работу других.
Задача: заставить поток работать
Flow рассматривает каталог, содержащий
.flowconfig
, как корень проекта.
Будучи консервативными, наша установка использовала индивидуальный поток для каждого пакета, хотя используется один потоковый бункер. Это означает, что поток и конфигурация настраиваются для каждого пакета. .flowconfig
не основан на js, нужны хитрые подходы, чтобы делиться общей конфигурацией. Вы также можете проверить flow-mono-cli, поскольку в настоящее время нет официальной поддержки со стороны flow.
Задача: встроить в CI только то, что изменилось
Если взять пример с CircleCI, который мы используем, у него пока нет официальной поддержки монорепозитория. Вы можете захотеть запускать сборку частично, а сборку производить только при изменении зависимых пакетов. Обходной путь - написать собственный настраиваемый скрипт сборки с помощью $ CIRCLE_COMPARE_URL или Lerna.
Задача: контроль доступа по модулю
Это, возможно, самый большой недостаток монорепозитория. У крупных фирм, таких как Google или Facebook, есть специализированные инструменты для этого, однако в остальном, насколько мне известно, хорошего решения не существует.
Для нас это не перевесило преимуществ монорепозитория, но, вероятно, помешало многим командам измениться.
Хорошие новости: инструменты JS со временем становятся лучше
Люди, использующие JS, умеют давать имена. Каждый раз, когда мне приходится останавливать разработку и отладку приложения для babel-plugin-X-not-working-with-Y, я задаюсь вопросом, почему мир такой сложный. Тем не менее, я не могу поблагодарить умных людей, усердно работающих над этими фреймворками - Babel 7, Webpack 4, текущая шутка, пряжа и т. Д. Намного лучше своих предшественников.
Как видите, есть довольно много работы и проблем, но, возможно, есть и другие виды конфигурационных работ и проблем, если я не нахожусь в рабочем пространстве monorepo / yarn.
Будет намного сложнее перейти с существующей кодовой базы, но это определенно вариант, за которым следует следить, и вышеупомянутые проблемы, скорее всего, будут лучше решены.
Пожалуйста, прокомментируйте и поделитесь, если вы знаете более эффективные способы решения вышеуказанных проблем (очень вероятно).
Присоединяйтесь к нам, удаленная команда разработчиков из Гонконга, Тайваня и Сингапура, если вы заинтересованы в использовании этих технологий для повышения качества обучения языкам.
Дополнительная информация