← Назад к вопросам

Как выбираешь архитектуру для проекта?

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

Заключение

Архитектура выбирается на основе:

  1. Размера проекта — MVP vs большой сервис
  2. Сложности логики — CRUD vs сложные вычисления
  3. Размера команды — solo vs большая группа
  4. Требований к масштабируемости — будет ли расти?

Мой принцип: начни просто, добавляй слои когда нужно. Не переусложняй маленький проект, и не упрощай большой.