Сегодня нет недостатка в статьях о дебатах Монорепо и Мульти-репо, в то время как инструменты для монорепозитория на основе 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.

Будет намного сложнее перейти с существующей кодовой базы, но это определенно вариант, за которым следует следить, и вышеупомянутые проблемы, скорее всего, будут лучше решены.

Пожалуйста, прокомментируйте и поделитесь, если вы знаете более эффективные способы решения вышеуказанных проблем (очень вероятно).

Присоединяйтесь к нам, удаленная команда разработчиков из Гонконга, Тайваня и Сингапура, если вы заинтересованы в использовании этих технологий для повышения качества обучения языкам.

Дополнительная информация