Что такое «Чистая архитектура» (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
}

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

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