← Назад к вопросам
Как выбираешь архитектуру для проекта?
1.8 Middle🔥 111 комментариев
#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как выбираешь архитектуру для проекта?
Архитектура — это не технология
Многие путают архитектуру с технологией (React vs Vue, Redux vs Zustand). На самом деле архитектура отвечает на вопрос: как организовать код, чтобы он был понятен, масштабируем и поддерживаем?
Фактор 1: Размер и масштаб проекта
Малые проекты (< 5 страниц)
Например, лэндинг или MVP:
- No state management (useState достаточно)
- Components flat structure
- API client в одном файле
app/
page.tsx
components/
Header.tsx
Hero.tsx
Footer.tsx
// lib/api.ts
export const api = {
getUsers: () => fetch('/api/users'),
getUser: (id) => fetch(`/api/users/${id}`)
};
Средние проекты (5-20 страниц)
Примеры: CRUD приложение, админ-панель:
- Простое state management (Zustand, Effector)
- Feature-based структура
- Разделение логики и представления
app/
(auth)/
login/
register/
(dashboard)/
questions/
page.tsx
professions/
page.tsx
components/
questions/
QuestionCard.tsx
QuestionList.tsx
ui/
Button.tsx
hooks/
useQuestions.ts
useAuth.ts
stores/
authStore.ts
Большие проекты (> 20 страниц, много логики)
Примеры: SaaS, платформы с интеграциями:
- Продвинутое state management (Redux Toolkit, Effector)
- DDD-подход (domain model)
- Четкие слои (presentation -> application -> domain)
Фактор 2: Сложность логики
Простая логика (CRUD, отображение)
// Достаточно useState + useEffect
function UsersList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
api.getUsers().then(setUsers).finally(() => setLoading(false));
}, []);
return loading ? <Spinner /> : <List users={users} />;
}
Средняя логика (взаимосвязанные операции)
// Используй custom hook для логики
function useUsers() {
const [users, setUsers] = useState([]);
const fetchUsers = useCallback(async () => {
const data = await api.getUsers();
setUsers(data);
}, []);
const deleteUser = useCallback(async (id) => {
await api.deleteUser(id);
setUsers(users => users.filter(u => u.id !== id));
}, []);
return { users, fetchUsers, deleteUser };
}
Сложная логика (асинхронные операции, побочные эффекты)
// Используй Effector для управления побочными эффектами
import { createEvent, createStore, createEffect, sample } from 'effector';
const fetchUsersFx = createEffect(async () => {
return await api.getUsers();
});
const deleteUserFx = createEffect(async (userId) => {
return await api.deleteUser(userId);
});
const $users = createStore([]);
const $loading = createStore(false);
const $error = createStore(null);
// Обновляем store когда данные загружены
sample({
clock: fetchUsersFx.doneData,
target: $users
});
// Оптимистичное удаление
sample({
clock: deleteUserFx,
fn: (userId, current) => current.filter(u => u.id !== userId),
source: $users,
target: $users
});
Фактор 3: Команда
Solo developer
Можно использовать гибкий подход:
- Simpler architecture
- Быстрое развитие
- Меньше боilerplate
Малая команда (2-3 разработчика)
Нужна четкость:
- Единый стиль кода
- Простые соглашения об именовании
- Четкая структура папок
Большая команда (5+ разработчиков)
Требуется строгая архитектура:
- DDD (Domain-Driven Design)
- Четкие слои (presentation -> application -> domain)
- Интеграционные тесты
- Code review процесс
Мой подход: Layered Architecture
Я использую луковичную архитектуру (Onion Architecture) для большинства проектов:
Слой 1: Presentation (компоненты)
// components/questions/QuestionCard.tsx
interface QuestionCardProps {
question: Question;
onSelect: (id: string) => void; // callback только
}
export function QuestionCard({ question, onSelect }: QuestionCardProps) {
return (
<div onClick={() => onSelect(question.id)}>
<h3>{question.title}</h3>
<p>{question.description}</p>
</div>
);
}
Слой 2: Application (hooks, сервисы)
// hooks/useQuestions.ts
import { useQuery } from '@tanstack/react-query';
import { questionService } from '@/services/questionService';
export function useQuestions(professionId: string) {
return useQuery({
queryKey: ['questions', professionId],
queryFn: () => questionService.getQuestions(professionId)
});
}
Слой 3: Infrastructure (API, БД, внешние сервисы)
// services/questionService.ts
import { api } from '@/lib/api';
import { Question } from '@/types/question';
export const questionService = {
async getQuestions(professionId: string): Promise<Question[]> {
const response = await api.get(`/questions?profession=${professionId}`);
return response.data;
},
async getQuestion(id: string): Promise<Question> {
const response = await api.get(`/questions/${id}`);
return response.data;
}
};
Слой 4: Domain (бизнес-логика, типы)
// types/question.ts
export interface Question {
id: string;
title: string;
description: string;
difficulty: 'easy' | 'medium' | 'hard';
tags: string[];
}
// domain/questionCalculator.ts
export function calculateDifficulty(question: Question): number {
const tagWeight = question.tags.length;
const baseScore = question.difficulty === 'easy' ? 1 : question.difficulty === 'medium' ? 2 : 3;
return baseScore * tagWeight;
}
Правило: Зависимости идут только вовнутрь
Presentation -> Application -> Infrastructure -> Domain
ОК: Presentation импортирует Application
ОШИБКА: Domain импортирует Application
Пример решения: Какую архитектуру выбрать?
Сценарий: Социальная платформа (200+ компонентов, 5 разработчиков)
Факторы:
- Большой проект
- Сложная логика (real-time, уведомления, рекомендации)
- Большая команда
Решение:
✓ Layered Architecture (Onion)
✓ Feature-based структура
✓ Effector для state management
✓ React Query для server state
✓ Jest + React Testing Library
✓ Strict TypeScript
✓ ESLint + Prettier
Структура для социальной платформы
src/
domain/ # Бизнес-логика
models/
User.ts
Post.ts
repositories/ # Контракты (interfaces)
IUserRepository.ts
application/ # Use cases
users/
GetUserUseCase.ts
CreateUserUseCase.ts
posts/
GetPostsUseCase.ts
CreatePostUseCase.ts
infrastructure/ # Реализация
services/
UserService.ts
PostService.ts
api/
apiClient.ts
presentation/ # UI
components/
user/
UserProfile.tsx
UserCard.tsx
post/
PostCard.tsx
PostList.tsx
hooks/
useUser.ts
usePosts.ts
pages/
users/[id]/page.tsx
posts/page.tsx
Антипаттерны, которых избегаю
// ❌ Божественный компонент
function Dashboard() {
// 500 строк кода
// логика, стили, API запросы, всё в одном месте
return <div>...</div>;
}
// ✓ Разделение забот
function Dashboard() {
const { data } = useDashboard();
return <DashboardView data={data} />;
}
// ❌ Циклические зависимости
// userService.ts импортирует postService.ts
// postService.ts импортирует userService.ts
// ✓ Прямые зависимости
// application слой импортирует domain
// presentation импортирует application
Заключение
Архитектура выбирается на основе:
- Размера проекта — MVP vs большой сервис
- Сложности логики — CRUD vs сложные вычисления
- Размера команды — solo vs большая группа
- Требований к масштабируемости — будет ли расти?
Мой принцип: начни просто, добавляй слои когда нужно. Не переусложняй маленький проект, и не упрощай большой.