Является усовершенствованием паттерна Model-View-Controller и более удобен для создания веб-приложений.
Большинство фреймворков на данный момент использует MVC. Впервые он был представлен в 70-х годах и предназначался для разработки десктопных приложений. Несмотря на это, в течении долгого времени, паттерн используется и для веб-приложений.
В этой статье мы познакомимся с паттерном Action-Domain-Responder (ADR — Действие-Домен-Ответчик) и научимся его применять в Laravel.
Что такое Action?
Класс Action можно сравнить с контроллером одного действия.
Каждый Action должен обрабатывать только одно действие в приложении.
Представьте, что вы создаете страницу пользователя User
. В обычном Laravel-приложении ваше определение маршрута выглядело примерно бы так:
Route::get('/users/{user}', [UsersController::class, 'show']);
В вашем контроллере UsersController
есть несколько методов, каждый из которых обрабатывает свой маршрут.
Если бы мы преобразовали этот маршрут в Action, то сделали бы так:
Route::get('/users/{user}', ShowUserAction::class);
Где задаются классы Action?
Обычное приложение хранит классы Controller
в файлах директории app/Http/Controllers
.
Так как мы используем шаблон ADR, то можем более плотно сгруппировать нашу бизнес-логику и использовать доменно-ориентированную структуру папок.
В нашем случае я создаю папку app/Users
. В ней будут храниться все компоненты, относящиеся к пользователям.
Тогда класс Action можно создать в файле app/Users/Actions/ShowUserAction.php
Что должен иметь Action?
Для того, чтобы использовать класс Action в маршруте, нам необходим метод __invoke
class ShowUserAction { public function __invoke() { // } }
Этот метод будет вызван при использовании конечной точки /users/{user}
Как Action генерирует Response?
Теперь, когда мы познакомились с классами Action, то можем посмотреть на возвращаемые объекты Response
.
Буква R в ADR означает Responders (Ответчик). Как и следует из названия, эти классы отвечают за создание объекта Response
.
Где хранятся классы Responder?
Как и для Action-классов мы можем создать для них отдельную папку app/Users/Responders
.
Для маршрута /users/{user}
создадим новый класс ShowUserResponder
.
namespace App\Users\Responders; class ShowUserResponder { }
Что должен иметь Responder?
Обычно я создают метод respond
для генерации ответа.
namespace App\Users\Responders; use Illuminate\Http\Response; class ShowUserResponder { public function respond() { } }
Вы можете назвать его send
, generate
или даже __invoke
.
Теперь мы можем получить экземпляр ShowUserResponder
в конструкторе ShowUserAction
.
namespace App\Users\Actions; use App\Users\Responders\ShowUserResponder; class ShowUserAction { public function __construct( protected ShowUserResponder $responder ) {} public function __invoke() { return $this->responder->respond(); } }
На данный момент ShowUserResponder
фактически не возвращает Response
.
Мы показываем User
и поэтому можем использовать привязку модели к маршруту в методе ShowUserAction::__invoke
namespace App\Users\Actions; use App\Users\Responders\ShowUserResponder; class ShowUserAction { public function __construct( protected ShowUserResponder $responder ) {} public function __invoke(User $user) { return $this->responder->respond($user); } }
Также передадим объект User
в метод ShowUserResponder::respond
, чтобы сгенерировать валидный Response
.
namespace App\Users\Responders; use Illuminate\Http\Response; class ShowUserResponder { public function respond(User $user) { return view('users.show', [ 'user' => $user, ]); } }
Теперь, когда у нас есть объект User
, мы можем использовать хелпер view
для создания объекта View
, который
автоматически сконвертируется в валидный объект Response
.
Что ещё должен делать Responder?
Помимо создания валидного объекта Response
(или Responsable
), Ответчик несёт ответственность за изменение всего, что связано с ответом.
Это может быть добавление/изменение заголовков, изменение формата ответа (JSON вместо HTML на основе механизма «Согласование контента»).
MVC или ADR?
Вот список того, в чём, на мой взгляд, ADR лучше, чем MVC:
1. Разделение доменов
Паттерн ADR специально был разработан с учётом предметно-ориентированного проектирования (domain-driven design, DDD).
И он лучше подходит для разделения доменов, чем MVC.
Вы можете группировать по цели, а не по типу компонента. У вас больше не будет кучи папок в app/Http/Controllers
, но будет много папок с Http/Controllers
.
Однако, в зависимости от размера вашего приложения, этот уровень абстракции может оказаться излишним. В итоге вы потратите больше времени на выяснение того, где и что должно находиться, вместо — что они должны делать.
2. Разделение ответственности
Для MVC довольно типично — раздувать контроллеры большим количеством бизнес-логики (да я и сам так делаю).
А в ADR сразу понятно, что Action отвечают только за обработку бизнес-логики, а Responder — за генерацию ответов.
Если обнаружите, что используете response()
или view()
в Action, то вы используете паттерн ADR неправильно.
Вообще этот паттерн поощряет использование мидлваров и перехватчиков запросов (request interceptors).
С помощью мидлваров вы можете избежать авторизационных проверок в Action. Это могут быть как глобальные переиспользуемые мидлвары, так и обработчики конкретных маршрутов.
Отсутствие таких проверок в Action позволит вам еще больше разделять ответственность.
Автор: Ryan Chandler
Перевод: Алексей Широков
Наш Телеграм-канал — следите за новостями о Laravel.