📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

Архитектура современных FRONTEND приложений. 5 видов. Преимущества и недостатки

Ulbi TV1:04:28

Transcription

Приветствую вас, друзья! И сегодня мы будем говорить про архитектуру современных фронтенд приложений. Рассмотрим 5 видов архитектуры от простых к более сложным. Материал, который мы рассмотрим, подойдет как для React разработчиков, так и для Vue разработчиков, разработчиков на Svelte, на нативном JavaScript. Единственное исключение, это, наверное, гулящики, потому что там своя специфика, но думаю, даже им будет полезно посмотреть.

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

И, друзья, буквально минутка саморекламы. Расскажу про свой продвинутый курс, и мы уже переходим непосредственно к архитектуре. Сейчас идет набор на мой продвинутый курс "Продвинутый фронтенд продакшен на React". Это второй поток, улучшенный, около 40 часов продвинутого материала. Плюс я планирую записать еще модуль. Но, скорее всего, когда вы смотрите это видео, до конца продаж осталось 5-7 дней. То есть, если у вас есть интерес, посмотрите анонс, ссылка на который будет в описании. Я буду ждать вас на курсе. Это курс не для начинающих, это курс именно повышения квалификации, расширения кругозора. Разбирается огромное количество тем, и что самое главное, это не оторванные от реальности темы. По тому или иному модулю, а последовательное создание одного большого продакшен проекта на протяжении 40 часов и применение тех или иных подходов к реальному проекту, который мы будем разрабатывать в курсе. Будем разбирать архитектуру, конфигурацию webpack, Jest, React testing library, всю пирамиду тестирования, начиная от Unit тестов, заканчивая скриншотными и E2E тестами, создание собственной библиотеки компонентов, включающей в себя около 20 компонентов: различные модалки, шторки, попапы, ленивые изображения. Всё это сочетается с семантикой, с доступностью. Также будет интернационализация, темизация, нормализация данных, виртуальные списки, асинхронная подгрузка библиотек, чанков, инъекция редюсеров, Redux Toolkit, TypeScript. В общем, очень-очень-очень много продвинутых тем. И более подробно вы сможете посмотреть в анонсе по ссылке в описании. А сейчас давайте вернемся к текущему ролику и поговорим плотненько про архитектуру.

В ходе этого курса мы рассмотрим классический подход, который вы чаще всего встречаете в простеньких проектах или учебных проектах на YouTube. Также рассмотрим простейшую модульную архитектуру, подумаем, как ее можно улучшить. Рассмотрим методологию Atomic Design. Рассмотрим модульную архитектуру на стероидах – это Feature Slice Design. Также поговорим про распределенную архитектуру, когда у нас монорепозиторий с микросервисами, с микрофронтендами, так называемыми, и как в этом всем применять модульную архитектуру.

И начнем с первой, самой простой архитектуры – это классическая архитектура. Ее можно также назвать "без архитектуры", потому что явного выделения каких-то модулей, связей между этими модулями здесь нет. Структура директорий в проекте выглядит примерно вот таким вот образом: у нас есть папка `pages`, в которой хранятся страницы нашего приложения; папка `components`, которая превращается в какой-то момент в огромную свалку компонентов; в папке `api` у нас находится запросы на сервер; `helpers` – это какие-то переиспользуемые функции: сортировка массива, обход дерева, форматирование дат и так далее, какие-то общие функции; `store` – это непосредственно взаимодействие с каким-то глобальным хранилищем, будь то Redux, будь то Effector или любой другой state менеджер; `hooks` – это уже специфика React, когда мы создаем какие-нибудь кастомные хуки и переиспользуемые: `useDebounce`, `useThrottle`, `useObserver`, `useFetching` и так далее; `assets` – мы храним, например, какие-нибудь шрифты, какие-нибудь иконки, изображения и все в таком духе.

И здесь прям очень хочется отметить, что структура папок не задает архитектуру, это лишь ее маленькая часть. Здесь папки могут быть совершенно по-другому названы, их может быть абсолютно разное количество, здесь могут добавляться и другие какие-то папки, в которые мы что-то складываем. Уточняю на этом момент, потому что начинающие часто путают вот структуру папок с архитектурой приложения. Если вы не смотрели мой первый вводный ролик и не очень понимаете, что такое архитектура, то стоит начать с него. А если кратко, то архитектура – это, в первую очередь, взаимодействие модулей, кусочков вашей системы друг с другом. И на фронтенде вот этими модулями, в первую очередь, являются наши компоненты, которые как-то внутри себя используют бизнес-логику. Это могут быть большие компоненты по типу страниц, либо же какие-то маленькие компоненты по типу кнопок, инпутов или каких-то компонентов с логикой.

Давайте посмотрим, как поток данных устроен в нашем случае. Есть набор страниц и набор компонентов. Причем поток данных от страниц к компонентам он однонаправлен. То есть, одна страница другую страницу внутри себя использовать не может. А вот какое-то определенный пул компонентов может. То есть, поток данных от страниц к компонентам у нас правильный, циклические зависимости не образуются. Мы внутри страниц используем компоненты, здесь все хорошо. Но вот самими компонентами здесь уже все сложнее. Здесь образуется много неявных связей, возможно, кольцевые зависимости. Один компонент использует внутри себя другой, третий, четвертый, пятый, образуются вот эти вот неявные связи. При этом побочным эффектом этого всего является то, что у нас UI-компоненты по типу кнопок, ссылок, инпутов, модальных окон, тут типов смешиваются с компонентами с бизнес-логикой. Например, редактирование профиля, выбор города, карточка товара. Четкие модули у нас не выделяются, и все это по итогу превращается в одну огромную свалку. До поры до времени это работает отлично, компонентов не так много, искать их легко. Но потом начинается просто хаос. Кто-то не нашел нужный ему компонент, создал чуть-чуть другой, но похоже. Потом кто-то также создал третий, четвертый. У нас образуется куча похожих компонентов с небольшими отличиями, и возникает проблема: во-первых, непонятно, какой из этих компонентов использовать, потому что их становится много. Во-вторых, нужные компоненты становится тяжело находить, потому что у них нет четкой привязки к конкретным сущностям, конкретным фичам. Также становится непонятно, когда у нас компонент бизнес-логикой, когда у нас UI-компонент. Приходится все это изучать, тратить время, ковыряться вот в этой вот свалке.

При этом важно, что это касается не только компонентов, это касается также взаимодействия с данными, то есть с нашим стором. Опять же, я буду показывать на примере редюсеров из Redux, но это могут быть истории, хуксы или сторы того же Effector. То есть, у нас изначально какой-то один большой компонент использует редюсер, в котором определена бизнес-логика. Второй компонент использует второй редюсер, ну и третий компонент использует третий редюсер. На данном этапе у нас все хорошо, каждый компонент использует свою бизнес-логику. Но по мере роста проекта, по мере подключения новых разработчиков, со временем у нас возникают вот такие вот связи. Кто-то в один редюсер логику запихнул, кто-то в другой, кто-то в третий. Явных модулей нет, и все это обрастает вот такими хаотичными связями. Если приводить пример, то, допустим, у нас был редюсер `state` который хранил в себе информацию о текущем авторизованном пользователе. И также, допустим, у нас был редюсер, который отвечал за доступы и установку этих доступов для того или иного пользователя. Из-за того, что все у нас хаотично разбросано, новый разработчик кусочек кода, который должен был находиться в редюсере с доступами, добавил в редюсер с информацией о пользователе. Потом другой разработчик увидел, что эта информация хранится в этом редюсере и тоже добавил что-то. И она вот так как снежный ком нарастает, нарастает, регистры обрастают лишней зоной ответственности, и потом разделить ее уже практически невозможно.

В итоге мы получаем нечто такое: между модулями (под модулем я подразумеваю компонент и его бизнес-логику, например, редюсер или какой-то другой стор) образуется неявные связи. Хотя и модули у нас как таковых здесь тоже нет. У нас есть просто набор компонентов, набор редюсеров, набор хелперов, которые между собой взаимодействуют. То есть, если возвращаться к вот этой вот диаграмме, где идеальным вариантом у нас является правый нижний угол, то при данном подходе, при данной архитектуре мы получаем левый нижний угол, когда у нас вообще какие-либо связи четкие, явные отсутствуют. Еще раз напомню, что мы стремимся к правому нижнему углу, когда у нас четко выделяются модули, каждый модуль решает конкретную задачу, и связи между модулями у нас явные.

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

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

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

То есть, давайте визуально это все еще закрепим. Итак, слой UI у нас представляет из себя только переиспользуемые компоненты. При этом крайне важно, чтобы какая-то бизнес-логика сюда не прорастала. То есть, внутри слоя использовать компоненты или модули категорически запрещено. Двигаемся на слой выше – компоненты. Компоненты не могут внутри себя содержать модули. Однако они могут внутри себя содержать компоненты из слоя UI. То есть, чаще всего это компоненты, которые могут обладать бизнес-дикой, но она, как правило, либо очень простая, либо отсутствует вообще. Если посмотреть, например, это карточка пользователя, которая может использоваться, например, в админке, на странице профиля или еще где-то. Product item – это, соответственно, сам вот этот элемент товара. Может быть комментарий, может быть таблица с документами, может быть карточка с рейтингом, где мы, например, можем поставить оценку от одной звезды до пяти. И вот эту вот карточку со звездами мы можем использовать на странице товара, на странице профиля, на странице, например, документа или еще где-то. То есть, здесь опять же, компоненты менее самостоятельные, чем на слоях выше.

Теперь давайте рассмотрим модули. То есть, здесь компоненты и вообще вот эти вот модули, они должны быть максимально самостоятельными, со своей зоной ответственности. Давайте пройдемся по примерам. Например, бесконечный список пользователей. Здесь мы используем `UserCard`, который был на слой ниже. Из вот этих карточек пользователей мы формируем список, навешиваем логику по бесконечной ленте. Тут у нас появляется API, которая этот список пользователей загружает. Здесь у нас появляется, например, `state`, в который мы этот список пользователей можем сохранить. Все зависит от технологии, которые мы используем: будь то React Query, тогда нам сохранять ничего не надо, либо же Redux, когда мы всех этих пользователей должны сохранить в `state`. Здесь же мы обрабатываем ошибки, если пользователи по какой-то причине у нас не подгрузились, то мы эту ошибку отрисовываем. Здесь же мы обрабатываем загрузку, то есть, в момент, пока пользователи подгружаются, мы показываем спиннер или же скелетон лоадер. Следующий модуль у нас `articleComments` – то есть, это уже список комментариев с возможностью их отправки, но привязанный конкретно к статье, со своими запросами, опять же, со своим стейтом. И внутри себя он переиспользует компоненты из слоя ниже и слоя `components`. Там у нас как раз был компонент `Comment`. Если приводить еще примеры, то это может быть форма регистрации, ее мы можем спрятать как в какую-нибудь модалку, так и поместить на какую-нибудь отдельную страницу. Это может быть дерево регионов, в котором мы можем выбрать нужный нам регион. Это может быть форма фидбэка для конкретного заказа и так далее. Думаю, примеры здесь более чем понятны.

Но пока что остается открытым вопрос: что должен представлять из себя этот модуль для того, чтобы он изолировал в себе необходимую всю логику? И как раз связи между остальными модулями были явными. Очевидно, у нас есть папка `modules`, и внутри у нас папка `registrationForm`, и внутри у нас будет все необходимое для работы этого модуля, то есть API, какие-то константы, хелперы, `store` – у нас не разбросаны по всему проекту, они изолированы именно в этом модуле. Один такой модуль может состоять у нас из большого количества компонентов, в принципе, не ограниченного. Важно, чтобы все эти компоненты решали одну конкретную задачу. То есть, здесь у нас может быть checkbox для принятия какого-то пользовательского соглашения, кнопка регистрации, сама форма, например, `input` для ввода номера со своей валидацией. При этом общий компонент номера у нас может лежать слоем ниже, в папке `components`, а компонент, который лежит в этом модуле, он решает задачу именно ввода номера при регистрации. То есть, здесь будут какие-то специфичные ошибки, специфичная, может быть, валидация, обработка ввода номера и так далее. В папке `api` у нас будут находиться запросы, например, на регистрацию или получение каких-то данных о пользователе, если у него, возможно, уже какие-то аккаунты есть, там, чтобы выдать, например, подсказку. То есть, как видите, все, что специфично связано именно с регистрацией, мы прячем внутри модуля. У нас не разбросаны эти запросы по всему проекту, как в предыдущей архитектуре. За счет этого мы достигаем как раз правого нижнего угла. Вы можете заметить, что модули у нас внутри себя содержат кружки одного цвета. Это значит, что модуль решает одну задачу. За счет того, что мы изолировали запросы, компоненты, константы, хелперы внутри одного модуля, мы достигли того, что они все решают одну задачу, тем самым мы раскрасили их в один цвет. То есть, если двигаться дальше по папкам, то здесь у нас могут быть хелперы, например, с валидацией формы, подготовкой данных для отправки на сервер для того, чтобы пользователя зарегистрировать. Ну и всякие такие вспомогательные функции. С папкой `store` здесь тоже все предельно просто: это наш экшены, редюсеры, асинхронные функции, саги, эпики, селекторы для получения данных из стейта и вся, все, что связано непосредственно со стором.

Если подытожить, то мы получаем структуру компонентов, функций, хелперов, запросов, которые решают одну конкретную задачу: заполнение формы регистрации с последующей отправкой данных из этой формы на сервер для того, чтобы пользователя нового зарегистрировать. Но здесь есть один очень важный момент: нам необходимо все вот эти внутренности как-то изолировать внутри модуля и запретить отдавать их наружу, потому что снаружи они все не нужны. Иначе мы получим опять те же самые неявные хаотичные связи. И вот здесь мы должны использовать Public API, так называемый, с помощью которого мы можем из модуля достать только то, что предусмотрено разработчиком. В корне модуля у нас создается `index.js` файл, внутри которого у нас будет реэкспорт только тех компонентов и функций, которые нужны снаружи. То есть, в случае вот с формой регистрации, нам снаружи нужен главный компонент `registrationForm`. То есть, какой-нибудь там `input` для ввода номера или чекбокс для принятия соглашения нам снаружи не нужны, они все изолированы в рамках главного компонента. И также нам может понадобиться еще редюсер для того, чтобы в корневой редюсер его подключить. Все, все остальные компоненты, API, функции, хелперы мы оставили внутри модуля. И единственный способ что-то из этого модуля достать – это как раз обращение к вот этому Public API, из которого идет реэкспорт нужных вещей, нужных компонентов или же функций. По-другому никак внутрь модуля залезать ни при каких условиях. Да, и это даже по хорошему ограничить каким-нибудь линтером.

То есть, еще раз, давайте подытожим: внутри модуля у нас много-много всего, модуль может быть достаточно сложным, но при этом снаружи нам это все не нужно. Снаружи нам нужны лишь определенные компоненты, лишь определенные функции. И вот Public API как раз позволяет это все инкапсулировать внутри модуля, а наружу отдать только то, что разрешено. Это похоже на инкапсуляцию, как раз в объектно-ориентированном программировании, когда в классе у нас может быть много приватных методов, снаружи мы к ним обращаться не можем. Однако внутри самого класса мы можем их смело использовать, а обращение к каким-то методам класса осуществляется только за счет публичных методов. То есть, мы одну из главных концепций – инкапсуляцию – применили к фронтенду и к нашим модулям. За счет вот этой изоляции и Public API.

С модулями разобрались. Теперь давайте посмотрим на страницы. По сути, это такие же модули, но верхнего уровня. Структура здесь примерно такая же. У нас есть главный компонент, например, `articlePage`, и могут быть другие компоненты, которые внутри себя содержат какие-то куски страницы, например, `header` страницы, `footer` страницы, если он какой-то специфичный. Также специфичные могут быть запросы для этой страницы, константы, хелперы и `store`. Здесь все то же самое. Но самое главное отличие от модулей в том, что страница должна оставаться максимально тонкой. То есть, по факту, это в идеале должно быть просто перечисление модулей и компонентов. Насколько это возможно, всю бизнес-логику, все запросы, все хелперы, стоит необходимо выносить на уровне модулей, и эти модули уже на уровне страницы использовать.

И что мы по итогу имеем? Мы получаем однонаправленный поток данных. Страница внутри себя использует модули, модули внутри себя используют компоненты, а компоненты строятся из базовых кирпичиков – из UI-компонентов по типу кнопок, инпутов и так далее. При этом важно, нижележащие слои не могут использовать вышележащие слои. И также важно, что модуль не может использовать внутри себя другой модуль, чтобы избегать вот этих вот самых кольцевых зависимостей. Поток данных всегда должен быть однонаправлен. Если нам необходимо объединить два модуля, то мы делаем Это уже на уровне страницы. При этом страницы, модули, компоненты, UI – это не структура папок, как я говорил ранее. Папки у нас могут здесь быть тоже разные. У нас может быть также какой-то глобальный `state`, в принципе, он скорее всего появится, какая-то глобальная конфигурация, глобальные хелперы, которые могут использоваться во всех модулях, опять же, те же ассеты, какие-то общие запросы, все, что не специфично для конкретного модуля. У нас вот также опять же свалки в кучу здесь и находится.

Теперь поговорим о преимуществах данного подхода над предыдущим, классическим. У нас здесь появляется изоляция за счет Public API, однонаправленный поток данных и переиспользуемость за счет того, что у нас появляются слои. И также эти модули удалять из кода, потому что у нас страницы тонкие, у нас идет просто перечисление модулей, и при необходимости один из модулей мы всегда очень легко можем удалить. Также эти модули легко прятать за фича-флагами, что хорошо вписывается в Trunk-based подход при разработке.

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

Если возвращаться к вот этой вот схеме, то здесь мы очень близки к тому, чтобы получить идеальную картинку – правый нижний угол. То есть, модули у нас отчасти проблему решают. Мы получаем как раз тот самый вид, что у нас один модуль решает одну конкретную задачу, и связи между модулями в принципе становятся прозрачными. У нас они все группируются на уровне страниц, явные связи между модулями не исключаем. Однако вот те самые функции, хелперы, `store`, глобальные – они по-прежнему могут образовывать вот эти неявные связи, и проблемы мы решили решать частично. Дальнейшем мы рассмотрим подходы, которые решают эту проблему в том числе.

То есть, давайте проведем сейчас сравнительный анализ. Для классической архитектуры мы выделили вот такие пять аспектов, когда ее стоит использовать. Давайте посмотрим теперь, когда использовать модульную архитектуру и какие недостатки, преимущества у нее есть. Во-первых, такой подход в 99% случаев лучше, чем первый рассмотренный, потому что он не намного сложнее. В приложении начинают выделяться четко модули и связи между ними. Команда фронтов 3-6 человек. Здесь я написал уже можно сказать от балды. Понятное дело, что это все зависит индивидуально от проекта, от его сложности, от времени поддержки, и абсолютно не значит, что если я здесь написал, что команда фронтов 3-6 человек, что именно при таком количестве людей этот подход необходимо использовать. Однако, как мы выяснили, здесь тоже есть проблемы. Для продукта с большой сложной бизнес-логикой, большим количеством бизнес-сущностей, бизнес-фич, такой подход не подходит, потому что рано или поздно вот эти неявные связи все равно начнут появляться. Как я уже сказал, чуть позже мы рассмотрим методологию, которая эти проблемы как раз в том числе решает.

И на этом с базовой модульной архитектурой, простой архитектурой мы заканчиваем и переходим к Atomic Design – методологии, которая была популярна три-четыре года назад. Это очень похоже на модульную архитектуру, которую мы рассмотрели ранее, однако со своей спецификой. Все приложение глобально разбивается у нас на 5 слоев: это атомы, молекулы, организмы, шаблоны и страницы. И здесь логика примерно такая же, как и в модульной архитектуре. Молекулы у нас строятся из атомов, организмы из молекул, шаблоны из организмов, и страницы уже есть конкретных шаблонов. С точки зрения разработки, мне эта методология не очень нравится, потому что на текущий момент есть намного лучшие методологии, которые мы рассмотрим дальше. Однако она себя хорошо зарекомендовала в дизайне. Дизайнерам удобно клепать макеты как раз из вот этих атомов, молекул, организмов, и в принципе, вот в методологию дизайнеров это вписывается хорошо. А с точки зрения разработки, сама методология не сильно отличается от той, которую мы рассмотрели ранее.

Итак, давайте пройдемся по слоям и посмотрим, что они из себя представляют. Начнем с атомов. Атомы – это наш слой, это как раз крутые кнопки, селекты и дропдауны, переиспользуемые UI-компоненты без бизнес-логики, кирпичики, из которых мы строим все остальное. Здесь думаю, все понятно. Молекулы – это уже более сложные компоненты, которые как правило, тоже не обладают бизнес-логикой, и они клепаются как раз из атомов. На слайде у нас как раз компонент, который состоит из лейбла, инпута и кнопки, то есть состоит из атомов. Организмы – это уже более сложные компоненты. Если сравнивать с предыдущей методологией, то это уже модули, которые обладают своей бизнес-логикой и состоят они как раз из молекул. Следующий слой – это шаблоны. Шаблоны задают уже непосредственно layout, структуру той или иной страницы, но без привязки к конкретному контенту. То есть, можно считать, что это абстрактная страница, и в дальнейшем, используя эти шаблоны, мы уже реализуем страницу, в которую подставляем нужные нам организмы. Скриншоты из официальной страницы документации с тем, как выглядит у нас структура, вы можете увидеть сейчас на слайде. Если что, ставьте паузу и смотрите. Но мы давайте проведем сейчас аналогию с модульной архитектурой. То есть, здесь во многом слои в принципе повторяются, но у нас отсутствуют шаблоны. То есть, модульную архитектуру мы можем прикрутить те же темплейты, и в принципе, мы получим то же самое. Но в данном случае мне не нравится привязка вот этим вот химическим терминам: атом, молекула, организм. Как по мне, модули, компоненты, UI – для разработчика это более понятные, очевидные термины, хотя, в принципе, это вопрос привычки.

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

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

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

Итак, давайте приступим. В первую очередь, выделим три таких понятия, как слой, слайс и сегмент. Слой, в свою очередь, представляет из себя модуль, примерно такой же, как мы рассматривали в предыдущих подходах. Слоев по этой методологии строго ограниченное количество — их 7 штук. При этом часть из этих слоев являются опциональными, то есть не обязательными. Каждый слой обладает своей зоной ответственности. И как вы можете заметить, здесь слои уже бизнес-ориентированы: entity, сущности, фичи, виджеты. Здесь иерархия тоже ступенчатая, то есть entity не может внутри себя использовать фичи, фичи не могут внутри себя использовать виджеты. То есть здесь вышележащий слой строго может использовать нижележащие. При этом фича другую фичу использовать тоже не может. Мы используем только нижележащие слои для того, чтобы получить линейный поток данных, однонаправленный поток данных. Про каждый слой мы поговорим очень подробно. Пока давайте рассмотрим основные.

Каждый слой внутри себя содержит слайсы, так называемые модули. В случае entity — это конкретные бизнес-сущности: пользователь, статья, заказ, документ, профиль. И каждый из таких модулей внутри себя содержит сегменты. Это все те же API, компоненты, какие-то константы, хелперы. Чуть позже мы про это тоже поговорим, но здесь уже привязка идет не абстрактно каким-то глобальным моментом, а конкретным бизнес-сущностям: запросы, которые относятся к пользователю, запросы, которые относятся к товару, хелперы, которые относятся к товару и так далее.

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

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

Давайте обсудим, какие у нас есть сегменты. Каждый слайс внутри себя содержит следующие сегменты: UI — это непосредственно наши компоненты. Model — это бизнес-логика, это взаимодействие, состоит он из селекторов, экшенов. Lib — это как раз всех хелперы, какие-то вспомогательные функции, которые могут использоваться внутри модуля. Config — достаточно редко встречающийся сегмент, но, однако, думаю, по названию понятно — это какая-то конфигурация нашего модуля, если она требуется. API — это непосредственно запросы на сервер, которые требуются для данного модуля. Ну и константы — это тоже какие-то константы, которые нужны нам в конкретном модуле.

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

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

Итак, shared — это максимально переиспользуемые модули: UI-компоненты, кнопки, модалки и так далее, какие-то хелперы, которые не привязаны к бизнес-логике, конфигурация приложения. В общем, shared-слой можно охарактеризовать так: он не привязан к бизнес-логике, и его можно переместить из одного проекта в другой. При этом один проект может являться интернет-магазином, а другой — социальной сетью. То есть упоминаний бизнес-логики никаких не должно быть.

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

Фичи — как я уже тоже говорил, это пользовательские сценарии, которые несут конкретную бизнес-ценность: подписка на пользователя, поставить лайк, оценка товара, например, с проставлением количества звезд от 1 до 5.

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

И остается последний слой — это слой App. Это инициализирующая логика приложения. Здесь определяются провайдеры, роутер, конфигурация, глобальные стили, если они есть, глобальные декларации с типами, корневой компонент. То есть это своего рода entry point нашего проекта. То есть, по итогу, мы получаем вот такую вот иерархию с однонаправленным потоком данных.

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

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

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

Перед тем, как подробно про это все поговорить, давайте вспомним рисунок, который мы рассматривали в первом самом подходе, классическом. Здесь у нас возможно неявные циклические зависимости между модулями, связи хаотичные, неявные. И вот этих вот модулей нету, и нету четких границ, нет изоляции. Компоненты не всегда получается переиспользовать. И вот сейчас мы попытаемся понять, как это все решить, как раз рассмотрим это все на примере концепции ООП и попытаемся применить это все к Feature Slice Design.

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

Инкапсуляция, вот та самая модульность и изоляция, у нас достигается за счет public API, за счет того самого публичного интерфейса. То есть к внутренностям модуля извне нельзя. Единственный способ что-то вытащить из модуля — это этот самый публичный интерфейс. И за счет этого модуль как раз внутри себя изолирует то, что снаружи абсолютно не нужно, это какие-то составные части модуля.

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

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

Первый блок — это shop product details, это основная информация о товаре. Второй блок — это с комментариями, которые подгружают для конкретного комментариев во-первых весь список комментариев, во-вторых, внутри этого блока уже присутствует форма для отправки нового комментария. И третий блок — это рекомендации товаров. То есть вы, например, хотите купить себе ноутбук, вы открыли Мак, и вам снизу еще предлагают на выбор там Acer, MSI и так далее. То есть страница внутри себя содержит три виджета или фичи. Здесь, в зависимости от того, как этот функционал реализовать, вся необходимая логика в этих виджетах уже реализована. Максимум, что они могут — это принять props, например, ID самого продукта, чтобы потом внутри уже всю необходимую информацию подгрузить самостоятельно.

Внутри себя они используют какое-то функционал и entity. То есть, очевидно, что здесь используется entity product и непосредственно уже из этой entity product достаются характеристики и описание. Это два компонента, который на вход просто ожидают какие-то props и в зависимости от этих props отрисовывают описание и характеристики. Комментарий, который относится непосредственно к блоку, они тоже внутри себя содержат какие-то другие компоненты. Comment list — это список самих комментариев, компонент, который на вход принимает комментарии, просто их отрисовывает. Comment form у нас, в свою очередь, может быть как entity, так и фичей. В теории, эту форму могли назвать Add Comment Form. Здесь зависит от того, насколько она самостоятельно реализует ли она всю необходимую логику внутри или предоставляет эту логику наружу. Тут опять же зависит от самостоятельности компонента. Ну и product recommendations внутри себя уже используют product list. Опять же, это просто массив компонентов без какой-либо логики, который просто вот отрисовывает этот массив. Здесь опять же, не столь важно, как я назвал эти компоненты, как они друг друга используют. Здесь важно посмотреть на поток данных.

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

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

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

В документации есть вот такой вот скриншот, на котором описано, когда и в какой слой мы размещаем тот или иной модуль. Здесь список вопросов, которые очень очевидны. Давайте по ним пройдемся.

Shared-слой: здесь мы задаем вопрос: "Этот модуль он не относится к бизнес-логике и является общим, переиспользуемым кодом?". Если на этот вопрос ответ "да", то мы помещаем наш модуль внутри слоя.

Слой Entity: здесь мы задаем вопрос: "Это относится непосредственно к бизнес-сущности? Например, этот модуль, компонент, хелпер, который мы хотим сделать, он относится только к товару, или только к заказу, или только к пользователю? И при этом, вот этот хелпер, который мы хотим сделать, он еще и может использоваться где-то в фичах, где-то в виджетах?". Тогда очевидно, мы помещаем его в entity.

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

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

Итак, слой Shared. Как мы выяснили, здесь нет какой-то бизнес-логики, и здесь находится максимально переиспользуемые кусочки нашей системы. Shared-слой — нет отдельных слайсов, нет отдельных модулей. Здесь сразу располагаются сегменты: const, lib, config, API, UI. UI находится как раз наши компоненты, переиспользуемые: это кнопки, тексты, спиннеры, input и модалки. Тут типы и все, что с этим связано. То есть, 105 раз уточню: компоненты без привязки к бизнес-логике, тот самый UI-кит.

Давайте теперь посмотрим другие директории. В API у нас может находиться instance Axios, созданный, настроенный с хедерами, какими-то дефолтными настройками безопасности. Может быть instance RTK Query, React Query, настройки Fetch, какие-то базовые. В папке Config у нас может находиться, например, конфигурация i18n или того же Storybook. С константами, думаю, понятно. В папке Lib сейчас находятся какие-то переиспользуемые функции. Это могут быть хуки, какие-то хелперы для работы с контекстом, какие-то хелперы для работы со стором, с URL, с объектами, с массивами, с деревьями, со связанными списками. В общем, не привязаны опять же к бизнес-логике функции, хелперы, вспомогательные. Shared-слоем, думаю, понятно. Давайте двигаться дальше.

Теперь посмотрим опять же на конкретных примерах. Документация нам показывает такой пример: здесь находится карточка поста. Но обратите внимание, в ней только общие части, которые специфичны для всех постов. А вот какие-то фичи по типу лайков, каких-то вспомогательных кнопок, кнопки поделиться, кнопки подписаться — их нет. Они представлены в виде слотов, в которые мы фичи уже где-то на уровне выше можем прокинуть. И сразу приведу пример: допустим, ВКонтакте у нас есть посты как в группах, так и на странице конкретного пользователя. Визуально они вроде бы как выглядят одинаково, но при этом, когда мы ставим лайк или нажимаем кнопку "подписаться", скорее всего, работает разная логика, отправляются запросы на разные эндпоинты. В одном случае — это подписка на пользователя, в другом — подписка на группу. И скорее всего, на бэкенде это обрабатывается по-разному. И соответственно, потом, когда мы будем вот эти карточки уже в финале, финальному виду приводить, мы создадим User Post Card и Group Post Card, и уже вот в эту entity, как в качестве слотов, перекинем нужные фичи со своей логикой и со своими запросами на конкретные бэкендовые эндпоинты.

Теперь посмотрим, как это все выглядит с технической точки зрения. Внутри слоя у нас находятся слайсы — конкретные модули. Они относятся к статье, комментарии, к заказу, к продукту или к пользователю. Внутри каждого из таких слайсов находятся сегменты — те самые папочки: API, components, lib, model. Опять же, эти папки в зависимости от проекта можно назвать по-разному. Где-то вместо components называют просто UI, где-то называют component. Опять же, название папок — это уже дело третье. Все зависит от того, как вы привыкли называть в команде. Здесь можно все это гибко настраивать.

Давайте еще раз закрепим, что находится в каждом сегменте. В API у нас находятся запросы и все, что с ними связано: запрос на создание нового пользователя, запрос на подгрузку каких-то данных о пользователе, Или, например, запрос на обновление пользователя. Здесь у нас находится CRUD-операции, которые привязаны к сущности пользователя. Здесь также могут находиться мапперы, которые подготавливают данные к отправке на сервер или наоборот, преобразовывают их к нужному формату для работы уже на фронтенде.

С компонентами здесь, думаю, тоже все понятно. Здесь находятся компоненты, которые привязаны конкретно к сущности пользователя, например, User Info. Можем использовать в админке, можем использовать в профиле, можем использовать еще где-то. То есть компонентов здесь может быть много: User Settings Info, User Card, User Avatar, в зависимости от того, какие компоненты нужны конкретно в вашем случае.

Давайте теперь полную структуру посмотрим. В папочке Lib у нас лежат хелперы, например, Get Full Name Helper, который склеивает имя и фамилию. Также здесь могут находиться хуки, например, useAuthData хук, который в компоненте позволяет получить информацию о пользователе. Ну и папочка Model — это уже бизнес-логика, работа с это опять же на примере с Redux: селекторы, слайсы, типы, экшены и все, что с этим связано.

И самое главное, что каждый из таких слайсов, каждый из таких модулей должен обладать public API. То есть, обратите внимание, мы не отдаем здесь абсолютно все. Мы отдаем лишь те компоненты и хелперы, которые снаружи действительно нужны. Все, что используется внутри, мы оставляем изолированным. Со слоем Entity разобрались.

Теперь переходим к фичам. Как вы можете заметить, в примерах здесь как раз идут те самые лайки, какие-то менюшки с действиями, кнопка "подписаться". То есть уже какой-то конкретный бизнес-функционал, который приводит к какому-то результату. И одна фича — один модуль. Фичи должны решать одну задачу, то есть выполнять какую-то одну функциональность. Структура здесь такая же, состоит из таких же сегментов. И в качестве примеров можно привести: авторизация по e-mail, смена языка, кнопка, которая открывает список уведомлений. А сам список он может находиться, например, в entity Notification. Like, dislike, кнопка "подписаться", кнопка Share — это вот все примеры каких-то бизнес-фичей.

Со фичами разобрались. Теперь перейдем к виджетам. Обратите внимание, что виджет — это как раз комбинация той самой entity, которая внутри себя содержала, потому что есть слоты. И обратите внимание, что в эти слоты как раз были подставлены фичи — те самые кнопки "подписаться", "лайк" и "дизлайки" и другие. То есть виджеты в данном случае — это уже прям самостоятельные смысловые блоки, которые комбинируют нижние слои. Как написано у нас в документации, если приводить еще примеры, то вот, например, ВКонтакте у нас верхний хейдер — это виджет, левое менюшка с ссылками с каким-то функционалом — это виджет, пост, который мы можем оставить на стене или в группе — это тоже виджет. Причем разные. Один виджет у нас называется User Post, другой виджет у нас называется Group Post, потому что с точки зрения функционала и работы с бэкендом они, скорее всего, будут разные.

Что фичи, что виджеты, что страницы состоят из тех же сегментов: это API, config, Model, components и так далее. Со страницами, думаю, здесь максимально все понятно. Страница собирается уже непосредственно из виджетов и фичей. И как я говорил уже ранее, страница всегда должна оставаться максимально тонкой. То есть лишней бизнес-логики, лишнего кода здесь быть не должно. Все, что можно унести на слои ниже, должно уноситься именно туда. То есть, в идеале, страница должна представлять из себя перечисление виджетов и фичей, обернутых в какой-то layout.

Ну и напоследок, еще раз поговорим про самый верхний слой — про слой App, который хранит в себе инициализирующую логику приложения. Про него я уже все, что необходимо, сказал. Это, грубо говоря, entry point, инициализирующая точка входа в наше приложение. Здесь хранятся все провайдеры, глобальные стили, глобальные типы и все, что не будет никогда, ни при каких условиях, использоваться где-то ниже.

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

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

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

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

Также сама методология не зависит от технологий, которые вы используете в проекте. Как я говорил ранее, используете Vue, React или Svelte — здесь не столь важно. Единственное, наверное, Angular за счет своей специфики не очень подойдет. Однако и на нем, я думаю, можно попытаться FSD применить. Неважно, какой стоит менеджер, неважно, какие инструменты для работы с API вы используете. Любой из этих инструментов можно привязать к методологии, поскольку она охватывает более верхнеуровневые моменты.

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

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

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

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

Ну и опять, если кратко подытожить и сравнить с простым модульным подходом, то Feature Slice Design выигрывает, ну, практически в 100% случаев за счет четкого ориентира на бизнес, за счет выделения сущностей, фичей, виджетов. И как раз все недостатки, которые были у нас в простой модульной архитектуре, FSD в принципе решает. Здесь, опять же, команда фронтенда — три плюс, я написал, можно сказать, от балды. Опять же, все зависит от проекта. Если у вас проект долгоживущий, то можно выбирать FSD. Если у вас прототип, проектная работа, то берем более простые варианты.

Итого, мы на данном этапе рассмотрели четвертый вид архитектуры, который применяется на фронтенде, и остается как раз распределенная архитектура, когда мы использовали микросервисы и монорепозиторий плюс модульный подход. И рассмотрим сначала монолитное приложение. Это как раз все четыре предыдущие варианта, которые мы рассматривали. У нас весь исходный код, вся конфигурация, все тесты, сборка, CI/CD, пайплайн и все вот эти моменты — они, в принципе, принадлежат одному приложению, большому, над которым работает, может, 5, 10, 15, 20 человек. В какой-то момент кодовая база становится слишком большой, тесты занимают много времени, сборка тоже. Хотелось бы ее как-то ускорить. Вносить какие-то изменения в конфигурацию слишком сложно, потому что проект слишком большой и постоянно что-то ломается при попытке внести какие-то изменения. Ну и плюс, проект, в котором используется, например, React и Redux, очень трудно его переписать на какие-то другие технологии. Если проект очень большой, рынок постоянно обновляется, и хочется использовать новые актуальные технологии, но обновление иногда стоит очень дорого.

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

Мы вот те самые большие части нашего одного монолитного проекта выделяем в отдельные микросервисы, в отдельные проекты: админ-панель, интернет-магазин, новостная лента, B2B-система. При этом мы должны создать определенный entry point, который будет отвечать за роутинг. То есть это определенная точка входа нашего приложения, которая перенаправляет уже непосредственно пользователя в нужный микросервис. Но здесь есть один нюанс: у нас в каждом из сервисов, вероятно, будет использоваться общий UI-кит, общая какая-то конфигурация, общие модули тоже могут быть, например, какие-нибудь хеддеры, сайдбары. И помимо самих сервисов, мы создаем также отдельные пакеты. Пакеты включают в себя тот самый UI-кит, модули, общую конфигурацию и любые другие вспомогательные моменты, которые вам могут понадобиться, например, какая-то конфигурация для CI-инфраструктуры, для логирования, общая настройка Webpack, допустим, или же Storybook, а который в каждом из проектов можно как-то расширить.

UI, думаю, понятно — это те самые кнопки, input и модалки, про которые мы в ходе этого курса уже неоднократно говорили. То есть все это мы выносим в отдельный пакет со своей сборкой, со своим Storybook, чтобы мы могли независимо его использовать. И при этом, если нам понадобится опубликовать, допустим, UI-модуль, это по сути отдельный пакет. С Feature Slice Design, здесь у нас будут все слои: pages, widgets, features, entities и shared, кроме App. В качестве слоя App будут выступать как раз отдельные микросервисы. При этом, если в одном из микросервисов вы используете, например, React, а в другом из микросервисов Vue, то вы можете создавать разные пакеты: React modules, View modules, Angular modules, Svelte modules и так далее. И в нужных проектах эти модули переиспользовать. Если модули нужны в разных проектах, например, модуль, который написан на React, нужен и во Vue-проекте, и в React-проекте, то там уже можно что-нибудь придумать и обычным монтированием в нужное место компонент просто рендерить.

Вся конфигурация также выносится в отдельные пакеты. Под конфигурацией подразумеваю всю инфраструктуру, которая обычно на фронтенде используется: это сборка, тестирование, линтинг, Storybook и прочие инструменты. При этом важно, чтобы все это располагалось в одном едином, так называемом монорепозитории. То есть у вас в Git хранятся четыре разных проекта, а один большой, в котором отдельно лежат сервисы, отдельно пакеты и отдельно entry point. В теории, это можно делать и в разных репозиториях, но тогда вот эти все пакеты придется в npm публиковать, следить за версионированием, что добавляет много геморроя, и тогда все преимущества данного подхода теряются. Поэтому монорепозиторий практически в 100% случаев — маст-хэв.

Теперь возникает вопрос: с помощью каких технологий все это делать? Не здесь существует ряд инструментов, которые позволяют в принципе вот эту всю микросервисную архитектуру реализовать. Во-первых, это Lerna или Yarn Workspaces для организации монорепозитория. Во-вторых, это Nx.js, это Lerna, есть еще также Bit, Single SPA и Webpack Module Federation. Это взаимозаменяемые инструменты, то есть все сразу использовать, конечно, не надо, надо выбрать что-то одно и это уже применять. Про эти инструменты быстро уже не расскажешь, это надо записывать отдельный большой курс. Но думаю, в каком-то базовом варианте, каких-нибудь статьях вы сами можете про них почитать.

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

Но есть огромное "но". Не все так просто на самом деле. Даже касательно четвертого пункта, для того, чтобы все это реализовать, нужна сложная инфраструктура и определенная экспертиза. Возможно, даже потребуется отдельная команда, которая будет заниматься как раз развитием и поддержкой того самого монорепозитория, всей инфраструктурой и, соответственно, развивать ее. Ну и также, при таком подходе размазываются зоны ответственности. У рядового разработчика возникают вопросы: должен ли он лезть в соседний проект, смежный? Должен ли он править UI-кит? Должен ли он заниматься поддержкой какой-то конфигурации модулей, особенно если в этом экспертизы нет? Могут возникать такие вот вопросы, потому что крайне, крайне редко бывает так, что одна команда занимается только одним проектом. Как правило, какие-то вот зоны ответственности они пересекаются.

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

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