Поддержка множественных зависимостей в распределенной архитектуре — большая проблема, особенно когда закон Конвея предсказал дальнейшую изоляцию команды на основе созданной архитектуры.

В настоящее время микросервисы являются золотым стандартом для интерфейсных и серверных решений. Нано-услуги также становятся очень популярными для быстрого предоставления ценности бизнесу. Это диктует еще большую атомарность поставляемого программного обеспечения и повышенный спрос на возможность повторного использования кода. Таким образом, разделение макроса на микро и микро на нано создает действительно сложный граф зависимостей и экспоненциально увеличивает усилия по поддержке управления зависимостями, когда основные уровни еще не полностью стабилизированы.

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

Полное решение пользовательского интерфейса может иметь следующий вид:

  • он состоит из набора независимых приложений, которые могут совместно использовать свое состояние аутентификации/аутентификации через систему единого входа.
  • Каждое приложение состоит из набора подпроектов (в основном каждый маршрут верхнего уровня указывает на выделенное подприложение), которые мы называли «микроинтерфейсом».
  • каждое приложение состоит из набора модулей (некоторые из них, такие как модуль AUTH, обычно являются общими для всех корневых приложений пользовательского интерфейса)
  • каждый модуль состоит из набора сторонних или собственных библиотек

Преследуя цель быть независимым в разработке функций и поддерживать собственный цикл выпуска для конкретных компонентов — обычно каждый блок на этом графике перемещается в собственный репозиторий со своими ограничениями выпуска.

Пока все хорошо, пока вам не нужно обновить самую нижнюю библиотеку в дереве зависимостей:

  • изменения кода в «библиотеке» требуют создания нового артефакта
  • новую версию библиотеки необходимо распространить на все принадлежащие модули и обновить ее версии
  • новые версии всех модулей должны распространяться на все компоненты-владельцы
  • ….

В зависимости от количества слоев, которые имеет конкретный продукт — это болезненный и трудоемкий процесс (особенно если процесс CI включает в себя комплексный процесс проверки) — восстановление каждого артефакта слоя может занять значительное время.

Миграция в монорепозиторий

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

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

Поэтому мы взяли рабочие области NX в качестве экспериментального инструмента для внедрения монорепозитория в нашей организации.

Первым делом описываем рабочее пространство, оно может выглядеть так:

{  
  "version": 2,  
  "projects": {    
    "products": "apps/products",    
    "products-e2e": "apps/products-e2e",    
    "users": "apps/users",    
    "users-e2e": "apps/users-e2e",    
    "core": "libs/core",    
    "react-ui-components": "libs/react-ui-components"  
  }
}

Не обращайте внимания на то, сколько библиотек могут использовать разные единицы монорепозитория — легко определить область и границы для конкретного корня, чтобы он вел себя как единое целое (например, тестирует все библиотеки и собственные приложения, когда где-то в этой области происходят изменения).

Затем мы создали отдельную библиотеку для каждой зависимости и связали репозиторий как подпроект, используя подмодули git.

Создать новую библиотеку так же просто, как вызвать nx generate @nrwl/angular:lib auth-core . Существует множество типов генераторов (начиная от примитивных узловых модулей, заканчивая сложными проектами).

Преимущества, полученные после внедрения:

  • все повторно используемые зависимости по-прежнему являются повторно используемыми зависимостями и могут быть выпущены индивидуально сами по себе (чтобы зависимые продукты не испытывали никаких трудностей во время этого переноса) — у каждой может быть своя собственная процедура выпуска/проверки (здесь это называется «целевой»)
{
  "root": "libs/core",
  "sourceRoot": "libs/core/src",
  "projectType": "library",
  "targets": {
    "build": {
      "executor": "@nrwl/web:package",
      "outputs": ["{options.outputPath}"],
      "options": ...
    },
    "release": {
      ...
    },
    "test": {
      "executor": "@nrwl/jest:jest",
      "outputs": ["coverage/libs/core"],
      "options": ...
    }
  }
}
---------------------------
> nx run core:test
Test Suites: 23 passed, 23 total
Tests:       72 passed, 72 total
Snapshots:   0 total
Time:        49.06 s
Ran all test suites.
>  NX   SUCCESS  Running target "test" succeeded
  • в то же время мы, как владельцы и основные пользователи этих модулей, можем получить к ним прямой доступ, не публикуя их где-либо, поэтому изменения в ядре могут быть немедленно отражены в используемом им слое.
{
  "compilerOptions": {
    ...
    "paths": {
      "@core": ["libs/core/src"],
      "@react-components": ["libs/react-components/src/index.ts"]
    }
  }
}
----------------------
import {MinValueValidator} from "@core/validators";
import {PlatformSection} from "@react-components/containers";
  • так как все зависимости теперь являются локальными подпроектами в основном проекте — мы можем настроить свой собственный граф зависимостей, и процесс сборки проекта будет чувствителен к порядку зависимостей (соответствующие библиотеки будут собраны до того, как будет собран корневой проект)
{
  "build": {
    "executor": "@nrwl/web:webpack",
    "outputs": ["dist/apps/products"],
    "options": {
      "index": "apps/products/src/app.html",
      "main": "apps/products/src/main.ts"
    },
    "dependsOn": [
      {
        "target": "build",
        "projects": "libs/react-ui-components"
      }
    ]
  }
}
------------------------
> nx build
> NX  Running target build for project "platform" and 2 task(s) that it depends on.
  • у каждой зависимости может быть своя процедура выпуска/проверки (например, для основной библиотеки достаточно только юнит-тестов, а для UI нужно инжектить сборник рассказов и запускать UI-тесты), поэтому атомарность в управлении библиотекой не нарушалась
  • даже будучи моно — у нас есть хорошо организованная структура зависимостей, и эту структуру можно легко визуализировать, вызвав nx dep-graph

Генератор как приятный бонус

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

После того, как вы определили и применили правила, вы можете легко описать их в виде кода с помощью генератора кода. К счастью, NX поставляется со встроенной поддержкой генератора, поэтому создание новых страниц, библиотек, модулей можно автоматизировать, например:

nx workspace-generator lib-generator auth-core

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