Что такое «Чистая архитектура» (Clean Architecture) и каковы её основные принципы?

Ответ

Чистая архитектура (Clean Architecture) — это набор принципов проектирования программного обеспечения, предложенный Робертом Мартином. Её главная цель — создание систем с четким разделением ответственностей, которые легко поддерживать, тестировать и развивать.

Основной принцип — Правило Зависимостей (The Dependency Rule):

Зависимости в исходном коде могут быть направлены только внутрь — от внешних слоев к внутренним. Ничего во внутреннем слое не может знать о внешнем.

Это достигается за счет разделения системы на концентрические слои:

  1. Entities (Сущности): Самый внутренний слой. Содержит объекты и бизнес-правила, общие для всего предприятия. Этот слой не зависит ни от чего.
  2. Use Cases (Сценарии использования): Содержит бизнес-логику, специфичную для приложения. Оркестрирует поток данных к сущностям и от них. Зависит только от Entities.
  3. Interface Adapters (Адаптеры интерфейсов): Слой преобразования данных. Здесь находятся контроллеры, презентеры, шлюзы (gateways). Этот слой адаптирует данные из наиболее удобного для Use Cases формата в формат, удобный для внешних систем (базы данных, UI, веб).
  4. Frameworks & Drivers (Фреймворки и Драйверы): Самый внешний слой. Здесь находятся все внешние детали: фреймворки (Gin, Echo), драйверы баз данных, UI и т.д. Этот слой является "клеем", который соединяет все вместе.

Пример на Go, иллюстрирующий правило зависимостей:

// 1. Entities (внутренний слой, ни от чего не зависит)
type User struct {
    ID   int
    Name string
}

// 2. Use Cases (определяет интерфейс, зависит от Entities)
type UserUseCase struct {
    repo UserRepository
}

// Интерфейс репозитория определяется здесь, во внутреннем слое!
type UserRepository interface {
    GetByID(id int) (*User, error)
}

func (uc *UserUseCase) GetUser(id int) (*User, error) {
    return uc.repo.GetByID(id)
}

// 3-4. Interface Adapters / Frameworks (внешний слой, реализует интерфейс)
// Эта структура зависит от интерфейса UserRepository, а не наоборот.
type PostgresUserRepository struct {
    db *sql.DB
}

// Реализация интерфейса из слоя Use Cases
func (r *PostgresUserRepository) GetByID(id int) (*User, error) {
    // ... логика получения пользователя из PostgreSQL
    return nil, nil
}

Ключевые преимущества:

  • Независимость от фреймворков и БД: Бизнес-логику можно легко перенести на другой фреймворк или СУБД.
  • Высокая тестируемость: Внутренние слои можно тестировать в изоляции, без поднятия веб-сервера или подключения к реальной БД.

Ответ 18+ 🔞

А, чистая архитектура! Ну это ж, блядь, как священный грааль для программиста, который устал от спагетти-кода, где всё перемешано хуже, чем в студенческом холодильнике после затяжных выходных.

Слушай, суть-то проще, чем кажется. Представь себе матрёшку, только не простую, а ебаную, с правилами. Самая маленькая и ценная — в центре. Это Сущности (Entities). Твои главные бизнес-понятия: Пользователь, Заказ, Деньги. Они нихуя не знают про базы данных, HTTP или твой любимый фреймворк. Они святые и неприкасаемые.

Дальше идёт слой Сценариев использования (Use Cases). Это уже конкретные действия: «Создать заказ», «Начислить бонусы». Они знают про сущности и крутят ими как хотят, но тоже, блядь, в вакууме! Им похуй, откуда данные пришли и куда уйдут. Они просто говорят: «Дай-ка мне репозиторий для пользователя» — через интерфейс, естественно.

А вот теперь, внимание, главное правило, его хуй забудешь: зависимости идут ТОЛЬКО внутрь! Внутренние слои НЕ ЗНАЮТ НИ ХУЯ про внешние. Это как будто твоя бизнес-логика — это царь, а всё остальное — холопы, которые ей прислуживают, но царь даже имён холопов не помнит. Он просто приказывает: «Подай мне данные!», а уж как холоп их достал — из базы, из файла, из своей жопы — царю похуй.

Самый внешний слой — это уже Фреймворки и Драйверы (Frameworks & Drivers). Веб-сервер, база данных, кэш, UI. Это обслуга, которая реализует те интерфейсы, которые нарисовали внутренние слои. Вот смотри, как это выглядит в коде, чтобы не быть пустословом:

// 1. Entities — царь в своём дворце. Ни от кого не зависит.
type User struct {
    ID   int
    Name string
}

// 2. Use Cases — приближённые царя. Знают только царя и свои интерфейсы для приказов.
type UserUseCase struct {
    repo UserRepository // Говорим: "Нужен кто-то, кто умеет давать пользователей"
}

// Интерфейс объявляем ЗДЕСЬ, во внутреннем мире! Царь диктует условия.
type UserRepository interface {
    GetByID(id int) (*User, error)
}

func (uc *UserUseCase) GetUser(id int) (*User, error) {
    // Царское повеление: "Принеси пользователя!"
    return uc.repo.GetByID(id)
}

// 3-4. Interface Adapters / Frameworks — холопы снаружи. Они ЗАВИСЯТ от интерфейса царя.
type PostgresUserRepository struct {
    db *sql.DB
}

// Холоп покорно реализует царский указ (интерфейс).
func (r *PostgresUserRepository) GetByID(id int) (*User, error) {
    // ... лезет в свою постгресную берлогу, достаёт данные, кланяется...
    row := r.db.QueryRow("SELECT id, name FROM users WHERE id = $1", id)
    var u User
    err := row.Scan(&u.ID, &u.Name)
    return &u, err
}

Видишь магию? Слой UseCase зависит от абстракции (UserRepository), а конкретная реализация (PostgresUserRepository) зависит от этой абстракции. Зависимость инвертирована, правило соблюдено. Царь (бизнес-логика) чист и незапятнан.

А нахуя это всё? Да затем, ёпта, чтобы завтра, когда начальник придёт и скажет «Меняем Постгрес на Монгу, а Джин на Эхо», ты не обосрался, а просто поменял холопов на других. Бизнес-логика, тесты к ней — они остались нетронутыми, потому что они нихуя не знали про эти внешние штуки. Это и есть независимость от фреймворков и БД.

И тестировать — одно удовольствие. Хочешь протестировать UseCase? Подсунь ему заглушку (mock), которая реализует UserRepository. Никаких поднятий баз, никаких веб-серверов. Чистая, быстрая, изолированная проверка логики. Красота, а не жизнь.

Короче, идея в том, чтобы система не превращалась в большой ком грязи, где всё связано со всем. А чтобы была как луковица — слоёная и чтобы от неё не плакали те, кто её потом поддерживает.