Страница 1 из 2

DDD и транзакции

Добавлено: 2017.01.15, 17:20
anton_z
Уважаемые члены сообщества, помогите внести ясность в вопрос.
Имею шину команд. Нужно обеспечить транзакционность. Видел решения https://php-and-symfony.matthiasnoback. ... mmand-bus/ связанные и использованием перехватов, шина декорируется классом, где разруливается транзакция. Т.е. обработчики команд ничего не знают о транзакции.

Таким образом, если написать обработчик, который будет работать с БД, а еще отправлять письмо (делать что-то нетранзакционное) прямо или косвенно (через события), то возникает окно для несогласованности - письмо отправлено, транзакция откатилась.

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

P.S. Полгода не заходил на форум..Очень радует, что появилось большое количество обсуждений архитектуры приложений и DDD. Сообщество растет качественно). Может и фреймворк в ответ на меняющиеся условия станет менее монолитным?) (риторический вопрос) Уж не осудите за оффтоп).

Re: DDD и транзакции

Добавлено: 2017.01.15, 17:29
samdark
А почему не отправлять письмо только в случае успешного завершения транзакции?

Re: DDD и транзакции

Добавлено: 2017.01.15, 17:36
zelenin
samdark писал(а): 2017.01.15, 17:29 А почему не отправлять письмо только в случае успешного завершения транзакции?
потому что транзакция в предлагаемом декораторе выше по уровню оригинального хэндлера - хэндлер не знает о транзакции.

Re: DDD и транзакции

Добавлено: 2017.01.15, 17:40
anton_z
zelenin писал(а): 2017.01.15, 17:36
samdark писал(а): 2017.01.15, 17:29 А почему не отправлять письмо только в случае успешного завершения транзакции?
потому что транзакция в предлагаемом декораторе выше по уровню оригинального хэндлера - хэндлер не знает о транзакции.
Верно подмечено.

Re: DDD и транзакции

Добавлено: 2017.01.15, 18:27
samdark
В хендлере нельзя триггернуть событие вроде "успех" и на него зацепиться чем-то ещё?

Re: DDD и транзакции

Добавлено: 2017.01.15, 20:16
ElisDN
В таких случаях декорирую и EventDispatcher, чтобы его метод dispatch в приватный массив все события сохранял. И после коммита в шине вызываю запуск обработки.

Re: DDD и транзакции

Добавлено: 2017.01.16, 05:14
anton_z
samdark писал(а): 2017.01.15, 18:27 В хендлере нельзя триггернуть событие вроде "успех" и на него зацепиться чем-то ещё?
Так и делаю, но проблему транзакции это не решает, Обработчик события, который будет отправлять почту, тем более не знает, завершилась транзакция или нет.

Re: DDD и транзакции

Добавлено: 2017.01.16, 05:20
anton_z
ElisDN писал(а): 2017.01.15, 20:16 В таких случаях декорирую и EventDispatcher, чтобы его метод dispatch в приватный массив все события сохранял. И после коммита в шине вызываю запуск обработки.
Т.е. декторатор шины команд, в котором обрабатывается транзакция, управляет шиной событий, верно?
Получается когда ввожу транзакции в приложение, должен буду сделать два декоратора - на шину команд и шину событий и из первого управлять вторым. В командах ничего нетранзакционного напрямую получается делать нельзя, только через шину событий. Все равно какая-то связь в виде умолчания (то, что нельзя ничего нетранзакционного делать в обработчике) остается. Компромиссное решение.

Не лучше ли управлять транзакциями в обработчике команды? Чем это хуже перехвата?

Re: DDD и транзакции

Добавлено: 2017.01.16, 10:21
ElisDN
anton_z писал(а): 2017.01.16, 05:20Т.е. декторатор шины команд, в котором обрабатывается транзакция, управляет шиной событий, верно?
Получается когда ввожу транзакции в приложение, должен буду сделать два декоратора - на шину команд и шину событий и из первого управлять вторым.
Верно:

Код: Выделить всё

class TransactionalCommandBus implements CommandBusInterface
{
    public function construct(
        CommandBusInterface $next,
        DeferredEventDispatcher $dispatcher,
        Transtaction $transaction
    ) { ... }

    public function handle($command) {
        $this->transaction->execute(function () use ($command) {
            call_user_func($this->next, $command);
            // $this->em->flush();
        });
        $this->dispatcher->handleDeferredEvents();
    }
}
anton_z писал(а): 2017.01.16, 05:20Не лучше ли управлять транзакциями в обработчике команды? Чем это хуже перехвата?
Управляйте:

Код: Выделить всё

public function handle(MyCommand $command)
{
    $entity1 = new Entity1(...);
    $entity2 = new Entity2(...);
    $this->transactionManager->execute(function () use ($entity1, $entity2) {
        $this->repository1->add($entity1);
        $this->repository2->add($entity2);
    });
    $this->eventDispatcher->dispatch(...);
}
Лучше тем, что:

- можно явно запускать нетранзакционные операции (хотя им всё равно, в сервисе их запускают или по событию).

Хуже тем, что:

- в сервис просочилось знание о транзакциях;
- теперь нужно запускать UnitOfWork::flush() в каждом репозитории, а не один раз с транзакцией в шине.

Re: DDD и транзакции

Добавлено: 2017.01.16, 11:10
samdark
Так и делаю, но проблему транзакции это не решает, Обработчик события, который будет отправлять почту, тем более не знает, завершилась транзакция или нет.
Кажется, пример плохой... Событие на отправку почты мы триггерим только в том случае, если успешно отработала первая часть и не триггерим в противном случае. В нашем случае фейл отправки почты не означает отката изменений базы, что значит что по сути это не транзакция, а последовательность действий.

Re: DDD и транзакции

Добавлено: 2017.01.16, 12:26
anton_z
samdark писал(а): 2017.01.16, 11:10
Так и делаю, но проблему транзакции это не решает, Обработчик события, который будет отправлять почту, тем более не знает, завершилась транзакция или нет.
Кажется, пример плохой... Событие на отправку почты мы триггерим только в том случае, если успешно отработала первая часть и не триггерим в противном случае. В нашем случае фейл отправки почты не означает отката изменений базы, что значит что по сути это не транзакция, а последовательность действий.
Отправку почты взял чтобы не было ни у кого желания предлагать обработать нетранзакционную операцию, например сохранение файла куда-то.

Re: DDD и транзакции

Добавлено: 2017.01.16, 12:36
anton_z
ElisDN писал(а): 2017.01.16, 10:21 Лучше тем, что:

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

Например, когда я пишу обработчик, я же не знаю, что у меня там за шина будет. Обрабатывает она транзакции или нет. Если буду опираться на то, что обрабатывает - получу тесную связь, не так ли?

Re: DDD и транзакции

Добавлено: 2017.01.16, 13:15
ElisDN
anton_z писал(а): 2017.01.16, 12:36Если буду опираться на то, что обрабатывает - получу тесную связь, не так ли?
Это и имеется в виду в пункте "в сервис просочилось знание о транзакциях".

Re: DDD и транзакции

Добавлено: 2017.01.16, 13:46
samdark
Отправку почты взял чтобы не было ни у кого желания предлагать обработать нетранзакционную операцию, например сохранение файла куда-то.
Так отправка почты и есть нетранзакционная операция...

Re: DDD и транзакции

Добавлено: 2017.01.16, 14:18
anton_z
samdark писал(а): 2017.01.16, 11:10
Так и делаю, но проблему транзакции это не решает, Обработчик события, который будет отправлять почту, тем более не знает, завершилась транзакция или нет.
Кажется, пример плохой... Событие на отправку почты мы триггерим только в том случае, если успешно отработала первая часть и не триггерим в противном случае. В нашем случае фейл отправки почты не означает отката изменений базы, что значит что по сути это не транзакция, а последовательность действий.
Вопрос как раз в том, как правильно соблюсти эту последовательность при использовании шины команд с транзакциями.

Re: DDD и транзакции

Добавлено: 2017.01.16, 14:47
samdark
Нельзя для примера с транзакциями рассматривать заведомо не транзакционные части операции, такие как отсылка почты. Это путает. Чтобы у нас вышла нормальная транзакция придётся реализовать что-то типа two-phase commit protocol на уровне шины. А для этого отсылку почты (или как минимум её постановку в очередь) нужно иметь возможность откатить.

Re: DDD и транзакции

Добавлено: 2017.01.16, 15:15
anton_z
Я знаю что такое 2PC. Мне нужно было узнать, как правильно работать на шине с транзакциями с учетом того, что в обработчиках могут быть заведомо нетранзакционные операции.

Вы хотите сказать, что шину надо либо делать полностью транзакционной (все хендлеры можно откатить), либо вообще не рулить транзакциями в декораторе шины?

Re: DDD и транзакции

Добавлено: 2017.01.16, 15:21
zelenin
да никак. Нельзя работать с нетранзакционными операциями без знания ими о транзакциях. Поэтому транзакции делают уровнем ниже - в репозиториях - транзакционно сохраняя агрегат в Repository::save(...). Оборачивание же шины в транзакцию некорректно, т.к. внутри хэндлера происходят действия разных слоев.

Re: DDD и транзакции

Добавлено: 2017.01.16, 15:52
anton_z
Про правило одного агрегата слышал. А как же быть со всякими групповыми операциями, охватывающими более одного агрегата? Например, сразу отменить 10 заказов - пользователь отмечает галочками и жмакает на кнопку.

Re: DDD и транзакции

Добавлено: 2017.01.16, 15:59
zelenin
anton_z писал(а): 2017.01.16, 15:52 Про правило одного агрегата слышал. А как же быть со всякими групповыми операциями, охватывающими более одного агрегата? Например, сразу отменить 10 заказов - пользователь отмечает галочками и жмакает на кнопку.
этот пример не требует атомарности.