Архитектура enterprise Angular приложений с использованием монорепозитория Nx

Aleksandr Serenko
F.A.F.N.U.R
Published in
10 min readApr 26, 2020
Angular + Monorepo Nx + Enterprise

В данной статье поговорим об архитектуре Angular приложений, а также рассмотрим использование Nx для enterprise.

Обновление: 27 марта 2021. За предыдущий год работы с Nx в продакшене, плюс добавив опыт пары senior’ов из tinkoff, написал обновленную статью, посвященную структуре приложений в Nx — Banx. Структура Angular приложения в монорепозитории Nx.

После смены работы, на новом месте мне поставили задачу переноса текущей front-end составляющей проекта на новый стек. До этого у меня был опыт с Angular и проблем с выбором платформы не было.

В моем случае подразумевалась реализация приложения в нескольких странах, в частности: Россия и Испания.

И тут были две задачи:

  • Мигрировать существующий код для России
  • Запустить аналог в Испании

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

Отсюда появился основной вопрос:
Как и что можно наследовать, а что должно быть уникальным для страны?

Проблема разделения

Благодаря использованию Angular’ом TypeScript’а, особых проблем с тем как наследовать нет. TypeScript позволяет создавать очень функциональные и детальные абстракции и разнести всю кодовую базу на 3 пакета не является сложной задачей:

  • core — общая функциональность для всех стран
  • russian — русская версия приложения
  • spanish — испанская версия приложения

Первая проблема с разделением кода в Javascript/Typescript — импорты.

Многоуровневые, сложные подключения внутренних пакетов:

import { UserService } from '../../../../core/users/user.service';

Решается это достаточно легко на уровне самого typescript’а с помощью алиасов.

{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@lib/core": ["src/libs/core/src/index.ts"],
}
}
}

В данном примере, мы создали алаис для файла src/libs/core/src/index.ts, в котором находятся все публичные классы, функции и константы, которые могут быть использованы.

Выше приведённое решение позволяет сократить импорты, но есть ряд проблем, которые решить нативно уже не получиться:

  • контроль циклических зависимостей
  • настройка контроля доступа к внутренним библиотекам и построение графа зависимостей

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

Под контролем доступа подразумевается решение, которое разрешает/запрещает конкретному файлу импортировать части внутренних библиотек.

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

Как упоминалось выше, стандартные angular/cli могут лишь помочь в создании алиасов, то Nx позволяет решить все вышеперечисленные проблемы.

Отметим, что в данном случае я не рассматриваю стандартное решение angular/cli с созданием библиотек. Данный подход является достаточно громоздким и не позволяет быстро и эффективно разрабатывать библиотеки и приложения одновременно. Постоянно необходимо компилировать библиотеки, вести версионность, что в конечном счёте приводит к dependency hell. И лучшим решением является монорепозиторий, который заставляет разработчика держать актуальными все пакеты, которые используют библиотеку.

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

И правильный вопрос — А нужен ли монорепозиторий, при чем для фронтенд приложения?

Ответ не однозначен. Всё будет зависеть от целей и объёма разрабатываемого приложения.

Монорепозиторий нужен, если вы планируете разрабатывать несколько фронтенд (бекенд) приложений, которые будут использовать общие интерфейсы.

Монорепозиторий не нужен, если:

  • планируется разработка только одного приложения
  • нужен быстрый результат

Как я уже неоднократно упоминал, Angular очень требователен к времени разработки. Получить быстрое, качественное и эффективное приложение на Angular является очень тяжёлой задачей. Не пытайтесь её решить, просто используйте другие инструменты. Команда facebook разрабатывает отличные инструменты.

Монорепозиторий Nx

Nx — это набор схематик и ряд утилит, разработанный бывшими разработчиками Angular team, для разработки enterprise приложений на основе монорепозитория.

Основные особенности Nx, которые выделяют сами разработчики:

  • Позволяет разделять и выносить общий код (shared code) — повторное использование кода разными приложениями и разными платформами.
  • Позволяет создавать атомарные изменения (atomic changes) — одновременное изменение частей back-end и front-end приложений
  • Предоставляет разработчикам набор инструментов для отслеживания изменений и тестирования для безопасной разработки (developer mobility)
  • Единые зависимости (single set of dependencies)— позволяет использовать одну версию сторонней библиотеки( или библиотек) во всех приложениях.

Если более детально, то Nx позволяет:

  • Создавать независимые библиотеки и отслеживать изменения и автоматически перекомпилировать приложение;
  • Создавать back-end на основе NestJS, который формально копирует идеи и подходы Angular;
  • Отслеживать циклические зависимости
  • Контролировать доступ и устанавливать права на доступ к тем или иным библиотекам и пакетам
  • Строить графы зависимостей
  • Анализировать текущие изменения и показывать все пакеты, на которые повлияли последние правки
  • Разрабатывать собственные schematics для автоматизации разработки

Популярность Nx и влияние Lerna (неявно) сменила фокуc core разработчиков c Angular, и теперь Nx предоставляет создание workspace не только с Angular, но также с React или нативным TypeScript’ом.

Также Nx предоставляет несколько инструментов для тестирования unit:

А для E2E позволяет выбрать из:

Более подробно о Nx на официальном сайте. На сайте представлены и разобраны все инструменты, а так же есть набор видео уроков по использованию и настройке различных компонентов и утилит.

Использование Nx для Enterprise

На сайте Nx есть раздел посвященный использованию Nx для enterprise приложений — Using Nx at Enterprises.

С точки зрения Nx в монопрозитории два основных блока: приложения и библиотеки.

  • Приложения настраивают и подключают библиотеки. Они не должны содержать никаких компонентов, сервисов или бизнес логики.
  • В библиотеках содержатся службы, компоненты, утилиты и т.д., у которых есть четко определённый публичный API.

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

“Хорошее соглашение — помещать библиотеки приложений в каталог, соответствующий имени приложения. Это обеспечивает достаточную гибкость для небольших и средних приложений.”

happynrwl
├── apps
│ ├── happynrwlapp
│ ├── happynrwlapp-e2e
│ ├── adminapp
│ └── adminapp-e2e
├── libs
│ ├── happynrwlapp
│ │ ├── feature-main
│ │ ├── ui-table
│ │ └── utils-testing
│ ├── adminapp
│ │ ├── feature-main
│ │ ├── feature-login
│ │ ├── ui
│ │ └── utils-testing
│ └── shared
│ ├── ui
│ └── utils-testing
├── tools
├── workspace.json
├── nx.json
├── package.json
└── tsconfig.json

Для более крупных проектов, Nx рекомендует объединять библиотеки в разделы:

happynrwl
├── apps
├── libs
│ ├── happynrwlapp
│ │ ├── registration
│ │ │ ├── feature-main
│ │ │ ├── feature-login
│ │ │ ├── ui-form
│ │ │ └── utils-testing
│ │ ├── search
│ │ │ ├── feature-results
│ │ │ └── utils-testing
│ │ └── shared
│ │ └── ui
│ ├── adminapp
| └── shared
│ ├── ui
│ └── utils-testing
├── tools
├── workspace.json
├── nx.json
├── package.json
└── tsconfig.json

В данном случае, мы имеем:

  • happynrwlapp/registration/feature-main — библиотека, которая используется только в одном месте одного приложения (feature logic)
  • happynrwlapp/shared/ui — общая библиотека, которая используется в конкретном приложении
  • shared/ui — общая библиотека, которая может быть использоваться в любом приложении

Субъективно, данная структура подходит лишь небольшому кругу проектов. Данный подход отлично подойдёт производителям автомобилей, у которых всегда есть основной сайт, и множество сайтов дилеров, которые зачастую копируют основной сайт и вносят незначительные изменения.

Nx рекомендует следующую структуру для enterpise решений, которые являются порталами:

happynrwl
├── apps
│ ├── happynrwlapp
│ ├── happynrwlapp-e2e
├── libs
│ ├── shell
│ │ └── feature-main
│ ├── registration
│ │ ├── feature-main
│ │ ├── data
│ │ ├── ui-form
│ │ └── utils-testing
│ ├── search
│ │ ├── feature-results
│ │ └── utils-testing
│ └── shared
│ ├── ui
│ └── utils-testing
├── tools
├── workspace.json
├── nx.json
├── package.json
└── tsconfig.json

В данном примере, библиотеки содержат:

  • utils — утилиты и сервисы
  • data — state библиотеки
  • ui — компоненты, директивы и пайпы
  • features — бизнес логика приложения

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

Другим хорошим примером, может являться разработка приложения для двух платформ: web приложение и мобильное приложение (Angular + NativeScript).

Когда я начинал разработку, рекомендаций Nx не было и структура проекта выглядела так:

my-workspace
├── apps
│ ├── russian
│ │ ├── admin
│ │ ├── admin-e2e
│ │ ├── lk
│ │ │ ├── customers
│ │ │ │ ├── customers-profile
│ │ │ │ ...
│ │ │ ├── registration
│ │ │ ...
│ │ └── lk-e2e
│ └── spanish
│ │ ├── admin
│ │ ├── admin-e2e
│ │ ├── lk
│ │ └── lk-e2e
├── libs
│ ├── registration
│ │ ├── registration-core
│ │ ├── registration-steps
│ │ └── registration-testing
│ ├── detectors
│ │ ├── detector-name
│ │ └── detector-name-testing
│ ├── shared
│ ...
├── tools
├── angular.json
├── nx.json
├── package.json
└── tsconfig.json

Как можно увидеть, здесь достаточно упрощённая версия, по сравнению с Nx.

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

Во-вторых, я не разделял state и утилиты на различные модули, а объединял их в один, который называл core. Можно сказать, что все библиотеки формально были реализацией бизнес логики, а приложение содержали в себе компоненты, которые отображали данную бизнес логику.

Но через некоторое время я пришел к аналогичной структуре предлагаемой Nx и вот почему:

  • Хранение всех компонентов в приложении привело к тому, что модули получались очень массивными и порождали множество связей между модулями, которые стало трудно контролировать
  • Отсутствие вынесения angular компонентов в отдельные библиотеки затрудняло тестирование. Приходилось тестировать все приложение целиком, вместо того, чтобы тестировать какие-то его части. Даже задание определённых префиксов в названии тестов, не позволяли полностью решить проблему быстрого тестирования
  • Отсутствие группировки библиотек по типу приложений породило множество библиотек на верхнем уровне, что затрудняло естественную навигацию и поиск в списке просматриваемых библиотек.
  • Объединение State и Services в один модуль привело к проблеме циклических зависимостей. Например, сервисы двух библиотек ссылаются на State каждой из библиотек, тем породив циклические зависимости.

Из этого, я вынес как минимум 2 подхода, которые необходимо соблюдать:

  • Разделять код модулей на небольшие составные части (state, utilites, api, ui). Это позволяет оптимально переиспользовать код, а также использование тегов в Nx, позволяет контролировать доступ.
  • Вынос всего кода из приложения в библиотеки является оправданным даже, если она будет использована только однажды и только в одном месте. Это позволяет более эффективно производить тестирование (занимает меньше времени), а также делает приложение более лёгким, убирая ненужную громоздкость.

Тестирование

Вторым важнейшим преимуществом Nx являются методы тестирования, без которых невозможен ни один enterprise проект. Именно возможность использования Jest из коробки, позволяет влюбить в себя любого разработчика, который писал тесты для UI.

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

Так как все компоненты и сервисы должны быть протестированы, часто встаёт вопрос: “А насколько детально нужно тестировать те или иные компоненты и сервисы”.

Некоторые рекомендации по написанию тестов в enterprise приложениях:

  • Тестирование state’ов должны облагаться максимальным количеством тестов. Так как stat’ы представляют собой обработку и сохранение данных, важно писать тесты на все случаи. В статье — Тестирование Ngrx store в Angular. Методы и подходы для упрощения тестирование stat’ов Ngrx в Nx, я уже описывал важность и потребность тестов.
  • Тестирование утилит и сервисов должны содержать как минимум 2 теста на успешное/неуспешное выполнение, а также проверки на все граничные условия.
  • Тестирование в Angular это всегда тестирование одной маленькой, независимой части. Если вы тестируете компонент, который содержит в себе другие компоненты, то вы не должны подключать что-то, что используется в других компонентах. Смысл тестов в компоненте заключается в корректной работе данного компонента, а именно проверки правильной интеграции и работы других компонентов. Если в потомке используется сервис, то в родительском компоненте не должно быть данного сервиса — он должен быть скрыт моком самого компонента.
    Для Angular есть отличная библиотека для создания моков — ng-mocks.

Применение Best Practices

“В команде из 10 человек всегда можно договориться за кухонным столом, однако если в компании работает 100 человек, то договориться уже не получиться”

При разработке enterprise приложения очень важно договориться о практиках и подходах, которые будут использованы в проекте.

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

И если стандартный механизм создания schematic достаточно накладный, то разработчики Nx разработали инструменты, которые позволяют быстро и просто создавать и использовать собственные (custom’ные) схематики.

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

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

Рефакторинг

Для меня, как одной из характеристик качества кода является желание отрефакторить код. Если желание минимально или его почти нет, значит код можно считать хорошим (на какой-то промежуток времени). А вот если есть ощущение, что некоторые части кода нужно изменить, то это говорит о том, что в вашем подходе есть некоторые изъяны.

Например, я более 3 лет занимался вёрсткой сайтов различной сложности. Для того, чтобы после вёрстки и интеграции сайта, у меня не было желания что-то исправлять, мне потребовалось более 2 лет, чтобы определить лучшие практики.

Рефакторинг в enterprise это одна из главных составляющих процесса разработки. И это связанно с тем, что в enterprise все время меняются требования.

Если система не развивается и не меняется, то значит она скоро умрет. А во фронтенде это произойдёт ещё быстрее, чем в других IT областях.

Однако, рефакторинг может быть очень ресурсозатратным, и поэтому в enterprise очень важно его минимизировать. Для этого необходимо:

  • использовать schematics
  • использовать собственные правила для linter’ов
  • анализировать код других разработчиков (Angular, Nx, Ngrx, Rgrx, React, Vue)

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

Резюме

В данной статье поговорили о причинах разработчиков использования монореозитория. Сделали краткий обзор основных возможностей Nx. Разобрали основные нюансы связанные с разработкой enterprise решений, в частности — структура проекта, фокус тестов, правила и схематики.

Спасибо за внимание!

Ссылки

Подписывайтесь на блог, чтобы не пропустить новые статьи про Angular и новости из мира фронтенд разработки.

Medium: https://medium.com/fafnur
Добавляйтесь в группу Vk: https://vk.com/fafnur
Добавляйтесь в группу в Fb: https://www.facebook.com/groups/fafnur/
Телеграм канал: https://t.me/f_a_f_n_u_r
Twitter: https://twitter.com/Fafnur1
Instagram: https://www.instagram.com/fafnur

Предыдущие статьи:

  1. Мультиязычность ngx-translate в Angular 9 c монорепозиторием Nx.
  2. Redux в Angular. Управление состояниями в Angular с помощью Ngrx и Nx.
  3. Структура и подходы к организации экшенов, селекторов, редьюсеров и эффектов в Ngrx и Nx.
  4. Тестирование сервисов в Angular с помощью Jest. Тестирование реактивной/асинхронной логики.
  5. Тестирование Ngrx store в Angular. Методы и подходы для упрощения тестирование stat’ов Ngrx в Nx.
  6. Сборка Typescript приложения с помощью Webpack
  7. Локализация в Angular 9 с помощью @angular/localize с universal

--

--

Aleksandr Serenko
F.A.F.N.U.R

Senior Front-end Developer, Angular evangelist, Nx apologist, NodeJS warlock