Open azinit opened 3 years ago
Возможно нужен только гайд
А возможно отдельно туториал (для демонстрации), а отдельно гайд (с конкретными шагами и советами практическими)
есть какие-нибудь гайд в стиле до -> после с поэтапным переходом (когда у нас было вообще все наваленно в components, store)?
С чего стоит начинать мигрировать существующий проект, какие-то идеи на плановый рефакторинг для приведения проекта к фьюче-слайс виду?
import WIP from '@site/src/shared/ui/wip/tmpl.mdx'
В статье агрегируется опыт нескольких компаний и проектов по переезду на feature-sliced с разными изначальными условиями
Насколько нужен переезд? "Смерть от тысячи порезов" и Техдолг. Чего не хватает? Чем может помочь методология?
См. доклад Илья Климова про необходимость и порядок рефакторинга
Стоит понимать, что все проекты очень уникальны - поэтому здесь описан максимально общий план по переходу проекта от легаси на архитектуру, которая больше завязана на предметную область
Все шаги стоит воспринимать и адаптировать конкретно под ваш случай
При этом каждый шаг, хоть и уводит нас от Big Ball of Mud архитектуры, но и требует затраты на рефакторинг
Поэтому учитывайте затраты и профиты на каждый пункт, когда будете составлять план
Допустим, имеем примерно такой по структуре типичный legacy-проект:
├── products/
| ├── components/
| ├── containers/
| ├── store/
| ├── styles/
├── checkout/
| ├── components/
| ├── containers/
| ├── helpers/
| ├── styles/
└── src/
├── actions/
├── api/
├── epics/
├── components/
├── containers/
├── constants/
├── i18n/
├── modules/
├── helpers/
├── pages/
├── routes/
├── utils/
├── reducers/
├── redux/
├── selectors/
├── store/
├── styles/
├── App.jsx
└── index.jsx
:::info Важно
Часто в проектах, модули, имеющие одну и ту же ответственность, называются по-разному или расположены в совсем разных местах
Чтобы избежать путанницы в разработке, следует унифицировать этот код и сам нейминг
:::
/src/
helpers/utils
=> helpers
(или лучше сразу lib
)redux/store/stores/data
=> store
pages/routes
=> pages
- ├── products/
- | ├── components/
- | ├── containers/
- | ├── store/
- | ├── styles/
- ├── checkout/
- | ├── components/
- | ├── containers/
- | ├── helpers/
- | ├── styles/
+ └── src/
├── actions/
├── api/
+ ├── components/
+ ├── containers/
├── constants/
├── epics/
+ ├── i18n/
├── modules/
+ ├── helpers/
+ ├── pages/
- ├── routes/
- ├── utils/
├── reducers/
- ├── redux/
├── selectors/
+ ├── store
+ ├── styles/
├── App.jsx
└── index.jsx
Если вашему проекту относительно "повезло", вполне возможно, что вы находитесь уже на этой стадии:
└── src/
├── actions/
├── api/
├── components/
├── containers/
├── constants/
├── epics/
├── i18n/
├── modules/
├── helpers/
├── pages/
├── reducers/
├── selectors/
├── store/
├── styles/
├── App.jsx
└── index.jsx
Теперь следует посмотреть на то - как декомпозирована логика по проекту
:::info Важно
Чаще всего логика в проектах, относящаяся к одному домену в предметной области, излишне размазана по проекту
Чтобы устранить этот неприятный эффект, стоит расположить такие модули рядом
См. подробнее в "Handbook: Desegmented"
:::
# Плохо - логика модулей разбросана по проекту
- ├── components/DeliveryCard.js
- ├── containers/DeliveryCard.js
- ├── actions/delivery.js
- ├── epics/delivery.js
- ├── constants/delivery.js
- ├── getters/delivery.js
- ├── selectors/delivery.js
- ├── helpers/delivery.js
# Еще хуже - нет консистентности
- ├── actions/delivery.js
- ├── epics/delivery.js
- ├── constants/delivery.js
- ├── entities/delivery/{getters, selectors, constants}
# Хорошо - все, близкое по предметной области, находится рядом
+ ├── entities/delivery
+ | ├── ui/ # ~ components
+ | | ├── DeliveryCard.js
+ | ├── model/
+ | | ├── actions.js
+ | | ├── epics.js
+ | | ├── getters.js
+ | | ├── selectors.js
+ | ├── lib/ # ~ helpers
Разбираем свалку в store
, и распределяем по предметной области
Устраняем излишнее дробление на компоненты контейнеры
└── src/
├── components/
| ├── delivery/
| | ├── deliveryCard.js
| | ├── deliveryChoice.js
| ├── region/
| | ├── regionSelect.js
| | ├── regionMap.js
| ├── user/
| | ├── userAvatar.js
| | ├── userPicker.js
По итогу получаем
└── src/
- ├── actions/
├── api/
- ├── components/
- ├── containers/
- ├── constants/
- ├── epics/
+ ├── entities/{...}
+ | ├── ui
+ | ├── model/{actions, selectors, ...}
+ | ├── lib
├── i18n/
| # Временно можем положить сюда оставшиеся сегменты
+ ├── modules/{helpers, constants}
- ├── helpers/
├── pages/
- ├── reducers/
- ├── selectors/
- ├── store/
├── styles/
├── App.jsx
└── index.jsx
На самом деле - уже хорошо, когда проект разбит похожим образом:
└── src/
├── api/
├── entities/{...}
| ├── ui
| ├── model/{actions, selectors, ...}
| ├── lib
├── i18n/
├── modules/{helpers, constants}
├── pages/
├── styles/
├── App.jsx
└── index.jsx
Но тем не менее, если мы посмотрим на получившиеся сущности и компоненты, то заметим интересную вещь:
:::info Важно
В проектах часто нет явного разделения модулей по скоупу их ответственности
Из-за этого рядом лежат совсем разные по уровню знаний модули, и впоследствие возникают кросс-импорты
Чтобы этого избежать, следует явно разделять их по слоям, или хотя бы локализовать их зоны влияния
См. подробнее в "Handbook: Cross-imports"
:::
└── src/entities
├── product # entity
├── product-title # entity [partition]
├── add-to-cart # feature
├── upload-image # feature
|
├── search-bar # feature
├── viewer-picker # feature
├── header # widget
Попытаться явно выделить слои согласно методологии: shared
, entities
, features
, (widgets)
, pages
, (process)
, app
└── src/
- ├── api/
+ ├── app/
+ | ├── index.jsx
+ | ├── style.css
├── pages/
+ ├── features/
+ | ├── add-to-cart/{ui, model, lib}
+ | ├── choose-delivery/{ui, model, lib}
+ ├── entities/{...}
+ | ├── delivery/{ui, model, lib}
+ | ├── cart/{ui, model, lib}
+ | ├── product/{ui, model, lib}
+ ├── shared/
+ | ├── api/
+ | ├── lib/ # helpers
+ | | ├── i18n/
+ | ├── config/ # constants
- ├── i18n/
- ├── modules/{helpers, constants}
└── index.jsx
К этому моменту проект уже по большей части переведен на feature-sliced
└── src/
├── app/
| ├── index.jsx
| ├── style.css
├── pages/
├── features/
| ├── add-to-cart/{ui, model, lib}
| ├── choose-delivery/{ui, model, lib}
├── entities/{...}
| ├── delivery/{ui, model, lib}
| ├── cart/{ui, model, lib}
| ├── product/{ui, model, lib}
├── shared/
| ├── api/
| ├── lib/
| | ├── i18n/
| ├── config/
└── index.jsx
Но при переносе могут подсветиться и другие слабые места:
Насколько стоит их исправлять при рефакторинге?
Зависит, опять же, от оставшихся ресурсов и соотношения профита & затрат после исправления этих проблем
Я бы в данном случае даж говорил не про "entities", а про "features"
Тип чаще всего на этой стадии люди же мыслят не сущностями, а фичами
И пусть даже будет фича "карточка товара", но это куда лучше, чем "сущность добавления в корзину" (хотя всякое бывает офк)
Туториал по разделению приложения (by-types => feature-sliced?)
Чтобы не усложнять понимание