Всем привет! 13 ноября, в официальном Telegram канале Feature Sliced Design состоялся релиз новой версии архитектурной методологии. Он принёс в себе несколько важных, фундаментальных изменений, о которых мы сегодня и поговорим.
Если вы не знакомы с архитектурной методологией Feature Sliced Design, можете познакомиться с ней здесь.
Предисловие
Если Вы не подписаны на Telegram канал FSD, вероятно Вы даже не знаете, что минорная версия методологии обновилась, так как нигде на официальном сайте (кроме страницы "Migration Guide") нельзя прочесть новость об этом (прошло уже более месяца).
Полный Changelog обновления можно посмотреть только в соответствующем файле на Github.
P.S: Весь нижеописанный текст является субъективным мнением автора и подлежит конструктивной критике в любом виде.
Про "Pages first" подход
Главным изменением в новой версии методологии стал "pages first" подход.
Он предлагает ограничивать декомпозицию слайсов на слое "pages" (страницы), если "entities", "features" и "wigdets" внутри данной страницы не требуется пере использовать где-то ещё. Приведём пример.
До версии 2.1, разбиение слайсов для простого ToDo List приложения могло выглядеть следующим образом:
-
📁 app
-
📁 pages
-
📁 ToDo
-
-
📁 features
-
📁 todo
-
📁 ui
-
📄CreateTask
-
📄RemoveTask
-
-
-
-
📁 entities
-
📁 todo
-
📁 ui
-
📄TaskForm
-
📄TaskList
-
-
-
Теперь же, с приходом "pages first", предлагается сохранить логику, которая не будет пере использоваться, внутри слоя "pages" (структура проекта примерная, так как официального примера в документации к 2.1. версии не существует):
-
📁 app
-
📁 pages
-
📁 ToDo
-
📁 logic
-
📄CreateTask
-
📄RemoveTask
-
-
📁 ui
-
📄TaskForm
-
📄TaskList
-
-
-
Казалось бы, стало лучше, ведь "entities" и "features" из слоёв на глобальном уровне были перемещены внутрь страницы, в которой используются и область их применения стала сужена только данной страницей. Однако, такие изменения приводят к существенным проблемам.
Почему "pages first" подход плох?
На мой взгляд, отдавая приоритет к декомпозиции на уровне одного слоя, разработчик потенциально столкнётся со следующим набором проблем:
-
Становится сложнее выстраивать структуру большого объёма кода в рамках одного слоя. То есть, среднестатистический разработчик уровня "junior" или "pre-middle", вероятно выстроит архитектуру внутри слоя "pages" хуже, чем с чётким разделением слоёв, задающих свою структуру.
-
Появляется "дополнительный шаг" при поиске нужного участка кода.
Ранее, храня всю логику внутри глобальных "entities", "features" и "wigdets" слоёв, было очевидно, что для поиска условного UI компонента "BlogPost" мне необходимо перейти по следующему пути "entities/blog/ui/BlogPost", теперь же, с равной вероятностью этот компонент может лежать как по пути "entities/blog/ui/BlogPost", так и по пути "pages/Blog/ui/BlogPost". -
Вся четкая иерархия слоёв, выстроенная до 2.1 версии, фактически "множится на 0", так как польза от её использования появляется только при декомпозиции пере используемой логики, что вероятно, не является частым кейсом при разработке.
Вообще, данный подход существовал и ранее, просто не был официально задокументирован, однако официальный линтер Steiger всегда предлагал перенести код, который не импортируется куда-либо более одного раза на слоях "entities", "features" и "wigdets" на более вышележащие слои.
На что можно заменить "pages first"?
Разумным вариантом я считаю такой формат декомпозиции слоёв:
-
На слое "entities" хранятся слайсы с UI, мапперами, хэлперами и т.д. не несущими внутри себя сложной логики и действующими по принципу "черной коробки", отображая, либо преобразуя уже имеющийся набор данных. (Например верстка компонента шапки, сайдбара и т.д.)
-
На слое "features" хранятся слайсы с вариантами использования "entities" или "shared" слайсов либо собственного, самодостаточного кода без их использования. (Например кнопка "Logout").
-
На слое "widgets" хранятся слайсы с крупными логическими блоками, а также вариантами использования "entities", "features" или "shared" слайсов либо собственного, самодостаточного кода без их использования. (Например форма заказа товаров).
-
На слое "pages" хранится только "скелет", компонующий в себе слайсы из нижележащих слоёв и содержащий логику для их взаимодействия. Собственного аналога логики, сходной с той, что уже находится в слайсах на слоях "entities", "features" и "widgets", слайс на слое "pages" иметь НЕ может.
Конечно же вышеописанный вариант чётким разделением логики на глобальном уровне слоёв имеет свои недостатки, такой например как излишне "широкая" декомпозиция, когда для разработки простой фичи приходится порождать внушительный объём кода на нескольких слоях, вместо того, чтобы объединить всю логику в одном файле. Однако же, субъективно, он выглядит более логичным, чем "pages first".
Про легальный механизм кросс-импортов
Проблема, с которой сталкивается наверное каждый разработчик, который хотя бы раз использовал FSD, это кросс-импорты.
Кросс-импорт - ситуация, при которой в рамках одного слоя появилась необходимость импорта данных одного слайса в другой.
Ранее FSD предлагала экспериментальный подход к разрешению кросс-импортов, однако теперь механизм, благодаря которому есть возможность в явном виде их описать вышел в стабильную фазу. Рассмотрим же его подробнее.
В Changelog к 2.1 версии не написано ни слова об этом механизме кросс-импортов, однако в рамках дискуссии с FSD Core Team Member на Youtube канале, было сказано, что данный механизм является частью обновления.
Для разрешения механизма кросс-импортов появилась "@x"-нотация, позволяющая описать публичный API, через который возможно выполнить кросс-импорт. Возьмём пример из документации FSD:
-
📂 entities
-
📂 A
-
📂 @x
-
📄 B.ts — специальный публичный API только для кода внутри entities/B/
-
-
📄 index.ts — обычный публичный API
-
-
Затем код внутри entities/B/ может импортировать из entities/A/@x/B:
import type { EntityA } from "entities/A/@x/B";
Такой механизм является более явным, за счёт создания явной промежуточной абстракции, описывающей конкретные экспортируемые модули, однако на мой взгляд, здесь также имеются существенные проблемы.
Чем плох "легальный" кросс-импорт?
На самом деле, создав дополнительную абстракцию, ситуация значительно не улучшилась. Кросс-импорт так и остался кросс-импортом, со следующим набором проблем:
-
Увеличивается связность слайсов по отношению друг к другу. Их становится сложнее переносить между слоями (если появилась необходимость). "@x"-нотация никак не решает эту проблему.
-
Не исключаются циклические зависимости между импортируемыми модулями. (Когда модулю "А" нужен кросс-импорт из модуля "B", а модулю "B" нужен кросс-импорт из модуля "А").
Лучшим решением для вашего проекта, субъективно, будут является полный отказ от кросс-импортов, однако же, если это невозможно, предложенный командой FSD вариант является приемлемым, если вы понимаете вышеописанные недостатки.
Послесловие
Новая версия методологии FSD предоставила несколько интересных нововведений, но необходимо понимать, чем может быть чревато их использование. Я постарался внести немного ясности для тех моментов, которые вызвали вопросы лично у меня.
Если у Вас есть другие моменты, которые не были описаны в данной статье и требуют внимания - милости прошу в комментарии, буду рад обсудить =)
Автор: alexanderpankratov03