Альтернатива Обратным Звонкам

Предположим, что нам нужно сделать железнодорожное приложение, которое позволит нам создавать заказ, создавать одну или несколько услуг в зависимости от входных данных заказа и резервировать некоторые ресурсы для этих услуг.

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

Простой пример: вы приобретаете SIM-карту для своего мобильного телефона.

К данной SIM-карте подключены услуги голосовой связи, SMS и MMS, мобильный Интернет (который имеет свои тарифы), автоответчик, определитель номера и т. д. К моменту окончания обработки вашего договора (заказа) все эти услуги должны быть включены и работать.

бег.

Далее вы можете заключить дополнительное соглашение.

заключить договор и перейти на другой тариф мобильного интернета и т. д. Это всего лишь пример логики, на который я буду ссылаться для ясности.

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

Создан новый заказ - установите ему статус новый - и прикрепите обратный звонок, который начинает создавать сервисы и т.д. Дальше я попробую объяснить, почему это абсолютное зло.



Обратные вызовы не контролируются

Пока в вашем приложении 10-15 коллбеков, вы все равно можете ими как-то управлять.

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

Один коллбек меняет данные, из-за этого срабатывает второй коллбек, меняет другие данные — срабатывает третий и т. д. В результате непросто восстановить всю цепочку и понять, что произошло и почему.

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

Или сначала сработал первый, потом третий, потом пятый, а потом второй (нумерация здесь произвольная — просто для наглядности).

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



История обратных звонков

У вас есть ордер.

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

Теперь нужно разобраться не только в текущем коде, но и посмотреть, какая версия кода использовалась в момент, когда должен был сработать обратный вызов — предположений становится только больше.



Логические версии

Допустим, вам нужно изменить логику обратного вызова.

Например, раньше при выполнении какого-то условия создавался один тип сервиса, теперь другой.

А вот для заказов, которые уже обрабатываются (те, которые начали использовать старую логику и еще не полностью обработаны), нужно сохранить старую логику.

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

У меня был случай, когда бизнес-пользователи пытались предварительно оформить заказ, который «стоял» незавершенным 7 лет. Только представьте, сколько за это время изменилось логики и кода.



Обратные вызовы отслеживаются ужасно

Когда к вам приходит бизнес-пользователь и задает вопрос «почему этот сервис имеет здесь такое-то значение» — вы сталкиваетесь с первой проблемой — нужно посмотреть базу данных, логи и т. д. Даже понять, какой обратный вызов сработал и изменил интересующие вас данные, может оказаться очень сложной задачей и может занять не один день.

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

Даже если у вас есть журналы, по мере роста вашей системы вы будете все чаще полагаться на догадки.

Возможно, сработало несколько обратных вызовов и установили одно и то же значение для этой модели.

Тогда это совсем плохо.

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



Привязка модели

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

Сегодня у вас ордер TypeA, завтра он заменяется на ордер TypeB — а необходимые коллбэки не срабатывают — цепочка обработки заказа разрывается.

Исправить, конечно, можно — технически это ошибка кода, но на практике лучше построить свое приложение так, чтобы эта ошибка в принципе не могла возникнуть.

Далее, предположим, что у вас есть два состояния заказа — новый и завершенный, а также связанные с ними обратные вызовы.

Нужно усложнить логику — добавить обратные вызовы, пока логика обрабатывается.

Например, не удалось, обработать — и добавить к ним еще несколько коллбеков.

Заказ может несколько раз попасть в отказ во время своей обработки и может несколько раз уйти в обработку — и каждый раз обратные вызовы будут работать.

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

Количество обратных звонков растет. При этом нужно учитывать, оплачен заказ или нет — а это еще несколько коллбеков, привязанных к разным параметрам модели.

Пока заказ не оплачен (оплачен - одно поле заказа, статус - другое), услуги не могут быть запущены и т.д. - начинаешь придумывать новые состояния заказа почти-сделано-еще не оплачено и т.д. - начинаешь придумывать все новые и новые состояния, чтобы к ним можно было прикрепить логику.

Комбинаций состояний становится все больше и больше (оплачено/обработано, неоплачено/обработано и т. д.).

Обратные вызовы становятся все более сложными.



Никакой изоляции логики

Попробую объяснить – допустим, у вас есть услуга мобильного интернета.

Был первый заказ, по которому была запущена эта услуга, потом был второй заказ - смена тарифного плана - услуга по сути та же, но тарифицируется по-разному, у них разная скорость интернета и т.д. В обратных вызовах нельзя изолировать логику, связанную с первым заказом, от логики, связанной со вторым заказом — все завязано на модели сервиса как таковой.

Более того, есть бухгалтерия, бизнес-логика которой связана с оплатой заказа, есть технический отдел, который должен обеспечивать работу Интернета — у него бизнес-логика другая — и их фактически надо изолировать.

На обратных вызовах все завязано на модели заказа — и разделить обработку бухгалтерии и технического отдела уже невозможно.

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

.

Вывод простой: логика приложения не должна быть привязана к модели.



Альтернатива

Первое, что вам нужно понять, это то, что если у вас есть бизнес-логика, то привязывать ее нужно не к модели.

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

Не мудрствуя, давайте назовем эту сущность «процессом».

Вся бизнес-логика привязана к процессу — опционально — в виде операций.

Те.

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

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

Плюс - хотелось бы иметь возможность посмотреть еще не завершенные процессы - какие еще операции можно выполнять внутри процесса.

Если в процессе что-то сломалось, нужно исправить данные/код и иметь возможность запускать процесс дальше (а не менять состояние какого-то объекта через консоль, чтобы сработали какие-то колбеки и процесс пошел дальше).

Далее пользователь вовлекается в ваш процесс.

Если приложение основано на обратных вызовах, пользователя фактически выбрасывает из логики — ввел какой-то заказ, изменил какие-то данные — срабатывают обратные вызовы и заказ дальше обрабатывается.

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

В рамках «кастомной» операции пользователь может изменить данные по нескольким моделям.

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

В результате модели становятся проще и легче.

Я пока оставлю это.

Если тема интересна, то можно написать еще много чего.

Кому интересно, посмотрите драгоценный камень рельсы_workflow и задавать вопросы.

Теги: #ruby onrails #workflow #управление бизнес-процессами #ruby onrails

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.