Архитектура

Сервисный слой в Laravel: куда уносить бизнес-логику

Никита Литвяков
Никита Литвяков
Senior PHP / Laravel Backend Developer
Обложка статьи: Сервисный слой

В прошлой статье мы остановились на проблеме: как только в проекте появляется бизнес-логика сложнее «достать и отдать», контроллеры и модели начинают пухнуть. Регистрация пользователя — это не просто User::create(). Это валидация, создание записи, отправка письма, запись в CRM, событие в очередь, отчёт в метрики.

Если всё это в контроллере — невозможно переиспользовать (например, ту же регистрацию из консольной команды или job-а). Если всё в модели — модель превращается в монстра.

Что такое сервисный слой

Service-слой — это набор классов, которые инкапсулируют бизнес-операции. Не «сервис на каждый CRUD», а сервис на сценарий: RegisterUserService, IssuePolicyService, SendSmsService.

Сервис ничего не знает про HTTP. Он принимает данные, выполняет операцию и возвращает результат. Его можно вызвать из контроллера, из консольной команды, из job-а в очереди — везде одинаково.

Как выглядит на практике

class RegisterUserService
{
    public function __construct(
        private CrmClient $crm,
        private Dispatcher $events,
    ) {}

    public function handle(RegisterUserData $data): User
    {
        return DB::transaction(function () use ($data) {
            $user = User::create($data->toArray());

            $this->crm->syncContact($user);
            $this->events->dispatch(new UserRegistered($user));

            return $user;
        });
    }
}

Контроллер становится плоским:

public function store(RegisterUserRequest $request, RegisterUserService $service)
{
    $user = $service->handle(RegisterUserData::fromRequest($request));

    return redirect()->route('dashboard');
}

Что даёт такой подход

  • Переиспользование. Тот же сервис вызываем из API, из artisan-команды, из очереди.
  • Тесты. Сервис — обычный класс, мокаем зависимости через конструктор, тестируем без HTTP.
  • Читаемость. Контроллер за 30 секунд показывает, что делает endpoint.
  • Транзакции в одном месте. Бизнес-операция атомарна.

Чего стоит избегать

  • «Сервис на каждую модель». UserService со 40 методами — это та же толстая модель, только сбоку. Сервис — это сценарий, а не CRUD-обёртка.
  • Сервисы, зовущие сервисы по цепочке. Если граф вызовов глубже двух уровней — пора подумать про доменные события или отдельный use-case.
  • Передача Request внутрь сервиса. Сервис не должен знать про HTTP. Передавайте DTO или массив.

Запомнить

  • Сервис = бизнес-сценарий, а не «обёртка над моделью».
  • Один публичный метод (handle, execute) — нормально и даже желательно.
  • Зависимости через конструктор, никаких app() и фасадов внутри.
  • Транзакции, события и интеграции — внутри сервиса, не в контроллере.
#Архитектура #PHP #Laravel #Backend
Поделиться:

Также может быть интересно