Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое Use Cases (Сценарии использования)?
Use Cases — это концепция из области проектирования программного обеспечения и бизнес-анализа, описывающая взаимодействие между пользователем (актором) и системой для достижения конкретной цели. В контексте разработки на Go (и любого другого языка) use case представляет собой не просто техническую спецификацию, а бизнес-правило или операцию, которую система должна выполнить, чтобы предоставить ценность пользователю. Это ключевой элемент архитектуры "Чистого" (Clean) или Гексагонального (Hexagonal) подхода, где бизнес-логика изолирована от инфраструктурных деталей (баз данных, HTTP. фреймворков и т.д.).
Ключевые характеристики Use Case в коде на Go
- Одна ответственность: Каждый use case реализует единственную бизнес.транзакцию (например, "Создать заказ", "Начислить бонусы пользователю", "Сгенерировать отчет").
- Изолированность от инфраструктуры: Use case не зависит от способа вызова (HTTP, CLI, gRPC) или способа хранения данных (PostgreSQL, MongoDB, in-memory). Он работает с интерфейсами (interfaces), а не с конкретными реализациями.
- Оркестрация бизнес-логики: Use case координирует вызовы к сущностям (Entities) и репозиториям (Repositories), применяет правила валидации и обрабатывает ошибки предметной области.
- Входные и выходные данные (DTO): Обычно принимает простую структуру данных на вход (запрос) и возвращает другую структуру или ошибку на выход. Это контракт для слоя доставки (delivery).
Пример Use Case на Go: "Создание пользователя"
Рассмотрим реализацию use case для регистрации нового пользователя.
1. Определение интерфейсов репозитория (инфраструктурный слой, НЕ в use case)
// user/repository.go
package user
// Repository - интерфейс, который знает use case.
// Конкретная реализация (на PostgreSQL, в памяти и т.д.) будет предоставлена "снаружи".
type Repository interface {
FindByEmail(email string) (*User, error)
Save(user *User) error
}
**2. Определение сущности (Entity) - ядра бизнес.
логики**
// user/entity.go
package user
import (
"errors"
"regexp"
)
var (
ErrInvalidEmail = errors.New("invalid email format")
ErrUserExists = errors.New("user with this email already exists")
)
type User struct {
ID string
Email string
Name string
}
// NewUser - бизнес-правила создания сущности, валидация.
func NewUser(email, name string) (*User, error) {
if !isValidEmail(email) {
return nil, ErrInvalidEmail
}
return &User{
Email: email,
Name: name,
}, nil
}
func isValidEmail(e string) bool {
// Упрощенная проверка для примера
re := regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\
-]+\.[a-z]{2,4}$`)
return re.MatchString(e)
}
3. Реализация самого Use Case
// user/create_usecase.go
package user
// CreateUserRequest - Data Transfer Object (DTO) для входных данных.
type CreateUserRequest struct {
Email string
Name string
}
// CreateUserResponse - DTO для выходных данных.
type CreateUserResponse struct {
UserID string
}
// CreateUserUseCase - сама реализация сценария использования.
type CreateUserUseCase struct {
userRepo Repository
}
// NewCreateUserUseCase - конструктор (dependency injection).
func NewCreateUserUseCase(repo Repository) *CreateUserUseCase {
return &CreateUserUseCase{userRepo: repo}
}
// Execute - основной метод use case, реализующий бизнес-сценарий.
func (uc *CreateUserUseCase) Execute(req CreateUserRequest) (*CreateUserResponse, error) {
// 1. Проверить, не существует ли уже пользователь с таким email (бизнес.
правило)
existingUser, err := uc.userRepo.FindByEmail(req.Email)
if err != nil && !errors.Is(err, ErrNotFound) { // ErrNotFound - допустимая ошибка репозитория
return nil, err // Прокидываем техническую ошибку (например, разрыв соединения с БД)
}
if existingUser != nil {
return nil, ErrUserExists // Возвращаем ошибку предметной области
}
// 2. Создать валидированную сущность User (бизнес-правила валидации)
user, err := NewUser(req.Email, req.Name)
if err != nil {
return nil, err // ErrInvalidEmail
}
// 3. Сохранить пользователя
err = uc.userRepo.Save(user)
if err != nil {
return nil, err
}
// 4. Вернуть ответ
return &CreateUserResponse{UserID: user.ID}, nil
}
Преимущества подхода Use Cases в Go
- Тестируемость: Use case можно протестировать в полной изоляции с помощью mock-реализаций репозиториев. Не нужна реальная база данных или HTTP-сервер.
func TestCreateUserUseCase_UserExists(t *testing.T) { // 1. Создаем mock репозитория mockRepo := new(MockRepository) // 2. Настраиваем ожидание: FindByEmail вернет существующего пользователя mockRepo.On("FindByEmail", "test@example.com").Return(&user.User{ID: "123"}, nil) // 3. Создаем use case с mock-зависимостью uc := user.NewCreateUserUseCase(mockRepo) // 4. Выполняем use case _, err := uc.Execute(user.CreateUserRequest{Email: "test@example.com"}) // 5. Проверяем, что получили ожидаемую бизнес-ошибку assert.ErrorIs(t, err, user.ErrUserExists) mockRepo.AssertExpectations(t) } - Поддержка и читаемость: Код организован вокруг бизнес-возможностей, а не технических деталей. Новому разработчику легче понять, что делает система.
- Гибкость: Один и тот же use case можно вызвать из HTTP-обработчика, gRPC-сервиса, CLI-команды или фонового worker'a, не изменяя его код.
- Защита доменной логики: Бизнес-правила (валидация email, проверка на дубликаты) надежно инкапсулированы внутри сущностей и use cases и не "расползаются" по всему коду.
Где Use Cases располагаются в архитектуре?
В типичной чистой архитектуре на Go:
- Слой Внутренних Сущностей (Entities):
user/entity.go— базовые структуры и правила. - Слой Use Cases (Сценарии использования):
user/create_usecase.go— прикладная бизнес-логика. - Слой Адаптеров/Инфраструктуры (Adapters/Infrastructure): Реализации
Repositoryна PostgreSQL, HTTP-обработчики (/api/v1/user), которые вызываютuc.Execute(...). - Слой Внешних Интерфейсов (External Interfaces): Точка входа (
cmd/api/main.go), которая "собирает" все компоненты вместе (внедряет реальный репозиторий в use case).
Таким образом, Use Cases в Go — это не просто функции, а четко структурированные компоненты, которые инкапсулируют конкретные бизнеспроцессы, делая приложение более тестируемым, поддерживаемым и независимым от внешних фреймворков и технологий. Они являются мостом между высокоуровневыми требованиями заказчика и низкоуровневой технической реализацией.