Введение
Как думаете, какой паттерн самый масштабируемый? Может быть, это вселенная? Или жир в вашем животе? А как насчет фрактала? Это бесконечный узор, где каждая из частей и есть отдельный узор. Выглядит это примерно так:
А что если вы можете использовать один из фрактальных принципов для масштабирования архитектуры? Но сначала поговорим о масштабируемости.
Недостатки мышления, отвергающего масштабируемость
Каждый развивающийся продукт рано или поздно приходится масштабировать. И если вам кажется, что вы не столкнетесь с масштабируемостью, это означает, что ваша архитектура еще “сырая”. Все боятся наступления этого дня, ведь вам придется потратить на масштабируемость много денег и времени. Давайте подумаем, от каких мыслей нужно отказаться, чтобы избежать большого стресса в будущем.
Я не знаю, что ждет меня впереди. Никто не может предсказать будущее. Но вряд ли это может служить оправданием для игнорирования возможностей масштабирования. С другой стороны, пытаться заглядывать в будущее слишком часто — тоже не лучшая идея. Это тормозит процесс разработки и приводит к чрезмерному техническому усложнению системы. Оптимальным решением будет следовать проверенным практикам, стандартам фреймворка и резервировать достаточно пространства для расширения.
У меня не хватает времени. Конечно, не хватает. В динамично меняющемся мире каждая компания стремится производить больше и за меньшее количество времени. Иначе бизнес просто погибнет. Подобный образ мышления приводит к тому, что вы меньше думаете о нужных вещах и почти всегда сталкиваетесь из-за этого с неприятными последствиями. Общий совет для всех: сделайте паузу и подумайте о том, как разделить приложение на инкапсулированные части и устранить зависимость между ними. Помните: архитектура не обязательно должна быть идеальной. Решения для всех вариантов уже существуют. Заимствуйте и адаптируйте их под свои потребности.
Я думаю о продукте, а не о команде. Команда развивается вместе с продуктом. Учтите, что команды тоже должны масштабироваться. Если команда не масштабируется, это приводит к непреодолимым проблемам. Потеря ключевых инженеров равносильна потере части бизнес-структуры. В результате происходит обмен знаниями внутри и между командами, затем знания “рассеиваются” по разным командам, из-за чего в голове у каждого инженера, недавно принятого на работу в компанию, начинается путаница. Чтобы избежать этих трудностей, нужно делать с командами то же самое, что и с продуктом: инкапсулировать и разделять.
Очистите свой разум и займитесь масштабированием
Разумеется, вы должны соблюдать крайнюю осторожность при проектировании архитектуры. Есть масса книг и теорий, описывающих различные способы масштабирования архитектуры. В то же время находятся бесчисленные эксперты с богатым опытом, у каждого из них свое мнение, и эти мнения часто противоречат друг другу.
Так какой же путь самый правильный и максимально продуктивный? Все они хороши и заслуживают внимания. Но нет смысла вести бесплодные дискуссии. Крупные технологические компании уже сталкивались с проблемами масштабирования, и пока никто не придумал ничего лучшего, чем монорепозиторий.
Монорепозиторий представляет собой единый репозиторий, который содержит все проекты, включая фронтенд и бэкенд. Рассмотрим основные принципы монорепозитория.
Возможность повторного использования. Вы можете совместно использовать данные, служебные функции, компоненты пользовательского интерфейса и интерфейсы во всех проектах. Это можно делать не только между фронтенд-приложениями, но даже между фронтендом и бэкендом.
Последовательность. Этот принцип вытекает из предыдущего. Вместо того чтобы дублировать каждую зависимость для каждого проекта, вы можете импортировать одну и ту же версию экземпляра. В результате, когда вы обновляете зависимость, она распространяется на все приложения монорепозитория.
Стандартизация. Даже если у команд есть большой выбор технологических стеков, принцип стандартизации помогает определять общие инструменты, такие как сборка и тестирование. Этот принцип также облегчает написание кода и способствует применению лучших способов разветвления.
Масштабируемость команд. Команды — неделимые организационные единицы. Они берут на себя ответственность за разработку проекта или библиотеки. В то же время работа с монорепозиториями способствует сплочению команд в результате следования общим стандартам.
Краеугольные камни масштабируемой конструкции Angular
После того, как мы коснулись высокого уровня масштабируемости, пришло время пойти дальше и узнать, как масштабировать фреймворк Angular без ограничений. Для начала поговорим об основных компонентах, из которых будет состоять наша архитектура.
Модули Angular — наиболее детализированная часть большого приложения. Модули инкапсулируют небольшую часть логики и состоят из набора компонентов, сервисов, директив и т. д. Они также могут встраиваться друг в друга. Идея проста — обернуть логику и встраивать ее по мере роста сложности.
Проект. Если проект увеличивается в размерах и его приходится поддерживать нескольким командам, это признак того, что нужно начать разбивать его на мелкие подпроекты. Если это вложенный проект, где есть один проект-оболочка и несколько микроприложений, вы можете масштабировать его с помощью архитектуры микрофронтенда. Если же вам не нужно вложение подпроектов, вы можете просто создать новый подпроект.
Библиотека. Это место, где находятся все общие данные: компоненты пользовательского интерфейса, хранилище данных, служебные функции. Библиотека похожа на пакет локального узла. Если библиотека находится на уровне одного проекта, она распределяет данные между модулями. В противном случае, если она находится между проектами или микроприложениями, она переходит на уровень рабочего пространства.
Рабочее пространство. Это и есть монорепозиторий. Он состоит из набора проектов и библиотек, которые обмениваются данными между собой.
Разделяйте и властвуйте в контексте масштабирования Angular
Вы наверняка слышали фразу “разделяй и властвуй” тысячу раз в своей жизни. В ней заложен глубокий смысл. Возьмем ее на вооружение для нашего проекта. Начнем со структуры папок проекта Angular и потом будем его масштабировать.
Назовем наше гипотетическое приложение Book Shelf. Оно будет состоять из двух функций: вход в систему и книги. Когда пользователь входит в систему, приложение перенаправляет нас в галерею книг, где мы можем выбрать книгу и открыть ее первую страницу, на которой будет указана подробная информация о книге.
Вроде все просто? Теперь давайте разберемся с терминами.
- Функции (features). Большой фрагмент бизнес-логики, обычно связанный с той или иной страницей и завернутый в один модуль. Когда он начинает расти и обзаводится дочерними страницами, он может быть сгруппирован более детально, например:
-features
-login
-books-gallery
И можно осуществить такое масштабирование:
-features
-login
-books
-books-gallery
-components
-services
-utils
-types
-configs
-books-details
- Libs. Это своего рода корзина, куда мы складываем все многократно используемые данные, разделенные между функциями. Она может содержать виджеты, компоненты пользовательского интерфейса, хранилище данных, утилиты, типы и конфигурации.
-libs
-ui-components
-widgets
-data-store
-utils
-types
-configs
- UI-компоненты (ui-components). Это формальные компоненты, очень специфичные для UI. Это своего рода библиотека пользовательского интерфейса. У них есть сходство с элементами материального интерфейса, состоящего из кнопок, карточек и диалоговых окон. Эти компоненты можно повторно использовать в других проектах для сохранения принципа последовательности.
- Виджеты (widgets). Небольшие бизнес-компоненты, строительные блоки нескольких функций. Они также могут состоять из ui-компонентов, например book-card содержит в себе ui-компоненты — карточку и кнопку, а также используется в нескольких функциях — books-gallery, books-details.
- Хранилище данных (data store). Содержит разделяемое состояние (state) и логику, связанную с API, которая может быть внедрена в другую функцию. Например, NgRx-хранилище.
- Утилиты (utils). Простые вспомогательные функции, используемые во всех функциях. Могут находиться внутри одной функции, но не являются многоразовыми.
- Типы (types). Совместно используемые типы, интерфейсы и перечисления.
- Настройки (configs). Папка для всех многократно используемых констант и mock-объектов.
Окончательная структура единого проекта будет выглядеть следующим образом:
-features
-login
-books
-books-gallery
-components
-services
-utils
-types
-configs
-books-details
-libs
-ui-components
-widgets
-data-store
-utils
-types
-configs
Эту структуру можно расширять и улучшать в зависимости от потребностей проекта, но такой архитектуры будет вполне достаточно для уверенного масштабирования.
Что дальше? Представьте, что приложение продолжает расти. Функции стали огромными, и нам приходится поручать их поддержку отдельной команде. Пришло время масштабировать приложение дальше и выделить функцию в отдельный проект. На этом этапе мы можем извлечь максимум преимуществ из монорепозитория и микрофронтенда. Вот как будет выглядеть окончательная масштабируемая архитектура:
-apps
-login
-features
-libs
-books
-features
-libs
-shell
-libs
-ui-components
-widgets
-data-store
-utils
-types
-configs
Возможно, вы заметили новое приложение под названием shell. Оно используется в микрофронтенде для определения приложения хоста, которое лениво загружает другое микроприложение.
Заключение
Эта структура подходит не только к Angular. Вы можете применить ее к любому другому фронтенд-фреймворку после проведения небольших корректировок.
Читайте также:
- Создаем библиотеку компонентов Angular
- Protractor мертв, да здравствует Cypress!
- Насколько хорошо вы разбираетесь в AngularJS?
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Vitalii Shevchuk: 🔥 How to Scale Angular Without Limits