docs: обновить слой infra в документации
- обновлены упоминания слоя infrastructure на infra - добавлены метаданные страниц архитектуры - исправлены ссылки между разделами архитектуры
This commit is contained in:
@@ -63,4 +63,4 @@
|
|||||||
- [Biome](./applied/biome.md) — Установка и настройка линтера-форматтера в новом проекте.
|
- [Biome](./applied/biome.md) — Установка и настройка линтера-форматтера в новом проекте.
|
||||||
- [PostCSS](./applied/postcss.md) — Установка и настройка CSS-процессора в новом проекте.
|
- [PostCSS](./applied/postcss.md) — Установка и настройка CSS-процессора в новом проекте.
|
||||||
- [VS Code](./applied/vscode.md) — Единые настройки редактора и расширений для команды.
|
- [VS Code](./applied/vscode.md) — Единые настройки редактора и расширений для команды.
|
||||||
- [Локализация](./applied/localization.md) — Как организовать локализацию как infrastructure-модуль.
|
- [Локализация](./applied/localization.md) — Как организовать локализацию как infra-модуль.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Алиасы импортов
|
title: Алиасы импортов
|
||||||
description: Какие алиасы импортов есть в проекте и как ими пользоваться.
|
description: Какие алиасы импортов есть в проекте и как ими пользоваться.
|
||||||
keywords: [алиасы, aliases, paths, tsconfig, импорты, baseUrl, app, layouts, screens, widgets, business, infrastructure, ui, shared]
|
keywords: [алиасы, aliases, paths, tsconfig, импорты, baseUrl, app, layouts, screens, widgets, business, infra, ui, shared]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Алиасы импортов
|
# Алиасы импортов
|
||||||
@@ -21,7 +21,7 @@ keywords: [алиасы, aliases, paths, tsconfig, импорты, baseUrl, app,
|
|||||||
"screens/*": ["./src/screens/*"],
|
"screens/*": ["./src/screens/*"],
|
||||||
"widgets/*": ["./src/widgets/*"],
|
"widgets/*": ["./src/widgets/*"],
|
||||||
"business/*": ["./src/business/*"],
|
"business/*": ["./src/business/*"],
|
||||||
"infrastructure/*": ["./src/infrastructure/*"],
|
"infra/*": ["./src/infra/*"],
|
||||||
"ui/*": ["./src/ui/*"],
|
"ui/*": ["./src/ui/*"],
|
||||||
"shared/*": ["./src/shared/*"]
|
"shared/*": ["./src/shared/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
---
|
---
|
||||||
title: Локализация
|
title: Локализация
|
||||||
description: Как организовать локализацию как infrastructure-модуль.
|
description: Как организовать локализацию как infra-модуль.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Локализация
|
# Локализация
|
||||||
|
|
||||||
Как организовать локализацию как infrastructure-модуль.
|
Как организовать локализацию как infra-модуль.
|
||||||
|
|
||||||
## Назначение
|
## Назначение
|
||||||
|
|
||||||
Локализация — инфраструктурная подсистема приложения. Она отвечает за текущую локаль, словари, форматирование переводов и API для компонентов.
|
Локализация — инфраструктурная подсистема приложения. Она отвечает за текущую локаль, словари, форматирование переводов и API для компонентов.
|
||||||
|
|
||||||
Код локализации живёт в `src/infrastructure/i18n/`. Компоненты и модули не читают словари напрямую — они используют публичный API infrastructure-модуля.
|
Код локализации живёт в `src/infra/i18n/`. Компоненты и модули не читают словари напрямую — они используют публичный API infra-модуля.
|
||||||
|
|
||||||
## Структура
|
## Структура
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/i18n/
|
src/infra/i18n/
|
||||||
├── config/
|
├── config/
|
||||||
│ └── i18n.config.ts
|
│ └── i18n.config.ts
|
||||||
├── dictionaries/
|
├── dictionaries/
|
||||||
@@ -31,16 +31,16 @@ src/infrastructure/i18n/
|
|||||||
└── index.ts
|
└── index.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
Набор сегментов может отличаться, но публичная точка входа остаётся одна — `infrastructure/i18n`.
|
Набор сегментов может отличаться, но публичная точка входа остаётся одна — `infra/i18n`.
|
||||||
|
|
||||||
## Подключение
|
## Подключение
|
||||||
|
|
||||||
`app/` только подключает готовый провайдер локализации. Реализация провайдера, словари и конфиг остаются в `infrastructure/i18n/`.
|
`app/` только подключает готовый провайдер локализации. Реализация провайдера, словари и конфиг остаются в `infra/i18n/`.
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// src/app/layout.tsx
|
// src/app/layout.tsx
|
||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import { I18nProvider } from 'infrastructure/i18n'
|
import { I18nProvider } from 'infra/i18n'
|
||||||
|
|
||||||
type RootLayoutProps = {
|
type RootLayoutProps = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@@ -62,7 +62,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|||||||
Компоненты получают переводы через готовый API модуля локализации:
|
Компоненты получают переводы через готовый API модуля локализации:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useTranslation } from 'infrastructure/i18n'
|
import { useTranslation } from 'infra/i18n'
|
||||||
|
|
||||||
export const ProfileTitle = () => {
|
export const ProfileTitle = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -73,9 +73,9 @@ export const ProfileTitle = () => {
|
|||||||
|
|
||||||
## Правила
|
## Правила
|
||||||
|
|
||||||
- Локализация живёт в `infrastructure/i18n/`.
|
- Локализация живёт в `infra/i18n/`.
|
||||||
- `app/` только подключает готовый provider и передаёт locale.
|
- `app/` только подключает готовый provider и передаёт locale.
|
||||||
- Словари не импортируются напрямую в компоненты, screens или business-модули.
|
- Словари не импортируются напрямую в компоненты, screens или business-модули.
|
||||||
- Ключи переводов не собираются динамически из строк, если это ломает типизацию и поиск.
|
- Ключи переводов не собираются динамически из строк, если это ломает типизацию и поиск.
|
||||||
- Тексты интерфейса не хардкодятся в переиспользуемых компонентах, если они должны переводиться.
|
- Тексты интерфейса не хардкодятся в переиспользуемых компонентах, если они должны переводиться.
|
||||||
- Форматирование дат, чисел и валют должно проходить через API локализации или отдельные утилиты infrastructure-модуля.
|
- Форматирование дат, чисел и валют должно проходить через API локализации или отдельные утилиты infra-модуля.
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ description: Как работать со страницами и другими
|
|||||||
| Прогрев SWR-кеша, начальное состояние, подключение провайдеров | `src/app/**`, только через готовые обёртки из нижних слоёв |
|
| Прогрев SWR-кеша, начальное состояние, подключение провайдеров | `src/app/**`, только через готовые обёртки из нижних слоёв |
|
||||||
| UI страницы | `screens/` |
|
| UI страницы | `screens/` |
|
||||||
| Каркас страницы: header, footer, sidebar | `layouts/` |
|
| Каркас страницы: header, footer, sidebar | `layouts/` |
|
||||||
| Провайдеры, сторы, хуки, API-клиенты, сервисы | нижние слои (`screens/`, `business/`, `infrastructure/`, `shared/`) |
|
| Провайдеры, сторы, хуки, API-клиенты, сервисы | нижние слои (`screens/`, `business/`, `infra/`, `shared/`) |
|
||||||
| CSS Modules и стили компонентов | рядом с компонентами, не в `src/app/**` |
|
| CSS Modules и стили компонентов | рядом с компонентами, не в `src/app/**` |
|
||||||
|
|
||||||
## Что можно делать в `page.tsx`
|
## Что можно делать в `page.tsx`
|
||||||
@@ -79,7 +79,7 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { notFound } from 'next/navigation'
|
import { notFound } from 'next/navigation'
|
||||||
import { userApi } from 'infrastructure/backend-api'
|
import { userApi } from 'infra/backend-api'
|
||||||
import { UserScreen } from 'screens/user'
|
import { UserScreen } from 'screens/user'
|
||||||
|
|
||||||
type UserPageProps = {
|
type UserPageProps = {
|
||||||
@@ -109,7 +109,7 @@ import {
|
|||||||
backendApi,
|
backendApi,
|
||||||
getCurrentUserKey,
|
getCurrentUserKey,
|
||||||
getPostListKey,
|
getPostListKey,
|
||||||
} from 'infrastructure/backend-api'
|
} from 'infra/backend-api'
|
||||||
|
|
||||||
type FeedLayoutProps = {
|
type FeedLayoutProps = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ src/
|
|||||||
├── screens/ # Контент конкретной страницы
|
├── screens/ # Контент конкретной страницы
|
||||||
├── widgets/ # Составные блоки интерфейса, не привязанные к домену
|
├── widgets/ # Составные блоки интерфейса, не привязанные к домену
|
||||||
├── business/ # Бизнес-домены (auth, catalog, orders)
|
├── business/ # Бизнес-домены (auth, catalog, orders)
|
||||||
├── infrastructure/ # Техсервисы (theme, i18n, API-адаптеры)
|
├── infra/ # Техсервисы (theme, i18n, API-адаптеры)
|
||||||
├── ui/ # UI-кит без бизнес-логики
|
├── ui/ # UI-кит без бизнес-логики
|
||||||
└── shared/ # Общие ресурсы (утилиты, типы, стили)
|
└── shared/ # Общие ресурсы (утилиты, типы, стили)
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
|
---
|
||||||
|
title: SLM Design
|
||||||
|
description: Назначение архитектуры, ключевые принципы и карта разделов документации
|
||||||
|
---
|
||||||
|
|
||||||
# SLM Design
|
# SLM Design
|
||||||
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
|
Scoped Layered Module Design — модульная архитектура фронтенд-приложений. Код организован по слоям ответственности, а модуль содержит всё, что ему нужно: компоненты, хуки, сторы, типы, стили.
|
||||||
|
|
||||||
::: warning Локальная копия
|
|
||||||
Документация по архитектуре — локальная копия. Оригинал находится на сайте [slm-design.gromlab.ru](https://slm-design.gromlab.ru/).
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Разделы спецификации
|
## Разделы спецификации
|
||||||
|
|
||||||
Спецификация SLM Design состоит из нескольких связанных разделов. Этот обзор даёт общий контекст, а детальные правила описаны дальше:
|
Спецификация SLM Design состоит из нескольких связанных разделов. Этот обзор даёт общий контекст, а детальные правила описаны дальше:
|
||||||
|
|
||||||
- [Слои](./layers.md) — уровни организации `src/`, направление зависимостей и зона ответственности каждого слоя.
|
- [Слои](/architecture/layers) — уровни организации `src/`, направление зависимостей и зона ответственности каждого слоя.
|
||||||
- [Модули](./modules.md) — границы ответственности, публичный API, типы модулей и отличие модуля от компонента.
|
- [Модули](/architecture/modules) — границы ответственности, публичный API, типы модулей и отличие модуля от компонента.
|
||||||
- [Сегменты](./segments.md) — внутренние папки модуля (`ui/`, `parts/`, `hooks/`, `types/` и другие) и правила размещения файлов.
|
- [Сегменты](/architecture/segments) — внутренние папки модуля (`ui/`, `parts/`, `hooks/`, `types/` и другие) и правила размещения файлов.
|
||||||
|
|
||||||
Рекомендуемый порядок чтения: обзор → слои → модули → сегменты.
|
Рекомендуемый порядок чтения: обзор → слои → модули → сегменты.
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Слои
|
||||||
|
description: Иерархия слоёв от app до shared, правила зависимостей и зона ответственности каждого слоя
|
||||||
|
---
|
||||||
|
|
||||||
# Слои
|
# Слои
|
||||||
|
|
||||||
Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом.
|
Раздел описывает слои SLM: что такое слой, какие бывают, как между ними направлены зависимости и какие правила действуют на каждом.
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Модули
|
||||||
|
description: Структура модуля, типы (UI, бизнес, инфра), публичный API, отличие модуля от компонента
|
||||||
|
---
|
||||||
|
|
||||||
# Модули
|
# Модули
|
||||||
|
|
||||||
Раздел описывает модуль как границу ответственности в SLM: что считается модулем, что такое компонент внутри модуля и как модуль взаимодействует с остальным кодом.
|
Раздел описывает модуль как границу ответственности в SLM: что считается модулем, что такое компонент внутри модуля и как модуль взаимодействует с остальным кодом.
|
||||||
@@ -146,7 +151,7 @@ backend-api/
|
|||||||
└── index.ts # публичный API
|
└── index.ts # публичный API
|
||||||
```
|
```
|
||||||
|
|
||||||
Подробное описание сегментов — в разделе [Сегменты](./segments.md).
|
Подробное описание сегментов — в разделе [Сегменты](/architecture/segments).
|
||||||
|
|
||||||
## Публичный API
|
## Публичный API
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: Сегменты
|
||||||
|
description: Сегменты внутри модуля (ui/, model/, lib/ и др.), назначение и правила размещения файлов
|
||||||
|
---
|
||||||
|
|
||||||
# Сегменты
|
# Сегменты
|
||||||
|
|
||||||
Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит.
|
Раздел описывает сегменты SLM: что такое сегмент, какие бывают и что в каждом из них лежит.
|
||||||
@@ -37,7 +42,7 @@
|
|||||||
- Не получает данные самостоятельно, не выбирает источник данных и не композирует данные.
|
- Не получает данные самостоятельно, не выбирает источник данных и не композирует данные.
|
||||||
- Не содержит бизнес-логику или сценарную логику.
|
- Не содержит бизнес-логику или сценарную логику.
|
||||||
|
|
||||||
Если UI-сущности нужно что-то за пределами этих ограничений, она должна быть оформлена как модуль. Полная граница описана в разделе [Компонент](./modules.md#компонент).
|
Если UI-сущности нужно что-то за пределами этих ограничений, она должна быть оформлена как модуль. Полная граница описана в разделе [Компонент](/architecture/modules#компонент).
|
||||||
|
|
||||||
Корневой файл модуля в `ui/` не размещается. Он лежит в корне модуля: `{module-name}.tsx`.
|
Корневой файл модуля в `ui/` не размещается. Он лежит в корне модуля: `{module-name}.tsx`.
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ keywords: [создать проект, новый проект, с нуля, in
|
|||||||
|
|
||||||
## Канон раскладки
|
## Канон раскладки
|
||||||
|
|
||||||
В `src/` допустимы только слои SLM: `app/`, `layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`, `shared/`. Любая другая папка в `src/` — нарушение канона ([Структура проекта](/docs/applied/project-structure), [Архитектура](/docs/basics/architecture/)).
|
В `src/` допустимы только слои SLM: `app/`, `layouts/`, `screens/`, `widgets/`, `business/`, `infra/`, `ui/`, `shared/`. Любая другая папка в `src/` — нарушение канона ([Структура проекта](/docs/applied/project-structure), [Архитектура](/docs/basics/architecture/)).
|
||||||
|
|
||||||
В частности: `src/app/` содержит только файлы роутинга Next.js и инициализации, без каталогов `styles/`, `assets/`, `components/`.
|
В частности: `src/app/` содержит только файлы роутинга Next.js и инициализации, без каталогов `styles/`, `assets/`, `components/`.
|
||||||
|
|
||||||
@@ -85,6 +85,6 @@ CSS-процессор поверх базовых стилей: `@custom-media`
|
|||||||
|
|
||||||
- **Порядок шагов фиксирован.** Перестановка ломает зависимости (PostCSS требует базовых стилей, VS Code — установленного Biome).
|
- **Порядок шагов фиксирован.** Перестановка ломает зависимости (PostCSS требует базовых стилей, VS Code — установленного Biome).
|
||||||
- **Между шагами обязательна проверка** из соответствующего раздела. Не переходить дальше, пока чеклист текущего шага не пройден.
|
- **Между шагами обязательна проверка** из соответствующего раздела. Не переходить дальше, пока чеклист текущего шага не пройден.
|
||||||
- **Слои `src/`** (`layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`) не создавать авансом. Появляются по мере первого модуля. Исключения — `src/app/` (создаётся `create-next-app`), `src/shared/styles/` (шаг 1) и `src/shared/sprites/icons/` (шаг 6).
|
- **Слои `src/`** (`layouts/`, `screens/`, `widgets/`, `business/`, `infra/`, `ui/`) не создавать авансом. Появляются по мере первого модуля. Исключения — `src/app/` (создаётся `create-next-app`), `src/shared/styles/` (шаг 1) и `src/shared/sprites/icons/` (шаг 6).
|
||||||
- **Посторонние каталоги в `src/`** (`assets/`, `utils/`, `lib/`, `components/` и т.п.) — запрещены.
|
- **Посторонние каталоги в `src/`** (`assets/`, `utils/`, `lib/`, `components/` и т.п.) — запрещены.
|
||||||
- **Подмножество шагов допустимо.** Можно ставить только Next.js и часть инструментов; полный набор — это эталон, а не обязательство.
|
- **Подмножество шагов допустимо.** Можно ставить только Next.js и часть инструментов; полный набор — это эталон, а не обязательство.
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
mkdir -p src/shared/styles
|
mkdir -p src/shared/styles
|
||||||
```
|
```
|
||||||
|
|
||||||
Остальные слои (`layouts/`, `screens/`, `widgets/`, `business/`, `infrastructure/`, `ui/`) заводятся при появлении первого модуля в них. `src/shared/styles/` — единственный подкаталог `shared/`, который заводится сразу: без него не настроить стили на следующих шагах.
|
Остальные слои (`layouts/`, `screens/`, `widgets/`, `business/`, `infra/`, `ui/`) заводятся при появлении первого модуля в них. `src/shared/styles/` — единственный подкаталог `shared/`, который заводится сразу: без него не настроить стили на следующих шагах.
|
||||||
|
|
||||||
## Правила
|
## Правила
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Источники данных
|
title: Источники данных
|
||||||
description: Какие источники данных используются в проекте и как с ними работать.
|
description: Какие источники данных используются в проекте и как с ними работать.
|
||||||
keywords: [данные, api, rest, realtime, клиент, swr, infrastructure, введение, карта раздела]
|
keywords: [данные, api, rest, realtime, клиент, swr, infra, введение, карта раздела]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Источники данных
|
# Источники данных
|
||||||
@@ -10,7 +10,7 @@ keywords: [данные, api, rest, realtime, клиент, swr, infrastructure,
|
|||||||
|
|
||||||
## Принципы раздела
|
## Принципы раздела
|
||||||
|
|
||||||
- **Клиент — в `infrastructure/`.** Каждый внешний сервис — отдельный модуль слоя `infrastructure/{service-name}/`.
|
- **Клиент — в `infra/`.** Каждый внешний сервис — отдельный модуль слоя `infra/{service-name}/`.
|
||||||
- **Прямой `fetch` запрещён.** Запросы идут только через клиент модуля. Исключения — точечные и обоснованные.
|
- **Прямой `fetch` запрещён.** Запросы идут только через клиент модуля. Исключения — точечные и обоснованные.
|
||||||
- **Источник данных диктует канал.** REST, realtime и т.п. — независимые подразделы, у каждого своя модель клиента и своё потребление.
|
- **Источник данных диктует канал.** REST, realtime и т.п. — независимые подразделы, у каждого своя модель клиента и своё потребление.
|
||||||
- **Серверные и клиентские компоненты потребляют по-разному.** Server Components — прямой `await` метода клиента, клиентские — через готовые GET-хуки REST-клиента (`useGetUserList`, `useGetPostDetail` и т.п.). SWR инкапсулирован в хуке, компонент про него не знает.
|
- **Серверные и клиентские компоненты потребляют по-разному.** Server Components — прямой `await` метода клиента, клиентские — через готовые GET-хуки REST-клиента (`useGetUserList`, `useGetPostDetail` и т.п.). SWR инкапсулирован в хуке, компонент про него не знает.
|
||||||
@@ -40,7 +40,7 @@ keywords: [данные, api, rest, realtime, клиент, swr, infrastructure,
|
|||||||
|
|
||||||
Канал push-данных: WebSocket, SSE, событийные шины. Транспорт не зашит в правила — важна абстракция «подписка».
|
Канал push-данных: WebSocket, SSE, событийные шины. Транспорт не зашит в правила — важна абстракция «подписка».
|
||||||
|
|
||||||
- [Realtime](/docs/data/realtime) — клиент realtime в `infrastructure/`, потребление через `useSWRSubscription` или прямые подписки.
|
- [Realtime](/docs/data/realtime) — клиент realtime в `infra/`, потребление через `useSWRSubscription` или прямые подписки.
|
||||||
|
|
||||||
## Что даёт раздел
|
## Что даёт раздел
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ keywords: [данные, api, rest, realtime, клиент, swr, infrastructure,
|
|||||||
|
|
||||||
- Где живёт код работы с API и почему именно там.
|
- Где живёт код работы с API и почему именно там.
|
||||||
- Когда генерировать клиент автоматически, а когда писать вручную, и как структурирован каждый из вариантов.
|
- Когда генерировать клиент автоматически, а когда писать вручную, и как структурирован каждый из вариантов.
|
||||||
- Какие GET-хуки относятся к REST-клиенту и почему они живут в `infrastructure/{service-name}/hooks/`.
|
- Какие GET-хуки относятся к REST-клиенту и почему они живут в `infra/{service-name}/hooks/`.
|
||||||
- Как выбрать стратегию получения REST-данных под конкретную ситуацию.
|
- Как выбрать стратегию получения REST-данных под конкретную ситуацию.
|
||||||
- Как подключать realtime-источники в общую модель работы с данными.
|
- Как подключать realtime-источники в общую модель работы с данными.
|
||||||
- Какие правила обязательны и какие отклонения допустимы.
|
- Какие правила обязательны и какие отклонения допустимы.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ keywords: [realtime, websocket, sse, подписка, swr subscription, useSWRS
|
|||||||
|
|
||||||
## Принципы
|
## Принципы
|
||||||
|
|
||||||
- **Клиент realtime — в `infrastructure/`** отдельным модулем по имени канала. То же правило, что и для REST: никаких прямых соединений в коде приложения.
|
- **Клиент realtime — в `infra/`** отдельным модулем по имени канала. То же правило, что и для REST: никаких прямых соединений в коде приложения.
|
||||||
- **Подписка — единица потребления.** Клиент даёт функцию `subscribe(topic, handler) → unsubscribe`. Внутри — конкретный транспорт.
|
- **Подписка — единица потребления.** Клиент даёт функцию `subscribe(topic, handler) → unsubscribe`. Внутри — конкретный транспорт.
|
||||||
- **Использование на клиенте — два сценария:**
|
- **Использование на клиенте — два сценария:**
|
||||||
- **`useSWRSubscription`** — для данных, которые показываются в UI и должны кешироваться/синхронизироваться с REST.
|
- **`useSWRSubscription`** — для данных, которые показываются в UI и должны кешироваться/синхронизироваться с REST.
|
||||||
@@ -19,7 +19,7 @@ keywords: [realtime, websocket, sse, подписка, swr subscription, useSWRS
|
|||||||
## Размещение клиента
|
## Размещение клиента
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/
|
src/infra/
|
||||||
└── {channel-name}/
|
└── {channel-name}/
|
||||||
├── connection.ts # установление соединения, реконнект
|
├── connection.ts # установление соединения, реконнект
|
||||||
├── subscribe.ts # subscribe(topic, handler) → unsubscribe
|
├── subscribe.ts # subscribe(topic, handler) → unsubscribe
|
||||||
@@ -33,7 +33,7 @@ src/infrastructure/
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import useSWRSubscription from 'swr/subscription'
|
import useSWRSubscription from 'swr/subscription'
|
||||||
import { subscribe } from 'infrastructure/notifications'
|
import { subscribe } from 'infra/notifications'
|
||||||
|
|
||||||
export function NotificationCounter() {
|
export function NotificationCounter() {
|
||||||
const { data: count } = useSWRSubscription(
|
const { data: count } = useSWRSubscription(
|
||||||
@@ -56,7 +56,7 @@ export function NotificationCounter() {
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { subscribe } from 'infrastructure/notifications'
|
import { subscribe } from 'infra/notifications'
|
||||||
import { showToast } from 'ui/toast'
|
import { showToast } from 'ui/toast'
|
||||||
|
|
||||||
export function NotificationsToaster() {
|
export function NotificationsToaster() {
|
||||||
@@ -74,6 +74,6 @@ export function NotificationsToaster() {
|
|||||||
|
|
||||||
## Запрет прямых соединений
|
## Запрет прямых соединений
|
||||||
|
|
||||||
Создавать `new WebSocket(...)`, `new EventSource(...)` или подписываться на событийные шины напрямую в коде приложения — запрещено. Все соединения проходят через клиент в `infrastructure/`.
|
Создавать `new WebSocket(...)`, `new EventSource(...)` или подписываться на событийные шины напрямую в коде приложения — запрещено. Все соединения проходят через клиент в `infra/`.
|
||||||
|
|
||||||
Исключения — точечные и обоснованные (например, диагностический скрипт), помечаются комментарием.
|
Исключения — точечные и обоснованные (например, диагностический скрипт), помечаются комментарием.
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ https://petstore3.swagger.io/api/v3/openapi.json
|
|||||||
Имена модуля:
|
Имена модуля:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/pet-store-api/
|
src/infra/pet-store-api/
|
||||||
petStoreApi
|
petStoreApi
|
||||||
pet-store-api.generated.ts
|
pet-store-api.generated.ts
|
||||||
```
|
```
|
||||||
@@ -31,7 +31,7 @@ pet-store-api.generated.ts
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"codegen:pet-store-api": "npx @gromlab/api-codegen@latest -i https://petstore3.swagger.io/api/v3/openapi.json -o src/infrastructure/pet-store-api/generated -n pet-store-api.generated"
|
"codegen:pet-store-api": "npx @gromlab/api-codegen@latest -i https://petstore3.swagger.io/api/v3/openapi.json -o src/infra/pet-store-api/generated -n pet-store-api.generated"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -53,7 +53,7 @@ npm run codegen:pet-store-api
|
|||||||
Ожидаемый результат:
|
Ожидаемый результат:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/pet-store-api/generated/
|
src/infra/pet-store-api/generated/
|
||||||
└── pet-store-api.generated.ts
|
└── pet-store-api.generated.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ petStoreApi.pet.getPetById(...)
|
|||||||
Сгенерированный код не должен напрямую использоваться из приложения. Сначала создаётся настроенный инстанс клиента.
|
Сгенерированный код не должен напрямую использоваться из приложения. Сначала создаётся настроенный инстанс клиента.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-store-api/client.ts
|
// src/infra/pet-store-api/client.ts
|
||||||
import { Api, HttpClient } from './generated/pet-store-api.generated'
|
import { Api, HttpClient } from './generated/pet-store-api.generated'
|
||||||
|
|
||||||
const httpClient = new HttpClient({
|
const httpClient = new HttpClient({
|
||||||
@@ -102,7 +102,7 @@ export const petStoreApi = new Api(httpClient)
|
|||||||
Сгенерированный файл не правится руками. Если OpenAPI-спецификация неполная или генератор дал слишком общий тип (`object`, `unknown`, отсутствующее поле), расширения живут в `types/`.
|
Сгенерированный файл не правится руками. Если OpenAPI-спецификация неполная или генератор дал слишком общий тип (`object`, `unknown`, отсутствующее поле), расширения живут в `types/`.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/biocad-less-api/
|
src/infra/biocad-less-api/
|
||||||
├── generated/
|
├── generated/
|
||||||
│ └── biocad-less-api.generated.ts
|
│ └── biocad-less-api.generated.ts
|
||||||
├── types/
|
├── types/
|
||||||
@@ -115,7 +115,7 @@ src/infrastructure/biocad-less-api/
|
|||||||
Пример расширения generated-типа:
|
Пример расширения generated-типа:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/biocad-less-api/types/term.ts
|
// src/infra/biocad-less-api/types/term.ts
|
||||||
import type { TermRecordItem } from '../generated/biocad-less-api.generated'
|
import type { TermRecordItem } from '../generated/biocad-less-api.generated'
|
||||||
|
|
||||||
declare module '../generated/biocad-less-api.generated' {
|
declare module '../generated/biocad-less-api.generated' {
|
||||||
@@ -149,7 +149,7 @@ export type TermRecordItemExtended = Omit<
|
|||||||
```
|
```
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/biocad-less-api/types/index.ts
|
// src/infra/biocad-less-api/types/index.ts
|
||||||
export type { TermRecordItemExtended } from './term'
|
export type { TermRecordItemExtended } from './term'
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -158,18 +158,18 @@ export type { TermRecordItemExtended } from './term'
|
|||||||
## Публичный API
|
## Публичный API
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-store-api/index.ts
|
// src/infra/pet-store-api/index.ts
|
||||||
export { petStoreApi } from './client'
|
export { petStoreApi } from './client'
|
||||||
export type { Pet } from './generated/pet-store-api.generated'
|
export type { Pet } from './generated/pet-store-api.generated'
|
||||||
export * from './hooks'
|
export * from './hooks'
|
||||||
```
|
```
|
||||||
|
|
||||||
Наружу импортируют только из `infrastructure/pet-store-api`, не из `generated/`.
|
Наружу импортируют только из `infra/pet-store-api`, не из `generated/`.
|
||||||
|
|
||||||
Если у модуля есть расширенные типы, они тоже реэкспортируются через `index.ts`:
|
Если у модуля есть расширенные типы, они тоже реэкспортируются через `index.ts`:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/biocad-less-api/index.ts
|
// src/infra/biocad-less-api/index.ts
|
||||||
export type { TermRecordItemExtended } from './types'
|
export type { TermRecordItemExtended } from './types'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: GET-хуки REST-клиента
|
title: GET-хуки REST-клиента
|
||||||
description: Прозрачные SWR-обёртки над GET-методами REST-клиента.
|
description: Прозрачные SWR-обёртки над GET-методами REST-клиента.
|
||||||
keywords: [rest, swr, get-хуки, client components, infrastructure]
|
keywords: [rest, swr, get-хуки, client components, infra]
|
||||||
---
|
---
|
||||||
|
|
||||||
# GET-хуки REST-клиента
|
# GET-хуки REST-клиента
|
||||||
@@ -13,7 +13,7 @@ GET-хуки REST-клиента — прозрачные SWR-обёртки н
|
|||||||
GET-хуки принадлежат REST-клиенту конкретного сервиса и живут рядом с ним:
|
GET-хуки принадлежат REST-клиенту конкретного сервиса и живут рядом с ним:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/
|
src/infra/
|
||||||
└── pet-store-api/
|
└── pet-store-api/
|
||||||
├── client.ts
|
├── client.ts
|
||||||
├── generated/
|
├── generated/
|
||||||
@@ -41,7 +41,7 @@ src/infrastructure/
|
|||||||
## Пример списка
|
## Пример списка
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts
|
// src/infra/pet-store-api/hooks/use-get-pet-list.hook.ts
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import type { SWRConfiguration } from 'swr'
|
import type { SWRConfiguration } from 'swr'
|
||||||
import { petStoreApi } from '../client'
|
import { petStoreApi } from '../client'
|
||||||
@@ -74,7 +74,7 @@ import { SWRConfig, unstable_serialize } from 'swr'
|
|||||||
import {
|
import {
|
||||||
getPetListKey,
|
getPetListKey,
|
||||||
petStoreApi,
|
petStoreApi,
|
||||||
} from 'infrastructure/pet-store-api'
|
} from 'infra/pet-store-api'
|
||||||
|
|
||||||
export default function PetsLayout({ children }: { children: ReactNode }) {
|
export default function PetsLayout({ children }: { children: ReactNode }) {
|
||||||
const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
||||||
@@ -102,7 +102,7 @@ const { data: pets } = useGetPetList('available')
|
|||||||
## Пример detail-запроса
|
## Пример detail-запроса
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-store-api/hooks/use-get-pet-detail.hook.ts
|
// src/infra/pet-store-api/hooks/use-get-pet-detail.hook.ts
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import type { SWRConfiguration } from 'swr'
|
import type { SWRConfiguration } from 'swr'
|
||||||
import { petStoreApi } from '../client'
|
import { petStoreApi } from '../client'
|
||||||
@@ -141,23 +141,23 @@ const key = isReady ? getPetDetailKey(id) : null
|
|||||||
## Экспорт
|
## Экспорт
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-store-api/hooks/index.ts
|
// src/infra/pet-store-api/hooks/index.ts
|
||||||
export { getPetListKey, useGetPetList } from './use-get-pet-list.hook'
|
export { getPetListKey, useGetPetList } from './use-get-pet-list.hook'
|
||||||
export type { PetStatus } from './use-get-pet-list.hook'
|
export type { PetStatus } from './use-get-pet-list.hook'
|
||||||
export { getPetDetailKey, useGetPetDetail } from './use-get-pet-detail.hook'
|
export { getPetDetailKey, useGetPetDetail } from './use-get-pet-detail.hook'
|
||||||
```
|
```
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-store-api/index.ts
|
// src/infra/pet-store-api/index.ts
|
||||||
export { petStoreApi } from './client'
|
export { petStoreApi } from './client'
|
||||||
export type { Pet } from './generated/pet-store-api.generated'
|
export type { Pet } from './generated/pet-store-api.generated'
|
||||||
export * from './hooks'
|
export * from './hooks'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Где заканчивается infrastructure
|
## Где заканчивается infra
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// Хорошо: infrastructure, прозрачный GET-хук
|
// Хорошо: infra, прозрачный GET-хук
|
||||||
const { data: pets } = useGetPetList('available')
|
const { data: pets } = useGetPetList('available')
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ const { data } = useSWR(
|
|||||||
() => petStoreApi.pet.findPetsByStatus({ status }),
|
() => petStoreApi.pet.findPetsByStatus({ status }),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Плохо — несколько GET внутри infrastructure-хука
|
// Плохо — несколько GET внутри infra-хука
|
||||||
export const usePetDashboard = () => {
|
export const usePetDashboard = () => {
|
||||||
const available = useGetPetList('available')
|
const available = useGetPetList('available')
|
||||||
const sold = useGetPetList('sold')
|
const sold = useGetPetList('sold')
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
---
|
---
|
||||||
title: Создание клиента
|
title: Создание клиента
|
||||||
description: Из чего состоит REST-клиент и какие части нужно подготовить перед использованием API.
|
description: Из чего состоит REST-клиент и какие части нужно подготовить перед использованием API.
|
||||||
keywords: [rest, клиент, infrastructure, методы, openapi, get-хуки, swr]
|
keywords: [rest, клиент, infra, методы, openapi, get-хуки, swr]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Создание клиента
|
# Создание клиента
|
||||||
|
|
||||||
REST-клиент — это infrastructure-модуль, через который проект работает с внешним REST API.
|
REST-клиент — это infra-модуль, через который проект работает с внешним REST API.
|
||||||
|
|
||||||
На этом этапе нужно подготовить клиент сервиса: создать оболочку клиента, получить методы API и добавить GET-хуки для клиентских компонентов.
|
На этом этапе нужно подготовить клиент сервиса: создать оболочку клиента, получить методы API и добавить GET-хуки для клиентских компонентов.
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ GET-хуки именуются с префиксом `useGet`: `useGetPetList`,
|
|||||||
## Структура модуля
|
## Структура модуля
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/{service-name}/
|
src/infra/{service-name}/
|
||||||
├── client.ts # самописная оболочка и инстанс клиента
|
├── client.ts # самописная оболочка и инстанс клиента
|
||||||
├── generated/ или methods/ # методы API
|
├── generated/ или methods/ # методы API
|
||||||
├── hooks/ # GET-хуки REST-клиента
|
├── hooks/ # GET-хуки REST-клиента
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Ручное создание
|
title: Ручное создание
|
||||||
description: Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
|
description: Создание REST-клиента вручную, когда OpenAPI нет или он неполный.
|
||||||
keywords: [rest, ручной клиент, fetch, methods, dto, errors, infrastructure]
|
keywords: [rest, ручной клиент, fetch, methods, dto, errors, infra]
|
||||||
---
|
---
|
||||||
|
|
||||||
# Ручное создание
|
# Ручное создание
|
||||||
@@ -13,7 +13,7 @@ keywords: [rest, ручной клиент, fetch, methods, dto, errors, infrast
|
|||||||
## Что нужно создать
|
## Что нужно создать
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/
|
src/infra/
|
||||||
└── pet-project-api/
|
└── pet-project-api/
|
||||||
├── methods/
|
├── methods/
|
||||||
│ └── posts.ts
|
│ └── posts.ts
|
||||||
@@ -43,7 +43,7 @@ src/infrastructure/
|
|||||||
DTO запросов и ответов живут в `types/`. `client.ts` не содержит DTO и доменные типы.
|
DTO запросов и ответов живут в `types/`. `client.ts` не содержит DTO и доменные типы.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-project-api/types/post.ts
|
// src/infra/pet-project-api/types/post.ts
|
||||||
export type PostDto = {
|
export type PostDto = {
|
||||||
id: string
|
id: string
|
||||||
slug: string
|
slug: string
|
||||||
@@ -57,14 +57,14 @@ export type PostListQueryDto = {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-project-api/types/index.ts
|
// src/infra/pet-project-api/types/index.ts
|
||||||
export type { PostDto, PostListQueryDto } from './post'
|
export type { PostDto, PostListQueryDto } from './post'
|
||||||
```
|
```
|
||||||
|
|
||||||
Типы, которые нужны только базовому транспорту, можно держать отдельно:
|
Типы, которые нужны только базовому транспорту, можно держать отдельно:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-project-api/types/client.ts
|
// src/infra/pet-project-api/types/client.ts
|
||||||
export type QueryParams = Record<string, string | number | boolean>
|
export type QueryParams = Record<string, string | number | boolean>
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ export type QueryParams = Record<string, string | number | boolean>
|
|||||||
Ошибка API тоже относится к REST-модулю.
|
Ошибка API тоже относится к REST-модулю.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-project-api/errors/pet-project-api.error.ts
|
// src/infra/pet-project-api/errors/pet-project-api.error.ts
|
||||||
export class PetProjectApiError extends Error {
|
export class PetProjectApiError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly status: number,
|
public readonly status: number,
|
||||||
@@ -90,7 +90,7 @@ export class PetProjectApiError extends Error {
|
|||||||
`client.ts` содержит только транспортную оболочку и сборку инстанса. Прямой `fetch` живёт здесь, а не в компонентах и не в методах верхних слоёв.
|
`client.ts` содержит только транспортную оболочку и сборку инстанса. Прямой `fetch` живёт здесь, а не в компонентах и не в методах верхних слоёв.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-project-api/client.ts
|
// src/infra/pet-project-api/client.ts
|
||||||
import { PetProjectApiError } from './errors/pet-project-api.error'
|
import { PetProjectApiError } from './errors/pet-project-api.error'
|
||||||
import type { QueryParams } from './types/client'
|
import type { QueryParams } from './types/client'
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ export class PetProjectApiClient {
|
|||||||
Методы группируются по сущностям в `methods/`. Они не знают про React, SWR и UI.
|
Методы группируются по сущностям в `methods/`. Они не знают про React, SWR и UI.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-project-api/methods/posts.ts
|
// src/infra/pet-project-api/methods/posts.ts
|
||||||
import type { PetProjectApiClient } from '../client'
|
import type { PetProjectApiClient } from '../client'
|
||||||
import type { PostDto, PostListQueryDto } from '../types/post'
|
import type { PostDto, PostListQueryDto } from '../types/post'
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ export function postsMethods(client: PetProjectApiClient) {
|
|||||||
`index.ts` собирает именованный API-объект и открывает наружу только публичные части модуля.
|
`index.ts` собирает именованный API-объект и открывает наружу только публичные части модуля.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-project-api/index.ts
|
// src/infra/pet-project-api/index.ts
|
||||||
import { PetProjectApiClient } from './client'
|
import { PetProjectApiClient } from './client'
|
||||||
import { postsMethods } from './methods/posts'
|
import { postsMethods } from './methods/posts'
|
||||||
|
|
||||||
@@ -173,7 +173,7 @@ export type { PostDto, PostListQueryDto } from './types'
|
|||||||
export * from './hooks'
|
export * from './hooks'
|
||||||
```
|
```
|
||||||
|
|
||||||
Внешний код импортирует только из `infrastructure/pet-project-api`, не из внутренних файлов модуля.
|
Внешний код импортирует только из `infra/pet-project-api`, не из внутренних файлов модуля.
|
||||||
|
|
||||||
## Правила
|
## Правила
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: REST
|
title: REST
|
||||||
description: Как правильно работать с REST API в проекте.
|
description: Как правильно работать с REST API в проекте.
|
||||||
keywords: [rest, api, данные, infrastructure, клиент, swr, стратегии]
|
keywords: [rest, api, данные, infra, клиент, swr, стратегии]
|
||||||
---
|
---
|
||||||
|
|
||||||
# REST
|
# REST
|
||||||
@@ -15,7 +15,7 @@ REST в проекте проходит через два главных эта
|
|||||||
|
|
||||||
## 1. Создание клиента
|
## 1. Создание клиента
|
||||||
|
|
||||||
На этом этапе внешний API оформляется как модуль слоя `infrastructure/`.
|
На этом этапе внешний API оформляется как модуль слоя `infra/`.
|
||||||
|
|
||||||
Клиент отвечает за:
|
Клиент отвечает за:
|
||||||
|
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ Business-композиция используется, когда просто
|
|||||||
- Нужно преобразовать DTO в доменную модель.
|
- Нужно преобразовать DTO в доменную модель.
|
||||||
- Нужно спрятать бизнес-сценарий за доменным API.
|
- Нужно спрятать бизнес-сценарий за доменным API.
|
||||||
|
|
||||||
Такая логика не пишется в `infrastructure/`. REST-клиент остаётся прозрачным адаптером к API.
|
Такая логика не пишется в `infra/`. REST-клиент остаётся прозрачным адаптером к API.
|
||||||
|
|
||||||
## Пример поверх одного GET-хука
|
## Пример поверх одного GET-хука
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/business/pets/hooks/use-available-pets.hook.ts
|
// src/business/pets/hooks/use-available-pets.hook.ts
|
||||||
import { useGetPetList } from 'infrastructure/pet-store-api'
|
import { useGetPetList } from 'infra/pet-store-api'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Доменный список доступных питомцев.
|
* Доменный список доступных питомцев.
|
||||||
@@ -36,13 +36,13 @@ export const useAvailablePets = () => {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`useGetPetList` — infrastructure-хук. `hasPets` — бизнес-интерпретация, поэтому она появляется в `business/pets`.
|
`useGetPetList` — infra-хук. `hasPets` — бизнес-интерпретация, поэтому она появляется в `business/pets`.
|
||||||
|
|
||||||
## Пример композиции нескольких GET-хуков
|
## Пример композиции нескольких GET-хуков
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/business/pets/hooks/use-pets-dashboard.hook.ts
|
// src/business/pets/hooks/use-pets-dashboard.hook.ts
|
||||||
import { useGetPetList } from 'infrastructure/pet-store-api'
|
import { useGetPetList } from 'infra/pet-store-api'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Данные dashboard по питомцам.
|
* Данные dashboard по питомцам.
|
||||||
@@ -64,13 +64,13 @@ export const usePetsDashboard = () => {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Композиция нескольких запросов не добавляется в `infrastructure/pet-store-api/hooks/`, потому что это уже сценарий потребления данных.
|
Композиция нескольких запросов не добавляется в `infra/pet-store-api/hooks/`, потому что это уже сценарий потребления данных.
|
||||||
|
|
||||||
## Пример auth-состояния
|
## Пример auth-состояния
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/business/auth/hooks/use-auth-state.hook.ts
|
// src/business/auth/hooks/use-auth-state.hook.ts
|
||||||
import { useGetCurrentUser } from 'infrastructure/backend-api'
|
import { useGetCurrentUser } from 'infra/backend-api'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Состояние авторизации текущего пользователя.
|
* Состояние авторизации текущего пользователя.
|
||||||
@@ -107,7 +107,7 @@ src/business/
|
|||||||
## Что запрещено
|
## Что запрещено
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// Плохо — business-смысл внутри infrastructure-хука
|
// Плохо — business-смысл внутри infra-хука
|
||||||
export const useGetPetList = (status: PetStatus) => {
|
export const useGetPetList = (status: PetStatus) => {
|
||||||
const query = useSWR(...)
|
const query = useSWR(...)
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ keywords: [rest, client components, swr, get-хук, client state]
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useGetPetList } from 'infrastructure/pet-store-api'
|
import { useGetPetList } from 'infra/pet-store-api'
|
||||||
import type { PetStatus } from 'infrastructure/pet-store-api'
|
import type { PetStatus } from 'infra/pet-store-api'
|
||||||
|
|
||||||
const statuses: PetStatus[] = ['available', 'pending', 'sold']
|
const statuses: PetStatus[] = ['available', 'pending', 'sold']
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ export function PetTabs() {
|
|||||||
Хук добавляется в REST-модуль сервиса:
|
Хук добавляется в REST-модуль сервиса:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts
|
src/infra/pet-store-api/hooks/use-get-pet-list.hook.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
Не создавайте локальный `useSWR` в компоненте.
|
Не создавайте локальный `useSWR` в компоненте.
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ keywords: [rest, swr, fallback, initial data, client hooks, unstable_serialize,
|
|||||||
## Ключ хука
|
## Ключ хука
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/infrastructure/pet-store-api/hooks/use-get-pet-list.hook.ts
|
// src/infra/pet-store-api/hooks/use-get-pet-list.hook.ts
|
||||||
export const getPetListKey = (status: PetStatus) =>
|
export const getPetListKey = (status: PetStatus) =>
|
||||||
['pet-store-api', 'pet', 'list', status] as const
|
['pet-store-api', 'pet', 'list', status] as const
|
||||||
```
|
```
|
||||||
@@ -46,7 +46,7 @@ import { SWRConfig, unstable_serialize } from 'swr'
|
|||||||
import {
|
import {
|
||||||
getPetListKey,
|
getPetListKey,
|
||||||
petStoreApi,
|
petStoreApi,
|
||||||
} from 'infrastructure/pet-store-api'
|
} from 'infra/pet-store-api'
|
||||||
|
|
||||||
type PetsLayoutProps = {
|
type PetsLayoutProps = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@@ -78,7 +78,7 @@ export default async function PetsLayout({ children }: PetsLayoutProps) {
|
|||||||
```tsx
|
```tsx
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useGetPetList } from 'infrastructure/pet-store-api'
|
import { useGetPetList } from 'infra/pet-store-api'
|
||||||
|
|
||||||
export function PetList() {
|
export function PetList() {
|
||||||
const { data: pets, isLoading } = useGetPetList('available')
|
const { data: pets, isLoading } = useGetPetList('available')
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ keywords: [rest, promise.all, параллельные запросы, server co
|
|||||||
## Хорошо
|
## Хорошо
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { petStoreApi } from 'infrastructure/pet-store-api'
|
import { petStoreApi } from 'infra/pet-store-api'
|
||||||
import { PetsDashboardScreen } from 'screens/pets-dashboard'
|
import { PetsDashboardScreen } from 'screens/pets-dashboard'
|
||||||
|
|
||||||
export default async function PetsDashboardPage() {
|
export default async function PetsDashboardPage() {
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ keywords: [rest, promise, suspense, streaming, server components]
|
|||||||
```tsx
|
```tsx
|
||||||
// src/app/(routes)/pets/page.tsx
|
// src/app/(routes)/pets/page.tsx
|
||||||
import { Suspense } from 'react'
|
import { Suspense } from 'react'
|
||||||
import { petStoreApi } from 'infrastructure/pet-store-api'
|
import { petStoreApi } from 'infra/pet-store-api'
|
||||||
import { PetListSection } from 'widgets/pet-list-section'
|
import { PetListSection } from 'widgets/pet-list-section'
|
||||||
import { PetListSkeleton } from 'widgets/pet-list-section'
|
import { PetListSkeleton } from 'widgets/pet-list-section'
|
||||||
import type { Pet } from 'infrastructure/pet-store-api'
|
import type { Pet } from 'infra/pet-store-api'
|
||||||
|
|
||||||
export default function PetsPage() {
|
export default function PetsPage() {
|
||||||
const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
const petsPromise = petStoreApi.pet.findPetsByStatus({ status: 'available' })
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ SSR/dynamic rendering нужен, когда данные зависят от т
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// src/app/(routes)/pets/page.tsx
|
// src/app/(routes)/pets/page.tsx
|
||||||
import { petStoreApi } from 'infrastructure/pet-store-api'
|
import { petStoreApi } from 'infra/pet-store-api'
|
||||||
import { PetsScreen } from 'screens/pets'
|
import { PetsScreen } from 'screens/pets'
|
||||||
|
|
||||||
export default async function PetsPage() {
|
export default async function PetsPage() {
|
||||||
@@ -48,7 +48,7 @@ export default async function PetsPage() {
|
|||||||
```tsx
|
```tsx
|
||||||
// src/app/(routes)/pets/[id]/page.tsx
|
// src/app/(routes)/pets/[id]/page.tsx
|
||||||
import { notFound } from 'next/navigation'
|
import { notFound } from 'next/navigation'
|
||||||
import { petStoreApi } from 'infrastructure/pet-store-api'
|
import { petStoreApi } from 'infra/pet-store-api'
|
||||||
import { PetDetailScreen } from 'screens/pet-detail'
|
import { PetDetailScreen } from 'screens/pet-detail'
|
||||||
|
|
||||||
type PetPageProps = {
|
type PetPageProps = {
|
||||||
|
|||||||
Reference in New Issue
Block a user