diff --git a/CHANGELOG.md b/CHANGELOG.md index d5ff4196d2..a85ab86bc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,64 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Since last release][since-last-release] + -### Added +## [2.1.0] - 2024-10-31 + +The new revision of Feature-Sliced Design is here! The main difference with FSD 2.0 is the new approach to decomposition — “pages first”. + +### What's “pages-first”? + +You do “pages first” by keeping more code in pages. For example, large blocks of UI, forms and data logic that are not reused on other pages should now stay in the slice of the page that they are used in. The division by segments (`ui`, `api`, `model`, etc.) still applies to all this code, and we encourage you to further split and organize code into folders inside of segments — don't just pile all the code into a single file. + +In the same way, widgets are no longer just a compositional layer, instead they should also store code that isn't currently needed outside of that widget, including its own stores, business logic, and API interactions. + +When you have a need to reuse code in several widgets or pages, consider putting it in Shared. If that code involves business logic (i. e. managing specific modal dialogs), consider breaking it up into infrastructural code, like the modal manager, and the business code, like the content of the modals. The infrastructure can then go to Shared, and the content can stay in the pages that use this infrastructure. + +### How is it different? + +In FSD 2.0 we explained how to identify entities and features in your application, and then combine them in widgets and pages. Over time we started disliking this approach, mostly for the following reasons: + +- Code cohesion is much worse in this approach + - You need to jump around several folders just to make changes to a single user flow + - Unused code is harder to delete because it's somewhere else +- Finding entities and features is still an advanced skill that needs to be developed over time + - It requires understanding of the business context, which not all developers want to bother with + - On the other hand, splitting by pages is natural and requires little training + - Different developers have different understandings of these concepts, which leads to everyone having their own idea of FSD, which causes conflict and misunderstanding + +### Is it hard to migrate from FSD 2.0? + +This is a non-breaking change, so you don’t even necessarily need to migrate your current FSD projects to FSD 2.1, but we still think the new way of thinking will lead to a more cohesive and less opinionated structure. We’ve compiled a few steps you can take in [the migration guide](https://feature-sliced.design/docs/guides/migration/from-v2-0). -- New article about how to use FSD with Next.js (#644). +### What else happened since the last release? + +Another exciting new thing in the FSD ecosystem is our architectural linter, [Steiger](https://github.com/feature-sliced/steiger). It's still in active development, but it is production-ready. + +A couple more minor clarifications to the docs were made as well: + +1. Application-aware things like the route constants, the API calls, or company logo, are now explicitly allowed in Shared. Business logic is still not allowed, but these things are not considered to be business logic. +2. Imports between segments in App and Shared were always allowed, but it's been made explicit too. + +And here's what happened to the documentation website: + +#### Added + +- Slightly rewritten and expanded overview page to give some details about FSD right away (#685). +- New partial translations: Korean (#739, #736, #735, #742, #732, #730, #715), Japanese (#728). - The tutorial was rewritten. Technical details were stripped out, more FSD theory has been added (#665). +- Guides on how to deal with common frontend issues like page layouts (#708), types (#701), authentication (#693). +- Guides on how to use FSD with Nuxt (#710, #689, #683, #679), SvelteKit (#698), Next.js (#699, #664, #644), and TanStack Query (#673). +- A new feedback widget, powered by PushFeedback! Go give it a try and let us know what you think of the new pages (#695). +- Comparison of FSD with Atomic Design (#671). + +#### Changed + +- The migration guide from a custom architecture (formerly known as "from legacy") has been actualized (#725). + +#### Removed + +- The decomposition cheatsheet is now unlisted for an undefined period of time. It proved to be more harmful than useful, but maybe it can be saved later (#649). ## [2.0.0] - 2023-10-01 @@ -41,5 +93,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The overview page has been rewritten to be more concise and informative (#512, #515, #516). - FSD has updated its branding, and there are now guidelines to the brand usage. The standard spelling of the name is now "Feature-Sliced Design" (#496, #499, #500, #465). -[since-last-release]: https://github.com/feature-sliced/documentation/compare/v2.0.0...HEAD +[since-last-release]: https://github.com/feature-sliced/documentation/compare/v2.1.0...HEAD +[2.1.0]: https://github.com/feature-sliced/documentation/releases/tag/v2.1.0 [2.0.0]: https://github.com/feature-sliced/documentation/releases/tag/v2.0.0 diff --git a/config/docusaurus/extensions.js b/config/docusaurus/extensions.js index fd52101074..1909f746b1 100644 --- a/config/docusaurus/extensions.js +++ b/config/docusaurus/extensions.js @@ -43,7 +43,7 @@ const presets = [ showLastUpdateTime: true, versions: { current: { - label: `v2.0.0 🍰`, + label: `v2.1`, }, }, sidebarItemsGenerator, diff --git a/config/docusaurus/routes.js b/config/docusaurus/routes.js index 6b06359c86..1a657eabbe 100644 --- a/config/docusaurus/routes.js +++ b/config/docusaurus/routes.js @@ -109,17 +109,17 @@ const LEGACY_ROUTES = [ { title: "Decouple of entities", from: "/docs/concepts/decouple-entities", - to: "/docs/reference/isolation/decouple-entities", + to: "/docs/reference/layers#import-rule-on-layers", }, { title: "Low Coupling & High Cohesion", from: "/docs/concepts/low-coupling", - to: "/docs/reference/isolation/coupling-cohesion", + to: "/docs/reference/slices-segments#zero-coupling-high-cohesion", }, { title: "Cross-communication", from: "/docs/concepts/cross-communication", - to: "/docs/reference/isolation", + to: "/docs/reference/layers#import-rule-on-layers", }, { title: "App splitting", @@ -276,6 +276,18 @@ const LEGACY_ROUTES = [ }, ], }, + { + group: "Deduplication of Reference", + details: + "Cleaned up the Reference section and deduplicated the material", + children: [ + { + title: "Isolation of modules", + from: "/docs/reference/isolation", + to: "/docs/reference/layers#import-rule-on-layers", + }, + ], + }, ]; // @returns { from, to }[] diff --git a/i18n/en/docusaurus-plugin-content-docs/current.json b/i18n/en/docusaurus-plugin-content-docs/current.json index 9af1eb6531..599055a3cf 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current.json +++ b/i18n/en/docusaurus-plugin-content-docs/current.json @@ -1,6 +1,6 @@ { "version.label": { - "message": "v2.0.0 🍰", + "message": "v2.1", "description": "The label for version current" }, "sidebar.getstartedSidebar.category.Tutorials": { diff --git a/i18n/en/docusaurus-plugin-content-docs/current/get-started/faq.md b/i18n/en/docusaurus-plugin-content-docs/current/get-started/faq.md index a09a1600a1..2f9b4bda5b 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/get-started/faq.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/get-started/faq.md @@ -13,7 +13,7 @@ You can ask your question in our [Telegram chat][telegram], [Discord community][ ### Is there a toolkit or a linter? -There is an official ESLint config — [@feature-sliced/eslint-config][eslint-config-official], and an ESLint plugin — [@conarti/eslint-plugin-feature-sliced][eslint-plugin-conarti], created by Aleksandr Belous, a community member. You're welcome to contribute to these projects or start your own! +Yes! We have a linter called [Steiger][ext-steiger] to check your project's architecture and [folder generators][ext-tools] through a CLI or IDEs. ### Where to store the layout/template of pages? @@ -58,10 +58,10 @@ Rather yes than no Answered [here](/docs/guides/examples/auth) +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools [import-rule-layers]: /docs/reference/layers#import-rule-on-layers [reference-entities]: /docs/reference/layers#entities -[eslint-config-official]: https://github.com/feature-sliced/eslint-config -[eslint-plugin-conarti]: https://github.com/conarti/eslint-plugin-feature-sliced [motivation]: /docs/about/motivation [telegram]: https://t.me/feature_sliced [discord]: https://discord.gg/S8MzWTUsmp diff --git a/i18n/en/docusaurus-plugin-content-docs/current/get-started/tutorial.md b/i18n/en/docusaurus-plugin-content-docs/current/get-started/tutorial.md index 346217a6a5..662020d924 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/get-started/tutorial.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/get-started/tutorial.md @@ -38,7 +38,7 @@ As such, our Pages folder will look like this: The key difference of Feature-Sliced Design from an unregulated code structure is that pages cannot reference each other. That is, one page cannot import code from another page. This is due to the **import rule on layers**: -*A module in a slice can only import other slices when they are located on layers strictly below.* +*A module (file) in a slice can only import other slices when they are located on layers strictly below.* In this case, a page is a slice, so modules (files) inside this page can only reference code from layers below, not from the same layer, Pages. diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/auth.md b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/auth.md index 5a96d32275..bf8a33b9e2 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/auth.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/examples/auth.md @@ -189,7 +189,7 @@ To store the token in the User entity, create a reactive store in the `model` se Since the API client is usually defined in `shared/api` or spreaded across the entities, the main challenge to this approach is making the token available to other requests that need it without breaking [the import rule on layers][import-rule-on-layers]: -> A module in a slice can only import other slices when they are located on layers strictly below. +> A module (file) in a slice can only import other slices when they are located on layers strictly below. There are several solutions to this challenge: diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md index 04eb9b40cc..235f7c34f3 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 1 sidebar_label: From a custom architecture --- diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md index 241a964b12..4c0a98a649 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md @@ -1,8 +1,8 @@ --- -sidebar_position: 4 +sidebar_position: 2 --- -# Migration from v1 +# Migration from v1 to v2 ## Why v2? @@ -158,7 +158,7 @@ Now it is much easier to [observe the principle of low coupling][refs-low-coupli - [New ideas v2 with explanations (atomicdesign-chat)][ext-tg-v2-draft] - [Discussion of abstractions and naming for the new version of the methodology (v2)](https://github.com/feature-sliced/documentation/discussions/31) -[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion +[refs-low-coupling]: /docs/reference/slices-segments#zero-coupling-high-cohesion [refs-adaptability]: /docs/about/understanding/naming [ext-v1]: https://feature-sliced.github.io/featureslices.dev/v1.0.html diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md new file mode 100644 index 0000000000..af057a421e --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md @@ -0,0 +1,45 @@ +--- +sidebar_position: 3 +--- + +# Migration from v2.0 to v2.1 + +The main change in v2.1 is the new mental model for decomposing an interface — pages first. + +In v2.0, FSD would recommend identifying entities and features in your interface, considering even the smallest bits of entity representation and interactivity for decomposition. Then you would build widgets and pages from entities and features. In this model of decomposition, most of the logic was in entities and features, and pages were just compositional layers that didn't have much significance on their own. + +In v2.1, we recommend starting with pages, and possibly even stopping there. Most people already know how to separate the app into individual pages, and pages are also a common starting point when trying to locate a component in the codebase. In this new model of decomposition, you keep most of the UI and logic in each individual page, maintaining a reusable foundation in Shared. If a need arises to reuse business logic across several pages, you can move it to a layer below. + +Another addition to Feature-Sliced Design is the standardization of cross-imports between entities with the `@x`-notation. + +## How to migrate {#how-to-migrate} + +There are no breaking changes in v2.1, which means that a project written with FSD v2.0 is also a valid project in FSD v2.1. However, we believe that the new mental model is more beneficial for teams and especially onboarding new developers, so we recommend making minor adjustments to your decomposition. + +### Merge slices + +A simple way to start is by running our linter, [Steiger][steiger], on the project. Steiger is built with the new mental model, and the most helpful rules will be: + +- [`insignificant-slice`][insignificant-slice] — if an entity or feature is only used in one page, this rule will suggest merging that entity or feature into the page entirely. +- [`excessive-slicing`][excessive-slicing] — if a layer has too many slices, it's usually a sign that the decomposition is too fine-grained. This rule will suggest merging or grouping some slices to help project navigation. + +```bash +npx steiger src +``` + +This will help you identify which slices are only used once, so that you could reconsider if they are really necessary. In such considerations, keep in mind that a layer forms some kind of global namespace for all the slices inside of it. Just as you wouldn't pollute the global namespace with variables that are only used once, you should treat a place in the namespace of a layer as valuable, to be used sparingly. + +### Standardize cross-imports + +If you had cross-imports between in your project before (we don't judge!), you may now take advantage of a new notation for cross-importing in Feature-Sliced Design — the `@x`-notation. It looks like this: + +```ts title="entities/B/some/file.ts" +import type { EntityA } from "entities/A/@x/B"; +``` + +For more details, check out the [Public API for cross-imports][public-api-for-cross-imports] section in the reference. + +[insignificant-slice]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice +[steiger]: https://github.com/feature-sliced/steiger +[excessive-slicing]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/excessive-slicing +[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/index.mdx b/i18n/en/docusaurus-plugin-content-docs/current/reference/index.mdx index f1f31cd9d9..d944747bd3 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/index.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/reference/index.mdx @@ -25,15 +25,9 @@ A detailed description of the key concepts of Feature-Sliced Design. to="/docs/reference/slices-segments" Icon={AppstoreOutlined} /> - diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/_category_.yaml b/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/_category_.yaml deleted file mode 100644 index df39e2f2b1..0000000000 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/_category_.yaml +++ /dev/null @@ -1 +0,0 @@ -label: Isolation diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md b/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md deleted file mode 100644 index 9ca0e6cd8c..0000000000 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Low Coupling & High Cohesion - -Application modules should be designed according to **high cohesion** (should solve one specific task) and **low coupling** (independent of other modules) principles. - -
- - -
- Image inspired by https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/ -
-
- -Within the methodology, this is achieved through: - -* Splitting the application into layers and slices that implement specific functionality -* Providing a [public access interface][refs-public-api] for each module -* Setting up restrictions for [modules interactions][refs-isolation] - each module can depend only on the modules below it, but not on modules from the same or higher layer - -## Components composition (UI level) - -The majority of modern UI frameworks and libraries provide a component model in which each component can have its own properties, state, child components, and even slots. - -This model allows you to design an interface as a **composition of various components that are not directly related to each other** and, thereby, achieve **low coupling** of the interface components. - -### Example - -Let's consider such a composition using the example of a **list with a header:** - -#### Laying the extensibility - -List component will not itself define the look and structure of the header components and list elements, instead it will accept them as parameters - -```tsx -interface ListProps { - Header: React.ReactNode; - Items: React.ReactNode; -} - -const List: Component = ({ Header, Items }) => ( -
- {Header} -
    - {Items} -
-
-) - -``` - -#### Using the composition - -This allows you to **reuse and independently change** components with different Header and list Items. Header and Items components can have both their own local state and their binding to the general state of the application - the List component will not know anything about it, and therefore will not depend on it - -```tsx -} Items={} /> - -} /> - -} Items={} /> - -``` - -## Layer composition (APP level) - -The methodology suggests putting the functionality that is valuable for the user into **features slice**, and the logic related to business entities - into **entities**. Both features and entities **should be designed as modules with high cohesion**, i.e. aimed at solving **one specific task** or related to **one specific entity.** - -All interactions between such modules, similar to the UI components from the example above, should be coordinated via a **modules composition**. - -### Example - -Let's use an example of a chat application with the following features: - -* user can open a contact list and select a friend -* user can open a conversation with a selected friend - -According to methodology principles, it can be represented as: - -Entities - -* User (contains user's state) -* Contact (state of the contact list, utilities for working with an individual contact) -* Chat (the state of the current chat and utilies for it) - -Features - -* Form for sending a message -* Chat selection menu - -#### Let's tie it all together - -The application, to begin with, will have one page, and the interface will be slightly modified from the first example - -```tsx title="page/main/ui.tsx" -} - Items={} - Footer={} -/> -``` - -#### Data model - -The page data model will be organized as a **composition of features and entities**. In this example, the features will be implemented as factories and they will access the interface of entities through the parameters of these factories. - -> However, the implementation using factory is optional - the feature may directly depend on the lower layers. - -```ts title="pages/main/model.ts" -import { userModel } from "entitites/user" -import { conversationModel } from "entities/conversation" -import { contactModel } from "entities/contact" - -import { createMessageInput } from "features/message-input" -import { createConversationSwitch } from "features/conversation-switch" - -import { beautifiy } from "shared/lib/beautify-text" - -export const { allConversations, setConversation } = createConversationSwitch({ - contacts: contactModel.allContacts, - setConversation: conversationModel.setConversation, - currentConversation: conversationModel.conversation, - currentUser: userModel.currentUser -}) - -export const { sendMessage, attachFile } = createMessageInput({ - author: userModel.currentUser - send: conversationModel.sendMessage, - formatMessage: beautify -}) -``` - -## Summary - -1. Modules must have **high cohesion** (have one responsibility, solve one specific task) and provide a [**public interface**][refs-public-api] access -2. **Low coupling** is achieved through the composition of elements - UI components, features and entities -3. To reduce entanglement, modules **should interact with each other only through a public interfaces** - this makes modules independent of each other's internal implementation - -## See also - -* [(Article) Low Coupling and High Cohesion in details](https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/) - * *The diagram at the beginning is inspired by this article* -* [(Article) Low Coupling and High Cohesion. The Law of Demeter](https://medium.com/german-gorelkin/low-coupling-high-cohesion-d36369fb1be9) -* [(Presentation) On design principles (including Low Coupling & High Cohesion)](https://www.slideshare.net/cristalngo/software-design-principles-57388843) - -[refs-public-api]: /docs/reference/public-api -[refs-isolation]: /docs/reference/isolation diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/decouple-entities.mdx b/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/decouple-entities.mdx deleted file mode 100644 index 6b8d757d7a..0000000000 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/decouple-entities.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -sidebar_position: 2 -sidebar_class_name: sidebar-item--wip ---- - -import WIP from '@site/src/shared/ui/wip/tmpl.mdx' - -# Decouple entities - - - -> About cross-imports of types, adapters and about how to explicitly build connections between entities - -> Also about mythical absolutely-decoupled entities - -## See also - -- [(Thread) Memo about decomposition by entities and building explicit links between them](https://t.me/feature_sliced/3633) -- [(Thread) Example of decomposition for "connected entities" (users/pets/friends)](https://t.me/feature_sliced/3316) -- [(Thread) About cross-imports of types/adapters in entities](https://t.me/feature_sliced/4276) -- [(Thread) About the boundaries of entities and features](https://t.me/feature_sliced/4521) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/index.md b/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/index.md deleted file mode 100644 index 42756834fd..0000000000 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/isolation/index.md +++ /dev/null @@ -1,71 +0,0 @@ -# Isolation of modules - -Within the framework of the methodology, all modules are distributed by scopes of responsibility (layer, slice, segment) - -The layers, in turn, are organized vertically: - -- "At the bottom" are the reused modules (ui-kit, internal libraries of the project), as the most abstract -- And as you move "up", more specific modules are located. - -Regardless of whether it belongs to any slice, each module [**is required to provide a public access interface**][refs-public-api]. - -## Requirements - -The interaction of each module with the rest of the application is designed taking into account a number of requirements: - -1. **Low coupling** with other modules - - *A change in one module should have a weak and predictable effect on others* - -1. **High cohesion** - the responsibilities of each module are "focused" on one task - - - *If the module has too many responsibilities (starts "doing too much") - this should be noticed as soon as possible* -1. **Absence of cyclic dependencies** on the scale of the entire application - - - *Often lead to unexpected, undesirable behavior, it is better to avoid them altogether* - -## Rule - -To meet these requirements, within the framework of the methodology, it is necessary to observe the basic rule: - -:::info Important - -A module can depend only on "underlying" modules, but not on modules from the same or higher layer - -::: - -- `features/auth` **cannot** depend on `features/filters` **and vice versa** -- `features/auth` **may** depend on `shared/ui/button`, **but not vice versa** - -Following this rule allows you to keep dependencies **"unidirectional"** - which automatically **eliminates cyclic imports** and significantly **simplifies tracking dependencies** between modules in the application. - -## Identifying problems - - -Violation of this rule is a signal of problems: - -1. The module has **import from another module** from its own layer - - - Perhaps the module was **unnecessarily fragmented** or has **unnecessary responsibility.** - - You should **combine** it with the imported module or **move it (partially or completely) to the layer below** or transfer the logic of relationships to modules on higher layers. - -1. The module **is imported by many modules** from its own layer - - - Perhaps the module has **extra responsibility.** - - You should **move it (partially or entirely) to the layer below**, or transfer the logic of connections to modules on higher layers. - -1. The module **has imports from many modules** from its own layer - - - Perhaps the module belongs to **another scope of responsibility.** - - You should **move it (partially or completely) to the layer above**. - -## See also - -- [(Guide) About achieving low coupling][refs-low-coupling] -- [(Discussion) Coupled entities](https://github.com/feature-sliced/documentation/discussions/49) -- [(Discussion) About cross-imports and analysis зависимостей](https://github.com/feature-sliced/documentation/discussions/65#discussioncomment-480822) -- [**GRASP** Patterns](https://en.wikipedia.org/wiki/GRASP_(object-oriented_design)) - -[refs-public-api]: /docs/reference/public-api -[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/layers.mdx b/i18n/en/docusaurus-plugin-content-docs/current/reference/layers.mdx index 8782c9d45f..e541e553ba 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/layers.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/reference/layers.mdx @@ -5,15 +5,7 @@ pagination_next: reference/slices-segments # Layers -Layers are the first level of organisational hierarchy in Feature-Sliced Design. Their purpose is to separate code based on how much responsibility it needs and how many other modules in the app it depends on. - -:::note - -On this page, a _module_ refers to an internal module in the application — a file or directory with an index file. Not to be confused with npm packages. - -::: - -Every layer carries special semantic meaning to help you determine how much responsibility you should allocate to a module in your code. The names and meanings of layers are standardized across all projects built with Feature-Sliced Design. +Layers are the first level of organisational hierarchy in Feature-Sliced Design. Their purpose is to separate code based on how much responsibility it needs and how many other modules in the app it depends on. Every layer carries special semantic meaning to help you determine how much responsibility you should allocate to your code. There are **7 layers** in total, arranged from most responsibility and dependency to least: @@ -28,133 +20,107 @@ There are **7 layers** in total, arranged from most responsibility and depe 6. Entities 7. Shared -You don't have to use every layer in your project — only add them if you think it brings value to your project. +You don't have to use every layer in your project — only add them if you think it brings value to your project. Typically, most frontend projects will have at least the Shared, Pages, and App layers. + +In practice, layers are folders with lowercase names (for example, `📁 shared`, `📁 pages`, `📁 app`). Adding new layers is _not recommended_ because their semantics are standardized. ## Import rule on layers -Layers are made up of _slices_ — highly cohesive groups of modules. Feature-Sliced Design promotes low coupling, which is why dependencies between slices are regulated by **the import rule on layers**: +Layers are made up of _slices_ — highly cohesive groups of modules. Dependencies between slices are regulated by **the import rule on layers**: + +> _A module (file) in a slice can only import other slices when they are located on layers strictly below._ + +For example, the folder `📁 ~/features/aaa` is a slice with the name "aaa". A file inside of it, `~/features/aaa/api/request.ts`, cannot import code from any file in `📁 ~/features/bbb`, but can import code from `📁 ~/entities` and `📁 ~/shared`, as well as any sibling code from `📁 ~/features/aaa`, for example, `~/features/aaa/lib/cache.ts`. -> _A module in a slice can only import other slices when they are located on layers strictly below._ +Layers App and Shared are **exceptions** to this rule — they are both a layer and a slice at the same time. Slices partition code by business domain, and these two layers are exceptions because Shared does not have business domains, and App combines all business domains. -For example, in `~/features/aaa`, `aaa` is the slice, so a file `~/features/aaa/api/request.ts` cannot import code from any module in `~/features/bbb`, but can import code from `~/entities` and `~/shared`, as well as any sibling code from `~/features/aaa`. +In practice, this means that layers App and Shared are made up of segments, and segments can import each other freely. ## Layer definitions +This section describes the semantic meaning of each layer to create an intuition for what kind of code belongs there. + ### Shared -Isolated modules, components and abstractions that are detached from the specifics of the project or business. -Warning: not to be treated like [a utility dump][ext--sova-utility-dump]! +This layer forms a foundation for the rest of the app. It's a place to create connections with the external world, for example, backends, third-party libraries, the environment. It is also a place to define your own highly contained libraries. -This layer, unlike others, does not consist of slices, and instead consists of segments directly. +This layer, like the App layer, _does not contain slices_. Slices are intended to divide the layer into business domains, but business domains do not exist in Shared. This means that all files in Shared can reference and import from each other. -**Content examples**: +Here are the segments that you can typically find in this layer: -* UI kit -* API client -* Code working with browser APIs +- `📁 api` — the API client and potentially also functions to make requests to specific backend endpoints. +- `📁 ui` — the application's UI kit. + Components on this layer should not contain business logic, but it's okay for them to be business-themed. For example, you can put the company logo and page layout here. Components with UI logic are also allowed (for example, autocomplete or a search bar). +- `📁 lib` — a collection of internal libraries. + This folder should not be treated as helpers or utilities ([read here why these folders often turn into a dump][ext-sova-utility-dump]). Instead, every library in this folder should have one area of focus, for example, dates, colors, text manipulation, etc. That area of focus should be documented in a README file. The developers in your team should know what can and cannot be added to these libraries. +- `📁 config` — environment variables, global feature flags and other global configuration for your app. +- `📁 routes` — route constants or patterns for matching routes. +- `📁 i18n` — setup code for translations, global translation strings. -### Entities +You are free to add more segments, but make sure that the name of these segments describes the purpose of the content, not its essence. For example, `components`, `hooks`, and `types` are bad segment names because they aren't that helpful when you're looking for code. -Concepts from the real world that form together the essence of the project. Commonly, these are the terms that the business uses to describe the product. +### Entities -Each slice in this layer contains static UI elements, data stores and CRUD operations. +Slices on this layer represent concepts from the real world that the project is working with. Commonly, they are the terms that the business uses to describe the product. For example, a social network might work with business entities like User, Post, and Group. -**Slice examples**: +An entity slice might contain the data storage (`📁 model`), data validation schemas (`📁 model`), entity-related API request functions (`📁 api`), as well as the visual representation of this entity in the interface (`📁 ui`). The visual representation doesn't have to produce a complete UI block — it is primarily meant to reuse the same appearance across several pages in the app, and different business logic may be attached to it through props or slots. - - -
For a social network For a Git frontend (e.g., GitHub)
    -
  • User
  • -
  • Post
  • -
  • Group
  • -
    -
  • Repository
  • -
  • File
  • -
  • Commit
  • -
+#### Entity relationships +Entities in FSD are slices, and by default, slices cannot know about each other. In real life, however, entities often interact with each other, and sometimes one entity owns or contains other entities. Because of that, the business logic of these interactions is preferably kept in higher layers, like Features or Pages. -:::tip +When one entity's data object contains other data objects, usually it's a good idea to make the connection between the entities explicit and side-step the slice isolation by making a cross-reference API with the `@x` notation. The reason is that connected entities need to be refactored together, so it's best to make the connection impossible to miss. -You may notice in the example of a Git frontend that a _repository_ contains _files_. This makes the repository a higher-level entity which has other entities nested inside. That is a common situation with entities, and sometimes it's hard to manage such higher-level entities without breaking the import rule on layers. +For example: -Here are a few suggestions to overcome this issue: -* The UI of entities should contain slots for places where the lower-level entities are to be inserted -* The business logic related to entity interaction should be placed in features (most of the time) -* The typings of database entities can be extracted to the Shared layer below, next to the API client +```ts title="entities/artist/model/artist.ts" +import type { Song } from "entities/song/@x/artist"; -::: +export interface Artist { + name: string; + songs: Array; +} +``` -### Features +```ts title="entities/song/@x/artist.ts" +export type { Song } from "../model/song.ts"; +``` -Actions that a user can make in the application to interact with the business entities to achieve a valuable outcome. This also includes actions that the app makes on behalf of the user to produce value for them. +Learn more about the `@x` notation in the [Public API for cross-imports][public-api-for-cross-imports] section. -Each slice in this layer can contain _interactive_ UI elements, internal state and API calls that enable value-producing actions. +### Features -**Slice examples**: +This layer is for the main interactions in your app, things that your users care to do. These interactions often involve business entities, because that's what the app is about. - - -
For a social network For a Git frontend (e.g., GitHub) Actions on behalf of users
    -
  • Authenticate
  • -
  • Create a post
  • -
  • Join a group
  • -
    -
  • Edit a file
  • -
  • Leave a comment
  • -
  • Merge branches
  • -
    -
  • Detect dark mode
  • -
  • Perform background computation
  • -
  • User-Agent-based actions
  • -
+A crucial principle for using the Features layer effectively is: **not everything needs to be a feature**. A good indicator that something needs to be a feature is the fact that it is reused on several pages. -### Widgets +For example, if the app has several editors, and all of them have comments, then comments are a reused feature. Remember that slices are a mechanism for finding code quickly, and if there are too many features, the important ones are drowned out. -Self-sufficient UI blocks that emerged from the composition of lower-level units like entities and features. +Ideally, when you arrive in a new project, you would discover its functionality by looking through the pages and features. When deciding on what should be a feature, optimize for the experience of a newcomer to the project to quickly discover large important areas of code. -This layer provides a way to fill in the slots left in the UI of Entities with other Entities and interactive elements from Features. Therefore, it is common not to have business logic on this layer, instead keeping it in Features. Each slice in this layer contains ready-to-use UI components and sometimes non-business logic such as gestures, keyboard interaction, etc. +A feature slice might contain the UI to perform the interaction like a form (`📁 ui`), the API calls needed to make the action (`📁 api`), validation and internal state (`📁 model`), feature flags (`📁 config`). -Sometimes, however, it is more convenient to have business logic on this layer. Usually it happens when the widget is quite rich in interactivity (e.g., interactive data tables) and the business logic inside them is not used in other places. +### Widgets -**Slice examples**: +The Widgets layer is intended for large self-sufficient blocks of UI. Widgets are most useful when they are reused across multiple pages, or when the page that they belong to has multiple large indepdendent blocks, and this is one of them. - - -
For a social network For a Git frontend (e.g., GitHub)
    -
  • Post card
  • -
  • User profile header (with actions)
  • -
    -
  • List of files in a repository (with actions)
  • -
  • Comment in a thread
  • -
  • Repository card
  • -
+If a block of UI makes up most of the interesting content on a page, and is never reused, it **should not be a widget**, and instead it should be placed directly inside that page. :::tip -If you're using a nested routing system (e.g. the router of [Remix][ext--remix]), it may be helpful to use the Widgets layer in the same way as a flat routing system would use the Pages layer — to create complete interface blocks, complete with related data fetching, loading states, and error boundaries. In the same way, you can store page layouts on this layer. +If you're using a nested routing system (like the router of [Remix][ext-remix]), it may be helpful to use the Widgets layer in the same way as a flat routing system would use the Pages layer — to create full router blocks, complete with related data fetching, loading states, and error boundaries. + +In the same way, you can store page layouts on this layer. ::: ### Pages -Complete pages for a page-based application (like a website) or screens/activities for screen-based applications (like mobile apps). - -This layer is similar to Widgets in its compositional nature, albeit on a larger scale. Each slice in this layer contains UI components that are ready to be plugged into a router and sometimes data-fetching logic and error handling. +Pages are what makes up websites and applications (also known as screens or activities). One page usually corresponds to one slice, however, if there are several very similar pages, they can be grouped into one slice, for example, registration and login forms. -**Slice examples**: +There's no limit to how much code you can place in a page slice as long as your team still finds it easy to navigate. If a UI block on a page is not reused, it's perfectly fine to keep it inside the page slice. - - -
For a social network For a Git frontend (e.g., GitHub)
    -
  • News feed
  • -
  • Community page
  • -
  • User's public profile
  • -
    -
  • Repository page
  • -
  • User's repositories
  • -
  • Branches in a repository
  • -
+In a page slice you can typically find the page's UI as well as loading states and error boundaries (`📁 ui`) and the data fetching and mutating requests (`📁 api`). It's not common for a page to have a dedicated data model, and tiny bits of state can be kept in the components themselves. ### Processes @@ -164,7 +130,7 @@ This layer has been deprecated. The current version of the spec recommends avoid ::: -Escape hatches for multi-page interactions. +Processes are escape hatches for multi-page interactions. This layer is deliberately left undefined. Most applications should not use this layer, and keep router-level and server-level logic on the App layer. Consider using this layer only when the App layer grows large enough to become unmaintainable and needs unloading. @@ -172,14 +138,15 @@ This layer is deliberately left undefined. Most applications should not use this All kinds of app-wide matters, both in the technical sense (e.g., context providers) and in the business sense (e.g., analytics). -This layer usually doesn't contain slices, like Shared, instead having segments directly. +This layer usually doesn't contain slices, as well as Shared, instead having segments directly. -**Content examples**: +Here are the segments that you can typically find in this layer: -* Styles -* Routing -* Store and other context providers -* Analytics initialization +- `📁 routes` — the router configuration +- `📁 store` — global store configuration +- `📁 styles` — global styles +- `📁 entrypoint` — the entrypoint to the application code, framework-specific -[ext--remix]: https://remix.run -[ext--sova-utility-dump]: https://dev.to/sergeysova/why-utils-helpers-is-a-dump-45fo +[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports +[ext-remix]: https://remix.run +[ext-sova-utility-dump]: https://dev.to/sergeysova/why-utils-helpers-is-a-dump-45fo diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/public-api.md b/i18n/en/docusaurus-plugin-content-docs/current/reference/public-api.md index 9a6ef1fc9a..8861ae5c13 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/public-api.md +++ b/i18n/en/docusaurus-plugin-content-docs/current/reference/public-api.md @@ -1,217 +1,153 @@ --- sidebar_position: 3 -pagination_next: about/index --- # Public API -Each entity of the methodology is designed as a **user-friendly and integrable module.** +A public API is a _contract_ between a group of modules, like a slice, and the code that uses it. It also acts as a gate, only allowing access to certain objects, and only through that public API. -## Goals +In practice, it's usually implemented as an index file with re-exports: -The convenience of using and integrating the module is achieved through the fulfillment of *a number of goals*: - -1. The application must be **protected from changes** to the internal structure of individual modules -1. The processing of the internal structure of the module **should not affect** other modules -1. Significant changes in the behavior of the module should be **easily detectable** - > **Significant changes in the behavior of the module** - changes that break the expectations of the user entities of the module. - -These goals can be achieved by introducing a public interface (Public API), which is a single access point to the module's capabilities and defines the "contract" of the module's interaction with the outside world. - -:::info Important - -The entity structure must have a single entry point that provides a public interface - -::: - -```sh -└── features/               #  - ├── auth-form / # Internal structure of the feature - | ├── ui/        # - | ├── model/     # - | ├── {...}/     # - ├── index.ts # Entrypoint features with its public API +```js title="pages/auth/index.js" +export { LoginPage } from "./ui/LoginPage"; +export { RegisterPage } from "./ui/RegisterPage"; ``` -```ts title="**/**/index.ts" -export { Form as AuthForm } from "./ui" -export * as authFormModel from "./model" -``` - -## Requirements for the public API - -Meeting these requirements allows you to reduce interaction with the module to **the implementation of a public interface-contract** and, thereby, achieve reliability and ease of use of the module. - -### 1. Access Control - -The public API must **control access** to the contents of the module - -- Other parts of the application can use **only those module entities that are presented in the public interface** -- The internal part of the module outside the public interface **is accessible only to the module itself**. - -#### Examples +## What makes a good public API? -##### Suspension from private imports +A good public API makes using and integrating into other code a slice convenient and reliable. It can be achieved by setting these three goals: -- **Bad**: There is a direct access to the internal parts of the module, bypassing the public access interface - it is dangerous, especially when refactoring the module +1. The rest of the application must be protected from structural changes to the slice, like a refactoring +1. Significant changes in the behavior of the slice that break the previous expectations should cause changes in the public API +1. Only the necessary parts of the slice should be exposed - ```diff - - import { Form } from "features/auth-form/components/view/form" - ``` +The last goal has some important practical implications. It may be tempting to create wildcard re-exports of everything, especially in early development of the slice, because any new objects you export from your files are also automatically exported from the slice: -- **Good:** The API exports only what is necessary and allowed in advance, the module developer now needs to think only about not breaking the Public API when refactoring - - ```diff - + import { AuthForm } from "features/auth-form" - ``` - -### 2. Sustainability for changes - -The public API should be sustainable for changes inside the module - -- Breaking changes in the behavior of the module are reflected in the change of the Public API - -#### Examples - -##### Abstracting from the implementation - -Changing the internal structure should not lead to a change in the Public API - -- **Bad:** moving or renaming this component inside the feature will lead to the need to refactor imports in all places where the component is used. +```js title="Bad practice, features/comments/index.js" +// ❌ BAD CODE BELOW, DON'T DO THIS +export * from "./ui/Comment"; // 👎 don't try this at home +export * from "./model/comments"; // 💩 this is bad practice +``` - ```diff - - import { Form } from "features/auth-form/ui/form" - ``` +This hurts the discoverability of a slice because you can't easily tell what the interface of this slice is. Not knowing the interface means that you have to dig deep into the code of a slice to understand how to integrate it. Another problem is that you might accidentally expose the module internals accidentally, which will make refactoring difficult if someone starts depending on them. -- **Good:** the interface of the feature does not display its internal structure, external "users" of the feature will not suffer from moving or renaming the component inside the feature +## Public API for cross-imports {#public-api-for-cross-imports} - ```diff - + import { AuthForm } from "features/auth-form" - ``` +Cross-imports are a situation when one slice imports from another slice on the same layer. Usually that is prohibited by the [import rule on layers][import-rule-on-layers], but often there are legitimate reasons to cross-import. For example, business entities often reference each other in the real world, and it's best to reflect these relationships in the code instead of working around them. -### 3. Integrability +For this purpose, there's a special kind of public API, also known as the `@x`-notation. If you have entities A and B, and entity B needs to import from entity A, then entity A can declare a separate public API just for entity B. -The public API should facilitate **easy and flexible integration** +- `📂 entities` + - `📂 A` + - `📂 @x` + - `📄 B.ts` — a special public API just for code inside `entities/B/` + - `📄 index.ts` — the regular public API -- Should be convenient for use by the rest of the application, in particular, to solve the problem of name collisions +Then the code inside `entities/B/` can import from `entities/A/@x/B`: -#### Examples +```ts +import type { EntityA } from "entities/A/@x/B"; +``` -##### Name collision +The notation `A/@x/B` is meant to be read as "A crossed with B". -- **Bad:** there will be a name collision +:::note - ```ts title="features/auth-form/index.ts" - export { Form } from "./ui" - export * as model from "./model" - ``` +Try to keep cross-imports to a minimum, and **only use this notation on the Entities layer**, where eliminating cross-imports is often unreasonable. - ```ts title="features/post-form/index.ts" - export { Form } from "./ui" - export * as model from "./model" - ``` +::: - ```diff - - import { Form, model } from "features/auth-form" - - import { Form, model } from "features/post-form" - ``` +## Issues with index files -- **Good:** the collision is solved at the interface level +Index files like `index.js`, also known as barrel files, are the most common way to define a public API. They are easy to make, but they are known to cause problems with certain bundlers and frameworks. - ```ts title="features/auth-form/index.ts" - export { Form as AuthForm } from "./ui" - export * as authFormModel from "./model" - ``` +### Circular imports - ```ts title="features/post-form/index.ts" - export { Form as PostForm } from "./ui" - export * as postFormModel from "./model" - ``` +Circular import is when two or more files import each other in a circle. - ```diff - + import { AuthForm, authFormModel } from "features/auth-form" - + import { PostForm, postFormModel } from "features/post-form" - ``` + -##### Flexible use +
+ Three files importing each other in a circle + Three files importing each other in a circle +
+ Pictured above: three files, `fileA.js`, `fileB.js`, and `fileC.js`, importing each other in a circle. +
+
-- **Bad:** it is inconvenient to write, it is inconvenient to read, the" user " of the feature suffers +These situations are often difficult for bundlers to deal with, and in some cases they might even lead to runtime errors that might be difficult to debug. - ```diff - - import { storeActionUpdateUserDetails } from "features/auth-form" - - dispatch(storeActionUpdateUserDetails(...)) - ``` +Circular imports can occur without index files, but having an index file presents a clear opporutnity to accidentally create a circular import. It often happens when you have two objects exposed in the public API of a slice, for example, `HomePage` and `loadUserStatistics`, and the `HomePage` needs to access `loadUserStatistics`, but it does it like this: -- **Good:** the "user" of the feature gets access to the necessary things iteratively and flexibly +```jsx title="pages/home/ui/HomePage.jsx" +import { loadUserStatistics } from "../"; // importing from pages/home/index.js - ```diff - + import { authFormModel } from "features/auth-form" - + dispatch(authFormModel.effects.updateUserDetails(...)) // redux - + authFormModel.updateUserDetailsFx(...) // effector - ``` +export function HomePage() { /* … */ } +``` -##### Resolution of collisions +```js title="pages/home/index.js" +export { HomePage } from "./ui/HomePage"; +export { loadUserStatistics } from "./api/loadUserStatistics"; +``` -Name collisions should be resolved at the level of the public interface, not the implementation +This situation creates a circular import, because `index.js` imports `ui/HomePage.jsx`, but `ui/HomePage.jsx` imports `index.js`. -- **Bad:** name collisions are resolved at the implementation level +To prevent this issue, consider these two principles. If you have two files, and one imports from the other: +- When they are in the same slice, always use _relative_ imports and write the full import path +- When they are in different slices, always use _absolute_ imports, for example, with an alias - ```ts title="features/auth-form/index.ts" - export { AuthForm } from "./ui" - export { authFormActions, authFormReducer } from "model" - ``` +### Large bundles and broken tree-shaking in Shared {#large-bundles} - ```ts title="features/post-form/index.ts" - export { PostForm } from "./ui" - export { postFormActions, postFormReducer } from "model" - ``` +Some bundlers might have a hard time tree-shaking (removing code that isn't imported) when you have an index file that re-exports everything. -- **Good:** name collisions are resolved at the interface level +Usually this isn't a problem for public APIs, because the contents of a module are usually quite closely related, so you would rarely need to import one thing and tree-shake away the other. However, there are two very common cases when the normal rules of public API in FSD may lead to issues — `shared/ui` and `shared/lib`. - ```ts title="features/auth-form/model.ts" - export { actions, reducer } - ``` +These two folders are both collections of unrelated things that often aren't all needed in one place. For example, `shared/ui` might have modules for every component in the UI library: - ```ts title="features/auth-form/index.ts" - export { Form as AuthForm } from "./ui" - export * as authFormModel from "./model" - ``` +- `📂 shared/ui/` + - `📁 button` + - `📁 text-field` + - `📁 carousel` + - `📁 accordion` - ```ts title="features/post-form/model.ts" - export { actions, reducer } - ``` +This problem is made worse when one of these modules has a heavy dependency, like a syntax highlighter or a drag'n'drop library. You don't want to pull those into every page that uses something from `shared/ui`, for example, a button. - ```ts title="features/post-form/index.ts" - export { Form as PostForm } from "./ui" - export * as postFormModel from "./model" - ``` +If your bundles grow undesirably due to a single public API in `shared/ui` or `shared/lib`, it's recommended to instead have a separate index file for each component or library: -## About re-exports +- `📂 shared/ui/` + - `📂 button` + - `📄 index.js` + - `📂 text-field` + - `📄 index.js` -In JavaScript, the public interface of a module is created by re-exporting entities from inside the module in an `index` file: +Then the consumers of these components can import them directly like this: -```ts title="**/**/index.ts" -export { Form as AuthForm } from "./ui" -export * as authModel from "./model" +```js title="pages/sign-in/ui/SignInPage.jsx" +import { Button } from '@/shared/ui/button'; +import { TextField } from '@/shared/ui/text-field'; ``` -### Disadvantages +### No real protection against side-stepping the public API + +When you create an index file for a slice, you don't actually forbid anyone from not using it and importing directly. This is especially a problem for auto-imports, because there are several places from which an object can be imported, so the IDE has to decide that for you. Sometimes it might choose to import directly, breaking the public API rule on slices. -- In most popular bundlers, due to re-exports, **the code-splitting works worse**, because [tree-shaking](https://webpack.js.org/guides/tree-shaking/) with this approach, it is safe to discard only the entire module, but not part of it. - > For example, importing `authModel` into the page model will cause the `AuthForm` component to get into the chunk of this page, even if this component is not used there. +To catch these issues automatically, we recommend using [Steiger][ext-steiger], an architectural linter with a ruleset for Feature-Sliced Design. -- As a result, initialization of the chunk becomes more expensive, because the browser must process all the modules in it, including those that got into the bundle "for the company" +### Worse performance of bundlers on large projects -### Possible solutions +Having a large amount of index files in a project can slow down the development server, as noted by TkDodo in [his article "Please Stop Using Barrel Files"][ext-please-stop-using-barrel-files]. -- `webpack` allows you to mark re-export files as [**side effects free**](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free) - this allows `webpack` to use more aggressive optimizations when working with such a file +There are several things you can do to tackle this issue: +1. The same advice as in ["Large bundles and broken tree-shaking in Shared" issue](#large-bundles) — have separate index files for each component/library in `shared/ui` and `shared/lib` instead of one big one +2. Avoid having index files in segments on layers that have slices. + For example, if you have an index for the feature "comments", `📄 features/comments/index.js`, there's no reason to have another index for the `ui` segment of that feature, `📄 features/comments/ui/index.js`. +3. If you have a very big project, there's a good chance that your application can be split into several big chunks. + For example, Google Docs has very different responsibilities for the document editor and for the file browser. You can create a monorepo setup where each package is a separate FSD root, with its own set of layers. Some packages can only have the Shared and Entities layers, others might only have Pages and App, others still might include their own small Shared, but still use the big one from another package too. -## See also + -- [(Discussion) Public Abstraction API][disc-src] -- [Principles **SOLID**][ext-solid] -- [Patterns **GRASP**][ext-grasp] + -[disc-src]: https://github.com/feature-sliced/documentation/discussions/41 -[ext-solid]: https://ru.wikipedia.org/wiki/SOLID -[ext-grasp]: https://ru.wikipedia.org/wiki/GRASP +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-please-stop-using-barrel-files]: https://tkdodo.eu/blog/please-stop-using-barrel-files diff --git a/i18n/en/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx b/i18n/en/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx index 01aeb65e24..faa27f9d82 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx @@ -8,50 +8,64 @@ pagination_next: reference/public-api ## Slices -Slices are the second level in the organizational hierarchy of Feature-Sliced Design. Their main purpose is to group code by its meaning for the product, business or just the application. +Slices are the second level in the organizational hierarchy of Feature-Sliced Design. Their main purpose is to group code by its meaning for the product, business, or just the application. -The names of slices are not standardized because they are directly determined by the business domain of your application. For example, a photo gallery might have slices `photo`, `create-album`, `gallery-page`. A social network would require different slices, for example, `post`, `add-user-to-friends`, `news-feed`. +The names of slices are not standardized because they are directly determined by the business domain of your application. For example, a photo gallery might have slices `photo`, `effects`, `gallery-page`. A social network would require different slices, for example, `post`, `comments`, `news-feed`. -Closely related slices can be structurally grouped in a directory, but they should exercise the same isolation rules as other slices — there should be **no code sharing** in that directory. +The layers Shared and App don't contain slices. That is because Shared should contain no business logic at all, hence has no meaning for the product, and App should contain only code that concerns the entire application, so no splitting is necessary. -![Features "compose", "like" and "delete" grouped in a directory "post". In that directory there is also a file "some-shared-code.ts" that is crossed out to imply that it's not allowed.](/img/graphic-nested-slices.svg) +### Zero coupling and high cohesion {#zero-coupling-high-cohesion} -The layers Shared and App don't contain slices. That is because Shared should contain no business logic at all, hence has no meaning for the product, and App should contain only code that concerns the entire application, so no splitting is necessary. +Slices are meant to be independent and highly cohesive groups of code files. The graphic below might help to visualize the tricky concepts of _cohesion_ and _coupling_: + +
+ + +
+ Image inspired by https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/ +
+
+ +An ideal slice is independent from other slices on its layer (zero coupling) and contains most of the code related to its primary goal (high cohesion). + +The independence of slices is enforced by the [import rule on layers][layers--import-rule]: + +> _A module (file) in a slice can only import other slices when they are located on layers strictly below._ ### Public API rule on slices -Inside a slice, the code could be organized very liberally, and that doesn't pose any issues as long as the slice provides a good public API. This is enforced with the **public API rule on slices**: +Inside a slice, the code could be organized in any way that you want. That doesn't pose any issues as long as the slice provides a good public API for other slices to use it. This is enforced with the **public API rule on slices**: > _Every slice (and segment on layers that don't have slices) must contain a public API definition._ > > _Modules outside of this slice/segment can only reference the public API, not the internal file structure of the slice/segment._ -Read more about the rationale of public APIs and the best practices on creating one in the [Public API reference][ref--public-api]. +Read more about the rationale of public APIs and the best practices on creating one in the [Public API reference][ref-public-api]. + +### Slice groups + +Closely related slices can be structurally grouped in a folder, but they should exercise the same isolation rules as other slices — there should be **no code sharing** in that folder. + +![Features "compose", "like" and "delete" grouped in a folder "post". In that folder there is also a file "some-shared-code.ts" that is crossed out to imply that it's not allowed.](/img/graphic-nested-slices.svg) ## Segments Segments are the third and final level in the organizational hierarchy, and their purpose is to group code by its technical nature. There a few standardized segment names: -* `ui` — UI components, data formatting functions -* `model` — business logic and data storage, as well as functions to manipulate this data -* `lib` — auxiliary and infrastructural code -* `api` — communication with external APIs, backend API methods -Custom segments are permitted, but should be created sparingly. The most common places for custom segments are the App layer and the Shared layer, where slices don't make sense. +- `ui` — everything related to UI display: UI components, date formatters, styles, etc. +- `api` — backend interactions: request functions, data types, mappers, etc. +- `model` — the data model: schemas, interfaces, stores, and business logic. +- `lib` — library code that other modules on this slice need. +- `config` — configuration files and feature flags. -### Examples +See the [Layers page][layers--layer-definitions] for examples of what each of these segments might be used for on different layers. -| Layer | `ui` | `model` | `lib` | `api` | -| :------- | :----------- | :----------- | :----------- | :----------- | -| Shared | UI kit | Usually not used | Utility modules of several related files.
If you need to use individual helpers, consider using utility libraries such as [`lodash-es`][ext--lodash]. | Rudimentary API client with additional features like authentication or caching. | -| Entities | Skeleton of a business entity with slots for interactive elements | Data storage of instances of this entity as well as functions for manipulating that data.
This segment is most fit for storing server-side data. If you use [TanStack Query][ext--tanstack-query] or other methods of implicit storage, you may choose to omit this segment. | Functions for manipulating instances of this entity that aren't related to storage | API methods using the API client from Shared for easy communication with the backend | -| Features | Interactive elements that enable users to use this feature | Business logic and infrastructure data storage, if needed (e.g., current app theme). This is the code that actually produces value for the user. | Infrastructural code that helps to concisely describe the business logic in the `model` segment | API methods that represent this feature on the backend.
May compose API methods from Entities. | -| Widgets | Composition of Entities and Features into self-contained UI blocks.
Can also contain error boundaries and loading states. | Infrastructure data storage, if needed | Non-business interactions (e.g., gestures) and other necessary code for the block to function on a page | Usually not used, but can contain data loaders in nested routing contexts (e.g., [Remix][ext--remix]) | -| Pages | Composition of Entities, Features and Widgets into complete pages.
Can also contain error boundaries and loading states. | Usually not used | Non-business interactions (e.g., gestures) and other necessary code for the page to deliver a complete user experience | Data loaders for SSR-oriented frameworks | +You can also create custom segments. The most common places for custom segments are the App layer and the Shared layer, where slices don't make sense. -[ref--public-api]: /docs/reference/public-api +Make sure that the name of these segments describes the purpose of the content, not its essence. For example, `components`, `hooks`, and `types` are bad segment names because they aren't that helpful when you're looking for code. -[ext--lodash]: https://www.npmjs.com/package/lodash-es -[ext--tanstack-query]: https://tanstack.com/query/latest -[ext--remix]: https://remix.run +[layers--layer-definitions]: /docs/reference/layers#layer-definitions +[layers--import-rule]: /docs/reference/layers#import-rule-on-layers +[ref-public-api]: /docs/reference/public-api diff --git a/i18n/ja/docusaurus-plugin-content-docs/current.json b/i18n/ja/docusaurus-plugin-content-docs/current.json index d991f18054..7585aa991d 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current.json +++ b/i18n/ja/docusaurus-plugin-content-docs/current.json @@ -1,6 +1,6 @@ { "version.label": { - "message": "v2.0.0 🍰", + "message": "v2.1", "description": "現在のバージョンのラベル" }, "sidebar.getstartedSidebar.category.🚀 Get Started": { diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/get-started/faq.md b/i18n/ja/docusaurus-plugin-content-docs/current/get-started/faq.md index 062f018fed..8d24ada9e0 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/get-started/faq.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/get-started/faq.md @@ -13,7 +13,7 @@ pagination_next: guides/index ### ツールキットやリンターはありますか? {#is-there-a-toolkit-or-a-linter} -公式のESLintコンフィグ — [@feature-sliced/eslint-config][eslint-config-official]、およびコミュニティメンバーのアレクサンドル・ベロウスによって作成されたESLintプラグイン — [@conarti/eslint-plugin-feature-sliced][eslint-plugin-conarti]があります。これらのプロジェクトへの貢献や独自のツールの作成を歓迎します! +はい!CLI または IDE を通じてプロジェクトのアーキテクチャと [フォルダー ジェネレーター][ext-tools] をチェックするための [Steiger][ext-steiger] というリンターがあります。 ### ページのレイアウト/テンプレートはどこに保存すればよいですか? {#where-to-store-the-layouttemplate-of-pages} @@ -58,10 +58,10 @@ FSDは、プロジェクトの主要な価値を提供するコンポーネン [こちら](/docs/guides/examples/auth)で回答しています。 +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools [import-rule-layers]: /docs/reference/layers#import-rule-on-layers [reference-entities]: /docs/reference/layers#entities -[eslint-config-official]: https://github.com/feature-sliced/eslint-config -[eslint-plugin-conarti]: https://github.com/conarti/eslint-plugin-feature-sliced [motivation]: /docs/about/motivation [telegram]: https://t.me/feature_sliced [discord]: https://discord.gg/S8MzWTUsmp diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md b/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md index 4387ebba47..d6003f47d2 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md @@ -144,7 +144,7 @@ v2では、**ロジックの複雑さと強い結合の問題を解消するた - [React Berlin Talk - Oleg Isonen "Feature Driven Architecture"][ext-kof-fdd] -[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion +[refs-low-coupling]: /docs/reference/slices-segments#zero-coupling-high-cohesion [refs-adaptability]: /docs/about/understanding/naming [ext-fdd]: https://github.com/feature-sliced/documentation/tree/rc/feature-driven diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/reference/index.mdx b/i18n/ja/docusaurus-plugin-content-docs/current/reference/index.mdx index 86d8f4b2c1..577abbd725 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/reference/index.mdx +++ b/i18n/ja/docusaurus-plugin-content-docs/current/reference/index.mdx @@ -24,15 +24,9 @@ FSDの重要な概念に関するセクション to="/docs/reference/slices-segments" Icon={AppstoreOutlined} /> - \ No newline at end of file +/> diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md b/i18n/ja/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md index 7b6be93160..ff23863bcd 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md @@ -18,7 +18,7 @@ FSDでは、以下の方法で達成されます。 * アプリケーションを層とスライスに分割する - 特定の機能を実現するモジュール。 * 各モジュールには、[公開API][refs-public-api]を提供することが求められます。 -* モジュール間の[相互作用][refs-isolation]に特別な制限を設ける - 各モジュールは「下位」のモジュールにのみ依存でき、同じ層またはそれ以上の層のモジュールには依存できません。 +* モジュール間の相互作用に特別な制限を設ける - 各モジュールは「下位」のモジュールにのみ依存でき、同じ層またはそれ以上の層のモジュールには依存できません。 ## コンポーネントの構成(UIレベル) {#components-composition-ui-level} @@ -144,4 +144,3 @@ export const { sendMessage, attachFile } = createMessageInput({ * [(プレゼンテーション) 設計原則について(低結合と高凝集を含む)](https://www.slideshare.net/cristalngo/software-design-principles-57388843) [refs-public-api]: /docs/reference/public-api -[refs-isolation]: /docs/reference/isolation diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/reference/isolation/index.md b/i18n/ja/docusaurus-plugin-content-docs/current/reference/isolation/index.md index 70de1bb20d..cb6c499f85 100644 --- a/i18n/ja/docusaurus-plugin-content-docs/current/reference/isolation/index.md +++ b/i18n/ja/docusaurus-plugin-content-docs/current/reference/isolation/index.md @@ -54,10 +54,8 @@ TODO 方法論の経験を積んだ後、このセクションをより詳細に ## 参照 {#see-also} -- [(ガイド) 低い結合性の達成について][refs-low-coupling] - [(ディスカッション) 方法論におけるエンティティとその結合性](https://github.com/feature-sliced/documentation/discussions/49) - [(ディスカッション) クロスインポートと依存関係の分析について](https://github.com/feature-sliced/documentation/discussions/65#discussioncomment-480822) - [パターン **GRASP**](https://ru.wikipedia.org/wiki/GRASP) [refs-public-api]: /docs/reference/public-api -[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion \ No newline at end of file diff --git a/i18n/kr/docusaurus-plugin-content-docs/current/get-started/faq.md b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/faq.md index c244282277..4b1d8468f4 100644 --- a/i18n/kr/docusaurus-plugin-content-docs/current/get-started/faq.md +++ b/i18n/kr/docusaurus-plugin-content-docs/current/get-started/faq.md @@ -13,7 +13,7 @@ pagination_next: guides/index ### toolkit이나 linter가 있나요? -공식 ESLint 설정인 [@feature-sliced/eslint-config][eslint-config-official]와 커뮤니티 멤버인 Aleksandr Belous가 만든 ESLint 플러그인 [@conarti/eslint-plugin-feature-sliced][eslint-plugin-conarti]가 있습니다. 이 프로젝트들에 기여하거나 여러분만의 프로젝트를 시작해보세요! +네! 우리는 CLI 또는 IDE를 통해 프로젝트의 아키텍처와 [폴더 생성기][ext-tools]를 확인하기 위한 [Steiger][ext-steiger]라는 linter를 가지고 있습니다. ### Where to store the layout/template of pages? @@ -59,10 +59,10 @@ pagination_next: guides/index [여기](/docs/guides/examples/auth)에서 답변했습니다. +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools [import-rule-layers]: /docs/reference/layers#import-rule-on-layers [reference-entities]: /docs/reference/layers#entities -[eslint-config-official]: https://github.com/feature-sliced/eslint-config -[eslint-plugin-conarti]: https://github.com/conarti/eslint-plugin-feature-sliced [motivation]: /docs/about/motivation [telegram]: https://t.me/feature_sliced [discord]: https://discord.gg/S8MzWTUsmp diff --git a/i18n/ru/docusaurus-plugin-content-docs/current.json b/i18n/ru/docusaurus-plugin-content-docs/current.json index 6487d3a7d4..0ecd0d7164 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current.json +++ b/i18n/ru/docusaurus-plugin-content-docs/current.json @@ -1,6 +1,6 @@ { "version.label": { - "message": "v2.0.0 🍰", + "message": "v2.1", "description": "The label for version current" }, "sidebar.getstartedSidebar.category.🚀 Get Started": { diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/faq.md b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/faq.md index f5ea8d3154..1a51f0c1d8 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/faq.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/faq.md @@ -13,7 +13,7 @@ pagination_next: guides/index ### Существует ли тулкит или линтер? {#is-there-a-toolkit-or-a-linter} -Есть официальный конфиг для ESLint — [@feature-sliced/eslint-config][eslint-config-official], и плагин для ESLint — [@conarti/eslint-plugin-feature-sliced][eslint-plugin-conarti], созданный участником сообщества Александром Белоусом. Мы будем рады вашим вкладам в эти проекты или созданию своих! +Да! У нас есть линтер [Steiger][ext-steiger] для проверки архитектуры вашего проекта и [генераторы папок][ext-tools] через CLI или IDE. ### Где хранить layout/template страниц? {#where-to-store-the-layouttemplate-of-pages} @@ -58,10 +58,10 @@ _Entity_ — это понятие из реальной жизни, с кото Ответили [здесь](/docs/guides/examples/auth) +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-tools]: https://github.com/feature-sliced/awesome?tab=readme-ov-file#tools [import-rule-layers]: /docs/reference/layers#import-rule-on-layers [reference-entities]: /docs/reference/layers#entities -[eslint-config-official]: https://github.com/feature-sliced/eslint-config -[eslint-plugin-conarti]: https://github.com/conarti/eslint-plugin-feature-sliced [motivation]: /docs/about/motivation [telegram]: https://t.me/feature_sliced [discord]: https://discord.gg/S8MzWTUsmp diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/tutorial.md b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/tutorial.md index 5063269dee..6638f7d837 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/get-started/tutorial.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/get-started/tutorial.md @@ -39,7 +39,7 @@ sidebar_position: 2 Ключевое отличие Feature-Sliced Design от произвольной структуры кода заключается в том, что страницы не могут зависеть друг от друга. То есть одна страница не может импортировать код с другой страницы. Это связано с **правилом импорта для слоёв**: -*Модуль в слайсе может импортировать другие слайсы только в том случае, если они расположены на слоях строго ниже.* +*Модуль (файл) в слайсе может импортировать другие слайсы только в том случае, если они расположены на слоях строго ниже.* В этом случае страница является слайсом, поэтому модули (файлы) внутри этой страницы могут импортировать код только из слоев ниже, а не из других страниц. diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/auth.md b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/auth.md index 1b09d12279..1807b69aed 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/auth.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/examples/auth.md @@ -189,7 +189,7 @@ export function login({ email, password }: { email: string, password: string }) Поскольку API-клиент обычно размещается в `shared/api` или распределяется между сущностями, главной проблемой этого подхода является обеспечение доступа к токену для других запросов, не нарушая при этом [правило импортов для слоёв][import-rule-on-layers]: -> Модуль в слайсе может импортировать другие слайсы только в том случае, если они расположены на слоях строго ниже. +> Модуль (файл) в слайсе может импортировать другие слайсы только в том случае, если они расположены на слоях строго ниже. Есть несколько решений этой проблемы: diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md index 917e7466b3..a348c40303 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-custom.md @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 1 sidebar_label: С кастомной архитектуры --- diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md index f7b3d77f9d..5613ab5d27 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v1.md @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 2 --- # Миграция с v1 @@ -157,7 +157,7 @@ sidebar_position: 4 - [Новые идеи v2 с пояснениями (atomicdesign-chat)][ext-tg-v2-draft] - [Обсуждение абстракций и нейминга для новой версии методологии (v2)](https://github.com/feature-sliced/documentation/discussions/31) -[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion +[refs-low-coupling]: /docs/reference/slices-segments#zero-coupling-high-cohesion [refs-adaptability]: /docs/about/understanding/naming [ext-v1]: https://feature-sliced.github.io/featureslices.dev/v1.0.html diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md new file mode 100644 index 0000000000..371843d981 --- /dev/null +++ b/i18n/ru/docusaurus-plugin-content-docs/current/guides/migration/from-v2-0.md @@ -0,0 +1,45 @@ +--- +sidebar_position: 3 +--- + +# Миграция с v2.0 на v2.1 + +Основным изменением в v2.1 является новая ментальная модель разложения интерфейса — сначала страницы. + +В версии FSD 2.0 рекомендовалось найти сущности и фичи в вашем интерфейсе, рассматривая даже малейшие части представления сущностей и интерактивность как кандидаты на декомпозицию. Затем вы бы могли строить виджеты и страницы из сущностей и фич. В этой модели декомпозиции большая часть логики находилась в сущностях и фичах, а страницы были просто композиционными слоями, которые сами по себе не имели большого значения. + +В версии FSD 2.1 мы рекомендуем начинать со страниц, и возможно даже на них и остановиться. Большинство людей уже знают, как разделить приложение на страницы, и страницы также часто являются отправной точкой при попытке найти компонент в кодовой базе. В новой модели декомпозиции вы храните большую часть интерфейса и логики в каждой отдельной странице, а повторно используемый фундамент — в Shared. Если возникнет необходимость переиспользования бизнес-логики на нескольких страницах, вы можете переместить её на слой ниже. + +Другим нововведением в Feature-Sliced Design 2.1 является стандартизация кросс-импортов между сущностями с помощью `@x`-нотации. + +## Как мигрировать {#how-to-migrate} + +В версии 2.1 нет ломающих изменений, что означает, что проект, написанный с использованием FSD v2.0, также является валидным проектом в FSD v2.1. Однако мы считаем, что новая ментальная модель более полезна для команд и особенно для обучения новых разработчиков, поэтому рекомендуем внести небольшие изменения в вашу декомпозицию. + +### Соедините слайсы + +Простой способ начать — запустить на проекте наш линтер, [Steiger][steiger]. Steiger построен с новой ментальной моделью, и наиболее полезные правила будут: + +- [`insignificant-slice`][insignificant-slice] — если сущность или фича используется только на одной странице, это правило предложит целиком переместить код этой сущности или фичи прямо в эту страницу. +- [`excessive-slicing`][excessive-slicing] — если у слоя слишком много слайсов, это обычно означает, что декомпозиция слишком мелкая. Это правило предложит объединить или сгруппировать некоторые слайсы, чтобы помочь в навигации по проекту. + +```bash +npx steiger src +``` + +Это поможет вам определить, какие слайсы используются только один раз, чтобы вы могли ещё раз подумать, действительно ли они необходимы. Помните, что слой формирует своего рода глобальное пространство имен для всех слайсов внутри него. Точно так же, как вы не захотите загрязнять глобальное пространство имен переменными, которые используются только один раз, вы должны относиться к месту в пространстве имен слоя как к ценному месту, которое следует использовать сдержанно. + +### Стандартизируйте кросс-импорты + +Если у вас были кросс-импорты в вашем проекте до этого (мы не осуждаем!), вы теперь можете воспользоваться новой нотацией для кросс-импортов в Feature-Sliced Design — `@x`-нотацией. Она выглядит так: + +```ts title="entities/B/some/file.ts" +import type { EntityA } from "entities/A/@x/B"; +``` + +Чтоб узнать больше об этом, обратитесь к разделу [Публичный API для кросс-импортов][public-api-for-cross-imports] в разделе справочника. + +[insignificant-slice]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice +[steiger]: https://github.com/feature-sliced/steiger +[excessive-slicing]: https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/excessive-slicing +[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/index.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/reference/index.mdx index 6312615359..10f8ffcda4 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/index.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/reference/index.mdx @@ -24,15 +24,9 @@ import { ApiOutlined, GroupOutlined, AppstoreOutlined, NodeIndexOutlined } from to="/docs/reference/slices-segments" Icon={AppstoreOutlined} /> - diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/_category_.yaml b/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/_category_.yaml deleted file mode 100644 index 2baf3c4c0a..0000000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/_category_.yaml +++ /dev/null @@ -1 +0,0 @@ -label: Изоляция diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md b/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md deleted file mode 100644 index 62cac23dcd..0000000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/coupling-cohesion.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -sidebar_position: 1 ---- - -# Low Coupling & High Cohesion - -Модули приложения должны проектироваться как обладающие **сильной связностью** (направленные на решение одной четкой задачи) и **слабой зацепленностью** (как можно менее зависимые от других модулей) - -
- - -
- Иллюстрация вдохновлена статьей https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/ -
-
- -В рамках методологии это достигается через: - -* Разбиение приложения на слои и слайсы - модули, реализующие конкретную функциональность. -* Требование к каждому модулю - предоставлять [публичный интерфейс доступа][refs-public-api] -* Введение специальных ограничений на [взаимодействие модулей между собой][refs-isolation] - каждый модуль может зависеть только от "нижележащих" модулей, но не от модулей с того же или более высокого слоя. - -## Композиция компонентов (UI level) {#components-composition-ui-level} - -Абсолютное большинство современных UI-фреймворков и библиотек предоставляют компонентную модель, в которой каждый компонент может иметь собственные свойства, собственное состояние и дочерние компоненты, а также, зачастую, слоты. - -Такая модель позволяет собирать интерфейс как **композицию различных, напрямую не связанных между собой компонентов** и, тем самым, достигать **слабой зацепленности** компонентов интерфейса - -### Пример {#example} - -Рассмотрим такую композицию на примере **списка с хедером:** - -#### Закладываем расширяемость {#laying-the-extensibility} - -Компонент списка не будет сам определять вид и структуру компонентов хедера и элементов списка, вместо этого будет принимать их в качестве параметров - -```tsx -interface ListProps { - Header: React.ReactNode; - Items: React.ReactNode; -} - -const List: Component = ({ Header, Items }) => ( -
- {Header} -
    - {Items} -
-
-) - -``` - -#### Используем композицию {#using-the-composition} - -Это позволяет **переиспользовать и независимо изменять** компоненты различных версий хедера и элементов списка. Компоненты хедера и элементов списка могут иметь как свое локальное состояние, так и свою привязку к любым частям общего состояния приложения - компонент списка не будет ничего про это знать, а следовательно, не будет от этого зависеть - -```tsx -} Items={} /> - -} /> - -} Items={} /> - -``` - -## Композиция слоев (APP level) {#layer-composition-app-level} - -Методология предлагает разделять ценную для пользователя функциональность на отдельные модули - **фичи (features)**, а логику, относящуюся к бизнес сущностям - в **сущности (entities)**. И фичи, и сущности **должны проектироваться как высоко-связные модули**, т.е. направленные на решение **одной конкретной задачи** или сконцентрированные вокруг **одной конкретной сущности.** - -Все взаимодействия между такими модулями, аналогично UI-компонентам из примера выше, должны быть организованы как **композиция различных модулей.** - -### Пример {#example} - -На примере приложения-чата с такими возможностями - -* можно открыть список контактов и выбрать друга -* можно открыть переписку с выбранным другом - -В рамках методологии, это может быть представлено примерно так: - -Entities - -* Пользователь (содержит состояние пользователя) -* Контакт (состояние списка контактов, инструменты для работы с отдельным контактом) -* Переписка (состояние текущей переписки и работа с ней) - -Features - -* Форма отправки сообщения -* Меню выбора переписки - -#### Свяжем все это вместе {#lets-tie-it-all-together} - -В приложении, для начала, будет одна страница, интерфейс будет основан на слегка модифицированном компоненте из первого примера - -```tsx title="page/main/ui.tsx" -} - Items={} - Footer={} -/> -``` - -#### Модель данных {#data-model} - -Модель данных страницы будет организована как **композиция фич и сущностей**. В рамках этого примера фичи будут реализованы как фабрики и получать доступ к интерфейсу сущностей через параметры этих фабрик. - -> Однако, реализация в виде фабрики необязательна - фича может зависеть от нижележащих слоев и напрямую - -```ts title="pages/main/model.ts" -import { userModel } from "entitites/user" -import { conversationModel } from "entities/conversation" -import { contactModel } from "entities/contact" - -import { createMessageInput } from "features/message-input" -import { createConversationSwitch } from "features/conversation-switch" - -import { beautifiy } from "shared/lib/beautify-text" - -export const { allConversations, setConversation } = createConversationSwitch({ - contacts: contactModel.allContacts, - setConversation: conversationModel.setConversation, - currentConversation: conversationModel.conversation, - currentUser: userModel.currentUser -}) - -export const { sendMessage, attachFile } = createMessageInput({ - author: userModel.currentUser - send: conversationModel.sendMessage, - formatMessage: beautify -}) -``` - -## Итого {#summary} - -1. Модули должны обладать **сильной связностью** (иметь одну ответственность, решать одну конкретную задачу) и предоставлять [**публичный интерфейс**][refs-public-api] доступа -2. **Слабая зацепленность** достигается через композицию элементов - компонентов UI, фич и сущностей -3. Также, для снижения зацепленности, модули **должны зависеть друг от друга только через публичные интерфейсы** - так достигается независимость модулей от внутренней реализации друг друга - -## См. также {#see-also} - -* [(Статья) Про Low Coupling и High Cohesion наглядно](https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/) - * *Схема в начале вдохновлена именно этой статьей* -* [(Статья) Low Coupling и High Cohesion. Закон Деметры](https://medium.com/german-gorelkin/low-coupling-high-cohesion-d36369fb1be9) -* [(Презентация) Про принципы проектирования (включая Low Coupling & High Cohesion)](https://www.slideshare.net/cristalngo/software-design-principles-57388843) - -[refs-public-api]: /docs/reference/public-api -[refs-isolation]: /docs/reference/isolation diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/decouple-entities.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/decouple-entities.mdx deleted file mode 100644 index 46b2c1e093..0000000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/decouple-entities.mdx +++ /dev/null @@ -1,21 +0,0 @@ ---- -sidebar_position: 2 -sidebar_class_name: sidebar-item--wip ---- - -import WIP from '@site/src/shared/ui/wip/tmpl.mdx' - -# Decouple entities - - - -> Про кросс-импорты типов, адаптеры и про то - как явно выстраивать связи между сущностями - -> Также про мифические absolutely-decoupled entities - -## См. также {#see-also} - -- [(Тред) Памятка про декомпозицию по сущностям и выстраивание явных связей между ними](https://t.me/feature_sliced/3633) -- [(Тред) Пример декомпозиции для "связных сущностей" (users/pets/friends)](https://t.me/feature_sliced/3316) -- [(Тред) Про кросс-импорты типов/адаптеров в сущностях](https://t.me/feature_sliced/4276) -- [(Тред) Про границы сущностей и фич](https://t.me/feature_sliced/4521) diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/index.md b/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/index.md deleted file mode 100644 index 4281508ba9..0000000000 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/isolation/index.md +++ /dev/null @@ -1,63 +0,0 @@ -# Изоляция модулей - -В рамках методологии все модули распределены по зонам ответственности (layer, slice, segment) - -Слои, в свою очередь, организованы вертикально: - -- "Внизу" находятся переиспользуемые модули (ui-kit, внутренние библиотеки проекта), как наиболее абстрактные -- А по мере продвижения "вверх" располагаются более специфичные модули. - -Независимо от принадлежности к какому-либо слайсу, каждый модуль [**обязан предоставлять публичный интерфейс доступа**][refs-public-api]. - -## Требования {#requirements} - -Взаимодействие каждого модуля с остальным приложением проектируется с учетом ряда требований: - -1. **Слабое зацепление** с другими модулями - - *Изменение в одном модуле должно слабо и предсказуемо влиять на другие* -1. **Высокая связность** - обязанности каждого модуля "сфокусированы" на одной задаче - - *Если модуль имеет слишком много ответственностей (начинает "делать слишком много") - это должно быть замечено как можно раньше* -1. **Отсутствие циклических зависимостей** на масштабе всего приложения - - *Часто приводят к неожиданному, нежелательному поведению, лучше избегать их совсем* - -## Правило {#rule} - -Для выполнения этих требований, в рамках методологии, необходимо соблюдать базовое правило: - -:::info Важно - -Модуль может зависеть только от "нижележащих" модулей, но не от модулей с того же или более высокого слоя - -::: - -- `features/auth` **не может** зависеть от `features/filters` **и наоборот** -- `features/auth` **может** зависеть от `shared/ui/button`, **но не наоборот** - -Следование этому правилу позволяет поддерживать зависимости **"однонаправленными"** - что автоматически **исключает циклические импорты** и значительно **упрощает отслеживание зависимостей** между модулями в приложении. - -## Выявление проблем {#identifying-problems} - - -Нарушение этого правила является сигналом проблем: - -1. Модуль имеет **импорт из другого модуля** со своего слоя - - Возможно, модуль был **излишне раздроблен** или имеет **лишнюю ответственность.** - - Следует **объединить** его с импортируемым модулем или **вынести его (частично или целиком) на слой ниже** или перенести логику связей в модули на вышестоящих слоях. -1. Модуль **импортируется многими модулями** со своего слоя - - Возможно, модуль имеет **лишнюю ответственность.** - - Следует **вынести его (частично или целиком) на слой ниже**, либо перенести логику связей в модули на вышестоящих слоях. -1. Модуль **имеет импорты из множества модулей** со своего слоя - - Возможно, модуль принадлежит к **другой области ответственности.** - - Следует **вынести его (частично или целиком) на слой выше**. - -## См. также {#see-also} - -- [(Гайд) Про достижение низкой связанности][refs-low-coupling] -- [(Обсуждение) Entities в методологии и их связность](https://github.com/feature-sliced/documentation/discussions/49) -- [(Обсуждение) Про cross-импорты и анализ зависимостей](https://github.com/feature-sliced/documentation/discussions/65#discussioncomment-480822) -- [Паттерны **GRASP**](https://ru.wikipedia.org/wiki/GRASP) - -[refs-public-api]: /docs/reference/public-api -[refs-low-coupling]: /docs/reference/isolation/coupling-cohesion diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/layers.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/reference/layers.mdx index e22758155f..c8703abcfb 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/layers.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/reference/layers.mdx @@ -5,155 +5,120 @@ pagination_next: reference/slices-segments # Слои -Слои - это первый уровень организационной иерархии в Feature-Sliced Design. Их цель - разделить код на основе того, сколько ответственности ему требуется и от скольких других модулей в приложении он зависит. +Слои - это первый уровень организационной иерархии в Feature-Sliced Design. Их цель - разделить код на основе того, сколько ответственности ему требуется и от скольких других модулей в приложении он зависит. Каждый слой несет особое семантическое значение, чтобы помочь вам определить, сколько ответственности следует выделить вашему коду. -:::note +Всего существует **7 слоев**, расположенных от наибольшей ответственности и зависимости к наименьшей: -На этой странице _модуль_ означает внутренний модуль в приложении - файл или каталог с индексным файлом. Не путать с npm-пакетами. +Дерево файловой системы с одной корневой папкой под названием src и семью подпапками: app, processes, pages, widgets, features, entities, shared. Папка processes слегка выцвечена. +Дерево файловой системы с одной корневой папкой под названием src и семью подпапками: app, processes, pages, widgets, features, entities, shared. Папка processes слегка выцвечена. -::: - -Каждый слой несет в себе особый семантический смысл, помогающий определить, какую ответственность следует возложить на модуль в вашем коде. Названия и значения слоёв стандартизированы для всех проектов, построенных с использованием Feature-Sliced Design. - -Всего существует **7 слоёв**, расположенных от наибольшей ответственности и зависимости к наименьшей: - -Дерево файловой системы с одной корневой папкой src и семью подпапками: app, processes, pages, widgets, features, entities, shared. Папка processes отображена немного тускло. -Дерево файловой системы с одной корневой папкой src и семью подпапками: app, processes, pages, widgets, features, entities, shared. Папка processes отображена немного тускло. - -1. App (Приложение) +1. App (Эпп) 2. Processes (Процессы, устаревший слой) 2. Pages (Страницы) 3. Widgets (Виджеты) 4. Features (Фичи/функции) 5. Entities (Сущности) -6. Shared (Общий) +6. Shared (Шэред) -Вы не обязаны использовать все слои в своем проекте - добавляйте только те, что приносят пользу вашему проекту. +Вы не обязаны использовать все слои в своем проекте - добавляйте только те, что приносят пользу вашему проекту. Как правило, в большинстве фронтенд-проектов будут как минимум слои Shared, Pages и App. -## Правило импортов для слоёв {#import-rule-on-layers} +На практике слои представляют собой папки с названиями в нижнем регистре (например, `📁 shared`, `📁 pages`, `📁 app`). Добавление новых слоев _не рекомендуется_, так как их семантика стандартизирована. -Слои состоят из _слайсов_ — сильно сцепленных групп модулей. Feature-Sliced Design поддерживает низкую связанность, поэтому зависимости между слайсами регулируются **правилом импортов для слоёв**: +## Правило импорта слоев -> _Модуль в слайсе может импортировать другие слайсы только в том случае, если они расположены на слоях строго ниже._ +Слои состоят из _слайсов_ — высокосвязанных групп модулей. Зависимости между слайсами регулируются **правилом импорта слоев**: -Например, в `~/features/aaa`, слайсом является `aaa`, поэтому файл `~/features/aaa/api/request.ts` не может импортировать код ни из какого модуля в папку из `~/features/bbb`, но может импортировать код из `~/entities` и `~/shared`, а также из соседних модулей в `~/features/aaa`. +> _Модуль (файл) в слайсе может импортировать другие слайсы только если они находятся на слоях строго ниже._ + +Например, папка `📁 ~/features/aaa` является слайсом с именем "aaa". Файл внутри нее, `~/features/aaa/api/request.ts`, не может импортировать код из любого файла в `📁 ~/features/bbb`, но может импортировать код из `📁 ~/entities` и `📁 ~/shared`, а также любой код из `📁 ~/features/aaa`, например, `~/features/aaa/lib/cache.ts`. + +Слои App и Shared являются **исключениями** из этого правила — они одновременно являются и слоем, и слайсом. Слайсы разделяют код по бизнес-доменам, и эти два слоя являются исключениями, потому что в Shared нет бизнес-доменов, а App объединяет все бизнес-домены. + +На практике это означает, что слои App и Shared состоят из сегментов, и сегменты могут свободно импортировать друг друга. ## Определения слоёв +В этом разделе описывается семантическое значение каждого слоя, чтобы создать интуитивное представление о том, какой код в нём будет лежать. + ### Shared -Изолированные модули, компоненты и абстракции, отдельные от специфики проекта или бизнеса. -Внимание: не следует использовать этот слой как [свалку утилит][ext--sova-utility-dump]! +Этот слой формирует фундамент для остальной части приложения. Это место для создания связей с внешним миром, например, бэкенды, сторонние библиотеки, среда выполнения приложения (environment). Также это место для ваших собственных библиотек, сконцентрированных на конкретной задаче. -Этот слой, в отличие от других, состоит не из слайсов, а непосредственно из сегментов. +Этот слой, как и слой App, _не содержит слайсов_. Слайсы предназначены для разделения слоя на предметные области, но предметные области не существуют в Shared. Это означает, что все файлы в Shared могут ссылаться и импортировать друг друга. -**Примеры содержимого**: +Вот сегменты, которые вы обычно можете найти в этом слое: -* UI-библиотека -* API-клиент -* Код, работающий с API браузера +- `📁 api` — API-клиент и, возможно, функции для выполнения запросов к конкретным эндпоинтам бэкенда. +- `📁 ui` — UI-кит приложения. + Компоненты на этом слое не должны содержать бизнес-логику, но могут быть тематически связаны с бизнесом. Например, здесь можно разместить логотип компании и макет страницы. Компоненты с UI-логикой также допустимы (например, автозаполнение или строка поиска). +- `📁 lib` — коллекция внутренних библиотек. + Эта папка не должна рассматриваться как хелперы или утилиты ([прочитайте здесь, почему эти папки часто превращаются в свалку][ext-sova-utility-dump]). Вместо этого каждая библиотека в этой папке должна иметь одну область фокуса, например, даты, цвета, манипуляции с текстом и т.д. Эта область фокуса должна быть задокументирована в файле README. Разработчики в вашей команде должны знать, что можно и что нельзя добавлять в эти библиотеки. +- `📁 config` — переменные окружения, глобальные фиче-флаги и другая глобальная конфигурация для вашего приложения. +- `📁 routes` — константы маршрутов или шаблоны для сопоставления маршрутов. +- `📁 i18n` — код, настраивающий систему переводов, а также глобальные строки перевода. + +Вы можете добавлять ещё сегментов, но убедитесь, что название этих сегментов описывает цель содержимого, а не его суть. Например, `components`, `hooks` и `types` — плохие имена сегментов, поскольку они не очень полезны при поиске кода. ### Entities -Понятия из реального мира, которые вместе образуют суть проекта. Как правило, это термины, которые бизнес использует для описания продукта. +Слайсы на этом слое представляют концепции из реального мира, с которыми работает проект. Обычно это термины, которые бизнес использует для описания продукта. Например, социальная сеть может работать с бизнес-сущностями, такими как Пользователь, Публикация и Группа. -Каждый слайс этого слоя содержит статические элементы пользовательского интерфейса, хранилища данных и операции CRUD (создание-чтение-изменение-удаление). +Слайс сущности может содержать хранилище данных (`📁 model`), схемы валидации данных (`📁 model`), функции запросов API, связанные с сущностями (`📁 api`), а также визуальное представление этой сущности в интерфейсе (`📁 ui`). Это визуальное представление не обязательно должно создавать полный блок пользовательского интерфейса — оно в первую очередь предназначено для переиспользования одного и того же внешнего вида на нескольких страницах приложения, и к нему может быть присоединена различная бизнес-логика через пропы или слоты. -**Примеры слайсов**: +#### Связи между сущностями - - -
Для социальной сети Для Git-фронтенда (например, GitHub)
    -
  • Пользователь
  • -
  • Публикация
  • -
  • Группа
  • -
    -
  • Репозиторий
  • -
  • Файл
  • -
  • Коммит
  • -
+Сущности в FSD являются слайсами, и по умолчанию слайсы не могут знать друг о друге. Однако в реальной жизни сущности часто взаимодействуют друг с другом, и иногда одна сущность владеет или содержит другие сущности. Из-за этого бизнес-логику этих взаимодействий лучше всего хранить на более высоких уровнях, таких как Features или Pages. +Когда объект данных одной сущности содержит другие объекты данных, обычно хорошей идеей является сделать связь между сущностями явной и обойти изоляцию слайсов, создав API для кросс-ссылок через `@x`-нотацию. Причина в том, что связанные сущности должны рефакториться вместе, поэтому лучше сделать так, чтоб связь было невозможно не заметить. -:::tip +Например: -Вы можете заметить в примере фронтенда для Git, что _репозиторий_ содержит _файлы_. Это делает репозиторий сущностью более высокого уровня, внутри которой вложены другие сущности. Это частая ситуация с сущностями, и иногда трудно иметь такие сущности высокого уровня, не нарушая правило импортов для слоёв. +```ts title="entities/artist/model/artist.ts" +import type { Song } from "entities/song/@x/artist"; -Вот несколько предложений по решению этой проблемы: -* UI сущностей должен содержать слоты для мест, куда будут вставляться сущности нижнего уровня -* Бизнес-логика, связанная с взаимодействием сущностей, должна быть размещена в Features (в большинстве случаев) -* Интерфейсы сущностей из базы данных могут быть извлечены в слой Shared, рядом с API-клиентом +export interface Artist { + name: string; + songs: Array; +} +``` -::: +```ts title="entities/song/@x/artist.ts" +export type { Song } from "../model/song.ts"; +``` + +Вы можете узнать больше о `@x`-нотации в разделе [Публичный API для кросс-импортов][public-api-for-cross-imports]. ### Features -Действия, которые пользователь может совершать в приложении для взаимодействия с бизнес-сущностями, чтоб достичь ценного для себя результата. Сюда также входят действия, которые приложение выполняет от имени пользователя, чтобы создать для него ценность. +Этот слой предназначен для основных взаимодействий в вашем приложении, действий, которые важны вашим пользователям. Эти взаимодействия часто затрагивают бизнес-сущности, поскольку сущности — это то, о чём ваше приложение. -Слайс на этом слое может содержать _интерактивные_ элементы пользовательского интерфейса, внутреннее состояние и запросы к API, которые позволяют выполнять действия, создающие ценность. +Важный принцип эффективного использования слоя Features: **не все должно быть фичей**. Хорошим показателем того, что что-то должно быть фичей, является тот факт, что оно переиспользуется. Например, если в приложении есть несколько редакторов, и у всех них есть комментарии, то комментарии являются переиспользуемой фичей. Помните, что слайсы — это механизм для быстрого поиска кода, и если фич слишком много, важные фичи теряются. -**Примеры слайсов**: +В идеале, когда вы приходите в новый проект, вы узнаёте о его функциональности, просматривая страницы и фичи. Принимая решение о том, что должно быть функцией, оптимизируйте опыт новичка в проекте, который/ая хочет быстро обнаружить большие важные области кода. - - -
Для социальной сети Для Git-фронтенда (например, GitHub) Действия от имени пользователя
    -
  • Авторизоваться
  • -
  • Создать публикацию
  • -
  • Вступить в группу
  • -
    -
  • Изменить файл
  • -
  • Оставить комментарий
  • -
  • Слить ветки
  • -
    -
  • Автоматически включить тёмную тему
  • -
  • Выполнить вычисления в фоне
  • -
  • Действия на основе User-Agent
  • -
+Слайс фичи может содержать UI для выполнения действия, например, форму (`📁 ui`), вызовы API, необходимые для выполнения действия (`📁 api`), валидацию и внутреннее состояние (`📁 model`), фиче-флаги (`📁 config`). ### Widgets -Самодостаточные блоки пользовательского интерфейса возникли из композиции единиц более низкого уровня, таких как сущности и функции. +Слой Widgets предназначен для больших самодостаточных блоков интерфейса. Виджеты наиболее полезны, когда они используются на нескольких страницах или когда страница, к которой они принадлежат, имеет несколько больших независимых блоков, и это один из них. -Этот слой предоставляет возможность заполнить слоты, оставленные в интерфейсе сущностей, другими сущностями и интерактивными элементами из фич. Поэтому обычно на этом слое не размещается бизнес-логика, вместо этого она хранится в фичах. Каждый слайс на этом слое содержит готовые к использованию компоненты пользовательского интерфейса и иногда не-бизнес-логику, например, жесты, взаимодействие с клавиатурой и т.д. +Если блок интерфейса составляет бо́льшую часть интересного контента на странице и никогда не используется повторно, он **не должен быть виджетом**, и вместо этого его следует разместить непосредственно на этой странице. -Иногда удобнее разместить бизнес-логику на этом слое. Зачастую, это происходит тогда, когда виджет имеет довольно много интерактивности (например, интерактивные таблицы) и бизнес-логика в нём не переиспользуется. - -**Примеры слайсов**: +:::tip - - -
Для социальной сети Для Git-фронтенда (например, GitHub)
    -
  • Карточка публикации
  • -
  • Шапка профиля пользователя (с действиями)
  • -
    -
  • Список файлов в репозитории (с действиями)
  • -
  • Комментарий в ветке комментариев
  • -
  • Карточка репозитория
  • -
+Если вы используете систему вложенного роутинга (например, роутер [Remix][ext-remix]), может быть полезно использовать слой Widgets так же, как плоская система роутинга использует слой Pages — для создания полных блоков роутинга, включая связанные запросы данных, состояния загрузки и границы ошибок. -:::tip +Таким же образом, вы можете хранить лейауты страниц на этом слое. -Если вы используете вложенную систему маршрутизации (например, роутер [Remix][ext--remix]), может быть полезно использовать слой Widgets аналогично слою Pages в плоской системе маршрутизации - для создания полноценных интерфейсных блоков с получением соответствующих данных с бэкенда, состояниями загрузки и ограничителями ошибок. Также здесь можно разместить лейауты для слоя Pages. ::: ### Pages -Полноценные страницы для страничных приложений (например, веб-сайтов) или экраны/активности для экранных приложений (например, мобильных приложений). - -По своей композиционной природе этот слой похож на Widgets, хоть и в большем масштабе. Каждый слайс на этом слое содержит компоненты пользовательского интерфейса, готовые к подключению к роутеру, а также может содержать логику получения данных и обработки ошибок. +Страницы — это то, из чего состоят веб-сайты и приложения (также называются "экраны" или "активности"). Одна страница обычно соответствует одному слайсу, однако, если есть несколько очень похожих страниц, их можно сгруппировать в один слайс, например, формы регистрации и входа. -**Примеры слайсов**: +Нет никаких ограничений на количество кода, которое можно разместить в слайсе страницы, по крайней мере, до тех пор, пока вашей команде не станет сложно ориентироваться в ней. Если блок интерфейса со страницы не переиспользуется на других страницах, вполне допустимо оставить его внутри слайса страницы. - - -
Для социальной сети Для Git-фронтенда (например, GitHub)
    -
  • Лента новостей
  • -
  • Страница сообщества
  • -
  • Публичный профиль пользователя
  • -
    -
  • Страница репозитория
  • -
  • Репозитории пользователя
  • -
  • Ветки в репозитории
  • -
+В слайсе страницы вы обычно найдете интерфейс страницы, а также состояния загрузки и границы ошибок (`📁 ui`). Также вы можете найти там запросы на получение и изменение данных (`📁 api`). Обычно у страницы нет выделенной модели данных, и небольшие части состояния могут храниться в самих компонентах. ### Processes @@ -165,20 +130,21 @@ pagination_next: reference/slices-segments Выход из ситуаций, когда требуется сложное многостраничное взаимодействие. -Этот уровень намеренно оставлен не очень определенным. Большинству приложений этот слой не пригодится, логику на уровне роутера и сервера следует оставить на уровне App. Рассмотрите возможность использования этого слоя только тогда, когда слой App вырастет настолько, что станет неподдерживаемым и потребует разгрузки. +Этот уровень намеренно оставлен не очень определенным. Большинству приложений этот слой не пригодится, логику на уровне роутера и сервера следует оставить на уровне App. Используйте этот слой только тогда, когда слой App вырастет настолько, что станет неподдерживаемым и потребует разгрузки. ### App -Всё, что касается всего приложения, как в техническом смысле (например, провайдеры контекста), так и в бизнес-смысле (например, аналитика). +Всё, что касается приложения целиком, как в техническом смысле (например, провайдеры контекста), так и в бизнес-смысле (например, аналитика). -Этот слой обычно не содержит слайсов, как и Shared, вместо этого он содержит непосредственно сегменты. +Этот слой обычно не содержит слайсов, как и Shared, и вместо этого внутри него сразу находятся сегменты. -**Примеры содержимого**: +Вот сегменты, которые вы обычно можете найти в этом слое: -* Стили -* Роутер -* Хранилища данных и прочие провайдеры контекста -* Инициализация аналитики +- `📁 routes` — конфигурация роутера +- `📁 store` — глобальная конфигурация хранилища +- `📁 styles` — глобальные стили +- `📁 entrypoint` — точка входа в код приложения, специфичная для вашего фреймворка -[ext--remix]: https://remix.run -[ext--sova-utility-dump]: https://sergeysova.com/ru/why-utils-and-helpers-is-a-dump/ +[public-api-for-cross-imports]: /docs/reference/public-api#public-api-for-cross-imports +[ext-remix]: https://remix.run +[ext-sova-utility-dump]: https://sergeysova.com/ru/why-utils-and-helpers-is-a-dump/ diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/public-api.md b/i18n/ru/docusaurus-plugin-content-docs/current/reference/public-api.md index fdf59a71ad..66ed9cfb5f 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/public-api.md +++ b/i18n/ru/docusaurus-plugin-content-docs/current/reference/public-api.md @@ -1,218 +1,154 @@ --- sidebar_position: 3 -sidebar_label: Public API -pagination_next: about/index --- -# Публичное API модуля приложения +# Публичный API -Каждая сущность методологии проектируется как **удобный в использовании и интеграции модуль.** +Публичный API — это _контракт_ между группой модулей, например, слайсом, и кодом, который его использует. Он также действует как ворота, разрешая доступ только к определенным объектам и только через этот публичный API. -## Цели {#goals} +На практике это обычно реализуется как индексный файл с реэкспортами: -Удобство использования и интеграции модуля достигается через выполнение *ряда целей*: - -1. Приложение должно быть **защищено от изменений** внутренней структуры отдельных модулей -1. Переработка внутренней структуры модуля **не должна затрагивать** другие модули -1. Существенные изменения поведения модуля должны быть **легко определяемы** - > **Существенные изменения поведения модуля** - изменения, ломающие ожидания сущностей-пользователей модуля. - -Достичь этих целей позволяет введение публичного интерфейса (Public API), представляющего собой единую точку доступа к возможностям модуля и определяющего "контракт" взаимодействия модуля с внешним миром. - -:::info Важно - -Структура сущности должна иметь единую точку входа, предоставляющую публичный интерфейс - -::: - -```sh -└── features/                        #  - └── auth-form/                  # Внутренняя структура фичи - ├── ui/                    # - ├── model/                 # - ├── {...}/                 # - └── index.ts               # Энтрипоинт фичи с ее публичным API +```js title="pages/auth/index.js" +export { LoginPage } from "./ui/LoginPage"; +export { RegisterPage } from "./ui/RegisterPage"; ``` -```ts title="**/**/index.ts" -export { Form as AuthForm } from "./ui" -export * as authFormModel from "./model" -``` - -## Требования к публичному API {#requirements-for-the-public-api} - -Выполнение этих требований позволяет свести взаимодействие с модулем к **выполнению публичного интерфейса-контракта** и, тем самым, достичь надежности и удобства в использовании модуля. - -### 1. Контроль доступа {#1-access-control} - -Public API должен осуществлять **контроль доступа** к содержимому модуля - -- Другие части приложения могут использовать **только те сущности модуля, которые представлены в публичном интерфейсе** -- Внутренняя часть модуля за пределами публичного интерфейса **доступны только самому модулю**. - -#### Примеры {#examples} +## Что делает публичный API хорошим? -##### Отстранение от приватных импортов {#suspension-from-private-imports} +Хороший публичный API делает использование и интеграцию слайса в другой код удобным и надежным. Этого можно достичь, поставив три цели: -- **Плохо**: Идет обращение напрямую к внутренним частям модуля, минуя публичный интерфейс доступа - опасно, особенно при рефакторинге модуля +1. Остальная часть приложения должна быть защищена от структурных изменений в слайсе, таких как рефакторинг. +2. Значительные изменения в поведении слайса, которые нарушают предыдущие ожидания, должны вызывать изменения в публичном API. +3. Только необходимые части слайса должны быть доступны. - ```diff - - import { Form } from "features/auth-form/components/view/form" - ``` +Последняя цель имеет важные практические последствия. Может возникнуть соблазн создать слепые реэкспорты всего, особенно на ранних этапах разработки слайса, потому что любые новые объекты, которые вы экспортируете из своих файлов, также автоматически экспортируются из слайса: -- **Хорошо:** API заранее экспортирует только нужное и разрешенное, разработчику модуля теперь нужно думать только о том, чтобы не ломать Public API при рефакторинге - - ```diff - + import { AuthForm } from "features/auth-form" - ``` - -### 2. Устойчивость к изменениям {#2-sustainability-for-changes} - -Public API должен быть **устойчивым к изменениям** внутри модуля - -- Изменения, ломающие поведения модуля, должны отражаться в изменении Public API - -#### Примеры {#examples} - -##### Абстрагирование от реализации {#abstracting-from-the-implementation} +```js title="Плохая практика, features/comments/index.js" +// ❌ НИЖЕ ПЛОХОЙ КОД, НЕ ДЕЛАЙТЕ ТАК +export * from "./ui/Comment"; // 👎 не пытайтесь повторить дома +export * from "./model/comments"; // 💩 это плохая практика +``` -Изменение внутренней структуры не должно приводить к изменению Public API +Это ухудшает понимаемость слайса беглым взглядом, потому что вы не можете легко определить, каков интерфейс этого слайса. Не зная интерфейс, вам придется глубоко погружаться в код слайса, чтобы понять, как его интегрировать. Еще одна проблема заключается в том, что вы можете случайно раскрыть внутренние модули, что усложнит рефакторинг, если кто-то начнет от них зависеть. -- **Плохо:** перемещение или переименование этого компонента внутри фичи приведет к необходимости рефакторить импорты во всех местах использования компонента. +## Публичный API для кросс-импортов {#public-api-for-cross-imports} - ```diff - - import { Form } from "features/auth-form/ui/form" - ``` +Кросс-импорты — это ситуация, когда один слайс импортирует из другого слайса на том же слое. Обычно это запрещено [правилом импорта для слоёв][import-rule-on-layers], но часто есть реальные причины, чтоб сделать кросс-импорт. Например, в реальном мире бизнес-сущности часто ссылаются друг на друга, и лучше отразить эти отношения в коде, а не пытаться избавиться от них. -- **Хорошо:** интерфейс фичи не отображает её внутреннюю структуру, внешние "пользователи" фичи не пострадают от перемещения или переименования компонента внутри фичи +Для этой цели существует особый вид публичного API, также известный как `@x`-нотация. Если у вас есть сущности A и B, и сущность B должна импортировать из сущности A, то сущность A может объявить отдельный публичный API только для сущности B. - ```diff - + import { AuthForm } from "features/auth-form" - ``` +- `📂 entities` + - `📂 A` + - `📂 @x` + - `📄 B.ts` — специальный публичный API только для кода внутри `entities/B/` + - `📄 index.ts` — обычный публичный API -### 3. Интегрируемость {#3-integrability} +Затем код внутри `entities/B/` может импортировать из `entities/A/@x/B`: -Public API должен способствовать **легкой и гибкой интеграции** +```ts +import type { EntityA } from "entities/A/@x/B"; +``` -- Должен быть удобен для использования остальными частями приложения, в частности, решать проблему коллизии имен +Нотацию `A/@x/B` следует читать как "пересечение A и B". -#### Примеры {#examples} +:::note -##### Коллизия имен {#name-collision} +Старайтесь свести кросс-импорты к минимуму и **используйте эту нотацию только на уровне Entities**, где устранение кросс-импортов часто неразумно. -- **Плохо:** будет коллизия имен +::: - ```ts title="features/auth-form/index.ts" - export { Form } from "./ui" - export * as model from "./model" - ``` +## Проблемы с индексными файлами - ```ts title="features/post-form/index.ts" - export { Form } from "./ui" - export * as model from "./model" - ``` +Индексные файлы, такие как `index.js`, также известные как barrel-файлы (файлы-бочки), являются самым распространенным способом определения публичного API. Их легко создать, но они иногда вызывают проблемы с некоторыми сборщиками и фреймворками. - ```diff - - import { Form, model } from "features/auth-form" - - import { Form, model } from "features/post-form" - ``` +### Циклические импорты -- **Хорошо:** коллизия решена на уровне интерфейса +Циклический импорт — это когда два или более файла импортируют друг друга по кругу. - ```ts title="features/auth-form/index.ts" - export { Form as AuthForm } from "./ui" - export * as authFormModel from "./model" - ``` + - ```ts title="features/post-form/index.ts" - export { Form as PostForm } from "./ui" - export * as postFormModel from "./model" - ``` +
+ Три файла, импортирующие друг друга по кругу + Три файла, импортирующие друг друга по кругу +
+ На изображении выше: три файла, `fileA.js`, `fileB.js` и `fileC.js`, импортирующие друг друга по кругу. +
+
- ```diff - + import { AuthForm, authFormModel } from "features/auth-form" - + import { PostForm, postFormModel } from "features/post-form" - ``` +Эти ситуации часто трудно обрабатывать сборщикам, и в некоторых случаях они могут даже привести к ошибкам во время выполнения кода, которые может быть трудно отладить. -##### Гибкое использование {#flexible-use} +Циклические импорты могут возникать и без индексных файлов, но наличие индексного файла создает явную возможность случайно создать циклический импорт. Это часто происходит, когда у вас есть два объекта, доступных в публичном API слайса, например, `HomePage` и `loadUserStatistics`, и `HomePage` нужно получить доступ к `loadUserStatistics`, но он делает это следующим образом: -- **Плохо:** неудобно писать, неудобно читать, "пользователь" фичи страдает +```jsx title="pages/home/ui/HomePage.jsx" +import { loadUserStatistics } from "../"; // импортируем из pages/home/index.js - ```diff - - import { storeActionUpdateUserDetails } from "features/auth-form" - - dispatch(storeActionUpdateUserDetails(...)) - ``` +export function HomePage() { /* … */ } +``` -- **Хорошо:** "пользователь" фичи получает доступ к нужным вещам итеративно и гибко +```js title="pages/home/index.js" +export { HomePage } from "./ui/HomePage"; +export { loadUserStatistics } from "./api/loadUserStatistics"; +``` - ```diff - + import { authFormModel } from "features/auth-form" - + dispatch(authFormModel.effects.updateUserDetails(...)) // redux - + authFormModel.updateUserDetailsFx(...) // effector - ``` +Эта ситуация создает циклический импорт, потому что `index.js` импортирует `ui/HomePage.jsx`, но `ui/HomePage.jsx` тоже импортирует `index.js`. -##### Разрешение коллизий {#resolution-of-collisions} +Чтобы предотвратить эту проблему, воспользуйтесь этими принципами. Если у вас есть два файла, и один импортирует из другого: +- Если они находятся в одном слайсе, всегда используйте _относительные_ импорты и пишите полный путь импорта +- Если они находятся в разных слайсах, всегда используйте _абсолютные_ импорты, например, через алиас -Коллизия имен должна решаться на уровне публичного интерфейса, а не реализации +### Большие бандлы и неработающий tree-shaking в Shared {#large-bundles} -- **Плохо:** коллизия имен решается на уровне реализации +Некоторым сборщикам может быть трудно выполнять tree-shaking (удаление неимпортированного кода), когда у вас есть индексный файл, который реэкспортирует все. - ```ts title="features/auth-form/index.ts" - export { AuthForm } from "./ui" - export { authFormActions, authFormReducer } from "model" - ``` +Обычно это не проблема для публичных API, потому что содержимое модуля обычно довольно тесно связано, поэтому вам редко нужно импортировать одну вещь, но удалить другую. Однако есть два очень распространенных случая, когда обычные правила публичного API в FSD могут привести к проблемам — `shared/ui` и `shared/lib`. - ```ts title="features/post-form/index.ts" - export { PostForm } from "./ui" - export { postFormActions, postFormReducer } from "model" - ``` +Эти две папки являются коллекциями несвязанных вещей, которые часто не нужны все в одном месте. Например, в `shared/ui` могут быть модули для каждого компонента в библиотеке UI: -- **Хорошо:** коллизия имен решается на уровне интерфейса +- `📂 shared/ui/` + - `📁 button` + - `📁 text-field` + - `📁 carousel` + - `📁 accordion` - ```ts title="features/auth-form/model.ts" - export { actions, reducer } - ``` +Эта проблема усугубляется, когда один из этих модулей имеет тяжелую зависимость, такую как подсветка синтаксиса или библиотека drag'n'drop. Вы не хотите подтягивать их на каждую страницу, которая использует что-то из `shared/ui`, например, кнопку. - ```ts title="features/auth-form/index.ts" - export { Form as AuthForm } from "./ui" - export * as authFormModel from "./model" - ``` +Если ваши бандлы нежелательно растут из-за единого публичного API в `shared/ui` или `shared/lib`, рекомендуется вместо этого иметь отдельный индексный файл для каждого компонента или библиотеки: - ```ts title="features/post-form/model.ts" - export { actions, reducer } - ``` +- `📂 shared/ui/` + - `📂 button` + - `📄 index.js` + - `📂 text-field` + - `📄 index.js` - ```ts title="features/post-form/index.ts" - export { Form as PostForm } from "./ui" - export * as postFormModel from "./model" - ``` +Тогда потребители этих компонентов могут импортировать их напрямую, как показано ниже: -## О реэкспортах {#about-re-exports} +```js title="pages/sign-in/ui/SignInPage.jsx" +import { Button } from '@/shared/ui/button'; +import { TextField } from '@/shared/ui/text-field'; +``` -В JavaScript публичный интерфейс модуля создается с помощью реэкспорта сущностей изнутри модуля в `index` файле: +### Нет реальной защиты от обхода публичного API -```ts title="**/**/index.ts" -export { Form as AuthForm } from "./ui" -export * as authModel from "./model" -``` +Когда вы создаете индексный файл для слайса, ничто не мешает другим не использовать его и импортировать напрямую. Это особенно заметно с автоимпортами, потому что существует несколько мест, откуда объект может быть импортирован, поэтому IDE должна решить за вас. Иногда она может выбрать прямой импорт, нарушая правило публичного API для слайсов. -### Недостатки {#disadvantages} +Чтобы автоматически выявлять эти проблемы, мы рекомендуем использовать [Steiger][ext-steiger], архитектурный линтер с набором правил для Feature-Sliced Design. -- В большинстве популярных бандлеров из-за реэкспортов **хуже работает код-сплиттинг**, т.к. [tree-shaking](https://webpack.js.org/guides/tree-shaking/) при таком подходе может безопасно отбросить только модуль целиком, но не его часть. - > Например, импорт `authModel` в модели страницы приведет к попаданию компонента `AuthForm` в чанк этой страницы, даже если этот компонент там не используется. +### Худшая производительность сборщиков на больших проектах -- Как следствие, инициализация чанка становится дороже, т.к. браузер должен обработать все модули в нем, в том числе и те, что попали в бандл "за компанию" +Наличие большого количества индексных файлов в проекте может замедлить работу сервера разработки, как отметил TkDodo в [своей статье "Please Stop Using Barrel Files"][ext-please-stop-using-barrel-files]. -### Возможные пути решения {#possible-solutions} +Есть несколько вещей, которые вы можете сделать, чтобы справиться с этой проблемой: -- `webpack` позволяет отметить файлы-реэкспорты как [**side effects free**](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free) - это разрешает `webpack` использовать более агрессивные оптимизации при работе с таким файлом +1. То же самое, что и в разделе "Большие бандлы и неработающий tree-shaking в Shared" — создайте отдельные индексные файлы для каждого компонента/библиотеки в shared/ui и shared/lib вместо одного большого +2. Избегайте наличия индексных файлов в сегментах на слоях, которые имеют слайсы. + Например, если у вас есть индекс для фичи "comments", `📄 features/comments/index.js`, нет смысла иметь еще один индекс для `ui` сегмента этой фичи, `📄 features/comments/ui/index.js`. +3. Если у вас очень большой проект, есть большая вероятность, что ваше приложение можно разделить на несколько больших кусков. + Например, у Google Docs очень разные обязанности для редактора документов и для файлового браузера. Вы можете создать монорепозиторий, где каждый пакет является отдельным корнем FSD со своим набором слоев. Некоторые пакеты могут иметь только слои Shared и Entities, другие могут иметь только Pages и App, а некоторые могут включать свой небольшой Shared, но при этом использовать большой Shared из другого пакета. -## См. также {#see-also} + -- [(Обсуждение) Public API абстракции][disc-src] -- [Принципы **SOLID**][ext-solid] -- [Паттерны **GRASP**][ext-grasp] + -[disc-src]: https://github.com/feature-sliced/documentation/discussions/41 -[ext-solid]: https://ru.wikipedia.org/wiki/SOLID -[ext-grasp]: https://ru.wikipedia.org/wiki/GRASP +[import-rule-on-layers]: /docs/reference/layers#import-rule-on-layers +[ext-steiger]: https://github.com/feature-sliced/steiger +[ext-please-stop-using-barrel-files]: https://tkdodo.eu/blog/please-stop-using-barrel-files diff --git a/i18n/ru/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx b/i18n/ru/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx index a0b81db38f..6ea0746798 100644 --- a/i18n/ru/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx +++ b/i18n/ru/docusaurus-plugin-content-docs/current/reference/slices-segments.mdx @@ -8,50 +8,64 @@ pagination_next: reference/public-api ## Слайсы -Слайсы - это второй уровень в организационной иерархии Feature-Sliced Design. Их основное назначение - группировать код по его значению для продукта, бизнеса или просто приложения. +Слайсы — это второй уровень в организационной иерархии Feature-Sliced Design. Их основное назначение — группировать код по его значению для продукта, бизнеса или просто приложения. -Имена слайсов не стандартизированы, поскольку они напрямую определяются бизнес-областью вашего приложения. Например, фотогалерея может иметь слайсы `photo`, `create-album`, `gallery-page`. Для социальной сети потребуются другие слайсы, например, `post`, `add-user-to-friends`, `news-feed`. +Имена слайсов не стандартизированы, поскольку они напрямую определяются бизнес-областью вашего приложения. Например, фотогалерея может иметь слайсы `photo`, `effects`, `gallery-page`. Для социальной сети потребуются другие слайсы, например, `post`, `comments`, `news-feed`. -Близко связанные слайсы могут быть структурно сгруппированы в одной папке, но они должны соблюдать те же правила изоляции, что и другие слайсы - в этом каталоге не должно быть **никакого кода для совместного использования несколькими слайсами**. +Слои Shared и App не содержат слайсов. Это потому, что Shared не должен содержать никакой бизнес-логики, следовательно, не имеет продуктового значения, а App должен содержать только код, касающийся всего приложения, поэтому разделение не требуется. -![Фичи "compose" (написать), "like" (отметить как понравившееся) и "delete" (удалить), сгруппированные в папку "post" (публикация). В этой папке также есть файл "some-shared-code.ts" (какой-то общий код), название которого перечеркнуто, что означает, что этот файл не должен там быть.](/img/graphic-nested-slices.svg) +### Нулевая сцепленность и высокая связность {#zero-coupling-high-cohesion} -Слои Shared и App не содержат слайсов. Это связано с тем, что Shared не должен содержать никакой бизнес-логики, следовательно, не имеет значения для продукта, а App должен содержать только код, относящийся ко всему приложению, поэтому в разбиении нет необходимости. +Слайсы задуманы как независимые и сильно связанные группы файлов кода. Картинка ниже может помочь визуализировать такие сложные концепции как _связность_ (cohesion) и _сцепленность_ (coupling): + +
+ + +
+ Картинка вдохновлена https://enterprisecraftsmanship.com/posts/cohesion-coupling-difference/ +
+
+ +Идеальный слайс независим от других слайсов на своем уровне (нулевая сцепленность) и содержит бо́льшую часть кода, связанного с его основной целью (высокая связность). + +Независимость слайсов обеспечивается [правилом импорта для слоёв][layers--import-rule]: + +> _Модуль (файл) в слайсе может импортировать другие слайсы только если они находятся на слоях строго ниже._ ### Правило публичного API для слайсов -Внутри слайса код может быть организован как угодно, и это не создаст никаких проблем до тех пор, пока срез имеет качественный публичный API. В этом суть правила **публичного API для слайсов**: +Внутри слайса код может быть организован как угодно, и это не создаст никаких проблем до тех пор, пока слайс имеет качественный публичный API. В этом суть правила **публичного API для слайсов**: > _Каждый слайс (и сегмент на слоях, не имеющих слайсов) должен содержать определение публичного API._ > > _Модули вне этого слайса/сегмента могут ссылаться только на публичный API, а не на внутреннюю файловую структуру этого слайса/сегмента._ -Подробнее о причинах требования публичных API и лучших практиках их создания читайте в [справочнике о публичных API][ref--public-api]. +Подробнее о причинах требования публичных API и лучших практиках их создания читайте в [справочнике о публичных API][ref-public-api]. + +### Группы слайсов + +Тесно связанные слайсы могут быть структурно сгруппированы в папку, но они должны соблюдать те же правила изоляции, что и другие слайсы — **никакого совместного использования кода** в этой папке быть не должно. + +![Функции "compose", "like" и "delete" сгруппированы в папку "post". В этой папке также есть файл "some-shared-code.ts", который зачеркнут, чтобы показать, что это запрещено.](/img/graphic-nested-slices.svg) ## Сегменты -Сегменты - это третий и последний уровень в организационной иерархии, их цель - группировать код по его технической природе. +Сегменты — это третий и последний уровень в организационной иерархии, их цель — группировать код по его техническому назначению. Существует несколько стандартизированных названий сегментов: -* `ui` - компоненты пользовательского интерфейса, функции форматирования данных -* `model` - бизнес-логика и хранилища данных, функции для обработки этих данных -* `lib` - вспомогательный инфраструктурный код -* `api` - взаимодействие с внешними API, API-методы бэкенда -Другие сегменты допускаются, но должны создаваться только по необходимости. Наиболее распространенными местами для других сегментов являются слои App и Shared, где срезы не имеют смысла. +- `ui` — все, что связано с отображением UI: UI-компоненты, форматтеры дат, стили и т.д. +- `api` — взаимодействие с бэкендом: функции запросов, типы данных, мапперы и т.д. +- `model` — модель данных: схемы, интерфейсы, хранилища и бизнес-логика. +- `lib` — библиотечный код, необходимый другим модулям в этом слайсе. +- `config` — конфигурационные файлы и фиче-флаги. -### Примеры +На [странице про Слои][layers--layer-definitions] есть примеры того, как каждый из этих сегментов может использоваться на разных слоях. -| Layer | `ui` | `model` | `lib` | `api` | -| :------- | :----------- | :----------- | :----------- | :----------- | -| Shared | UI-библиотека | Обычно не используется | Утилитарные модули из нескольких связанных файлов.
Если вам нужны индивидуальные вспомогательные функции, обратите внимание на библиотеки утилит, например, [`lodash-es`][ext--lodash]. | Примитивный API-клиент с дополнительными функциями, такими как аутентификация или кэширование. | -| Entities | Скелет бизнес-сущности со слотами для интерактивных элементов | Хранилище объектов этой сущности, а также функции для обработки этих объектов.
Этот сегмент лучше всего подходит для хранения данных с сервера. Если вы используете [TanStack Query][ext--tanstack-query] или другие методы неявного хранения, вы можете опустить этот сегмент. | Функции над объектами этой сущности, не связанные с хранением данных | API-методы, использующие API-клиент из Shared для упрощения коммуникации с бэкендом | -| Features | Интерактивные элементы, позволяющие пользователям использовать эту функцию | Бизнес-логика и хранилище инфраструктурных данных, если требуется (например, текущая тема приложения). Здесь лежит код, который непосредственно создает пользу для пользователя | Инфраструктурный код, который позволяет сегменту `model` более кратко описать бизнес-логику | API-методы, представляющие эту функцию на бэкенде.
Может объединять API-методы из Entities. -| Widgets | Композиция Entities и Features в самодостаточные блоки интерфейса.
Также может содержать ограничители ошибок и состояния загрузки. | Хранилище инфраструктурных данных, если требуется | Не-бизнес-взаимодействия (например, жесты) и прочий код, необходимый для функционирования этого блока на странице | Обычно не используется, но может содержать загрузчики данных в контексте вложенного роутинга (например, [Remix][ext--remix]) | -| Pages | Композиция Entities, Features и Widgets в полноценные страницы.
Также может содержать ограничители ошибок и состояния загрузки. | Обычно не используется | Не-бизнес-взаимодействия (например, жесты) и прочий код, необходимый для создания полноценного пользовательского опыта на этой странице | Загрузчики данных для фреймворков, ориентированных на SSR (рендеринг на сервере) | +Вы также можете создавать свои собственные сегменты. Наиболее распространенные места для кастомных сегментов — это слои App и Shared, где слайсы не имеют смысла. -[ref--public-api]: /docs/reference/public-api +Убедитесь, что название этих сегментов описывает, для чего нужно его содержимое, а не чем оно является. Например, `components`, `hooks` и `types` — плохие названия сегментов, потому что они не так полезны, когда вы ищете код. -[ext--lodash]: https://www.npmjs.com/package/lodash-es -[ext--tanstack-query]: https://tanstack.com/query/latest -[ext--remix]: https://remix.run +[layers--layer-definitions]: /docs/reference/layers#layer-definitions +[layers--import-rule]: /docs/reference/layers#import-rule-on-layers +[ref-public-api]: /docs/reference/public-api diff --git a/i18n/uz/docusaurus-plugin-content-docs/current.json b/i18n/uz/docusaurus-plugin-content-docs/current.json index 6e1b91b131..e8aeebd580 100644 --- a/i18n/uz/docusaurus-plugin-content-docs/current.json +++ b/i18n/uz/docusaurus-plugin-content-docs/current.json @@ -1,6 +1,6 @@ { "version.label": { - "message": "v2.0.0 🍰", + "message": "v2.1", "description": "The label for version current" }, "sidebar.getstartedSidebar.category.Tutorials": { diff --git a/i18n/uz/docusaurus-plugin-content-docs/current/reference/index.mdx b/i18n/uz/docusaurus-plugin-content-docs/current/reference/index.mdx index fd3743f114..9bc362620a 100644 --- a/i18n/uz/docusaurus-plugin-content-docs/current/reference/index.mdx +++ b/i18n/uz/docusaurus-plugin-content-docs/current/reference/index.mdx @@ -25,12 +25,6 @@ import { ApiOutlined, GroupOutlined, AppstoreOutlined, NodeIndexOutlined } from to="/docs/reference/slices-segments" Icon={AppstoreOutlined} /> - -

- Feature-Sliced Design v2.0.0 (Current) -

+

Feature-Sliced Design v2.1 (Current)

{translate({ id: "pages.versions.current" })}

+
@@ -46,6 +44,11 @@ function Version() { Migration from v1 + + Migration from v2.0 + +
diff --git a/static/img/circular-import-dark.svg b/static/img/circular-import-dark.svg new file mode 100644 index 0000000000..14630e1882 --- /dev/null +++ b/static/img/circular-import-dark.svg @@ -0,0 +1 @@ +📄 fileA.js📄 fileB.js📄 fileC.js \ No newline at end of file diff --git a/static/img/circular-import-light.svg b/static/img/circular-import-light.svg new file mode 100644 index 0000000000..1c302f119b --- /dev/null +++ b/static/img/circular-import-light.svg @@ -0,0 +1 @@ +📄 fileA.js📄 fileB.js📄 fileC.js \ No newline at end of file