Что такое Use Case (сценарий использования) в контексте чистой архитектуры в Go?

Ответ

Use Case (или Interactor) — это ключевой компонент слоя бизнес-логики в «чистой архитектуре» (Clean Architecture). Он инкапсулирует и реализует один конкретный сценарий использования системы, например, «Регистрация пользователя», «Создание заказа» или «Расчет стоимости доставки».

Основная цель Use Case — отделить бизнес-правила от деталей инфраструктуры (базы данных, веб-фреймворки, внешние API).

Принципы реализации в Go:

  1. Зависимость от абстракций: Use Case зависит не от конкретных реализаций (например, репозитория для PostgreSQL), а от интерфейсов (портов). Это позволяет легко подменять реализации.
  2. Инкапсуляция логики: Вся логика, связанная со сценарием (валидация, вызов нужных репозиториев, координация доменных моделей), находится внутри Use Case.
  3. Простота тестирования: Благодаря зависимостям от интерфейсов, Use Case легко тестировать в изоляции, передавая в него mock-объекты (заглушки).

Пример: Use Case для регистрации пользователя

package usecase

import (
    "errors"
    "strings"
)

// User - простая доменная модель
type User struct {
    Name  string
    Email string
}

// UserRepository - это порт (интерфейс), который должен быть реализован
// слоем инфраструктуры (например, postgres_repo.go).   ype UserRepository interface {
    Save(user User) error
    FindByEmail(email string) (*User, error)
}

// RegisterUserUseCase - структура, реализующая сценарий.   ype RegisterUserUseCase struct {
    repo UserRepository // Зависимость от интерфейса, а не от реализации
}

// NewRegisterUserUseCase - конструктор для создания Use Case.
func NewRegisterUserUseCase(repo UserRepository) *RegisterUserUseCase {
    return &RegisterUserUseCase{repo: repo}
}

// Execute - основной метод, выполняющий бизнес-логику.
func (uc *RegisterUserUseCase) Execute(name, email string) error {
    // 1. Валидация входных данных
    if !strings.Contains(email, "@") {
        return errors.New("некорректный email")
    }

    // 2. Проверка бизнес-правила (пользователь не должен существовать)
    if _, err := uc.repo.FindByEmail(email); err == nil {
        return errors.New("пользователь с таким email уже существует")
    }

    // 3. Выполнение основной операции
    user := User{Name: name, Email: email}
    return uc.repo.Save(user)
}

Преимущества такого подхода:

  • Чистая архитектура: Четкое разделение ответственности между слоями.
  • Гибкость: Можно легко заменить базу данных (PostgreSQL на MongoDB), просто написав новый адаптер для интерфейса UserRepository.
  • Тестируемость: Логику можно проверить юнит-тестами без поднятия БД или веб-сервера.

Ответ 18+ 🔞

Ну слушай, вот есть у нас эта ваша «чистая архитектура», а там, в самом её сердце, сидит себе такой важный чувак — Use Case, или, по-нашему, Сценарий Использования. Представь, что это такой отдельный, блядь, начальник цеха, который знает ровно одну операцию наизусть. «Зарегистрировать пользователя», «Создать заказ», «Посчитать, сколько с тебя содрать за доставку» — вот это всё его работа. И делает он её так, что ему похуй, что вокруг творится: база данных горит, фреймворк обновился или апишка внешняя сдохла. Его дело — правила бизнеса выполнить, а не с инфраструктурой, сука, целоваться.

Зачем он, этот Use Case, вообще нужен? А затем, чтобы бизнес-логика не превратилась в такое месиво, где в одном файле и SQL-запросы, и проверка пароля, и отправка письма «С днём рождения, пидор!». Он эту логику инкапсулирует, как в банку, и говорит: «Всё, что нужно для этого сценария — лежит тут. Не лезть!».

Как его, падлу, в Go делать правильно? Тут главное — не накосячить с зависимостями.

  1. Хуй на конкретику, даёшь абстракции! Use Case не должен знать, что у тебя база — PostgreSQL, MongoDB или, прости господи, Excel-файл на флешке. Он знает только интерфейс (порт), типа UserRepository. А кто его реализует — его, блядь, проблемы. Это как нанять грузчика: тебе важно, чтобы он коробки таскал, а он там хоть на спине, хоть на жопе — тебе похуй.
  2. Вся логика — внутри. Валидация, проверки, вызовы к репозиториям, манипуляции с доменными моделями — всё это кипит в одном котле под названием Execute.
  3. Тестируется — заебись. Раз он зависит от интерфейсов, то ты можешь накормить его любыми муляжами (mock-объектами) и проверить логику в полной изоляции. Без поднятия целого сервера, блядь. Удобно же!

Смотри, как это выглядит в коде. Пример: «Зарегистрировать пользователя»

package usecase

import (
    "errors"
    "strings"
)

// User - наша доменная сущность, простая как три копейки.
type User struct {
    Name  string
    Email string
}

// UserRepository - это ПОРТ (интерфейс). Контракт, который мы диктуем внешнему миру.
// Кто хочет с нами работать — пусть реализует. Нам похуй как.
type UserRepository interface {
    Save(user User) error
    FindByEmail(email string) (*User, error)
}

// RegisterUserUseCase - вот он, наш главный по регистрации.
type RegisterUserUseCase struct {
    repo UserRepository // Храним зависимость от интерфейса. Не от конкретной реализации, блядь!
}

// NewRegisterUserUseCase - конструктор. Даём ему репозиторий, он делает своё дело.
func NewRegisterUserUseCase(repo UserRepository) *RegisterUserUseCase {
    return &RegisterUserUseCase{repo: repo}
}

// Execute - метод, где вся магия и происходит. Или не происходит, если данные кривые.
func (uc *RegisterUserUseCase) Execute(name, email string) error {
    // 1. Валидация. Элементарная, но своя.
    if !strings.Contains(email, "@") {
        return errors.New("некорректный email")
    }

    // 2. Проверка бизнес-правила. Такого пользователя ещё не должно быть.
    if _, err := uc.repo.FindByEmail(email); err == nil {
        return errors.New("пользователь с таким email уже существует")
    }

    // 3. Если всё чики-пики — создаём доменный объект и сохраняем.
    user := User{Name: name, Email: email}
    return uc.repo.Save(user)
}

И что мы, блядь, выигрываем?

  • Архитектура чище некуда. Слои не перемешаны, как салат оливье после пятого тоста. Каждый знает своё место.
  • Гибкость — овердохуища. Захотел сменить базу данных? Просто напиши новую реализацию для UserRepository, которая будет с новой БД общаться. Use Case даже не чихнёт.
  • Тестируемость на высоте. Писал же уже — подсовываешь заглушки и проверяешь логику. Никаких лишних движений.

Вот и весь сказ, ёпта. Use Case — это такой работяга, который делает одно дело, но делает его хорошо и независимо от всего окружающего пиздеца.