Как стать автором
Обновить

Простая масштабируемая структура Angular приложения

Время на прочтение5 мин
Количество просмотров8.3K

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

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

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

Ниже представлены свойства, которые имеет идеальная, на мой взгляд, структура приложения:

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

  • Масштабируемая. В любом момент может появиться необходимость добавить в приложение новую функцию или новый раздел. Это не должно быть проблемой. При этом производительность не должна проседать с ростом приложения.

Принципы, которые необходимо соблюдать, чтобы достичь такой структуры приложения:

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

  • Lazy loading для каждого раздела. Мы должны загружать модуль раздела только тогда, когда пользователь переходит в него. При таком подходе при масштабировании приложения его производительность не будет страдать.

  • Provide in root только тогда, когда это действительно необходимо. Не раз встречал подход, когда тимлид говорит, что все сервисы должны быть внедрены в корневой модуль. Не знаю для чего это делать, может быть для того, чтобы избежать ошибки появление нескольких экземпляров одного и того же сервиса. Я считаю, если сервис используется только в одном модуле, то он должен быть внедрен в этот модуль и находиться рядом с ним в одной папке. Такие сервисы не должны жить все время работы приложения.

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

  • Общие модули. Когда мы имеем пайп/директиву/компонент, который используется только в нескольких разделах приложения (не во всех), то нет необходимости внедрять его в корневой модуль и загружать при старте приложения. Необходимо создать отдельный независимый модуль и импортировать его только в те разделы, где он используется. Таким образом, этот пайп/директива/компонент будет загружаться по требованию вместе с соответствующими разделами приложения.

  • Небольшая вложенность папок и однообразность структуры модулей. Чем меньше вложенность папок в проекте, тем удобнее с ним работать – мелочь, а приятно. Также однообразная структура модулей позволяет поддерживать порядок независимо от размера проекта.

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

https://dynalist.io/d/iZZJgMzUewPX9Thji4xebcGa

Рассмотрим подробнее одну из веток – "system" модуль:

system
-- core
---- services
---- interfaces
---- store
-- transactions
---- core
------ services
------ interfaces
---- transactions.component.html
---- transactions.component.scss
---- transactions.component.ts
---- transactions.module.ts
---- transactions-routing.module.ts
-- header
---- header.component.html
---- header.component.scss
---- header.component.ts
-- system.component.html
-- system.component.scss
-- system.component.ts
-- system.module.ts
-- system-routing.module.ts

Я предлагаю в каждом модуле создавать папку "core". В нее мы будем складывать все сервисы, интерфейсы, константы и прочие сущности, которые относятся к модулю. Соответственно, все эти сущности должны быть внедрены именно в этот модуль.

Файлы модуля ("system.module.ts", "system-routing.module.ts") и файлы главного компонента модуля ("system.component.html", "system.component.scss", "system.component.ts") лежат в корне. Все остальные компоненты которые относятся к модулю (например, "header" компонент) мы также будем складывать в корень, но уже в папках. Еще в корень мы будем складывать все модули внутренних разделов (например, "transactions" модуль).

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

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

import { TransactionsService } "../../../../../core/services/transactions.service";
import { UsersService } "../../../../../core/services/users.service";

В каждой папке, которые находятся в "core" создаем файл "index.ts" и экспортируем все содержимое папки в нем:

core
-- services
---- transactions.service.ts
---- users.service.ts
---- index.ts

// содержимое файла index.ts
export * from './transactions.service';
export * from './users.service';

После направляемся в файл "tsconfig.json", ищем раздел "paths" и указываем в нем алиасы для каждого из модулей первого уровня (обычно этого достаточно, потому что зачастую только модули первого уровня являются зависимостями для остальных модулей):

"paths": {
  "@environments/*": ["src/environments/*"],
  "@app/*": ["src/app/*"],
  "@auth/*": ["src/app/auth/*"],
  "@system/*": ["src/app/system/*"],
  "@form/*": ["src/app/shared/form/*"]
}

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

import { TransactionsService, UsersService } from '@app/core/services';
import { TransactionDto, UserDto } from '@app/core/interfaces';
import { AuthService } from '@auth/core/services';
import { Size, Color } from '@form/core/enums';
import { FieldOptions, ButtonOptions } from '@form/core/interfaces';

Описанная мной структура хорошо показала себя на практике. До сих пор в проектах, где я ее использовал, мне не приходилось ее менять в процессе роста проекта. Она проста и масштабируема. Над проектом с такой структурой комфортно работать командой. Все наглядно и всегда порядок.

Спасибо всем, кто прочитал статью! Буду рад любой обратной связи. Успехов в разработке приложений на Angular.

Теги:
Хабы:
Всего голосов 8: ↑7 и ↓1+6
Комментарии13

Публикации

Истории

Работа

Ближайшие события