Подскажите вы имеет ввиду проблему связанную с возможными ошибками? Если да, то об этом в статье упомяналось. Обработка ошибок намерено не была добавлена, чтобы не усложнять пример.
… а если у вас случилась произвольная ошибка, откатывать не надо? А ресурсы отпускать тоже не надо?
Если необходимо обрабатывать все ошибки, в классе OrderService необходимо будет добавить возможность отправки события о том, что произошла ошибка и вызывать событие onFailedOrderCreation в котором можно выполнить rollback.
И это мы еще не перешли к тому моменту, что вам надо обрабатывать ошибки внутри самих наблюдателей...
Нет никаких препятствий для обработки ошибок внутри наблюдателя. Там есть все знания о текущем процессе + знание инфраструктуры.
Проблема в том, что код OrderService больше не может быть уверенным в этой консистентности.
Почему это проблема? Мы можем организовывать код так, чтобы часть его не заботилась о консистентности. То есть открывая класс OrderService мы можем быть уверены, что не увидим множество инфтраструктурных задач, которые необходимо решить. Тем самым мы разбиваем ответственность разных частей кода.
Так это и плохо. Вы не можете забыть про конкуренцию, она меняет поведение системы.
Поведение всей системы в целом она безусловно меняет. Но разве это не преимущество, что появляется возможность организовывать кодовую базу так, чтобы некоторые модули были менее восприимчивы к наличию или отсутствию конкуренции.
То есть организовав код так, что работа с транзакциями была вынесена в инфраструктурный модуль, получилось добиться того, что наличие или отсутствие конкуренции в большей степени влияет только на соответствующие инфраструктурный модуль и в значительно меньшей степени на модуль core.
Нет, не будет. Дедлоки возникают из-за порядка захвата ресурсов внутри потребителя (один потребитель сначала читает — и блокирует — пользователя, а потом продукты, а второй сначала читает продукты, но уже не может заблокировать пользователя), а наблюдатель ничего про это не знает.
В том числе из-за порядка захвата ресурсов. Если все инфраструктурные запросы выполнять на уровне наблюдателя как это было продемонтрировано в последнем 6 разделе, то появляется возможность контролировать даже порядок запросов не меняя ничего в OrderService. Даже в крайнем случае упорядочевание запросов возможно проводить в OrderService, если не отказываться от интерфейсов репозиториев, при этом никак не раскрывая инфраструктуру.
Нет, вопрос в том, нужно ли нам в основной логике говорить о том, что нам нужны консистентные данные.
Надеюсь мы не расходимся во мнении о том, что данные в примере все равно будут консистентны, если предположить, что для этого было достаточно только открыть транзакцию.
OrderService не знает работает ли он вообще в какой-либо конкуренции с кем-либо. И тут я бы ответил, что это удобно, когда мы можем выделить код, который не зависит от инфраструктуры. Задачу по конкуренции приложение в целом решает. Но её не решает OrderService, а решает инфраструктурный модуль базы данных.
Консистентность — это не инфраструктурное знание.
С этим нет противоречий. Управление транзакциями - это инфраструктурное знание и механизм, позволяющий добиться консистентности. В приведенных примерах нет борьбы против знания о консистентности, в приведенных примерах борьба против явного использования инфраструктурных механизмов.
Это вам кажется, что она слабо повлияла. На самом же деле, если у вас не было транзакции, а вы ее через свой подписчик добавили, то у вас внезапно могут начаться дедлоки (и поэтому код в модуле core должен ее учитывать), а если у вас была транзакция, а вы ее убрали, то у вас внезапно данные могут перестать быть консистентными (и код в core тоже должен это учитывать).
Прошу вас привести конкретный пример. Если из-за транзакции могут возникнуть дедлоки, не будет ли возможности решить все эти проблемы внутри наблюдателя ...ObserverImpl, так чтобы это существенно не отразилось на основной сервис?
Более того, и вы это упоминаете в посте, чтобы была транзакция, должен быть не только start/end, а еще и откат при ошибке — и это тоже связность, которая все затрудняет.
Данный пример, если бы нужно было мог быть расширен дополнительным событием перед отправкой исключения. Например, afterCheckingUserBalanceFailed, для которого в наблюдателе CreateOrderObserverImpl был бы вызван TransactionManagerImpl.rollback()
Так это как раз неправильно. Сервису заказов нужна транзакция. Он, сервис, хочет быть уверенным, что данные консистентны. Так зачем вы это прячете?
Согласен с вами, что если мы посмотрим на всё приложение целиком, то оно должно гарантировать, что данные консистенты и что нужна транзакция. Но вопрос нужно ли нам в основной логике говорить о том, что нужна транзакция?
Тут зависит от того, что вы хотите видеть в основной логике. Хотите ли вы видеть задачи связанные с управлением транзакцией, решение задачи по трассировке запросов, решение задачи по идемпотентности и реализации request-reply с correlationId в основной логике?
Если да, то в этом случае вам не стоит прятать через шаблон наблюдателя все эти инфраструктурные задачи.
Если нет, то шаблон наблюдатель позволит это скрыть для того, чтобы в основной логике остался только код, реализующий поставленные бизнес-требования.
То есть класс OrderService не ожидает, что данные будут консистентны внутри запросов? И не защищается от блокировок, которые могут возникать благодаря транзакциям?
Тогда зачем вы изначально добавили транзакцию?
Идея в том, чтобы в классе OrderService была только основная логика, лишенная инфраструктурных знаний. И в этом смысле это верно, OrderService не пытается решить задачу по консистентности данных. Он делигирует эту задачу в инфраструктурный модуль.
Проблема в том, что это очень плохой пример. Транзакция слишком сильно влияет на код, чтобы ее можно было игнорировать.
В данном примере транзакция слабо повлияла на код в модуле core, чего и хотелось добиться. В этом отчасти смысл всех этих приемов, чтобы достигнуть возможности написания кода, который не будет зависеть от инфраструктуры. Могли бы вы превисти пример, который бы продемонстрировал невозможность применения представленных приемов в контексте использования транзакций?
Вот только семантика у событий "начало"-"конец" и у транзакций — разная, поэтому вы не можете заменить второе на первое без потери смысла.
Уточню некоторый момент, чтобы быть уверенным, что мы одинаково пониманием контекст. В разделе 3 реализация TransactionManagerImpl осталась без изменений с семантикой методов begin и commit, а интерфейс TransactionManager был удален и заменён на CreateOrderObserver, у которого в реализации в обработке методов onStart и onEnd в классе CreateOrderObserverImpl вызываются методы begin и commit от класса TransactionManagerImpl.
Если мы говорим про изменения в классе OrderService, то как раз вы говорите верно, что с точки зрения основной логики меняется смысл. Как раз этого эффекта и хочется достигнуть, чтобы смысл вызываемых методов не был никак связан с управлением транзакцией.
Фактически, ваш OrderService все равно знает, что там внутри транзакция, просто она называется иначе; а CreateOrderObserverImpl знает, где конкретно вызываются эти события, чтобы сделать транзакцию — и тем самым эти модули стали тесно связаны, только через очень неявный интерфейс.
Прошу вас уточнить, что вы подразумеваете под "знает"? Если смотреть только на код модуля core и класс OrderService в частности отсутствует какое-либо знание о том, что будет открыта и зафиксирована транзакция. Также отсутствует знание о том, как это будет сделано, а именно какие нужно передавать значения в вызоваемые методы. Единственное знание, которое есть в OrderService, это то, что будет отправлено событие всем наблюдателям по ходу выполнения процесса.
Обратите внимание, когда есть прямой вызов метода, то мы отправляем команду в класс/модуль/сервис, который вызываем. Мы знаем кого вызываем и знаем, что необходимо сделать. Когда вместо прямого вызова происходит отправка события, то сторона, которая отправляет событие не знает, как и кем оно будет обработано.
Транзакция сама по себе достаточно понятная абстракция, не надо ее заменять на события. Если вам очень не нравятся транзакции — есть Unit of Work.
Согласен с вами, транзакция достаточно понятный механизм. Тут больше вопрос в том, что если мы хотим в основной логике видеть только код, который лишен каких-либо инфраструктурных знаний, то как это сделать. Примеры с транзакцией выбраны как наиболее удобные, чтобы не усложнять. Во второй части им будет найдено более подходящее место.
Тогда я вас попрашу показать, так как мне не удается увидеть проблему
Подскажите вы имеет ввиду проблему связанную с возможными ошибками? Если да, то об этом в статье упомяналось. Обработка ошибок намерено не была добавлена, чтобы не усложнять пример.
Есть ли какие-либо противоречия с таким решением?
Если необходимо обрабатывать все ошибки, в классе OrderService необходимо будет добавить возможность отправки события о том, что произошла ошибка и вызывать событие onFailedOrderCreation в котором можно выполнить rollback.
Нет никаких препятствий для обработки ошибок внутри наблюдателя. Там есть все знания о текущем процессе + знание инфраструктуры.
Почему это проблема? Мы можем организовывать код так, чтобы часть его не заботилась о консистентности. То есть открывая класс OrderService мы можем быть уверены, что не увидим множество инфтраструктурных задач, которые необходимо решить. Тем самым мы разбиваем ответственность разных частей кода.
Поведение всей системы в целом она безусловно меняет. Но разве это не преимущество, что появляется возможность организовывать кодовую базу так, чтобы некоторые модули были менее восприимчивы к наличию или отсутствию конкуренции.
То есть организовав код так, что работа с транзакциями была вынесена в инфраструктурный модуль, получилось добиться того, что наличие или отсутствие конкуренции в большей степени влияет только на соответствующие инфраструктурный модуль и в значительно меньшей степени на модуль core.
В том числе из-за порядка захвата ресурсов. Если все инфраструктурные запросы выполнять на уровне наблюдателя как это было продемонтрировано в последнем 6 разделе, то появляется возможность контролировать даже порядок запросов не меняя ничего в OrderService. Даже в крайнем случае упорядочевание запросов возможно проводить в OrderService, если не отказываться от интерфейсов репозиториев, при этом никак не раскрывая инфраструктуру.
Надеюсь мы не расходимся во мнении о том, что данные в примере все равно будут консистентны, если предположить, что для этого было достаточно только открыть транзакцию.
OrderService не знает работает ли он вообще в какой-либо конкуренции с кем-либо. И тут я бы ответил, что это удобно, когда мы можем выделить код, который не зависит от инфраструктуры. Задачу по конкуренции приложение в целом решает. Но её не решает OrderService, а решает инфраструктурный модуль базы данных.
С этим нет противоречий. Управление транзакциями - это инфраструктурное знание и механизм, позволяющий добиться консистентности. В приведенных примерах нет борьбы против знания о консистентности, в приведенных примерах борьба против явного использования инфраструктурных механизмов.
Прошу вас привести конкретный пример. Если из-за транзакции могут возникнуть дедлоки, не будет ли возможности решить все эти проблемы внутри наблюдателя ...ObserverImpl, так чтобы это существенно не отразилось на основной сервис?
Данный пример, если бы нужно было мог быть расширен дополнительным событием перед отправкой исключения. Например, afterCheckingUserBalanceFailed, для которого в наблюдателе CreateOrderObserverImpl был бы вызван TransactionManagerImpl.rollback()
Согласен с вами, что если мы посмотрим на всё приложение целиком, то оно должно гарантировать, что данные консистенты и что нужна транзакция. Но вопрос нужно ли нам в основной логике говорить о том, что нужна транзакция?
Тут зависит от того, что вы хотите видеть в основной логике. Хотите ли вы видеть задачи связанные с управлением транзакцией, решение задачи по трассировке запросов, решение задачи по идемпотентности и реализации request-reply с correlationId в основной логике?
Если да, то в этом случае вам не стоит прятать через шаблон наблюдателя все эти инфраструктурные задачи.
Если нет, то шаблон наблюдатель позволит это скрыть для того, чтобы в основной логике остался только код, реализующий поставленные бизнес-требования.
Идея в том, чтобы в классе OrderService была только основная логика, лишенная инфраструктурных знаний. И в этом смысле это верно, OrderService не пытается решить задачу по консистентности данных. Он делигирует эту задачу в инфраструктурный модуль.
В данном примере транзакция слабо повлияла на код в модуле core, чего и хотелось добиться. В этом отчасти смысл всех этих приемов, чтобы достигнуть возможности написания кода, который не будет зависеть от инфраструктуры. Могли бы вы превисти пример, который бы продемонстрировал невозможность применения представленных приемов в контексте использования транзакций?
Уточню некоторый момент, чтобы быть уверенным, что мы одинаково пониманием контекст. В разделе 3 реализация TransactionManagerImpl осталась без изменений с семантикой методов begin и commit, а интерфейс TransactionManager был удален и заменён на CreateOrderObserver, у которого в реализации в обработке методов onStart и onEnd в классе CreateOrderObserverImpl вызываются методы begin и commit от класса TransactionManagerImpl.
Если мы говорим про изменения в классе OrderService, то как раз вы говорите верно, что с точки зрения основной логики меняется смысл. Как раз этого эффекта и хочется достигнуть, чтобы смысл вызываемых методов не был никак связан с управлением транзакцией.
Прошу вас уточнить, что вы подразумеваете под "знает"? Если смотреть только на код модуля core и класс OrderService в частности отсутствует какое-либо знание о том, что будет открыта и зафиксирована транзакция. Также отсутствует знание о том, как это будет сделано, а именно какие нужно передавать значения в вызоваемые методы. Единственное знание, которое есть в OrderService, это то, что будет отправлено событие всем наблюдателям по ходу выполнения процесса.
Обратите внимание, когда есть прямой вызов метода, то мы отправляем команду в класс/модуль/сервис, который вызываем. Мы знаем кого вызываем и знаем, что необходимо сделать. Когда вместо прямого вызова происходит отправка события, то сторона, которая отправляет событие не знает, как и кем оно будет обработано.
Согласен с вами, транзакция достаточно понятный механизм. Тут больше вопрос в том, что если мы хотим в основной логике видеть только код, который лишен каких-либо инфраструктурных знаний, то как это сделать. Примеры с транзакцией выбраны как наиболее удобные, чтобы не усложнять. Во второй части им будет найдено более подходящее место.