Создание User-Friendly движка бизнес-процессов на основе Windows Workflow Foundation

    Постановка задачи




    Одной из неотъемлемых частей любой ECM-системы является управление бизнес-процессами, или workflow.

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

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

    Это диктует некоторые требования, которые предъявлялись к движку бизнес-процессов:
    • Процессы должны разрабатываться на основе высокоуровневых блоков. Примером такого блока может быть создание задания на согласование документа, старт подзадачи, выполнение произвольного куска кода и т.д.
    • При изменении схемы процесса нужно обеспечить возможность конвертации уже запущенных процессов на новую версию схемы.

    При разработке новой версии движка бизнес-процессов мы решили попробовать Windows Workflow Foundation (далее WF).

    Разработка на основе высокоуровневых блоков


    Упрощение разработки бизнес-процессов


    Каждый высокоуровневый блок маршрута может состоять из большого количества Activity (Например, для блока задания нужно 68 активностей). Это связано с тем, что каждый блок имеет несколько событий, в обработчики которых можно писать код. Так же для каждой части блока (события, внутренняя логика блока) должна работать обработка ошибок. Обработка эта делает следующее: если было брошено исключение, то оно анализируется, и в некоторых случаях нужно не прерывать процесс, а попытаться еще раз через некоторое время. Причем время ожидания до следующей попытки постепенно возрастает от 5 минут до 1 часа. Это нужно для ситуаций, когда не удалось совершить операцию из-за проблем со связью, таймаута SQL сервера и т.д.

    Можно было бы сделать блоки составными активностями, но WF не позволяет делать активности с несколькими исходящими стрелками. Например, блок маршрута «Задание на согласование документа» должен выглядеть следующим образом:



    А WF позволяет сделать только так:



    Причем еще придется делать переменную и передавать через нее результат выполнения задания.
    Вторая проблема – блоки, выполняемые параллельно. Единственный способ сделать это в WF – использовать блок Parallel. Но тогда вместо интуитивного:



    Мы получаем:


    Все это привело нас к тому, что нам не достаточно активностей WF как таковых, нам нужна схема более высокого порядка, которая описывает маршрут «сверху». При разработке маршрута используются наши классы блоков (никак не связанные с WF), а уже потом готовая схема конвертируется в Activity. Схемы процессов хранятся в виде XML, генерация Activity происходит в момент публикации маршрута на сервер приложения. Кроме блоков схемы содержат связи между блоками (стрелки из одного блока в другой).

    Преобразование блоков в Activity


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

    public override System.Activities.Activity BuildContent()
    {
      var result = new Variable<bool>(this.Block.ResultVariableName);
      
      return new Sequence()
       {
    	 Variables =
    	{
    	  result
    	},
    	 Activities =
    	{
    	  new Assign
    	  {
    		To = new OutArgument<bool>(this.result), 
    		Value = new InArgument<bool>(false)
    	  },
    	  //...          
    	  new Persist()
    	}
       };
    }
    

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

    Единственная сложность в конвертации маршрута, описанного нашими блоками, состоит в параллельных ветках. Такие ветки маршрута обрабатываются отдельно, потом результат объединяется в Parallel.

    Конвертация уже запущенного процесса на новую схему


    Конвертация в WF


    Конвертация процесса WF происходит в несколько этапов:

    1. Для старой версии Activity вызывается InstanceConverter.PrepareForUpdate. Этот вызов кэширует текущее описание схемы в нее саму же.
    2. Activity модифицируется.
    3. Для модифицированной Activity вызывается DynamicUpdateServices.CreateUpdateMap. Этот вызов создает UpdateMap – карту изменений, на основе которой конвертируются запущенные экземпляры схемы.
    4. При загрузке сохраненного экземпляра в WorkflowApplication указывается карта изменений.

    Основная проблема здесь – невозможность создать UpdateMap на основе двух Activity. Т.е. если на одном сервере развернута версия 1, на другом – версия 2, на третьем – версия 3, то обновиться на версию 5 будет проблематично. Еще сложнее будет обновить первый сервер с версии 1 на версию 4.

    Как мы решаем проблему с конвертацией


    Схемы на сервере хранятся в виде XML, в котором лежат наши блоки, а не активности. Таким образом, конвертировать нужно с одной версии нашего представления маршрута на другую. Это происходит так:

    1. Для старой версии строится Activity.
    2. Для построенной Activity вызывается InstanceConverter.PrepareForUpdate.
    3. Строится дифф между старой версией маршрута и новой. Он состоит из добавленных, удаленных, измененных блоков и связей. Для построения корректного диффа у каждого блока и каждой связи есть свой уникальный ИД.
    4. По этому диффу изменяется подготовленная Activity.
    5. Строится карта изменений.
    6. Каждый инстанс маршрута загружается с этой картой изменений и сразу же выгружается. Это делается сразу для всех экземпляров, чтобы карта изменений использовалась ровно один раз.

    Изменение Activity в пункте 4 происходит так: сгенерированная для блока активность упаковывается в FlowStep (если из блока выходит несколько стрелок с условиями, то после FlowStep генерируется FlowDecision). При изменении/добавлении/удалении связей изменяются значения свойств FlowStep.Next.

    Каждый блок хранится в переменной в схеме в сериализованном виде. При изменении свойств блока меняется дефолтное значение этой переменной.

    При добавлении блока генерируется соответствующий набор активностей и вставляется в нужное место схемы. Удаление блока – это просто очистка FlowStep.Next, который в него ведет.

    Конвертация при изменении генерируемых активностей


    Кроме изменения бизнес-процесса, конвертация может потребоваться и при изменении генерируемых для блока активностей. Например, если нужно добавить новый функционал в блок, или просто исправить баг. Мы сделали это так:
    Каждая схема маршрута хранит версию алгоритма генерации Activity.
    При изменении логики генерации активности для блока версия увеличивается, а конвертер учится конвертировать активности этого блока со старого варианта на новый.
    При конвертации маршрута конвертер конвертирует активности блоков, для которых изменилась логика генерации (определяется по версии схемы).
    Единственная особенность – конвертация так же должна проходить в виде изменения существующих активностей, а не генерации с нуля, иначе UpdateMap не подхватится.

    Заключение


    После прочтения статьи может создаться впечатление, что мы зря использовали Workflow Foundation – это не так. Благодаря использованию WF мы получили из коробки хостинг, хранение экземпляров процессов, всю логику выполнения процессов, в том числе и параллельного.

    В статье описано лишь решение проблемы низкоуровневости WF. За кадром остались вопросы хостинга процессов, проблемы конвертации некоторых наборов Activity и многое другое.

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

    Интересна ли эта тема и стоит ли продолжать?

    НПО «Компьютер»
    32,00
    Компания
    Поделиться публикацией

    Комментарии 11

      +4
      Откуда брали информацию по конвертации уже запущенных в работу инстансов?
      +1
      Что использовалось раньше в качестве движка для workflow? В какой версии произошел (или пока не произошел) переход? Чем не устраивал «старый»? Потребителю (пользователю системы) переход на новый движок что-то даст?
        0
        Сейчас используется самописный движок.
        Движок на WF — лабораторное решение, до продакшена пока не дошел.
        +1
        Три года мучились с WF (правда для .Net 3.5). Основной проблемой — была невозможность менять маршрут без deploy (система используется 24/7). Ну, и несколько других меньших. В результате, за пару месяцев изобрели велосипед, на который не нарадуемся. Однако, в любом случае, тема все же интересная.
          0
          Абсолютно аналогичная история. У WF к тому же есть четкое ограничение производительности, на которое можно достаточно быстро наткнуться.
            0
            Да, с десериализацией тоже были проблемы, правда смогли их обойти без выноса кода: при невозможности десериализации, движок убивал старые данные и создавал уже новый объект, переводя его в последнее состояние старого. Однако для нормальной работы пришлось некоторые данные дублировать в обычных таблицах.
          0
          После прочтения статьи может создаться впечатление, что мы зря использовали Workflow Foundation – это не так. Благодаря использованию WF мы получили из коробки хостинг, хранение экземпляров процессов, всю логику выполнения процессов, в том числе и параллельного.

          А можете рассказать, какая у Вас нагрузка на систему? И сколько лет она уже живет? Ведь эти модули в Workflow сделаны далеко не самым лучшим образом.
          Просто по пунктам:
          • хостинг — что здесь имеется ввиду? Workflow запускается после метода StartRuntime. Функция получения сообщения сводится к простейшим действиям: забрать объект из базы, десериализовать его, найти структуру, которая относится к Activity, вызвать у неё метод, <Ваша бизнес-логика>, сериализовать состояние, сохранить данные в БД. Не так много работы, если в итоге Вы получаете полностью подконтрольное поддерживаемое решение..
          • хранение экземпляров процессов — дело в том, что эта система работает не слишком эффективно. Если у Вас будет расти нагрузка, то Вам придется её переделывать.
          • «всю логику выполнения процессов, в том числе и параллельного» — а логика простая: блокируем Instance, выполняем у него последовательно все задачи, которые требуется, сохраняем его. Внутри одного Workflow Instance кооперативная многозадачность, сложного здесь ничего нет.


          Вы можете увидеть комментарии выше — Workflow Runtime неоднократно уже переписывался разными людьми, причем общая причина одна: что-то работает не так, как требуется, а код менять нельзя. Просто, скорее всего, Вам придется заняться этим в дальнейшем. Получается, что Workflow для Вас — это временное решение, чтобы побыстрее сделать релиз, а дальше продолжать уже создавать своё, заменяя классы MS.
            +1
            Выше уже писал, что движок пока не используется в продакшене. На тестах проблем с производительностью не было.
            В СЭД бизнес-процессы долгоиграющие и нет разницы, отработает WF за 10 миллисекунд или 10 секунд.

            Насчет переписывания — возможно потом мы и доберемся до ограничений WF, пока нас все устраивает.
              0
              Когда-то имел опыт работы со Staffware — уже отживший своё workflow движок. Так вот там workitem из состояния в состояние мог переходить и минут 10-15, без видимых причин. Но зато движок мог обрабатывать одновременно миллионы workitems.
            0
            Модуль Maestro для Drupal, слегка доработанный, достаточно неплохо справляется с поставленными задачами. Да, можно сказать, велосипед, но он едет и, главное, устраивает заказчика.

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

            Самое читаемое