Обновить
0
0

Пользователь

Отправить сообщение

Большая часть ваших аргументов основывается на том, что вы зацепились за то, что контейнер для какой-либо ошибки наследуется от класса \Exception, но по большей степени тут суть в том, что это лишь контейнер для ошибки(тип), который просто имеет возможность выбрасываться если того требует ситуация на усмотрение разработчика.
В подходе AlexLeonov (если я его правильно понял) — выброс этих объектов не происходит, а происходит выброс коллекции, которая содержит у себя в айтемах ексепшены. И то, выброс этой коллекции подразумевается в вышестоящем коде (судя по комментариям AlexLeonov ). Для примера throw new ValidationExceptions($this->arrayOfSpecificValidationExceptions).


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


Ваша коллекция ошибок:


class ConstraintViolationList {
  protected results = [
     'name' => [ConstraintViolation('name is blank'), ConstraintViolation('name is not string')],
     'quantity' => ConstraintViolation('quntity not valid'),
  ];

  ...
};

@AlexLeonov коллекция ошибок:


class ValidationResults extends \Exception {
  protected results = [
     'name' => [BlankValidatorException('name is blank'), TypeValidationException('name is not string')],
     'quantity' => QuantityValidatorException('quntity not valid'),
  ];
  ...
};

(Код просто для наглядного сравнения подходов без погружения в ньансы)


Соответственно суть спора можно свести к главному вопросу как лучше получить эту самую коллекцию после вызова ->validate().


  1. Вы предлагаете возвращать коллекции ошибок из функции ->validate().
  2. @AlexLeonov предлагает выбрасывать эту коллекцию в виде ексепшена.

И в вашем случае, и в случае AlexLeonov я вижу сайд эффекты.
Эксепшен — бесспорно сайд-эффект, но в вашем случае чуть сложнее и субъективно с моей стороны. Метод validate в моём представлении должен быть void и делать только то, что определено в его названии — валидировать (правило наименьшего удивления, на которое вы уже где-то тут ссылались + SRP на уровне функции?:D ). Поэтому для получения ошибок, было бы логичней вызвать отдельную функцию getErrors() : ViolationList или же добавить новую validateAndGetErrors() : ViolationList. Соответственно по закону деметры тогда в интерфейсе валидатора появятся такие функции как hasErrors() : bool, countErrors() : int и т.д.


Вероятно этой же логикой руководствовался AlexLeonov.


Должна ли функция validate просто тихо отрабатывать при возникновении ошибок? Тут я хз) Т.к. вижу аргументы и в том, что бы бросить эксепшен, и в том, что бы тихо отработать) Но больше за то, что бы тихо отработать, а вышестоящий код (который вызывал эту функцию) уже решил, что делать с полученной коллекцией ошибок, если они есть. (отрендерить в контроллере или выбросить на глобальный хендлер) О чём кста упоминал AlexLeonov. (о том, что решение за выброс принимает вышестоящий код, который инициировал валидацию)


К примеру в ларе, при валидация пост реквеста при использовании FormRequest происходит выброс в глобальный хендлер, а потом redirect()->back()->withErrors($errors). Думаю подобный же код встретился бы у вас в контроллере, если валидировать реквест в контроллере. Или вы отдаёте хтмл в ответ на POST после субмита формы?


По итогу. Я не вижу принципиальных различий у вас обоих)

Я не про сеттеры

Я про сеттеры что ли? Я про нормальные доменные методы...


Моя претензия была именно к command bus так как проблему это не решает и из вашего описания создает новые.

А я пытаюсь всё это время узнать почему? и какие новые проблемы?


это вопрос в контексте компонента workflow. Ваше предложение не отвечает на вопрос.

На мой взгляд это вопрос в контексте приложения. Смысл рассматривать компонент в отрыве от приложения и того как он там используется?


Вопросы были:


  1. Как организовать транзакции в которые можно запихнуть какой-то пейлоад? Я так понял транзакции имелось ввиду не в терминах автора статьи… Что корректней называть переходом.


  2. Как сделать автопереход на следующий плейс, при достижении всех необходимых условий для перехода?

С command bus + workflow и транзакции с пейлоадом тебе, и автопереходы. И я понимаю, что это не единственный возможный вариант...

доменные ивенты вам помогут. Не предлагаете же вы делать сущности тупыми property-bag-ами?

Ну в моём представлении это проперти бэг + методы которые манипулируют этими проперти. Ну т.е. стейт и то как этот стейт менять, как и вы и упомянули в конце.


но вы же уже достаете в контроллере репозиторий, достаете сущность по запросу и только потом формируете команду. Так что зоны ответственности уже размазаны.
Если что у меня обычно есть хэндлеры команд и команды, в них была бы только айдишка сущности а не вся сущность.

Согласен, ток айдишку ложить в команду — лучше. Но как это всё относится к самым первоначальным вопросам?) Вытянуть статью в команде или положить статью в команду где-то выше — это несущественный момент реализации в контексте основных вопросов.


Сущность ничего не знает о базе, она знает только о том что у нее есть стэйт и только ей дано знать как этот стэйт должен поменяться.
Ваще с этим не спорю и не ставил под сомнения. К чему вы это всё пишете?)

Короче, какой-то холивар на пустом месте) VolCh спрашивал:


  1. Как организовать транзакции с пейлоадом? Я предложил команды (статья или айдишка статьи в пейлоаде — в данный момент не суть). И из всего что вы написали, я так и не понял, что здесь не так.


  2. Я не понял вопроса.


  3. Как сделать автонакатывание перехода? Я предложил евенты, миддлвари для команд.

Если хотите дальше продолжать, то давайте по пунктам и по делу, где и что не так. Или ещё лучше, предложите ваш вариант решения этих вопросов, что, думаю, будет всем интересно.

если у вас УЖЕ есть статья то вам не нужно городить команды. Мы УЖЕ потеряли весь профит от Command Bus и теперь нам намного проще будет вызывать методы сущности нежели городить еще один слой кода.

Почему? Где вы предлагаете дёргать методы сущности в соответствии с какой-то бизнес логикой?
В контролере? Контроллер раздуется, получится лапшекод и жесть, которую потом хз как переиспользовать. А команды можно кинуть будет и через джобы потом, если потребуется. И тестировать есть что. Да это я думаю вы и так понимаете.
В сервисах? Ну это кому как, на вкус как говорится) Ну кода ещё по-меньше будет.


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

Ну я подразумеваю, что перед переходом возможна какая-то нетривиальная логика после отработки которой и должен осуществиться переход. Если это запихать в сущность, то это получится не сущность, а сервис какой-то или вообще хз что. Да даже простая операция записи в базу. Сущность сама себя в базу записывать будет?) Даже с активрекордом такое, я думаю, лучше не делать, т.к. выглядит как побочный эффект.

Блин, вы что-то всё усложнили сильно и зачем — непонятно)


Вопроса было 3, я дал 3 ответа, причём последний содержит 3 варианта. Возможно я плохо их отформатировал и вы подумали, что пронумерованный список это соответственные ответы на все 3 вопроса. Пронумерованный список относится только к последнему вопросу. И естественно, я не углублялся в детали и не расписывал весь флоу обработки запроса в вакууме с прикручиванием cqrs или cqs, ведь это не относится к вопросам.


откуда статья возьмется в пэйлоаде? Кто и на каком уровне ее вытащит и добавит в пэйлоад? То есть мы должны достать сущность еще на уровень выше?

какая мне разница, вопрос о другом!) Где-то в коде, хоть в контроллере, создастся какая-то команда, в которую положат статью, потому, что это будет обязательный параметр конструктора, т.к. статья требуется для обработки данной команды, и задиспатчат.


Как обработчик команды сделает работу? Из вашего описания у нас есть некая эдакая процедура "approved_by_spellchecker" которая на вход получает данные (инстанс Article) и что-то с ней делает.

Опять, какая мне разница?) Да, есть комманда(простой класс с необходимым пейлоадом для обработки), которая отправляется обработчику, который выполняет какую-то бизнес-логику и делает транзишн в следующее состояние в конце работы, если всё ок.


А ведь вопрос был в

Это был 3й вопрос. И для его решения я бы сделал post middleware, которая автоматически будет проверять сущности из пейлоада команд и диспатчить новые команды для авматических переходов. Или да, можно генерить сет евентов, о чём я говорил в 1м варианте ответа на 3й вопрос.

Да, про CQRS, вернее про его часть.


К сожалению не понимаю что вы хотели сказать и что побудило)
Я не отождествлял команды с евентами, а писал про использование их в вместе в ответе на 3й вопрос. Да и то, только в 1м варианте, но в целом это никак не противоречит cqrs. К слову, cqrs очень хорошо сочетается с event-based programming.


Там не команды записываются а ивенты которые произошли в контексте агрегатов сущностей. Вот тогда да, тогда можно будет делать $article->apply($nextState);.

Можете расшифровать? Куда записываются, какие ивенты? Вы про event sourcing? Если да, то причём он тут?)


Происходит какая-то бизнес-логика и диспатчится комманда (со статьёй в кач-ве пейлоада) --> миддлваря проверяет может ли выполниться комманда $w->can($command->article, 'approved_by_spellchecker'); --> обработчик чё-то делает, а потом в конце $w->apply($command->article, 'approved_by_spellchecker');. Всё. Что тут не так?)

Можно хранить всё как хочешь и где хочешь, и формировать динамически.

Не уверен, что правильно понял все вопросы, но попробую предложить свои варианты решения.


как лучше реализовать транзакции не просто состояние (place) процесса изменяющие, но и несущие полезную нагрузку, например отредактированную статью в транзакции "утверждение корректором"

Использовать Command Bus. Создаётся 2 класса (команда и обработчик команды). Собственно команда и несёт в себе полезную нагрузку (пейлоад), это просто тупой класс с гетерами, а соответствующий обработчик производит манипуляции с этим пейлоадом в конце которой, происходит ->apply($article, 'next_state');
https://habrahabr.ru/post/280512


как лучше реализовать воркфлоу, в которое вовлечено множество сущностей (возможно даже на старте флоу — неизвестное множество, которое к концу заполняется)

К сожалению не понял, что имеется ввиду)


как лучше реализовать автоматическую транзакцию типа "публиковать", когда выполнены две типа "утверждена журналистом" и "утверждена корректором"

Евенты, команды, умная мидлваря для команд.


  1. Кидать евент try_to_publish в конце каждой комманды-транзакции (утверждение журналистом, утверждение корректором), в обработчике которого будет происходить проверка $workflow->can($article, 'publish'); и если тру, то кидаться команда PublishAtricle. Или продублировать проверку $workflow->can($article, 'publish'); в командах и если тру сразу кидать команду на паблишинг.


  2. Диспатчить команду PublishArticle, в конце каждой комманды-транзакции (утверждение журналистом, утверждение корректором), а проверку $workflow->can($object, 'state') вынести в миддлварю.


  3. Можно добавить в конфиг параметр auto: true/false для определённых переходов и написать умную мидлварю, которая будет проходиться по всем возможным переходам (те которые ->can($obj, 'state); //true) для сущности, и если переход помечен как auto: true, то диспатчить соответствующую команду.

Я лично за последний вариант)

Извиняюсь, по первому взгляду на https://github.com/JosephSilber/bouncer/blob/master/src/Clipboard.php#L26 думал, что все позитивные проверки будут дальше проваливаться в polices или abilities.
Если бы автор там возвращал null, то всё так и работало бы.
Извиняюсь ещё раз за невнимательность)
Bouncer — может такое :)
Как разрешить пользователю редактировать только свои статьи?

Roles, Bouncer — могут такое
Да. Уже вполне себе стандартный кейс (раздавать статику через server push).
> а dd бесполезен в терминале
Ахаха, делает http запрос к сайту и удивляется, что ему в консоль падает html от dd… К слову, в cli — dd не генерирует html.
Вы похоже такой же капитан, как и автор статьи)
Прост в шоке… Какой чудесный недокументированный метод… А какой прекрасный подход. Спасибо, что сообщили о своей находке. Обязательно возьму на вооружение, а то эти 100500 запросов при моих постоянных выборках для вывода по 500-1000 элементов на страницу уже задрали.
А это норм создавать модели для форм?)
и в entity (валидация на уникальность)

Должна ли она быть там?
Ещё проскочила мысль:
Должна ли бизнес логика находиться в хандлере? Мне кажется в команде она выглядела бы логичней. Даже по названию абстракций.
Банальный пример, к примеру нам нужно логировать именно эту команду, миддлварю для этого писать как-то не айс, и тогда в хандлере код логирования смешается с кодом бизнес логики. Более симпатичней, на мой взгляд, если бы хандлер выглядел как-то так:
public function handle(CreateProjectCommand $command, array $data)
{
$this->logger->log('Creating new project');
$entity = new Project();
$command->execute($entity, $data);
$this->logger->log('New project created');
}

Таким образом хандлер получается этаким экшеном для команд.
В контроллерах экшены для реквестов, а в хандлерах — для команд) Так же возможно имеет смысл в этом месте снабжать команды всеми необходимыми зависимостями, которые будут резолвиться в конструкторе хандлера.
Немного смущает то, что валидация получается как-то размазана. В entity, в command хандлере, в контролере при edit.
Имхо, лучшим вариантом было бы описывать валидацию где-нибудь в одном месте и валидировать реквест. Да, как в ларе)
А по поводу эксепшенов валидации, то у вас немного другой случай по сравнению со статьёй которую вы скинули. Там предлагалось на ошибку сразу же выкидывать эксепшен, когда у вас выкидывается только по факту существования ошибок. Другими словами, вы просто в другом месте делаете if(count($violations) != 0).
Спасибо за статью)
А видели остальные «примеры»? Которых, к слову, довольно много?)
Больше всего в своё время зацепил вот этот мини вариант.
www.fiveminutes.gs
1

Информация

В рейтинге
Не участвует
Откуда
Москва, Москва и Московская обл., Россия
Зарегистрирован
Активность