Створення 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 і багато іншого.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.