Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add post about cross-imports #711

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,117 @@ import WIP from '@site/src/shared/ui/wip/tmpl.mdx'

> Cross-imports appear when the layer or abstraction begins to take too much responsibility than it should. That is why the methodology identifies new layers that allow you to uncouple these cross-imports

## Introduction {#start}

Before we look at how to work with cross-imports to comply with the FSD methodology, we need to understand what "cross-import" is in principle.

**Cross-import** is a situation in which, within a layer, there is a need to import data from one slice to another.

:::warning Important

The article only deals with conflict situations when importing between **slices**. Within one slice, fragments can import each other, such a situation is not prohibited

:::

Let's look at an abstract example:

- `📂 entities`
- `📂 countries`
- `📁 lib`
- `📄 getCountriesList `
- `📁 cities`
- `📁 lib`
- `📄 getCitiesByCountryName`

In this example, to call the `getCitiesByCountryName` method, you need to get the result of calling the `getCountriesList` method.
It is not possible to obtain the result of the `getCountriesList` method directly, as this would violate the principle of low coupling and complicate the project's maintenance in the future.

How to resolve situations when there is a need to make a cross-import? Let's consider in this article.

## Cross-import solutions options {#solutions}

### Merging multiple slices into one {#slices-merge}
As you can see from the example above, the domain scope of the `getCitiesByCountryName` and `getCountriesList` methods is quite similar, which means it is possible to create a derived slice from the two existing ones, where both methods would be in the same directory.

Result of merging slices `countries` and `cities`:
- `📂 entities`
- `📂 geography`
- `📁 lib`
- `📄 getCountriesList`
- `📄 getCitiesByCountryName`

As you can see from the example, we got rid of the situation where one slice imports data from another and achieved high cohesion and low cohesion within a segment of one slice.

### Splitting slices into different layers {#slices-moving}
This option is more suitable for those cases where combining several slices into one is difficult or impossible.

It is likely that the slice into which you are importing data from another slice is quite logically rich and can be moved to a higher layer in order to eliminate their connectivity within a single layer.

The result of splitting the `countries` and `cities` slices into different layers:
- `📂 entities`
- `📂 countries`
- `📁 lib`
- `📄 getCountriesList `
- `📂 features`
- `📁 cities`
- `📁 lib`
- `📄 getCitiesByCountryName`

By moving the `cities` slice to the `features` layer (not necessarily to it, any layer higher in the hierarchy will do), we have more specifically specified its domain area and made it possible to have a correct import mechanism from the point of view of the layer hierarchy.

### Code duplication {#duplication-code}
If the amount of data you are going to import from another slice is small, it is not a bad idea to duplicate it into the current slice. However, this option should not be a priority among all the above.

## But what if cross-import is inevitable? {#cross-import-is-inevitable}

:::caution Attention

The information in this section **is not a guide to action**. It does not reflect the official position of the authors of the Feature Sliced ​​Design methodology.
However, in exotic situations, the information in this section may be useful

:::

If your project requires cross-import and the two methods described above did not help, the Feature Sliced ​​Design community offers the following solution to the problem

- `📂 entities`
- `📂 countries`
- `📁 lib`
- `📄 getCountriesList `
- `@x`
- `📁 cities`
- `📁 lib`
- `📄 getCitiesByCountryName`
- `@x`

### @x {#export-private-api}
**@x** - this is an approach where within a slice you can create an explicit public export of only those modules that can be used within several slices of the same layer

Let's look at an example:
```ts title="entities/countries/@x.ts"
export { getCountriesList } from './lib/getCountriesList.ts';
```
The method inside the slice that requires cross import:
```ts title="entities/cities/lib/getCitiesByCountryName"
import { getCountriesList } from '/entities/countries/@x/getCountriesList';

const countries = getCountriesList();

const getCitiesByCountryName = (countryName: string) => {} // logic

return getCitiesByCountryName(countries[0]);
```
As you can see from the import path in the `getCitiesByCountryName` file, thanks to the export from the @x file, it is clear that this module is not intended for public use, but exists only to be imported inside another slice.

### Lifting the ban on cross-imports {#disable-cross-imports-rule}
If you have tried all the methods in the article, but for some reason still could not get rid of cross-imports, it may be worth considering lifting the ban on them within specific slices, or the entire project as a whole.
However, it is important to understand that lifting the ban on cross-imports can potentially negatively affect the overall structure of the project.

## Useful links {#useful-links}

1. The Feature Sliced ​​Design team has developed a tool that allows you to automatically check for cross-imports in a project: [Steiger](https://github.com/feature-sliced/steiger)
2. More information about the cross-import rule: [Steiger-Forbidden-Imports](https://github.com/feature-sliced/steiger/blob/master/packages/steiger-plugin-fsd/src/forbidden-imports/README.md)


## See also

- [(Thread) About the supposed inevitability of cross-ports](https://t.me/feature_sliced/4515)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,116 @@ import WIP from '@site/src/shared/ui/wip/tmpl.mdx'

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

## Введение {#start}

Перед тем, как разбираться, как необходимо работать с кросс-импортами для соответствия методологии FSD, необходимо понять, что такое "кросс-импорт" в принципе.

**Кросс-импорт** - ситуация, при которой в рамках какого-либо слоя появилась необходимость импорта данных одного слайса в другой.

:::warning Важно

Речь в статье идёт только о конфликтных ситуациях при импортах между **слайсами**. В рамках одного слайса, фрагменты могут импортировать друг друга, такая ситуация не запрещена

:::

Рассмотрим абстрактный пример:

- `📂 entities`
- `📂 countries`
- `📁 lib`
- `📄 getCountriesList `
- `📁 cities`
- `📁 lib`
- `📄 getCitiesByCountryName`

В данном примере для вызова метода `getCitiesByCountryName` необходимо получить результат вызова метода `getCountriesList`.
Получить результат работы метода `getCountriesList` напрямую нельзя, так как это нарушит принцип низкой связности и усложнит поддержку проекта в дальнейшем.

Как разрешать ситуации, когда появилась необходимость сделать кросс-импорт? Рассмотрим в этой статье.

## Варианты решения кросс-импортов {#solutions}

### Объединение нескольких слайсов в один {#slices-merge}
Как видно из примера выше, доменная область методов `getCitiesByCountryName` и `getCountriesList` довольно похожа, а значит существует возможность создания производного слайса из двух существующих, где оба метода находились бы в одной директории

Результат объединения слайсов `countries` и `cities`:
- `📂 entities`
- `📂 geography`
- `📁 lib`
- `📄 getCountriesList`
- `📄 getCitiesByCountryName`

Как видно из примера, мы избавились от ситуации, когда один слайс импортирует данные из другого и достигли высокой сцепленности и низкой связности в рамках сегмента одного слайса

### Разбиение слайсов на разные слои {#slices-moving}
Данный вариант скорее подходит для тех случаев, для которых объединение нескольких слайсов в один затруднено или невозможно.

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

Результат разбиения слайсов `countries` и `cities` на разные слои:
- `📂 entities`
- `📂 countries`
- `📁 lib`
- `📄 getCountriesList `
- `📂 features`
- `📁 cities`
- `📁 lib`
- `📄 getCitiesByCountryName`

Переместив слайс `cities` на слой `features` (не обязательно на него, подойдёт любой слой выше по иерархии), мы более конкретно уточнили его доменную область и дали возможность правильного с точки зрения иерархии слоёв механизма импортов.

### Дублирование кода {#duplication-code}
Если объем данных, который вы собираетесь импортировать из другого слайса невелик, не самой плохой идеей будет продублировать его в текущий слайс. Тем не менее, такой вариант не должен быть приоритетным, среди всех вышеописанных.

## А если кросс-импорт неизбежен? {#cross-import-is-inevitable}

:::caution Внимание

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

:::

Если в Вашем проекте необходим кросс-импорт и два вышеописанных способа не помогли, сообщество Feature Sliced Design предлагает следующий вариант решения проблемы

- `📂 entities`
- `📂 countries`
- `📁 lib`
- `📄 getCountriesList `
- `@x`
- `📁 cities`
- `📁 lib`
- `📄 getCitiesByCountryName`
- `@x`

### @x {#export-private-api}
**@x** - это подход, когда внутри слайса можно создать явный публичный экспорт только тех модулей, которые можно использовать в рамках нескольких слайсов одного слоя

Рассмотрим пример:
```ts title="entities/countries/@x.ts"
export { getCountriesList } from './lib/getCountriesList.ts';
```
Метод внутри слайса, которому необходим кросс-импорт:
```ts title="entities/cities/lib/getCitiesByCountryName"
import { getCountriesList } from '/entities/countries/@x/getCountriesList';

const countries = getCountriesList();

const getCitiesByCountryName = (countryName: string) => {} // logic

return getCitiesByCountryName(countries[0]);
```
Как видно по пути импорта в файле `getCitiesByCountryName`, благодаря экспорту из файла @x, явно видно, что данный модуль не предназначен для публичного использования, а существует только для того, чтобы быть импортированным внутрь другого слайса

### Отмена запрета на кросс-импорты {#disable-cross-imports-rule}
Если Вы попробовали все способы в статье, но по каким-либо причинам всё равно не смогли избавиться от кросс-импортов, возможно, стоить задуматься о том, чтобы снять запрет на них в рамках конкретных слайсов, либо всего проекта в целом.
Однако, важно понимать, что отмена запрета на кросс-импорты, потенциально может негативно сказываться на общей структуре проекта.

## Полезные ссылки {#useful-links}

1. Команда Feature Sliced Design разработала инструмент, позволяющий автоматически проверить наличие кросс-импортов в проекте: [Steiger](https://github.com/feature-sliced/steiger)
2. Больше информации о правиле кросс-импортов: [Steiger-Forbidden-Imports](https://github.com/feature-sliced/steiger/blob/master/packages/steiger-plugin-fsd/src/forbidden-imports/README.md)

## См. также {#see-also}

- [(Тред) Про предполагаемую неизбежность кросс-импортов](https://t.me/feature_sliced/4515)
Expand Down