Как стать автором
Обновить

Комментарии 22

Хорошая, простая и понятная статья. По сути вы пришли к паттерну команда и архитетуре CQRS. Последнее время чем чаще думаю об архитетуре, тем больше приходит идей переходить на команды.
Нужно сделать декоратор? Проще сделать с командой, так как не нужно будет 100500 методов заглушек, если бы делали декоратор для большого сервиса и его одного метода.
Нужно выполнить очередь (или перевести какой то фунцкионал на нее) — команда как раз для этого отлично подходит.

Единственное но — это больше кода, а если проект небольшой, то плюсы которые выходят в статье — просто могут не понадобиться в небольшом проекте.

Неплохой доклад на тему CQRS, как раз с разбором нарастающих проблем.
www.youtube.com/watch?v=mvIXCgwGf9E

Декоратор хорошо, но SRP нарушен.

Не могли бы вы развернуть тезис?

Без проблем.
Берём класс LogCreateUser и задаемся вопросом что он делает:
— создаёт пользователя
— логирует создание пользователя
— всё выше сказанное.
Ответ: всё выше сказанное. А значит нарушили SRP.
Второй момент: с какого испуга класс логирования должен наследоваться от класса создания? Композиция — да, но не наследование.


Мой вывод: логирование — это прекрасно. Но это отдельная операция, которая живет в другом слое кода, никак не связанном с созданием.
Простейший пример на псевдокоде:


log('User creation: start')
userService->create()
log('User creation: end')
Композиция — понятно.
Но получается, если логирование — это отдельная операция, то понадобится делать правки во всех местах где создается юзер.

У меня для вас две новости.
Во-первых, если у вас в приложении не одно место, где создается пользователь — что-то явно не так с вашим приложением.
Во-вторых, раз у вас несколько мест — хорошо, используйте декоратор. Но сделайте его так, как упомянул товарищ DExploN, а именно через правильный класс (который не наследуется, а реально декорирует вызов метода другого объекта).

Если я правильно понял, то в правильном приложении должно быть только одно место где выполняется такой код?
log('User creation: start')
userService->create()
log('User creation: end')

Тогда вопросы:
1-В какой метод/класс надо обернуть эти строки?
2-Разве теперь уже этот метод/класс не нарушает SRP?

Если уж так зацикливаться на "правильности" и не использовать сквозное логирование на уровня среды испольнения а-ля AOP, то, да, только одно (c AOP — ни одного в исходниках). И ответственность этого метода/класса будет в декомпозиции и делегировании вызова чего-то типа $userServiceWithLogging->create() к $this->logger->info() и $this->userService->create(). Никакой другой логики кроме трех подобных строчек в нём быть не должно.

Откуда желание постоянно что-то оборачивать? Оставьте эти три строки в той Closure, которая отвечает за этот endpoint в роутере.


Можно даже сделать так:


class CreateUser
{
    public function __construct(); // сохраняем входные данные
    public function __invoke(); // создаем юзера
}

class LogCreateUser
{
    public function __construct(Logger $logger, CreateUser $createUser) {
        $logger->start();
        $createUser();
        $logger->end();
    }
}

Некое подобие middleware, только не для request, а для методов сервисов.

И уже этот LogCreateUser не нарушает SRP?

Как минимум, такой код точно не нарушает CSP.
Common Sense Principle

Нет, его единственная отвественность декмпозировать входной вызов и делегировать его другим сервисам, диспетчер или оркестратор, что бы это ни значило :)

Что значит декомпозировать входной вызов?

Опредлить, что нужно дернуть логгер и сервис.

а если через события? или CreateUser должен кидать несколько событий, или CreateUserWithEvents как-то пытается создать события, если нет доступа правки CreateUser? или события уже не круто?

Это не класс логирование, это класс создания пользователя с логированием.

+1. Типичный пример когда плохие формулировки заставляют делать ненужные действия.

Еще добавлю, паттерн декоратор работает с композицией, а не с наследованием. При паттерне декоратор, можно в теории делать сколько угодно слоев декорирования, в этом его смысл. Например вы захотите сделать еще один декоратор, вы будете что декорировать наследованием? LogCreateUser или CreateUser?
Декораторы выглядят так:

SendEmailCreateUser(new StatCreateUser(new LogCreateUser(new CreateUser())))

Слишком маленькие классы, настолько же плохи как и слишком большие, особенно когда у вас 10000 файлов вместо 1000. Большая часть из которых состоит из 5-10 строк, мило, но рано или поздно разработчики перестанут понимать куда лезть чтобы добавить флажок при регсистрации пользователя...

не стоит буквально воспринимать что тут написано. Это просто компактный пример… пища для ума на случай рефакторинга. Ну или если с нуля начинаете проект и сразу понимаете что User обрастет кучей методов и кода.

Уверен, в 80%+ проектов User это просто черная неведомая дыра. И тут показан хороший способ уменьшить энтропию системы.

Да и в основном как раз существует проблема огромных классов, говнокода, лапшекода, который нужно как-то обобщать и логично разбивать на части. Обратной проблемы, когда сразу все разбили на 1000 классов, трейтов, хелперов, компонентов, а потом найти ничего не могут, потому что это сайт-визитка парикмахерской всего лишь — почти не существует.
$user = User::create($request->all());

$user = $createUser($request->all());

Эти 2 строки ничем не отличаются в плане дублирования.

Заменой статическоего метода на сервис решается проблема зависимости от конкретной реализации (D в SOLID), нерасширяемости и нетестируемости статических методов.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации