Может войдёшь?
Черновики Написать статью Профиль

Статьи EugeneErg

Соблюдение принципов SOLID при работе с фреймворком Laravel

SOLID

SOLID — принципы программирования, следуя которым можно добиться легко-масштабируемого и легко-поддерживаемого кода.

Controller Service Repository

Controller Service Repository — архитектурный паттерн, необходимый для разделения ответственности и помогающий соблюдать принципы SOLID в фреймворке Laravel

Контроллеры

Контроллеры — классы, отвечающие за обработку запросов. Таким образом ответственность контроллера — это формирование ответа на пользовательские запросы.

Метод контроллера должен…

nailfor

CSR — одно из самых неудачных архитектурных решений со времен Б4.

Предлагается для каждой модели написать свой репозиторий и сервис к нему. Но, простите, а что делать, если, например, одной модели оказывается недостаточно? К примеру пользователю понадобился адрес, его куда прикажете запихивать? В UserRepository? Или уже в AddressRepository?
Если в последний, то как передать пользователя? Через id? Тогда будет N+1.
Через ids? Тогда что делать, если сам UserRepository используется в каком-нибудь CustomerService который, не к ночи будет сказано, подтягивает еще какой-нибудь OrderRepositiory?
Т.е. дальше одного шага проблема не решается принципиально, придется городить какие-то обходы выборок, сбор ids, передача всего этого в репы... и всё ради чего???


PHP
try {
            
$user $service->getUser($request->getUserId());
        }

Изюмительно. Я прямо сижу.. и старческие слезы умиления наворачиваются на глазах. Простите, а что Вы собираетесь делать, когда в запрос нужно будет передать новый параметр, кроме id? Ну, например, СНИЛС или что-то еще?
Давайте я Вам помогу. Небольшой реверс-энжинеринг.
Так как getUserId — это какой то малопонятный огород, вангую, что это обычная обертка над Request за каким-то бесом засунутая в виде метода в ShowUserRequest

PHP
protected int $userId;
public function 
__construct()
{
    
$this->userId = (int) $this->id;
}

public function 
getUserId(): int
{
    return 
$this->userId
}

Зачем? Да просто, ради писанины. Ведь теперь, чтобы добавить новый параметр и передать его в сервис нужно еще методов в ShowUserRequest закинуть.
Почему нельзя просто сделать (int) $request->get('id', 0) ? Вопрос чисто риторический.

Ну оке.. с ид понятно, добавление всего одного поля в такую систему приводит к каскаду правок в
1. реквесте
2. контроллере
3. сервисе(он же получает не реквест, а параметры отдельно)
4. репозитории(его же нужно как то обработать)
Несколько многовато для такого простого действия.

PHP
catch (UserNotFoundException $exception) {
            return 
view('user.not_found', ['user_id' => $request->getUserId()]);
        }

тут еще интереснее. Вместо совершенно логичного и общего для любых моделей ModelNotFound требуется на каждый реп описать свой эксепшен. За ради что?
Т.е. в репе User::find($userId) заменить на User::findOrFail($userId) — уже не православно, а наогородить

PHP
if ($user === null) {
            throw new 
UserNotFoundException("User {$userId} not found.');
        }

— это феншуй. Ню... токооое.


«Негативные последствия такой реализации в том, что при необходимости получения разных ответов в разных методах контроллеров, одно и то же исключение может быть преобразовано по разному.»

Это в каком мире 404 страница должна быть преобразована по разному? Нет модели — пульнул одну для всех 404 и забыл. Я так понимаю, что разные ответы нужны чтобы программисты не скучали.


«В теле метода сервиса не выполняются запросы к базе данных. Только бизнесовая логика»
и это приводит к тому, что запросы выполняются катастрофически долго, а логика превращается в спагетти-стайл с вызовом сервисов внутри других сервисов обернутое циклами внутри других циклов со вложенностью 5 и более и нытьём что ой у нас все как то сложно получилось давайте перейдем на DDD там всё еще сложнее.


«Репозитории — классы, отвечающие за сохранение и извлечение некоторого набора данных»
Нет конечно. Ничего подобного репозитории НЕ ДЕЛАЮТ. Этим занимается DAO! Запомни, а лучше запиши и никогда в дальнейшем не путай!!!
Репозиторий — это высокоуровневая абстракция доступа к данным. Никаких изменений он делать не может и не умеет.


«В идеале репозиторий не должен возвращать или принимать объекты класса Model»
В сферическом вакууме, надо понимать. Там, где сферические кони.
Паттерн был придуман задолго до того, как появилась эта ваша богомерзкая DDD и предназначался, в первую очередь, для быстрого доступа к уже полученным данным. Возможно ты в курсе, что есть языки, которые не выгружаются из памяти всякий раз на каждом запросе, так вот в них(например в яве) репы очень комфортно себя чувствуют, так как являются эдаким кэшем справочника в памяти, ну и, разумеется, хранят они самые что ни на есть Models, а не эти ваши «энтити», простихоспиди.


«Лучше создать собственные entity или dto.»
Ага.. давай еще один огород сверху прикостылим. А то там абстракций чет маловато, ОРМ уже не хватает. Теперь для добавления одного поля мало того, что нужно будет сделать миграцию и добавить логику в сервис, так еще написать пару-десятков методов гетеров и сетеров в entity, dto, valueObject commandBus и всё заверте... но это не точно.


«Но с опытом вы поймете...»
что простое лучше чем сложное, что код, который никогда не ломается — это код, которого нет. Его проще сопровождать, проще добавлять новые фичи, проще разбираться с багами. YAGNI + KISS.

← Назад | Дальше → Движется на Habravel