Управление сетевыми запросами (HTTP / API Layer)
Цель
Инфраструктурный слой (HTTP / API) отвечает только за выполнение HTTP-запросов и обработку аспектов (baseURL, headers, retries, interceptors, отмена). Он не управляет состоянием данных и не знает о UI.
Управление жизненным циклом данных с сер вера описано в разделе "Управление Server State" регламента
Общие принципы
MUST
- Вся логика сетевого взаимодействия находится только в слое API:
src/shared/api/*. - В проекте есть единый HTTP-клиент (singleton / factory), который:
- выполняет HTTP-запросы
- централизованно конфигурируется (
baseURL,timeout,headers) - поддерживает middleware/interceptors
- перехватывает неуспешные ответы (4xx/5xx)
- логирует сетевые ошибки
- поддерживает отмену запросов (
AbortController)
- централизованно конфигурируется (
- выполняет HTTP-запросы
- Все запросы строго типизированы.
SHOULD
- API слой предоставляет тонкие сервисы по доменам (например
usersApi,ordersApi) без бизнес-логики. - Ошибки приводятся к единому формату (
ApiError) для удобной обработки выше по стеку.
FORBIDDEN
- Использовать HTTP-клиент напрямую вне
src/shared/api/*. - Реализовывать бизнес-логику внутри
src/shared/api/*. - Знать про UI-детали в API слое (toast, router, store, components).
Допустимые библиотеки HTTP-клиента
XiorWretchKy
В проекте должна быть выбрана одна библиотека и один единый клиент.
Контракт API и типизация
MUST
- Каждый endpoint имеет типы Request/Response (DTO).
- Допустимые источники типов:
- сгенерированные DTO из OpenAPI
- вручную описанные DTO:
/packages/apiилиsrc/shared/api/dto
FORBIDDEN
- Использовать
any,unknown,object,{}в DTO-типах. - Использовать inline-типы в сигнатурах запросов.
Стиль экспорта API методов
Функциональный стиль (именованные экспорты) разрешён и часто удобнее в больших проектах, если соблюдать правила слоя shared/api.
// ✅ Хорошо (functions + строгая типизация + DTO)
import type { UserDto, UpdateUserDto } from '@/shared/api/dto';
export function getUserById(id: string, signal?: AbortSignal) {
return http.get<UserDto>(`/users/${id}`, { signal });
}
export function updateUser(id: string, dto: UpdateUserDto, signal?: AbortSignal) {
return http.put<UserDto>(`/users/${id}`, { json: dto, signal });
}
Почему это хорошо:
- Все типы вынесены в DTO (UserDto, UpdateUserDto)
- Нет
any/unknown/object/{}в DTO - Есть поддержка отмены (AbortSignal)
- Нет бизнес-логики, только транспортный вызов
// ✅ Хорошо (functions + единый контекст запроса)
import type { UserDto } from '@/shared/api/dto';
export function searchUsers(params: { q: string; limit?: number }, signal?: AbortSignal) {
return http.get<UserDto[]>('/users/search', {
searchParams: params,
signal
});
}
// ❌ Плохо (inline types + any)
export const getUserById = (id: string) => http.get<any>(`/users/${id}`);
export const updateUser = (id: string, dto: { name: string }) =>
http.put(`/users/${id}`, { json: dto });
Почему плохо:
- any ломает API и отключает типобезопасность выше по стеку
- Inline DTO
({ name: string })разъезжается между файлами и усложняет рефакторинг - Нет AbortSignal, поэтому труднее правильно управлять отменой запросов
// ❌ Плохо (бизнес-логика внутри shared/api)
import type { UserDto } from '@/shared/api/dto';
export async function getUserAndRedirectIfMissing(id: string) {
const user = await http.get<UserDto>(`/users/${id}`);
if (!user) {
router.push('/404'); // ❌ UI/Router зависимость в shared/api
}
return user;
}
Почему плохо:
- shared/api не должен зависеть от UI/Router/Store
- Логика "что делать в UI" должна находиться выше (server state / feature / page)
Почему функции часто лучше
- Лучше для tree-shaking и бандла
- Именованные экспорты проще оптимизируются сборщиком, чем большой объект, который импортируют целиком.
- Чище API и проще импорты
import { getUserById, updateUser } from '@/shared/api/users/users.api';
// VS
import { usersApi } from '@/shared/api/users';
С функ циями легче "тащить только нужное", особенно когда файлов много. Проще тестирование и моки:
- функции проще мокать точечно (jest.mock/vi.mock по именам)
- меньше риска "случайно замокали весь объект"
Рекомендация для большого проекта
MUST
- Функции в shared/api/* строго типизированы и используют DTO.
- Нет
any/unknown/object/{}в DTO. - Нет UI/Router/Store зависимостей в shared/api.
SHOULD
- Один файл = один домен (users.api.ts, orders.api.ts, billing.api.ts)
- Экспорт только именованных функций (или дополнительно index.ts как public API домена)
- Опционально: принимать AbortSignal во всех чтениях/модификациях