From 9b14de3201bc6011924121c231fb15ab5c2a5126 Mon Sep 17 00:00:00 2001 From: Minsu Date: Sun, 14 Dec 2025 00:34:39 +0900 Subject: [PATCH 1/5] docs: code smells/cross-imports add --- .../current/guides/issues/cross-imports.mdx | 567 +++++++++++++++++- 1 file changed, 556 insertions(+), 11 deletions(-) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx index 1e29ae65fd..5bb4245611 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx @@ -1,21 +1,566 @@ --- sidebar_position: 4 -sidebar_class_name: sidebar-item--wip pagination_next: reference/layers --- -import WIP from '@site/src/shared/ui/wip/tmpl.mdx' +# Cross-import -# Cross-imports +This section gives a **high-level overview** of what this document covers. - +Here, we define a cross-import as an import **between different slices within the same `layer`**. +The goal of this document is to explain, with code examples, why this is an important problem to solve in FSD and how to think about it. -> Cross-imports appear when the layer or abstraction begins to take too much responsibility than it should. That is why the methodology identifies new layers that allow you to uncouple these cross-imports +Most of the examples will use the `features/*` and `widgets/*` slices +(e.g. `features/cart` → `features/product`). -## See also +Cross-imports in the `entities` layer are only covered briefly in this document. +Here, we describe only the mechanisms currently used in real-world projects. -- [(Thread) About the supposed inevitability of cross-ports](https://t.me/feature_sliced/4515) -- [(Thread) About resolving cross-ports in entities](https://t.me/feature_sliced/3678) -- [(Thread) About cross-imports and responsibility](https://t.me/feature_sliced/3287) -- [(Thread) About imports between segments](https://t.me/feature_sliced/4021) -- [(Thread) About cross-imports inside shared](https://t.me/feature_sliced/3618) +We **do not** try to decide long-term design direction here, such as: + +- how strictly we should constrain `entities` as a domain/type-only layer, +- whether to completely forbid UI in `entities`, etc. + +These topics are still under discussion and should be considered an evolving area. + +--- + +## Scope + +This section clarifies **exactly what scope** this document addresses. + +In this document, **cross-import** is limited to the following case: + +> Two different `slice`s inside the same `layer` importing each other. + +In other words, this page only covers the situation where different slices inside the **same** layer import each other. +Import rules **between layers**, usage patterns of the `shared` layer, and the Public API principles are **out of scope** for this page. +For those topics, see [Layers][layers] and [Public API][public-api]. + +The general direction for the `entities` layer is to treat it as a **domain/model/type-centric layer**, +and we recommend that **new code does not place UI in `entities`**. + +Cross-imports between `entities` should only be allowed in a **limited fashion at the domain/type level**. + +This page focuses mainly on the `features` / `widgets` layers: + +- how to reason about cross-imports there, +- and what strategies we can use to handle them. + +For the `entities` layer, we only give minimal guidance and rules. + +--- + +## Why is this a code smell? + +This section explains why cross-import is not just a matter of style or personal preference, +but is generally considered a **code smell**. + +For example, if the `cart` slice depends directly on `product` UI or business logic, +the domain/responsibility boundaries become blurry. +Deep imports, path changes, and strong coupling to internal files of a `slice` make refactoring or splitting modules increasingly complex. + +With small code examples, we’ll illustrate these problems and define what it means when: + +- the folder structure *looks* well separated, +- but the actual dependency graph is tangled underneath. + +--- + +### Bad cross-import (features → features deep import) + +```tsx title="features/cart/ui/CartSummary.tsx" +import React from 'react'; +import { useProductDetails } from '@/features/product/model/useProductDetails'; +import { ProductPrice } from '@/features/product/ui/ProductPrice'; + +type CartItem = { + productId: string; + quantity: number; +}; + +interface CartSummaryProps { + items: CartItem[]; +} + +export function CartSummary(props: CartSummaryProps) { + return ( +
+ {props.items.map((item) => ( + + ))} +
+ ); +} + +function CartItemRow({ item }: { item: CartItem }) { + const product = useProductDetails(item.productId); // cross-import into product model + + return ( +
+ {product.name} + {/* cross-import into product UI */} + x {item.quantity} +
+ ); +} +``` + +Example of a problematic situation: + +- `features/cart` depends on both `features/product/model/*` and `features/product/ui/*` +- a small structural change inside the `product` slice can immediately break `cart` +- responsibility boundaries get blurred + → the cart starts to handle not only **cart logic** but also **product UI** + +In such a situation, it’s more accurate to say that: + +The cart is deeply entangled with the internal implementation of product, +Rather than cart and product being well-separated slices. + +--- + +### Better direction (orchestrate from an upper layer) + +```tsx title="pages/CheckoutPage.tsx" +import React from 'react'; +import { CartSummary } from '@/features/cart'; +import { ProductListForCart } from '@/features/product'; + +export function CheckoutPage() { + const cartItems = [ + { productId: 'p1', quantity: 2 }, + { productId: 'p2', quantity: 1 }, + ]; + + return ( +
+

Checkout

+ + +
+ ); +} +``` + +```tsx title="features/product/index.ts" +export { ProductListForCart } from './ui/ProductListForCart'; +``` + +```tsx title="features/cart/index.ts" +export { CartSummary } from './ui/CartSummary'; +``` + +In the improved structure, `CheckoutPage` knows about both `cart` and `product` and composes them. +We **minimize direct cross-imports** between `features/cart` ↔ `features/product`. +Each `slice` exposes only its **Public API**, and does not access the internals of other slices directly. + +In other words: + +- orchestration/flow belongs to an upper layer, +- each slice operates only within its own responsibility and boundaries. + +--- + +## No cross-import concept in the shared layer + +The `shared` layer does not have the concept of `slice`. +Therefore, this document explicitly states that imports between components **inside `shared`** are **not considered cross-imports**. + +Most forms of reuse within `shared` are **allowed without additional constraints**. + +--- + +### Internal reuse inside the shared layer + +```tsx title="shared/Button.tsx" +import React from 'react'; + +interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: 'primary' | 'secondary'; +} + +export function Button({ variant = 'primary', ...rest }: ButtonProps) { + return + + + ); +} +``` + +If both components live inside `shared`, +imports between them are **not** considered “cross-imports” in the sense used in this document. + +For dependencies inside `shared`, it is enough to follow the kind of principles +we usually apply when designing a generic library (complexity, coupling, etc.). + +--- + +## Entities Layer cross-imports + +This section describes, from a **practical standpoint**, how we currently deal with cross-imports in the `entities` layer. + +The `entities` layer is designed as a **domain/type-centered** layer. +It typically contains domain models, IDs, DTOs, and other domain primitives. + +Therefore, the baseline guideline is: + +- we recommend **not putting UI in `entities`**, +- and when UI does exist in `entities`, we treat it as a **code smell** and as a candidate to gradually move to an upper layer (`features`, `widgets`, etc.). + +Reasons for discouraging UI in `entities` include: + +- the domain layer becomes directly dependent on presentation details (React, Router, design system, etc.) + → domain reusability and stability decrease. +- the direction of dependencies between layers becomes unclear, + and cross-imports shift from **domain relationships** to **tangled UI relationships**. +- UI tends to change frequently (experiments, redesigns), + while domain should stay relatively stable. Mixing them in one layer makes change management much harder. +- if we allow UI in `entities`, UI reuse between entities creates **new cross-import points**, making the overall structure more complex. + +In real projects, we often need to share types or DTOs between entities because of domain relationships +(for example, `Artist–Song`, `Order–OrderItem`, etc.). + +To express such domain-level relationships, we use `@x` as a dedicated **Public API surface** for cross-imports between `entities`. +Here, `@x` should be seen more as a **pragmatic compromise** that works in the current situation, rather than a final, ideal design. + +As the design of the `entities` layer becomes more refined, +and as we move remaining UI-related code smells out of `entities` into upper layers, +the role and structure of `@x` may also evolve. + +--- + +### Defining domain types in entities + +```tsx title="entities/order/model/orderTypes.ts" +export type OrderId = string; + +export interface OrderItemRef { + itemId: OrderItemId; + quantity: number; +} + +export interface Order { + id: OrderId; + items: OrderItemRef[]; + totalAmount: number; +} +``` + +```tsx title="entities/orderItem/model/orderItemTypes.ts" +export type OrderItemId = string; + +export interface OrderItem { + id: OrderItemId; + sku: string; + unitPrice: number; +} +``` + +--- + +### Cross-import via @x (domain types only) + +```tsx title="entities/order/@x/payment.ts" +// Public API for other entities +export type { OrderId, Order, OrderItemRef } from '../model/orderTypes'; +export type { OrderItemId } from '@/entities/orderItem/model/orderItemTypes'; +``` + +```tsx title="entities/payment/model/paymentTypes.ts" +import type { OrderId } from '@/entities/order/@x'; + +export interface Payment { + id: string; + orderId: OrderId; + amount: number; +} +``` + +Key points: + +- cross-imports are only allowed through paths like `@/entities/order/@x`. +- `@x` exposes **types/domain data only**, without UI. +- other entities always import related types **via `@x`**, and never from arbitrary internal paths. + +--- + +### Example of code smell: UI inside Entities + +```tsx title="entities/product/ui/ProductCard.tsx" +import React from 'react'; +import type { Product } from '../model/productTypes'; + +interface ProductCardProps { + product: Product; +} + +export function ProductCard(props: ProductCardProps) { + return ( +
+ {props.product.name} + {props.product.price} +
+ ); +} +``` + +This type of UI causes several problems: + +- it introduces React as a dependency into `entities`, +- it encourages other layers to import `entities/product/ui/ProductCard` directly, +- in the end, the `entities` layer becomes a mix of **domain + UI**. + → this is clearly a **code smell**. + +> Recommended direction: +> Move `ProductCard` to an upper layer such as `features/product/ui/ProductCard.tsx`, +> and leave only the Product type, ID, and domain logic in `entities`. + +--- + +## Features/Widgets: Multiple Strategies + +For the `features` and `widgets` layers, it is more realistic to say that we have **multiple strategies** for dealing with cross-imports, +rather than declaring cross-imports as **absolutely forbidden in all cases.** + +This section focuses less on specific code, and more on **which patterns (strategies)** we can use. + +--- + +### Strategy A: Slice merge + +If two `slice`s are not actually independent enough to exist separately, +and they are always changed together, we can merge them into a larger `feature`/`widget` `slice`. + +Example (Before): + +- `features/profile` +- `features/profileSettings` + +If these two keep cross-importing each other and always move together, +they are practically one feature. +→ In this case, it is often better to merge them into a single `features/profile` slice. + +--- + +### Strategy B: Push shared domain flows down into `entities` (domain-only) + +If two `feature`s share the same domain-level flow, +we can push that flow down into a domain `slice` inside `entities` (e.g. `entities/session`). +Inside that slice, the flow is represented **only in terms of domain types/logic**. + +The important point is that this domain flow **must not depend on UI**. +UI remains in upper layers such as `features`/`widgets` or above. + +#### Domain logic in entities/session only + +```tsx title="entities/session/model/sessionTypes.ts" +export interface Session { + userId: string; + roles: string[]; + expiresAt: Date; +} +``` + +```tsx title="entities/session/model/session.ts" +import type { Session } from './sessionTypes'; + +export function createSessionFromToken(token: string): Session { +// parse token, extract claims, etc. + return { + userId: 'user-1', + roles: ['user'], + expiresAt: new Date(Date.now() + 3600 * 1000), + }; +} + +export function isSessionExpired(session: Session): boolean { + return session.expiresAt.getTime() <= Date.now(); +} +``` + +#### Using the domain logic from features (UI separated) + +```tsx title="features/auth/ui/AuthGate.tsx" +import React from 'react'; +import type { ReactNode } from 'react'; +import { createSessionFromToken, isSessionExpired } from '@/entities/session/model/session'; + +interface AuthGateProps { + token: string | null; + children: ReactNode; +} + +export function AuthGate(props: AuthGateProps) { + if (!props.token) { + return
Please log in.
; + } + + const session = createSessionFromToken(props.token); + + if (isSessionExpired(session)) { + return
Session expired. Please log in again.
; + } + + return <>{props.children}; +} +``` + +In short: + +- domain logic (`createSessionFromToken`, `isSessionExpired`, etc.) lives in `entities`, +- UI lives only in upper layers, e.g. `features/auth/ui`. + +--- + +### Strategy C: Orchestrate from upper layers (pages / app) + +Instead of having slices within the same `layer` import each other, +we can compose them at a higher level—`pages` / `app`—using DI, slot patterns, or higher-level composition. + +In other words, instead of directly connecting slices with cross-imports, +we let an upper layer **orchestrate and assemble the flow**. + +#### Example: + +```tsx title="features/userProfile/index.ts" +export { UserProfilePanel } from './ui/UserProfilePanel'; +``` + +```tsx title="features/activityFeed/index.ts" +export { ActivityFeed } from './ui/ActivityFeed'; +``` + +```tsx title="pages/UserDashboardPage.tsx" +import React from 'react'; +import { UserProfilePanel } from '@/features/userProfile'; +import { ActivityFeed } from '@/features/activityFeed'; + +export function UserDashboardPage() { + return ( +
+ + +
+ ); +} +``` + +In this structure, `features/userProfile` and `features/activityFeed` don’t know about each other. +`pages/UserDashboardPage` composes them together to build the full screen. + +--- + +### Strategy D: Cross-feature reuse only via Public API + +If cross-feature reuse is truly necessary, it should be allowed **only via a clear Public API** +(e.g. exported hooks, UI components). +Direct access to another slice’s `store`/`model` or internal implementation details should be avoided. + +#### Example Public API + +```tsx title="features/auth/index.ts" + +export { useAuth } from './model/useAuth'; +export { AuthButton } from './ui/AuthButton'; +``` + +```tsx title="features/profile/ui/ProfileMenu.tsx" + +import React from 'react'; +import { useAuth, AuthButton } from '@/features/auth'; + +export function ProfileMenu() { + const { user } = useAuth(); + + if (!user) { + return ; + } + + return
{user.name}
; +} +``` + +For example, we should prevent `features/profile` from accessing paths like `features/auth/model/internal/*`. +It should only rely on what `features/auth` has explicitly exposed as its Public API. + +--- + +## When should cross-imports be treated as a problem? + +After going through the various strategies above, the next natural question is: + +> When is it acceptable to leave a cross-import as is? +> And when should we treat it as a **code smell** and consider refactoring? + +Typical warning signs include: + +- direct dependencies on another slice’s store/model/business logic +- deep imports into another slice’s internal files +- **bidirectional dependencies** between slices, such as A ↔ B +- changes in one slice almost always breaking another slice +- flows that would be much clearer if orchestrated in an upper layer (`pages` / `app`), + but are instead forced into cross-imports within the same layer + +When you see these signals, +you should treat the cross-import as a **code smell** +and check whether at least one of the strategies above can be applied. + +--- + +## How strict you are is a team/project decision + +Finally, this section emphasizes that: + +- how strictly to enforce these rules +- depends on the team and the project. + +For example: + +- For **early-stage products** where experimentation and throwaway work are common, + it may be reasonable to allow some cross-imports for short-term development speed. +- On the other hand, for **long-lived or heavily regulated systems** (e.g. fintech, large-scale services), + stricter boundaries and layer design may be preferable to achieve long-term stability and maintainability. + +We don’t treat cross-imports as an **absolute prohibition**. +Rather, we treat them as dependencies that are **generally best avoided**. + +When introducing a cross-import, we should always be aware that it is a **deliberate architectural choice**. +It’s also a good idea to document that choice, and review it periodically as the system evolves. + +Teams should align on questions such as: + +- What level of strictness do we want for this team/project? +- How do we reflect that strictness in lint rules, code review, documentation, etc.? +- As the domain and architecture mature, how often and according to what criteria do we revisit existing cross-imports? + +--- + +## References + +- (Thread) Discussion of situations where cross-imports are unavoidable: [https://t.me/feature_sliced/4515](https://t.me/feature_sliced/4515) +- (Thread) Approaches to solving cross-imports in Entity: [https://t.me/feature_sliced/3678](https://t.me/feature_sliced/3678) +- (Thread) Cross-imports and responsibility boundaries: [https://t.me/feature_sliced/3287](https://t.me/feature_sliced/3287) +- (Thread) Resolving import issues between segments: [https://t.me/feature_sliced/4021](https://t.me/feature_sliced/4021) +- (Thread) Cross-imports within Shared internal structure: [https://t.me/feature_sliced/3618](https://t.me/feature_sliced/3618) + +[layers]: https://feature-sliced.github.io/documentation/docs/reference/layers +[public-api]: https://feature-sliced.github.io/documentation/docs/reference/public-api From 1467a84cf9e6d4a27c62c7bd2433bbbce3af225b Mon Sep 17 00:00:00 2001 From: Minsu Date: Sun, 14 Dec 2025 00:38:03 +0900 Subject: [PATCH 2/5] fix: Title typo correction --- .../current/guides/issues/cross-imports.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx index 5bb4245611..24d0ebd757 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx @@ -3,7 +3,7 @@ sidebar_position: 4 pagination_next: reference/layers --- -# Cross-import +# Cross-imports This section gives a **high-level overview** of what this document covers. From 7f565e50e8fca2c64ee38b6557224043f753fb2e Mon Sep 17 00:00:00 2001 From: Minsu Date: Thu, 8 Jan 2026 23:52:11 +0900 Subject: [PATCH 3/5] docs: reflecting feedback --- .../current/guides/issues/cross-imports.mdx | 509 +++--------------- 1 file changed, 72 insertions(+), 437 deletions(-) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx index 24d0ebd757..44addf6974 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx @@ -3,443 +3,94 @@ sidebar_position: 4 pagination_next: reference/layers --- -# Cross-imports -This section gives a **high-level overview** of what this document covers. -Here, we define a cross-import as an import **between different slices within the same `layer`**. -The goal of this document is to explain, with code examples, why this is an important problem to solve in FSD and how to think about it. +# Cross-import -Most of the examples will use the `features/*` and `widgets/*` slices -(e.g. `features/cart` → `features/product`). +A **cross-import** is an import **between different slices within the same layer**. -Cross-imports in the `entities` layer are only covered briefly in this document. -Here, we describe only the mechanisms currently used in real-world projects. - -We **do not** try to decide long-term design direction here, such as: - -- how strictly we should constrain `entities` as a domain/type-only layer, -- whether to completely forbid UI in `entities`, etc. - -These topics are still under discussion and should be considered an evolving area. - ---- - -## Scope - -This section clarifies **exactly what scope** this document addresses. - -In this document, **cross-import** is limited to the following case: - -> Two different `slice`s inside the same `layer` importing each other. - -In other words, this page only covers the situation where different slices inside the **same** layer import each other. -Import rules **between layers**, usage patterns of the `shared` layer, and the Public API principles are **out of scope** for this page. -For those topics, see [Layers][layers] and [Public API][public-api]. - -The general direction for the `entities` layer is to treat it as a **domain/model/type-centric layer**, -and we recommend that **new code does not place UI in `entities`**. - -Cross-imports between `entities` should only be allowed in a **limited fashion at the domain/type level**. - -This page focuses mainly on the `features` / `widgets` layers: - -- how to reason about cross-imports there, -- and what strategies we can use to handle them. +For example: +- importing `features/product` from `features/cart` +- importing `widgets/sidebar` from `widgets/header` -For the `entities` layer, we only give minimal guidance and rules. +:::note +The `shared` and `app` layers do not have the concept of a slice, so imports *within* those layers are **not** considered cross-imports. +::: --- ## Why is this a code smell? -This section explains why cross-import is not just a matter of style or personal preference, -but is generally considered a **code smell**. - -For example, if the `cart` slice depends directly on `product` UI or business logic, -the domain/responsibility boundaries become blurry. -Deep imports, path changes, and strong coupling to internal files of a `slice` make refactoring or splitting modules increasingly complex. +This document explains why cross-imports are not just a matter of style, but are generally considered a **code smell**. -With small code examples, we’ll illustrate these problems and define what it means when: +For example, when the `cart` slice directly depends on `product` UI or business logic, the domain/responsibility boundary becomes blurry. -- the folder structure *looks* well separated, -- but the actual dependency graph is tangled underneath. +In the sections below, we outline how these issues typically appear in real projects and what strategies you can use to address them. --- -### Bad cross-import (features → features deep import) - -```tsx title="features/cart/ui/CartSummary.tsx" -import React from 'react'; -import { useProductDetails } from '@/features/product/model/useProductDetails'; -import { ProductPrice } from '@/features/product/ui/ProductPrice'; - -type CartItem = { - productId: string; - quantity: number; -}; - -interface CartSummaryProps { - items: CartItem[]; -} - -export function CartSummary(props: CartSummaryProps) { - return ( -
- {props.items.map((item) => ( - - ))} -
- ); -} +## Entities layer cross-imports -function CartItemRow({ item }: { item: CartItem }) { - const product = useProductDetails(item.productId); // cross-import into product model +The `entities` layer is designed as a **domain/type-centric** layer. +It typically contains domain types, IDs, DTOs, and domain-level logic. - return ( -
- {product.name} - {/* cross-import into product UI */} - x {item.quantity} -
- ); -} -``` - -Example of a problematic situation: +In real-world projects, entity-to-entity relationships often require sharing types or DTOs (for example, `Order` and `OrderItem`, `Artist` and `Song`). -- `features/cart` depends on both `features/product/model/*` and `features/product/ui/*` -- a small structural change inside the `product` slice can immediately break `cart` -- responsibility boundaries get blurred - → the cart starts to handle not only **cart logic** but also **product UI** +To express these domain-level references, many teams use `@x` as a dedicated cross-import surface for entities. -In such a situation, it’s more accurate to say that: +For details about `@x`, see the [Public API documentation](/docs/reference/public-api). -The cart is deeply entangled with the internal implementation of product, -Rather than cart and product being well-separated slices. +For concrete examples of cross-references between business entities, see: +- [Types guide — Business entities and their cross-references](/docs/guides/examples/types#business-entities-and-their-cross-references) +- [Layers reference — Entities](/docs/reference/layers#entities) --- -### Better direction (orchestrate from an upper layer) +## Features and widgets: Multiple strategies -```tsx title="pages/CheckoutPage.tsx" -import React from 'react'; -import { CartSummary } from '@/features/cart'; -import { ProductListForCart } from '@/features/product'; +In the `features` and `widgets` layers, it’s usually more realistic to say there are **multiple strategies** for handling cross-imports, rather than declaring them **always forbidden**. -export function CheckoutPage() { - const cartItems = [ - { productId: 'p1', quantity: 2 }, - { productId: 'p2', quantity: 1 }, - ]; - - return ( -
-

Checkout

- - -
- ); -} -``` - -```tsx title="features/product/index.ts" -export { ProductListForCart } from './ui/ProductListForCart'; -``` - -```tsx title="features/cart/index.ts" -export { CartSummary } from './ui/CartSummary'; -``` - -In the improved structure, `CheckoutPage` knows about both `cart` and `product` and composes them. -We **minimize direct cross-imports** between `features/cart` ↔ `features/product`. -Each `slice` exposes only its **Public API**, and does not access the internals of other slices directly. - -In other words: - -- orchestration/flow belongs to an upper layer, -- each slice operates only within its own responsibility and boundaries. - ---- - -## No cross-import concept in the shared layer - -The `shared` layer does not have the concept of `slice`. -Therefore, this document explicitly states that imports between components **inside `shared`** are **not considered cross-imports**. - -Most forms of reuse within `shared` are **allowed without additional constraints**. - ---- - -### Internal reuse inside the shared layer - -```tsx title="shared/Button.tsx" -import React from 'react'; - -interface ButtonProps extends React.ButtonHTMLAttributes { - variant?: 'primary' | 'secondary'; -} - -export function Button({ variant = 'primary', ...rest }: ButtonProps) { - return - - - ); -} -``` - -If both components live inside `shared`, -imports between them are **not** considered “cross-imports” in the sense used in this document. - -For dependencies inside `shared`, it is enough to follow the kind of principles -we usually apply when designing a generic library (complexity, coupling, etc.). - ---- - -## Entities Layer cross-imports - -This section describes, from a **practical standpoint**, how we currently deal with cross-imports in the `entities` layer. - -The `entities` layer is designed as a **domain/type-centered** layer. -It typically contains domain models, IDs, DTOs, and other domain primitives. - -Therefore, the baseline guideline is: - -- we recommend **not putting UI in `entities`**, -- and when UI does exist in `entities`, we treat it as a **code smell** and as a candidate to gradually move to an upper layer (`features`, `widgets`, etc.). - -Reasons for discouraging UI in `entities` include: - -- the domain layer becomes directly dependent on presentation details (React, Router, design system, etc.) - → domain reusability and stability decrease. -- the direction of dependencies between layers becomes unclear, - and cross-imports shift from **domain relationships** to **tangled UI relationships**. -- UI tends to change frequently (experiments, redesigns), - while domain should stay relatively stable. Mixing them in one layer makes change management much harder. -- if we allow UI in `entities`, UI reuse between entities creates **new cross-import points**, making the overall structure more complex. - -In real projects, we often need to share types or DTOs between entities because of domain relationships -(for example, `Artist–Song`, `Order–OrderItem`, etc.). - -To express such domain-level relationships, we use `@x` as a dedicated **Public API surface** for cross-imports between `entities`. -Here, `@x` should be seen more as a **pragmatic compromise** that works in the current situation, rather than a final, ideal design. - -As the design of the `entities` layer becomes more refined, -and as we move remaining UI-related code smells out of `entities` into upper layers, -the role and structure of `@x` may also evolve. - ---- - -### Defining domain types in entities - -```tsx title="entities/order/model/orderTypes.ts" -export type OrderId = string; - -export interface OrderItemRef { - itemId: OrderItemId; - quantity: number; -} - -export interface Order { - id: OrderId; - items: OrderItemRef[]; - totalAmount: number; -} -``` - -```tsx title="entities/orderItem/model/orderItemTypes.ts" -export type OrderItemId = string; - -export interface OrderItem { - id: OrderItemId; - sku: string; - unitPrice: number; -} -``` - ---- - -### Cross-import via @x (domain types only) - -```tsx title="entities/order/@x/payment.ts" -// Public API for other entities -export type { OrderId, Order, OrderItemRef } from '../model/orderTypes'; -export type { OrderItemId } from '@/entities/orderItem/model/orderItemTypes'; -``` - -```tsx title="entities/payment/model/paymentTypes.ts" -import type { OrderId } from '@/entities/order/@x'; - -export interface Payment { - id: string; - orderId: OrderId; - amount: number; -} -``` - -Key points: - -- cross-imports are only allowed through paths like `@/entities/order/@x`. -- `@x` exposes **types/domain data only**, without UI. -- other entities always import related types **via `@x`**, and never from arbitrary internal paths. - ---- - -### Example of code smell: UI inside Entities - -```tsx title="entities/product/ui/ProductCard.tsx" -import React from 'react'; -import type { Product } from '../model/productTypes'; - -interface ProductCardProps { - product: Product; -} - -export function ProductCard(props: ProductCardProps) { - return ( -
- {props.product.name} - {props.product.price} -
- ); -} -``` - -This type of UI causes several problems: - -- it introduces React as a dependency into `entities`, -- it encourages other layers to import `entities/product/ui/ProductCard` directly, -- in the end, the `entities` layer becomes a mix of **domain + UI**. - → this is clearly a **code smell**. - -> Recommended direction: -> Move `ProductCard` to an upper layer such as `features/product/ui/ProductCard.tsx`, -> and leave only the Product type, ID, and domain logic in `entities`. - ---- - -## Features/Widgets: Multiple Strategies - -For the `features` and `widgets` layers, it is more realistic to say that we have **multiple strategies** for dealing with cross-imports, -rather than declaring cross-imports as **absolutely forbidden in all cases.** - -This section focuses less on specific code, and more on **which patterns (strategies)** we can use. +This section focuses less on code and more on the **patterns** you can choose from depending on your team and product context. --- ### Strategy A: Slice merge -If two `slice`s are not actually independent enough to exist separately, -and they are always changed together, we can merge them into a larger `feature`/`widget` `slice`. - -Example (Before): +If two slices are not truly independent and they are always changed together, merge them into a single larger slice. +Example (before): - `features/profile` - `features/profileSettings` -If these two keep cross-importing each other and always move together, -they are practically one feature. -→ In this case, it is often better to merge them into a single `features/profile` slice. +If these keep cross-importing each other and effectively move as one unit, they are likely one feature in practice. +In that case, merging into `features/profile` is often the simpler and cleaner choice. --- ### Strategy B: Push shared domain flows down into `entities` (domain-only) -If two `feature`s share the same domain-level flow, -we can push that flow down into a domain `slice` inside `entities` (e.g. `entities/session`). -Inside that slice, the flow is represented **only in terms of domain types/logic**. - -The important point is that this domain flow **must not depend on UI**. -UI remains in upper layers such as `features`/`widgets` or above. - -#### Domain logic in entities/session only - -```tsx title="entities/session/model/sessionTypes.ts" -export interface Session { - userId: string; - roles: string[]; - expiresAt: Date; -} -``` - -```tsx title="entities/session/model/session.ts" -import type { Session } from './sessionTypes'; +If multiple features share a domain-level flow, +move that flow into a domain slice inside `entities` (for example, `entities/session`). -export function createSessionFromToken(token: string): Session { -// parse token, extract claims, etc. - return { - userId: 'user-1', - roles: ['user'], - expiresAt: new Date(Date.now() + 3600 * 1000), - }; -} +Key principles: +- `entities` contains **domain types and domain logic only** +- UI remains in `features` / `widgets` +- features import and use the domain logic from `entities` -export function isSessionExpired(session: Session): boolean { - return session.expiresAt.getTime() <= Date.now(); -} -``` +For example, if both `features/auth` and `features/profile` need session validation, +place session-related domain functions in `entities/session` and reuse them from both features. -#### Using the domain logic from features (UI separated) - -```tsx title="features/auth/ui/AuthGate.tsx" -import React from 'react'; -import type { ReactNode } from 'react'; -import { createSessionFromToken, isSessionExpired } from '@/entities/session/model/session'; - -interface AuthGateProps { - token: string | null; - children: ReactNode; -} - -export function AuthGate(props: AuthGateProps) { - if (!props.token) { - return
Please log in.
; - } - - const session = createSessionFromToken(props.token); - - if (isSessionExpired(session)) { - return
Session expired. Please log in again.
; - } - - return <>{props.children}; -} -``` - -In short: - -- domain logic (`createSessionFromToken`, `isSessionExpired`, etc.) lives in `entities`, -- UI lives only in upper layers, e.g. `features/auth/ui`. +For more guidance, see [Layers reference — Entities](/docs/reference/layers#entities). --- -### Strategy C: Orchestrate from upper layers (pages / app) +### Strategy C: Orchestrate from an upper layer (pages / app) -Instead of having slices within the same `layer` import each other, -we can compose them at a higher level—`pages` / `app`—using DI, slot patterns, or higher-level composition. +Instead of connecting slices within the same layer via cross-imports, compose them at a higher level (`pages` / `app`) using DI (dependency injection), slot patterns, or higher-level composition. -In other words, instead of directly connecting slices with cross-imports, -we let an upper layer **orchestrate and assemble the flow**. +In other words, rather than wiring slices together directly, let an upper layer **assemble and orchestrate the flow**. -#### Example: +#### Example code: ```tsx title="features/userProfile/index.ts" export { UserProfilePanel } from './ui/UserProfilePanel'; @@ -463,19 +114,17 @@ export function UserDashboardPage() { ); } ``` - -In this structure, `features/userProfile` and `features/activityFeed` don’t know about each other. -`pages/UserDashboardPage` composes them together to build the full screen. +With this structure, `features/userProfile` and `features/activityFeed` do not know about each other. +`pages/UserDashboardPage` composes them to build the full screen. --- ### Strategy D: Cross-feature reuse only via Public API -If cross-feature reuse is truly necessary, it should be allowed **only via a clear Public API** -(e.g. exported hooks, UI components). -Direct access to another slice’s `store`/`model` or internal implementation details should be avoided. +If cross-feature reuse is truly needed, allow it only through an explicit **Public API** (for example: exported hooks or UI components). +Avoid directly accessing another slice’s `store`/`model` or internal implementation details. -#### Example Public API +#### Example code: ```tsx title="features/auth/index.ts" @@ -499,68 +148,54 @@ export function ProfileMenu() { } ``` -For example, we should prevent `features/profile` from accessing paths like `features/auth/model/internal/*`. -It should only rely on what `features/auth` has explicitly exposed as its Public API. +For example, prevent `features/profile` from importing from paths like `features/auth/model/internal/*`. +Restrict usage to only what `features/auth` explicitly exposes as its Public API. --- ## When should cross-imports be treated as a problem? -After going through the various strategies above, the next natural question is: +After reviewing these strategies, a natural question is: -> When is it acceptable to leave a cross-import as is? -> And when should we treat it as a **code smell** and consider refactoring? +> When is a cross-import acceptable to keep, and when should it be treated as a code smell and refactored? -Typical warning signs include: - -- direct dependencies on another slice’s store/model/business logic +Common warning signs: +- directly depending on another slice’s store/model/business logic - deep imports into another slice’s internal files -- **bidirectional dependencies** between slices, such as A ↔ B -- changes in one slice almost always breaking another slice -- flows that would be much clearer if orchestrated in an upper layer (`pages` / `app`), - but are instead forced into cross-imports within the same layer +- **bidirectional dependencies** (A and B) +- changes in one slice frequently breaking another slice +- flows that should be composed in `pages` / `app`, but are forced into cross-imports within the same layer -When you see these signals, -you should treat the cross-import as a **code smell** -and check whether at least one of the strategies above can be applied. +When you see these signals, treat the cross-import as a **code smell** and consider applying at least one of the strategies above. --- ## How strict you are is a team/project decision -Finally, this section emphasizes that: - -- how strictly to enforce these rules -- depends on the team and the project. +How strictly to enforce these rules depends on the team and project. For example: +- In **early-stage products** with heavy experimentation, allowing some cross-imports may be a pragmatic speed trade-off. +- In **long-lived or regulated systems** (for example, fintech or large-scale services), stricter boundaries often pay off in maintainability and stability. -- For **early-stage products** where experimentation and throwaway work are common, - it may be reasonable to allow some cross-imports for short-term development speed. -- On the other hand, for **long-lived or heavily regulated systems** (e.g. fintech, large-scale services), - stricter boundaries and layer design may be preferable to achieve long-term stability and maintainability. - -We don’t treat cross-imports as an **absolute prohibition**. -Rather, we treat them as dependencies that are **generally best avoided**. +Cross-imports are not an absolute prohibition here. They are dependencies that are **generally best avoided**, but sometimes used intentionally. -When introducing a cross-import, we should always be aware that it is a **deliberate architectural choice**. -It’s also a good idea to document that choice, and review it periodically as the system evolves. +If you do introduce a cross-import: +- treat it as a deliberate architectural choice +- document the reasoning +- revisit it periodically as the system evolves -Teams should align on questions such as: - -- What level of strictness do we want for this team/project? -- How do we reflect that strictness in lint rules, code review, documentation, etc.? -- As the domain and architecture mature, how often and according to what criteria do we revisit existing cross-imports? +Teams should align on: +- what strictness level they want +- how to reflect it in lint rules, code review, and documentation +- when and how to reevaluate existing cross-imports over time --- ## References -- (Thread) Discussion of situations where cross-imports are unavoidable: [https://t.me/feature_sliced/4515](https://t.me/feature_sliced/4515) -- (Thread) Approaches to solving cross-imports in Entity: [https://t.me/feature_sliced/3678](https://t.me/feature_sliced/3678) -- (Thread) Cross-imports and responsibility boundaries: [https://t.me/feature_sliced/3287](https://t.me/feature_sliced/3287) -- (Thread) Resolving import issues between segments: [https://t.me/feature_sliced/4021](https://t.me/feature_sliced/4021) -- (Thread) Cross-imports within Shared internal structure: [https://t.me/feature_sliced/3618](https://t.me/feature_sliced/3618) - -[layers]: https://feature-sliced.github.io/documentation/docs/reference/layers -[public-api]: https://feature-sliced.github.io/documentation/docs/reference/public-api +- [(Thread) About the supposed inevitability of cross-ports](https://t.me/feature_sliced/4515) +- [(Thread) About resolving cross-ports in entities](https://t.me/feature_sliced/3678) +- [(Thread) About cross-imports and responsibility](https://t.me/feature_sliced/3287) +- [(Thread) About imports between segments](https://t.me/feature_sliced/4021) +- [(Thread) About cross-imports inside shared](https://t.me/feature_sliced/3618) From 74a8df79c605847ef253d342f8e7ed4c76e9e5bc Mon Sep 17 00:00:00 2001 From: Minsu Date: Tue, 20 Jan 2026 23:25:55 +0900 Subject: [PATCH 4/5] docs: reflecting feedback --- .../current/guides/issues/cross-imports.mdx | 163 +++++++++++++----- 1 file changed, 116 insertions(+), 47 deletions(-) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx index 44addf6974..7176dcb1b5 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx @@ -4,7 +4,6 @@ pagination_next: reference/layers --- - # Cross-import A **cross-import** is an import **between different slices within the same layer**. @@ -13,30 +12,40 @@ For example: - importing `features/product` from `features/cart` - importing `widgets/sidebar` from `widgets/header` +Cross-imports are a code smell: a warning sign that slices are becoming coupled. In some situations they may be hard to avoid, but they should always be deliberate and either documented or shared within the team/project. + :::note The `shared` and `app` layers do not have the concept of a slice, so imports *within* those layers are **not** considered cross-imports. ::: ---- - ## Why is this a code smell? -This document explains why cross-imports are not just a matter of style, but are generally considered a **code smell**. +Cross-imports are not just a matter of style—they are generally considered a **code smell** because they blur the boundaries between domains and introduce implicit dependencies. -For example, when the `cart` slice directly depends on `product` UI or business logic, the domain/responsibility boundary becomes blurry. +Consider a case where the `cart` slice directly depends on `product` business logic. At first glance, this might seem convenient. However, this creates several problems: -In the sections below, we outline how these issues typically appear in real projects and what strategies you can use to address them. +1. **Unclear ownership and responsibility.** When `cart` imports from `product`, it becomes unclear which slice "owns" the shared logic. If the `product` team changes their internal implementation, they might unknowingly break `cart`. This ambiguity makes it harder to reason about the codebase and assign responsibility for bugs or features. ---- +2. **Reduced isolation and testability.** One of the main benefits of sliced architecture is that each slice can be developed, tested, and deployed independently. Cross-imports break this isolation—testing `cart` now requires setting up `product` as well, and changes in one slice can cause unexpected test failures in another. + +3. **Increased cognitive load.** Working on `cart` also requires accounting for how `product` is structured and how it behaves. As cross-imports accumulate, tracing the impact of a change requires following more code across slice boundaries, and even small edits demand more context to be held in mind. + +4. **Path to circular dependencies.** Cross-imports often start as one-way dependencies but can evolve into bidirectional ones (A imports B, B imports A). This tends to lock slices together, making dependencies harder to untangle and increasing refactoring cost over time. + +The purpose of clear domain boundaries is to keep each slice focused and changeable within its own responsibility. When dependencies are loose, it becomes easier to predict the impact of a change and to keep review and testing scope contained. Cross-imports weaken this separation, expanding the impact of changes and increasing refactoring cost over time—this is why they are treated as a code smell worth addressing. + +In the sections below, we outline how these issues typically appear in real projects and what strategies you can use to address them. ## Entities layer cross-imports -The `entities` layer is designed as a **domain/type-centric** layer. -It typically contains domain types, IDs, DTOs, and domain-level logic. +The `entities` layer is designed as a **domain/type-centric** layer. It typically contains domain types, IDs, DTOs, and domain-level logic. In real-world projects, entity-to-entity relationships often require sharing types or DTOs (for example, `Order` and `OrderItem`, `Artist` and `Song`). -To express these domain-level references, many teams use `@x` as a dedicated cross-import surface for entities. +In practice, cross-imports in `entities` are often caused by splitting entities too granularly. Before reaching for `@x`, consider whether the boundaries should be merged instead. +Some teams use `@x` as a dedicated cross-import surface for `entities`, but it should be treated as a **last resort** — a **necessary compromise**, not a recommended approach. + +Think of `@x` as an explicit gateway for unavoidable domain references—not a general-purpose reuse mechanism. Overuse tends to lock entity boundaries together and makes refactoring more costly over time. For details about `@x`, see the [Public API documentation](/docs/reference/public-api). @@ -44,15 +53,9 @@ For concrete examples of cross-references between business entities, see: - [Types guide — Business entities and their cross-references](/docs/guides/examples/types#business-entities-and-their-cross-references) - [Layers reference — Entities](/docs/reference/layers#entities) ---- - ## Features and widgets: Multiple strategies -In the `features` and `widgets` layers, it’s usually more realistic to say there are **multiple strategies** for handling cross-imports, rather than declaring them **always forbidden**. - -This section focuses less on code and more on the **patterns** you can choose from depending on your team and product context. - ---- +In the `features` and `widgets` layers, it's usually more realistic to say there are **multiple strategies** for handling cross-imports, rather than declaring them **always forbidden**. This section focuses less on code and more on the **patterns** you can choose from depending on your team and product context. ### Strategy A: Slice merge @@ -62,35 +65,31 @@ Example (before): - `features/profile` - `features/profileSettings` -If these keep cross-importing each other and effectively move as one unit, they are likely one feature in practice. -In that case, merging into `features/profile` is often the simpler and cleaner choice. - ---- +If these keep cross-importing each other and effectively move as one unit, they are likely one feature in practice. In that case, merging into `features/profile` is often the simpler and cleaner choice. ### Strategy B: Push shared domain flows down into `entities` (domain-only) -If multiple features share a domain-level flow, -move that flow into a domain slice inside `entities` (for example, `entities/session`). +If multiple features share a domain-level flow, move that flow into a domain slice inside `entities` (for example, `entities/session`). Key principles: - `entities` contains **domain types and domain logic only** - UI remains in `features` / `widgets` - features import and use the domain logic from `entities` -For example, if both `features/auth` and `features/profile` need session validation, -place session-related domain functions in `entities/session` and reuse them from both features. +For example, if both `features/auth` and `features/profile` need session validation, place session-related domain functions in `entities/session` and reuse them from both features. For more guidance, see [Layers reference — Entities](/docs/reference/layers#entities). ---- +### Strategy C: Compose from an upper layer (pages / app) -### Strategy C: Orchestrate from an upper layer (pages / app) +Instead of connecting slices within the same layer via cross-imports, compose them at a higher level (`pages` / `app`). This approach uses **Inversion of Control (IoC)** patterns—rather than slices knowing about each other, an upper layer assembles and connects them. -Instead of connecting slices within the same layer via cross-imports, compose them at a higher level (`pages` / `app`) using DI (dependency injection), slot patterns, or higher-level composition. +Common IoC techniques include: +- **Render props (React)**: Pass components or render functions as props +- **Slots (Vue)**: Use named slots to inject content from parent components +- **Dependency injection**: Pass dependencies through props or context -In other words, rather than wiring slices together directly, let an upper layer **assemble and orchestrate the flow**. - -#### Example code: +#### Basic composition example (React): ```tsx title="features/userProfile/index.ts" export { UserProfilePanel } from './ui/UserProfilePanel'; @@ -114,15 +113,92 @@ export function UserDashboardPage() { ); } ``` -With this structure, `features/userProfile` and `features/activityFeed` do not know about each other. -`pages/UserDashboardPage` composes them to build the full screen. ---- +With this structure, `features/userProfile` and `features/activityFeed` do not know about each other. `pages/UserDashboardPage` composes them to build the full screen. + +#### Render props example (React): + +When one feature needs to render content from another, use render props to invert the dependency: + +```tsx title="features/commentList/ui/CommentList.tsx" +interface CommentListProps { + comments: Comment[]; + renderUserAvatar?: (userId: string) => React.ReactNode; +} + +export function CommentList({ comments, renderUserAvatar }: CommentListProps) { + return ( +
    + {comments.map(comment => ( +
  • + {renderUserAvatar?.(comment.userId)} + {comment.text} +
  • + ))} +
+ ); +} +``` + +```tsx title="pages/PostPage.tsx" +import { CommentList } from '@/features/commentList'; +import { UserAvatar } from '@/features/userProfile'; + +export function PostPage() { + return ( + } + /> + ); +} +``` + +Now `CommentList` doesn't import from `userProfile`—the page injects the avatar component. + +#### Slots example (Vue): + +Vue's slot system provides a natural way to compose features without cross-imports: + +```vue title="features/commentList/ui/CommentList.vue" + + + +``` + +```vue title="pages/PostPage.vue" + + + +``` + +The `CommentList` feature remains independent of `userProfile`. The page uses slots to compose them together. ### Strategy D: Cross-feature reuse only via Public API -If cross-feature reuse is truly needed, allow it only through an explicit **Public API** (for example: exported hooks or UI components). -Avoid directly accessing another slice’s `store`/`model` or internal implementation details. +If the above strategies don't fit your case and cross-feature reuse is truly unavoidable, allow it only through an explicit **Public API** (for example: exported hooks or UI components). Avoid directly accessing another slice's `store`/`model` or internal implementation details. + +Unlike strategies A-C which aim to eliminate cross-imports, this strategy accepts them while minimizing the risks through strict boundaries. #### Example code: @@ -148,10 +224,7 @@ export function ProfileMenu() { } ``` -For example, prevent `features/profile` from importing from paths like `features/auth/model/internal/*`. -Restrict usage to only what `features/auth` explicitly exposes as its Public API. - ---- +For example, prevent `features/profile` from importing from paths like `features/auth/model/internal/*`. Restrict usage to only what `features/auth` explicitly exposes as its Public API. ## When should cross-imports be treated as a problem? @@ -160,16 +233,14 @@ After reviewing these strategies, a natural question is: > When is a cross-import acceptable to keep, and when should it be treated as a code smell and refactored? Common warning signs: -- directly depending on another slice’s store/model/business logic -- deep imports into another slice’s internal files -- **bidirectional dependencies** (A and B) +- directly depending on another slice's store/model/business logic +- deep imports into another slice's internal files +- **bidirectional dependencies** (A imports B, and B imports A) - changes in one slice frequently breaking another slice - flows that should be composed in `pages` / `app`, but are forced into cross-imports within the same layer When you see these signals, treat the cross-import as a **code smell** and consider applying at least one of the strategies above. ---- - ## How strict you are is a team/project decision How strictly to enforce these rules depends on the team and project. @@ -190,8 +261,6 @@ Teams should align on: - how to reflect it in lint rules, code review, and documentation - when and how to reevaluate existing cross-imports over time ---- - ## References - [(Thread) About the supposed inevitability of cross-ports](https://t.me/feature_sliced/4515) From e4a18f3095ad79fbe08b5d44f2b4a8bd8018f6d4 Mon Sep 17 00:00:00 2001 From: Minsu Date: Sat, 31 Jan 2026 00:10:59 +0900 Subject: [PATCH 5/5] Update i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx Co-authored-by: Solant --- .../current/guides/issues/cross-imports.mdx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx b/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx index 7176dcb1b5..b6dfa6eaec 100644 --- a/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx +++ b/i18n/en/docusaurus-plugin-content-docs/current/guides/issues/cross-imports.mdx @@ -38,11 +38,7 @@ In the sections below, we outline how these issues typically appear in real proj ## Entities layer cross-imports -The `entities` layer is designed as a **domain/type-centric** layer. It typically contains domain types, IDs, DTOs, and domain-level logic. - -In real-world projects, entity-to-entity relationships often require sharing types or DTOs (for example, `Order` and `OrderItem`, `Artist` and `Song`). - -In practice, cross-imports in `entities` are often caused by splitting entities too granularly. Before reaching for `@x`, consider whether the boundaries should be merged instead. +Cross-imports in `entities` are often caused by splitting entities too granularly. Before reaching for `@x`, consider whether the boundaries should be merged instead. Some teams use `@x` as a dedicated cross-import surface for `entities`, but it should be treated as a **last resort** — a **necessary compromise**, not a recommended approach. Think of `@x` as an explicit gateway for unavoidable domain references—not a general-purpose reuse mechanism. Overuse tends to lock entity boundaries together and makes refactoring more costly over time.