feature-sliced / documentation

🍰 Architectural methodology for frontend projects
https://feature-sliced.design
MIT License
1.53k stars 164 forks source link

INTEGRATION: Добавить туториал миграции с legacy (v1) #166

Open azinit opened 3 years ago

azinit commented 3 years ago

Туториал по разделению приложения (by-types => feature-sliced?)

Чтобы не усложнять понимание

azinit commented 3 years ago

Возможно нужен только гайд

А возможно отдельно туториал (для демонстрации), а отдельно гайд (с конкретными шагами и советами практическими)

azinit commented 3 years ago
есть какие-нибудь гайд в стиле до -> после с поэтапным переходом (когда у нас было вообще все наваленно в components, store)?
azinit commented 3 years ago
С чего стоит начинать мигрировать существующий проект, какие-то идеи на плановый рефакторинг для приведения проекта к фьюче-слайс виду?
azinit commented 3 years ago

sidebar_position: 3

import WIP from '@site/src/shared/ui/wip/tmpl.mdx'

Миграция с legacy

В статье агрегируется опыт нескольких компаний и проектов по переезду на feature-sliced с разными изначальными условиями

Зачем?

Насколько нужен переезд? "Смерть от тысячи порезов" и Техдолг. Чего не хватает? Чем может помочь методология?

См. доклад Илья Климова про необходимость и порядок рефакторинга

approaches-themed-bordered

Про что стоит помнить

Адаптируйте под ваш проект

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

Все шаги стоит воспринимать и адаптировать конкретно под ваш случай

Взвесьте "все за и против"

При этом каждый шаг, хоть и уводит нас от 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

Какой план?

1. Унификация кодовой базы

:::info Важно

Часто в проектах, модули, имеющие одну и ту же ответственность, называются по-разному или расположены в совсем разных местах

Чтобы избежать путанницы в разработке, следует унифицировать этот код и сам нейминг

:::

  1. Если у вас несколько директорий, где хранятся одни и те же сущности/фичи/компоненты - самое время переместить их в одно место
    • Чаще всего - это /src/
  2. Если у вас есть несколько типов директорий для модулей, который отвечают за одно и тоже - унифицируйте и их
    • 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

2. Собираем вместе излишне раздробленное

Если вашему проекту относительно "повезло", вполне возможно, что вы находитесь уже на этой стадии:

└── 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

Как исправить

  1. Разбираем свалку в store, и распределяем по предметной области

    • Еще хуже, когда некоторые экшены расположены "глобально", а некоторые в store папке
    • В этом случае, будет еще ощутимее польза от того, что мы соберем эти разбросанные сегменты вместе
  2. Устраняем излишнее дробление на компоненты контейнеры

    • Поначалу может показаться странным - "Почему мы кладем components/containers рядом с entity?"
    • Но если мы попробуем отдельно сгруппировать компоненты/контейнеры - то увидим, что так или иначе они будут группироваться по доменной области
      └── 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

3. Выделяем скоупы ответственности

На самом деле - уже хорошо, когда проект разбит похожим образом:

└── 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

4. Final ?

К этому моменту проект уже по большей части переведен на 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

Но при переносе могут подсветиться и другие слабые места:

  • Complexity & Discoverability: Логика некоторых модулей излишне разбухшая, из-за чего сложно их изучать и вносить правки
  • Explicit sharing: Часть логики из предметной области встречается несколько раз по всему проекту (и каждый раз реализуется заново)
  • Public API: При использовании модулей, местами используются приватные импорты - к внутренним частям модулей (нарушая Public API контракт)

Насколько стоит их исправлять при рефакторинге?

Зависит, опять же, от оставшихся ресурсов и соотношения профита & затрат после исправления этих проблем

См. также

azinit commented 3 years ago

Я бы в данном случае даж говорил не про "entities", а про "features"

Тип чаще всего на этой стадии люди же мыслят не сущностями, а фичами

И пусть даже будет фича "карточка товара", но это куда лучше, чем "сущность добавления в корзину" (хотя всякое бывает офк)

image