Предположим, что нам нужно сделать железнодорожное приложение, которое позволит нам создавать заказ, создавать одну или несколько услуг в зависимости от входных данных заказа и резервировать некоторые ресурсы для этих услуг.
В процессе обработки заказ меняет свое состояние с нового на выполненное, при этом создается несколько сервисов (в зависимости от данных), которые должны быть запущены к моменту окончания обработки заказа.
Простой пример: вы приобретаете SIM-карту для своего мобильного телефона.
К данной SIM-карте подключены услуги голосовой связи, SMS и MMS, мобильный Интернет (который имеет свои тарифы), автоответчик, определитель номера и т. д. К моменту окончания обработки вашего договора (заказа) все эти услуги должны быть включены и работать.
бег.
Далее вы можете заключить дополнительное соглашение.
заключить договор и перейти на другой тариф мобильного интернета и т. д. Это всего лишь пример логики, на который я буду ссылаться для ясности.
Подавляющее большинство программистов начнут создавать такие приложения, используя обратные вызовы или триггеры.
Создан новый заказ - установите ему статус новый - и прикрепите обратный звонок, который начинает создавать сервисы и т.д. Дальше я попробую объяснить, почему это абсолютное зло.
Обратные вызовы не контролируются
Пока в вашем приложении 10-15 коллбеков, вы все равно можете ими как-то управлять.Как только моделей в приложении становится больше, количество обратных вызовов увеличивается.
Один коллбек меняет данные, из-за этого срабатывает второй коллбек, меняет другие данные — срабатывает третий и т. д. В результате непросто восстановить всю цепочку и понять, что произошло и почему.
Я уже молчу о таких опциях, как зацикливание колбеков — сработал первый, сработал второй… сработал десятый и в результате его изменений первый колбек снова заработал.
Или сначала сработал первый, потом третий, потом пятый, а потом второй (нумерация здесь произвольная — просто для наглядности).
Ни о какой контролируемой последовательности операций обратного вызова речи не идет. Каждый сам за себя.
История обратных звонков
У вас есть ордер.Оно было частично обработано, после чего вы выкатили очередное обновление приложения — изменилась логика — к вам приходит пользователь и задает тот же вопрос — «а почему здесь такое значение».
Теперь нужно разобраться не только в текущем коде, но и посмотреть, какая версия кода использовалась в момент, когда должен был сработать обратный вызов — предположений становится только больше.
Логические версии
Допустим, вам нужно изменить логику обратного вызова.Например, раньше при выполнении какого-то условия создавался один тип сервиса, теперь другой.
А вот для заказов, которые уже обрабатываются (те, которые начали использовать старую логику и еще не полностью обработаны), нужно сохранить старую логику.
Ваш колбэк растёт — вам нужно реализовать новую логику и сохранить старую, которая срабатывает для ордеров, запущенных, например, до определённой даты.
У меня был случай, когда бизнес-пользователи пытались предварительно оформить заказ, который «стоял» незавершенным 7 лет. Только представьте, сколько за это время изменилось логики и кода.
Обратные вызовы отслеживаются ужасно
Когда к вам приходит бизнес-пользователь и задает вопрос «почему этот сервис имеет здесь такое-то значение» — вы сталкиваетесь с первой проблемой — нужно посмотреть базу данных, логи и т. д. Даже понять, какой обратный вызов сработал и изменил интересующие вас данные, может оказаться очень сложной задачей и может занять не один день.На самом деле, сколько бы вы ни копались в логах, в результате можно только предположить, что «этот колбек сработал из-за того-то и того-то».
Даже если у вас есть журналы, по мере роста вашей системы вы будете все чаще полагаться на догадки.
Возможно, сработало несколько обратных вызовов и установили одно и то же значение для этой модели.
Тогда это совсем плохо.
Сложность анализа, воспроизведения и устранения проблемы растет в геометрической прогрессии.
Привязка модели
Мой «любимый» грех обратных вызовов — то, что они жестко привязаны к модели.Сегодня у вас ордер TypeA, завтра он заменяется на ордер TypeB — а необходимые коллбэки не срабатывают — цепочка обработки заказа разрывается.
Исправить, конечно, можно — технически это ошибка кода, но на практике лучше построить свое приложение так, чтобы эта ошибка в принципе не могла возникнуть.
Далее, предположим, что у вас есть два состояния заказа — новый и завершенный, а также связанные с ними обратные вызовы.
Нужно усложнить логику — добавить обратные вызовы, пока логика обрабатывается.
Например, не удалось, обработать — и добавить к ним еще несколько коллбеков.
Заказ может несколько раз попасть в отказ во время своей обработки и может несколько раз уйти в обработку — и каждый раз обратные вызовы будут работать.
Потом оказывается, что логика после сбоя другая и появляется статус повторной обработки (к примеру) и логика становится еще больше.
Количество обратных звонков растет. При этом нужно учитывать, оплачен заказ или нет — а это еще несколько коллбеков, привязанных к разным параметрам модели.
Пока заказ не оплачен (оплачен - одно поле заказа, статус - другое), услуги не могут быть запущены и т.д. - начинаешь придумывать новые состояния заказа почти-сделано-еще не оплачено и т.д. - начинаешь придумывать все новые и новые состояния, чтобы к ним можно было прикрепить логику.
Комбинаций состояний становится все больше и больше (оплачено/обработано, неоплачено/обработано и т. д.).
Обратные вызовы становятся все более сложными.
Никакой изоляции логики
Попробую объяснить – допустим, у вас есть услуга мобильного интернета.Был первый заказ, по которому была запущена эта услуга, потом был второй заказ - смена тарифного плана - услуга по сути та же, но тарифицируется по-разному, у них разная скорость интернета и т.д. В обратных вызовах нельзя изолировать логику, связанную с первым заказом, от логики, связанной со вторым заказом — все завязано на модели сервиса как таковой.
Более того, есть бухгалтерия, бизнес-логика которой связана с оплатой заказа, есть технический отдел, который должен обеспечивать работу Интернета — у него бизнес-логика другая — и их фактически надо изолировать.
На обратных вызовах все завязано на модели заказа — и разделить обработку бухгалтерии и технического отдела уже невозможно.
Бухгалтерия может оплачивать услугу несколькими платежами - несколькими «процессами» - и за это же время технический отдел может несколько раз запускать техподдержку и некоторые сервисные операции для оказания одной и той же услуги, и вся эта логика не должна быть привязана к модели.
.
Вывод простой: логика приложения не должна быть привязана к модели.
Альтернатива
Первое, что вам нужно понять, это то, что если у вас есть бизнес-логика, то привязывать ее нужно не к модели.Есть поток обработки заказов, а значит, нам нужна не модель, а сущность, к которой все это привязано.
Не мудрствуя, давайте назовем эту сущность «процессом».
Вся бизнес-логика привязана к процессу — опционально — в виде операций.
Те.
технически у вас должна быть возможность открыть процессы, изменившие порядок - и посмотреть, когда они были созданы, как обрабатывались, какие пользователи были задействованы, что было изменено и что было сделано, какие операции были обработаны (и почему) - т.е.
у вас должны быть не просто предположения, а точная история процесса.
Плюс - хотелось бы иметь возможность посмотреть еще не завершенные процессы - какие еще операции можно выполнять внутри процесса.
Если в процессе что-то сломалось, нужно исправить данные/код и иметь возможность запускать процесс дальше (а не менять состояние какого-то объекта через консоль, чтобы сработали какие-то колбеки и процесс пошел дальше).
Далее пользователь вовлекается в ваш процесс.
Если приложение основано на обратных вызовах, пользователя фактически выбрасывает из логики — ввел какой-то заказ, изменил какие-то данные — срабатывают обратные вызовы и заказ дальше обрабатывается.
В случае с процессом можно ввести такое понятие, как «пользовательская операция» — и сказать, что какой-то процесс ожидает определенных пользовательских операций, что в ходе процесса были созданы и выполнены следующие пользовательские операции.
В рамках «кастомной» операции пользователь может изменить данные по нескольким моделям.
Далее нам нужна некая конфигурация процесса — возможность указать, какие операции он содержит, зависимости операций и т. д. Процесс и операции должны иметь собственную область данных — некие промежуточные переменные процесса, позволяющие не загружать модели данными, необходимыми только процессам.
В результате модели становятся проще и легче.
Я пока оставлю это.
Если тема интересна, то можно написать еще много чего.
Кому интересно, посмотрите драгоценный камень рельсы_workflow и задавать вопросы.
Теги: #ruby onrails #workflow #управление бизнес-процессами #ruby onrails
-
Напряжение Без Контакта
19 Oct, 24 -
Гомо-Вики
19 Oct, 24 -
Вертикальные Вкладки В Chrome-Dev
19 Oct, 24 -
Твиттер + Динамический Ip
19 Oct, 24