Перейти к основному содержимому

Слои и архитектура

Описание

Слой – это уровень ответственности в архитектуре проекта, который определяет:

  • Какие задачи решает код
  • От кого может зависеть
  • Какие зависимости запрещены

Архитектура основана на принципах low coupling (слабая связность) и high cohesion (сильная связность модулей).

MUST

Иерархия слоев

Следовать строгой иерархии слоев FSD

app/ → pages/ → widgets/ → features/ → entities/ → shared/

Импорт возможен только вниз по иерархии

  • features/ может импортировать из entities/ и shared/
  • entities/ может импортировать только из shared/
  • shared/ не импортирует из других слоев

Каждый модуль имеет Public API (файл index.ts)

  • Экспортирует только публичные элементы
  • Скрывает детали реализации
  • Не содержит логики

Следовать принципам LIFT внутри модулей

  • L (Locate) – связанные файлы рядом
  • I (Identify) – понятные имена файлов
  • F (Flat) – плоская структура (max 3 уровня)
  • T-DRY – избегать преждевременных абстракций

FORBIDDEN

Импорты "вверх" по иерархии

  • shared/entities/
  • entities/features/
  • features/widgets/
  • pages/app/

Импорты между модулями одного слоя

  • features/authfeatures/article
  • entities/userentities/order

Циклические зависимости

  • Любая циклическая зависимость = архитектурная ошибка

Группировка по типам файлов внутри модуля

  • hooks/, types/, constants/ как отдельные папки
  • ✅ Использовать сегменты: model/, ui/, api/, lib/

Анонимные названия файлов

  • styles.ts, index.tsx, types.ts
  • index.ts только как Public API

Вложенность более 3 уровней внутри модуля

  • Это сигнал о необходимости рефакторинга

SHOULD

Использовать WET подход (Write Everything Twice)

  • Код повторился 1-2 раза → не выносить в shared/
  • Выносить в shared/ после 3+ повторений

Применять Dependency Injection для общего кода

  • Если двум модулям нужен общий код → поднять выше (Lifting Up)
  • Либо передать зависимость извне

Диаграмма принятия решений

Процесс принятия решений при размещении кода:

Направление зависимостей


Запрещенные зависимости

ЗапрещеноПочему
shared/entities/Инфраструктура не знает домен
entities/features/Домен не знает сценариев
features/features/Независимость сценариев
widgets/pages/Композиционный слой не знает точки входа
pages/app/Точки входа не управляют окружением
Любое "вверх"Нарушение иерархии ответственности

Типы ответственности и слои

1. Инициализация и конфигурация → app/

Ответственность:

  • Запуск приложения
  • Подключение провайдеров (i18n, theme, analytics)
  • Глобальные настройки и окружение
  • Маршрутизация
  • Error Boundary

MUST:

  • Инициализация глобальных провайдеров
  • Настройка роутинга

FORBIDDEN:

  • Бизнес-логика
  • UI-компоненты страниц
  • Прямые запросы к API

2. Точка входа → pages/

Ответственность:

  • Код, связанный с маршрутом или экраном
  • Композиция features/widgets/entities
  • Подготовка данных на уровне страницы (SSR/SSG)

MUST:

  • Композиция widgets и features
  • Минимальная логика (только композиция)

FORBIDDEN:

  • Бизнес-логика
  • Прямые запросы к API
  • Импорты из других pages

3. Композиция без бизнес-логики → widgets/

Ответственность:

  • Объединение независимых модулей
  • Формирование UI-блоков
  • Визуальная структура без принятия решений

MUST:

  • Композиция features и entities
  • UI-логика без бизнес-решений

FORBIDDEN:

  • Бизнес-логика
  • Импорты из pages или других widgets
  • Прямые мутации данных

4. Пользовательский сценарий → features/

Ответственность:

  • Последовательность действий пользователя
  • Управление состоянием сценария
  • Обработка ошибок и переходов между состояниями

MUST:

  • Управление сценарием от начала до конца
  • Обработка состояния и ошибок
  • Изолированные действия пользователя

FORBIDDEN:

  • CRUD операции (размещать в shared/api/endpoints/)
  • Доменные правила (вынести в entities)
  • Импорты из других features

5. Предметная область (домен) → entities/

Ответственность:

  • Бизнес-сущности
  • Инварианты и правила
  • Доменные ограничения

MUST:

  • Бизнес-правила и инварианты
  • Типы и интерфейсы сущностей
  • Переиспользуемая доменная логика

FORBIDDEN:

  • CRUD операции (размещать в shared/api/endpoints/)
  • Пользовательские сценарии
  • Импорты из features
  • Зависимости между entities

6. Техническая инфраструктура → shared/

Ответственность:

  • Переиспользуемые утилиты
  • Generic-хуки
  • Клиенты и адаптеры
  • Код, не зависящий от предметной области

MUST:

  • Технические утилиты
  • Generic UI-компоненты
  • Инфраструктурные клиенты
  • CRUD операции (в shared/api/endpoints/)

FORBIDDEN:

  • Бизнес-логика
  • Доменные правила
  • Зависимости от верхних слоев

Принципы LIFT

L – Locate (Находимость)

Принцип: Связанные файлы рядом с местом использования

Правило: Группировка по функциональности, а не по типам файлов

❌ Плохо - группировка по типам файлов
features/article-create/
├── hooks/
│ └── use-article-form.ts
├── types/
│ └── article-form.types.ts
├── components/
│ └── article-form.tsx
└── styles/
└── article-form.module.css

✅ Хорошо - файлы рядом с использованием
features/article-create/
├── model/
│ ├── use-article-form.ts
│ └── article-form.types.ts
└── ui/
├── article-form.tsx
└── article-form.module.css

I – Identify (Понятность)

Принцип: Имя файла отражает его суть

Правило: Избегать анонимных файлов

❌ FORBIDDEN: Неясные имена
features/article-create/ui/
├── index.tsx # Что это?
├── form.tsx # Какая форма?
└── styles.module.css # Стили чего?

✅ MUST: Говорящие имена
features/article-create/ui/
├── article-form.tsx
├── article-form.module.css
└── form-field.tsx

F – Flat (Плоскость)

Принцип: Максимально плоская структура

Правило: Вложенность > 3 уровней = architectural smell

❌ FORBIDDEN: Избыточная вложенность
features/article-create/
└── src/
└── components/
└── forms/
└── article/
└── create/
└── article-form.tsx # 6 уровней!

✅ MUST: Плоская структура
features/article-create/
├── model/
│ └── use-article-form.ts
└── ui/
└── article-form.tsx # 3 уровня

T-DRY (Try to stay DRY)

Принцип: Баланс между повторением и абстракцией

Правило: WET подход (Write Everything Twice)

// ✅ SHOULD: Дублирование 1-2 раза допустимо
// entities/article/lib/slugify.ts
export function slugify(text: string): string {
return text.toLowerCase().replace(/\s+/g, '-');
}

// entities/product/lib/slugify.ts
export function slugify(text: string): string {
return text.toLowerCase().replace(/\s+/g, '-');
}

// ❌ НЕ выносить преждевременно в shared/lib/slugify.ts

// ✅ MUST: Вынос после 3+ повторений
// shared/lib/validators/email.ts
export function isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// Используется в: features/auth, features/profile, features/settings

Public API модулей

Правила Public API

MUST

  • Каждый модуль имеет явный контракт через index.ts
  • Экспортировать только публичные элементы

FORBIDDEN

  • Экспортировать детали реализации
  • Содержать логику в index.ts

Примеры

// ✅ entities/article/index.ts
export { ArticleCard } from './ui/article-card';
export { useArticle, useArticles } from './model/use-article';
export type { Article } from './model/article.types';
export { slugify } from './lib/slugify';

// ❌ НЕ экспортируем internal helpers
// export { formatDate } from './lib/format-date'; // приватная утилита

Ограничения barrel files

Возможные проблемы:

  • Циклические зависимости при реэкспортах
  • Wildcard-экспорты (export *) ухудшают tree-shaking
  • Снижение производительности в крупных проектах

Рекомендации:

  • Избегать "глобальных" barrel-файлов
  • Предпочитать явные экспорты вместо export *
  • Для lazy-загрузки использовать отдельные entry points

Структура слоев

Интеграция с Next.js

Вариант 1: Pages Router

.
├── pages/ # Next.js роутинг (корень проекта)
│ ├── _app.tsx # Root App с провайдерами
│ ├── index.tsx # Роут "/" → экспортирует из src/pages/home
│ ├── article/
│ │ └── [slug].tsx # Роут "/article/:slug" → экспортирует из src/pages/article-details
│ └── api/ # API routes
└── src/ # FSD слои
├── app/ # Layer 1: Инициализация (FSD)
├── pages/ # Layer 2: Компоненты страниц (FSD)
├── widgets/ # Layer 3: Композиция
├── features/ # Layer 4: Сценарии
├── entities/ # Layer 5: Домен
└── shared/ # Layer 6: Инфраструктура

Пример Pages Router:

// src/pages/home/ui/home-page.tsx
import { ArticleFeed } from '@/widgets/article-feed';

export function HomePage() {
return <ArticleFeed />;
}

// src/pages/home/index.ts
export { HomePage } from './ui/home-page';

// pages/index.tsx (Next.js роутинг, корень проекта)
export { HomePage as default } from '@/pages/home';

// pages/_app.tsx
import { Providers } from '@/app/providers';
import '@/app/styles/globals.css';

export default function App({ Component, pageProps }) {
return (
<Providers>
<Component {...pageProps} />
</Providers>
);
}

Вариант 2: App Router

.
├── app/ # Next.js роутинг (корень проекта)
│ ├── layout.tsx # Root layout с провайдерами
│ ├── page.tsx # Роут "/" → экспортирует из src/pages/home
│ └── article/
│ └── [slug]/
│ └── page.tsx # Роут "/article/:slug" → экспортирует из src/pages/article-details
└── src/ # FSD слои
├── app/ # Layer 1: Инициализация (FSD)
├── pages/ # Layer 2: Компоненты страниц (FSD)
├── widgets/ # Layer 3: Композиция
├── features/ # Layer 4: Сценарии
├── entities/ # Layer 5: Домен
└── shared/ # Layer 6: Инфраструктура

Пример App Router:

// src/pages/home/ui/home-page.tsx
import { ArticleFeed } from '@/widgets/article-feed';

export function HomePage() {
return <ArticleFeed />;
}

// src/pages/home/index.ts
export { HomePage } from './ui/home-page';

// app/page.tsx (Next.js роутинг, корень проекта)
export { HomePage as default } from '@/pages/home';

// app/layout.tsx
import { Providers } from '@/app/providers';
import '@/app/styles/globals.css';

export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}

Принцип: Next.js роутинг (корень проекта), FSD слои в src/ для организации кода.


Layer 1: src/app/ - Инициализация приложения

Назначение: Глобальные провайдеры, инициализация сервисов, конфигурация.

Структура:

src/app/
├── providers.tsx # Глобальные провайдеры
└── styles/
└── globals.css # Глобальные стили

Пример:

// src/app/providers.tsx
'use client';

import { ReactNode } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

export function Providers({ children }: { children: ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}

// app/layout.tsx (Next.js роутинг, корень проекта)
import { Providers } from '@/app/providers';
import '@/app/styles/globals.css';

export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<Providers>
{children}
</Providers>
</body>
</html>
);
}

Содержит:

  • Bootstrap приложения
  • Глобальные провайдеры (i18n, theme, analytics)
  • Маршрутизацию
  • Error Boundary

MUST:

  • Инициализация провайдеров
  • Глобальные стили

FORBIDDEN:

  • Бизнес-логика
  • UI-компоненты страниц
  • Инфраструктурный код

Layer 2: src/pages/ - Компоненты страниц

Назначение: Композиция модулей под конкретный роут. Компоненты страниц без привязки к роутингу.

Структура:

src/pages/
├── home/
│ ├── ui/
│ │ └── home-page.tsx
│ └── index.ts
├── article-details/
│ ├── ui/
│ │ └── article-details-page.tsx
│ └── index.ts
└── profile/
├── ui/
│ └── profile-page.tsx
└── index.ts

Интеграция с Next.js роутингом:

app/                                    # Next.js роутинг (корень проекта)
├── page.tsx # → экспортирует HomePage из src/pages/home
├── article/
│ └── [slug]/
│ └── page.tsx # → экспортирует ArticleDetailsPage из src/pages/article-details
└── profile/
└── page.tsx # → экспортирует ProfilePage из src/pages/profile

Пример:

// src/pages/article-details/ui/article-details-page.tsx
import { ArticleDetail } from '@/widgets/article-detail';
import { ArticleComments } from '@/widgets/article-comments';

interface ArticleDetailsPageProps {
slug: string;
}

export function ArticleDetailsPage({ slug }: ArticleDetailsPageProps) {
return (
<div>
<ArticleDetail slug={slug} />
<ArticleComments slug={slug} />
</div>
);
}

// src/pages/article-details/index.ts
export { ArticleDetailsPage } from './ui/article-details-page';

// app/article/[slug]/page.tsx (Next.js роутинг)
import { ArticleDetailsPage } from '@/pages/article-details';

interface PageProps {
params: { slug: string };
}

export default function Page({ params }: PageProps) {
return <ArticleDetailsPage slug={params.slug} />;
}

// src/pages/home/ui/home-page.tsx
import { ArticleFeed } from '@/widgets/article-feed';
import { PopularTags } from '@/widgets/popular-tags';

export function HomePage() {
return (
<main>
<ArticleFeed />
<PopularTags />
</main>
);
}

// src/pages/home/index.ts
export { HomePage } from './ui/home-page';

// app/page.tsx (Next.js роутинг)
export { HomePage as default } from '@/pages/home';

Содержит:

  • Компоненты страниц без привязки к роутингу
  • Композиция 'widgets/', 'features/', 'entities/'
  • Подготовка данных (SSR/SSG)

MUST:

  • Композиция модулей
  • Минимальная логика

FORBIDDEN:

  • Бизнес-логика
  • Прямые API-вызовы
  • Импорты из других pages

Layer 3: widgets/ - Композиционные блоки

Назначение: Самодостаточные UI-блоки, объединяющие features и entities без бизнес-логики.

Структура:

widgets/
├── article-detail/
│ ├── ui/
│ │ ├── article-detail.tsx
│ │ └── article-detail.module.css
│ └── index.ts
├── article-comments/
│ ├── ui/
│ │ ├── article-comments.tsx
│ │ └── comment-list.tsx
│ └── index.ts
└── feed/
├── ui/
│ ├── feed.tsx
│ └── feed-tabs.tsx
└── index.ts

Пример:

// widgets/article-detail/ui/article-detail.tsx
import { ArticleCard, useArticle } from '@/entities/article';
import { FavoriteButton } from '@/features/article-favorite';
import { FollowButton } from '@/features/profile-follow';
import { Spinner } from '@/shared/ui/spinner';
import { ErrorMessage } from '@/shared/ui/error-message';

interface ArticleDetailProps {
slug: string;
}

export function ArticleDetail({ slug }: ArticleDetailProps) {
const { data: article, isLoading, error } = useArticle(slug);
const t = useTranslations('article');

if (isLoading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
if (!article) return <ErrorMessage message={t('notFound')} />;

return (
<div>
<ArticleCard article={article} />
<div>
<FavoriteButton record={article} />
<FollowButton profile={article.author} />
</div>
</div>
);
}

Содержит:

  • Композиция features + entities
  • UI-структура
  • Повторно используемые блоки

MUST:

  • Композиция модулей
  • UI-логика без решений

FORBIDDEN:

  • Бизнес-логика
  • Импорты из 'pages/', 'widgets/'
  • Прямые мутации

Layer 4: features/ - Пользовательские сценарии

Назначение: Законченные пользовательские действия и сценарии.

Структура:

features/
├── article-create/
│ ├── model/
│ │ ├── use-article-form.ts
│ │ └── validation.ts
│ ├── ui/
│ │ ├── article-form.tsx
│ │ └── article-form.module.css
│ └── index.ts
├── article-favorite/
│ ├── model/
│ │ └── use-toggle-favorite.ts
│ ├── ui/
│ │ └── favorite-button.tsx
│ └── index.ts
└── comment-add/
├── model/
│ └── use-add-comment.ts
├── ui/
│ └── comment-form.tsx
└── index.ts

Пример:

// features/article-create/model/use-article-form.ts
'use client';

import { useMutation } from '@tanstack/react-query';
import { useRouter } from 'next/navigation';
import { createArticle } from '@/shared/api/endpoints/article.api';
import { articleSchema } from './validation';

export function useArticleForm() {
const router = useRouter();

return useMutation({
mutationFn: createArticle,
onSuccess: (article) => {
router.push(`/article/${article.slug}`);
}
});
}

// features/article-create/ui/article-form.tsx
import { useTranslations } from 'next-intl';
import { useArticleForm } from '../model/use-article-form';
import { ErrorMessage } from '@/shared/ui/error-message';

export function ArticleForm() {
const { mutate, isLoading, error } = useArticleForm();
const t = useTranslations('article');

const handleSubmit = (data: ArticleFormData) => {
mutate(data);
};

if (error) {
return <ErrorMessage error={error} />;
}

return (
<form onSubmit={handleSubmit}>
{/* Форма создания статьи */}
<button type="submit" disabled={isLoading}>
{isLoading ? t('creating') : t('createArticle')}
</button>
</form>
);
}

Содержит:

  • UI-компоненты сценария
  • Состояние сценария
  • Хуки сценария
  • Оркестрация взаимодействия с entities

MUST:

  • Управление сценарием
  • Обработка ошибок
  • Изоляция действий

FORBIDDEN:

  • CRUD в features (использовать shared/api/endpoints/)
  • Доменные правила (вынести в entities)
  • Импорты из других features

Layer 5: entities/ - Доменная логика

Назначение: Бизнес-сущности, инварианты и правила предметной области.

Структура:

entities/
├── article/
│ ├── model/
│ │ ├── article.types.ts
│ │ └── use-article.ts
│ ├── lib/
│ │ ├── format-date.ts
│ │ └── slugify.ts
│ ├── ui/
│ │ ├── article-card.tsx
│ │ └── article-meta.tsx
│ └── index.ts
├── user/
│ ├── model/
│ │ ├── user.types.ts
│ │ └── use-current-user.ts
│ ├── ui/
│ │ ├── user-avatar.tsx
│ │ └── user-link.tsx
│ └── index.ts
└── comment/
├── model/
│ └── comment.types.ts
├── ui/
│ └── comment-card.tsx
└── index.ts

Пример:

// entities/article/model/article.types.ts
export interface Article {
slug: string;
title: string;
description: string;
body: string;
tagList: string[];
createdAt: string;
updatedAt: string;
favorited: boolean;
favoritesCount: number;
author: Profile;
}

// entities/article/lib/slugify.ts
export function slugify(title: string): string {
return title
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}

// entities/article/ui/article-card.tsx
import { Article } from '../model/article.types';
import { formatArticleDate } from '../lib/format-date';

export function ArticleCard({ article }: Props) {
return (
<article>
<h2>{article.title}</h2>
<p>{article.description}</p>
<time>{formatArticleDate(article.createdAt)}</time>
</article>
);
}

// entities/article/index.ts
export { ArticleCard } from './ui/article-card';
export { useArticle, useArticles } from './model/use-article';
export type { Article } from './model/article.types';
export { slugify } from './lib/slugify';

Содержит:

  • Бизнес-сущности
  • Доменные типы, инварианты, правила
  • Entity-ориентированный UI

MUST:

  • Бизнес-правила
  • Типы сущностей
  • Доменная логика

FORBIDDEN:

  • CRUD без бизнес-правил (использовать shared/api/endpoints/)
  • Сценарии
  • Импорты из features
  • Прямые копии DTO

Layer 6: shared/ - Техническая инфраструктура

Назначение: Переиспользуемый технический код, не зависящий от бизнес-логики.

Структура:

shared/
├── api/
│ ├── client.ts
│ ├── interceptors.ts
│ ├── types.ts
│ ├── dto/
│ │ ├── article.dto.ts # DTO для articles
│ │ ├── user.dto.ts # DTO для users
│ │ └── comment.dto.ts # DTO для comments
│ └── endpoints/
│ ├── article.api.ts # CRUD для articles
│ ├── user.api.ts # CRUD для users
│ └── comment.api.ts # CRUD для comments
├── ui/
│ ├── button/
│ │ ├── button.tsx
│ │ └── button.module.css
│ ├── input/
│ │ └── input.tsx
│ └── modal/
│ └── modal.tsx
├── lib/
│ ├── validation/
│ │ └── email.ts
│ └── hooks/
│ ├── use-toggle.ts
│ └── use-local-storage.ts
└── config/
└── api.config.ts

Пример:

// shared/api/client.ts
import axios from 'axios';

export const apiClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
headers: {
'Content-Type': 'application/json'
}
});

// shared/api/dto/article.dto.ts
export interface ArticleDTO {
slug: string;
title: string;
description: string;
body: string;
tagList: string[];
createdAt: string;
updatedAt: string;
favorited: boolean;
favoritesCount: number;
author: ProfileDTO;
}

export interface ProfileDTO {
username: string;
bio: string;
image: string;
following: boolean;
}

// shared/api/endpoints/article.api.ts
import { apiClient } from '../client';
import type { ArticleDTO } from '../dto/article.dto';

export async function getArticle(slug: string): Promise<ArticleDTO> {
const { data } = await apiClient.get<{ article: ArticleDTO }>(`/articles/${slug}`);
return data.article;
}

export async function getArticles(): Promise<ArticleDTO[]> {
const { data } = await apiClient.get<{ articles: ArticleDTO[] }>('/articles');
return data.articles;
}

export async function favoriteArticle(slug: string): Promise<ArticleDTO> {
const { data } = await apiClient.post<{ article: ArticleDTO }>(`/articles/${slug}/favorite`);
return data.article;
}

export async function unfavoriteArticle(slug: string): Promise<ArticleDTO> {
const { data } = await apiClient.delete<{ article: ArticleDTO }>(`/articles/${slug}/favorite`);
return data.article;
}

// shared/lib/hooks/use-toggle.ts
export function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = () => setValue(prev => !prev);
return [value, toggle] as const;
}

// shared/ui/button/button.tsx
export function Button({ children, variant, ...props }: Props) {
return (
<button className={styles[variant]} {...props}>
{children}
</button>
);
}

Содержит:

  • UI-kit и атомарные компоненты
  • Утилиты, generic-хуки
  • API-клиенты и CRUD операции
  • Конфигурация

MUST:

  • Технические утилиты
  • Generic компоненты
  • CRUD операции в endpoints/

FORBIDDEN:

  • Бизнес-логика
  • Доменное состояние
  • Зависимости от других слоев

DTO → Entity: Правило зависимостей

Правило:

  • DTO в shared/ описывают контракт с внешним API (технический слой)
  • Entities в entities/ содержат доменную логику и могут расширять DTO
  • DTO не может импортировать из entities

Пример

// ✅ shared/api/dto/article.dto.ts
export interface ArticleDTO {
slug: string;
title: string;
favorited: boolean;
favoritesCount: number;
}

// ✅ entities/article/model/article.types.ts
import type { ArticleDTO } from '@/shared/api/dto/article.dto';

// Entity может импортировать и расширять DTO
export interface Article extends ArticleDTO {
// Добавляем доменные методы/свойства
isPublished: boolean;
canEdit: (userId: string) => boolean;
}

// ❌ shared/api/dto/article.dto.ts
import type { Article } from '@/entities/article'; // ЗАПРЕЩЕНО!

Контрольный чеклист

Перед добавлением кода, проверьте:

  • Код размещен в правильном слое согласно диаграмме?
  • Направление зависимостей корректно (только вниз)?
  • Нет импортов между модулями одного слоя?
  • Нет циклических зависимостей?
  • Модуль имеет Public API (index.ts)?
  • Соблюдаются принципы LIFT?
  • Вложенность не превышает 3 уровня?
  • CRUD операции в shared/api/endpoints/?
  • Нет группировки по типам файлов?