Русский вариант поста не исправляется, и возможно немного устарел, поэтому если вы видите явную ошибку, то гляньте английскую версию вернее всего там она исправлена. У меня просто нет времени на синхронизацию исправлений между двумя версиями.
Иногда в приложениях, нам необходимо создавать рабочие процессы исполнение которых занимает продолжительное время. Например, после создания документа отправить письмо менеджеру с ссылкой на подтверждение публикации. Это самый простой пример, в реальном мире рабочие процессы могут быть чрезвычайно сложны. В веб приложениях типичный пример это оформление покупок в интернет магазине. На рынке существуют готовые решения этой проблемы, например Microsoft Workflow Foundation. Но для некоторых задач это явный оверкил. В большинстве приложений, не требуется позволять пользователям создавать и редактировать свои процессы. Давайте же попробуем написать свое легковесное решение и для начала определим список требований.
Описание процесса должно быть похоже на простую функцию.
Процессы это композиция других процессов и активностей.
Активность, описывающая, что должно быть сделано в терминах доменной модели, должна быть простым POCO классом.
Конкретный код, который будет исполнять активности и процессы, не должен быть связан с процессом, и должен быть выделен в отдельную сущность.
Легкое сохранение и сериализация процесса.
Просмотр текущего состояния процесса и всех его выполненных шагов.
Легкость добавления новых возможностей. Например отмена последней осуществленной активности, таймауты и т. д.
Для начала определим классы активностей.
Action это базовый класс для всех активностей. Ask и Show просто два примера конкретных активностей. Пока все просто.
Теперь определим стратегию сохранения процессов.
Очень простой вспомогательный класс, который сериализует объекты в json и сохраняет в текстовый файл и также делает обратную операцию. Теперь определим класс процесса и шага процесса. Вернее всего мы будем использовать процессы примерно так:
Определить процесс
Создать экземпляр процесса
Вызвать метод Execute
Проверить результат, он может содержать конечный результат или активность для исполнения. В случае с результатом мы можем закончить исполнение, но в другом случае мы должны выполнить запрашиваемую активность.
Сохранить процесс в хранилище.
При получении результата от активности, загрузить процесс из хранилища и добавить результат.
Перейти к шагу 2.
Давайте опишем это в коде и определим поведение команд Ask и Show.
Метод GetResult это суть нашего решения, но в данный момент он не работает. Давайте посмотрим на этот метод внимательней и опишем поведение каждой строки.
Теперь мы можем выразить это в реальном коде.
Мы можем убрать код возвращения в специальную функцию return, но мы не можем убрать “if .. return ..” в какую либо функцию. Решением будет переписать код в стиле продолжений.
Теперь все компилиться, но это не выглядит как обычная функция c#. Можем ли мы сделать это лучше? Определенно да, наши функции return и bind это паттерн монады и мы можем, как и прежде, добавить синтаксический сахар linq.
Теперь мы можем переписать нашу функцию определения процесса.
Мы все еще не знаем как написать функции IsExecuted и GetResult класса WokflowStep. Мы должны где-то сохранять результаты уже выполненных активностей. Мы можем инкрементно присваивать номера на каждое создание активностей и использовать List как хранилище сериализованных результатов. Мы будем использовать класс ExecutionСontext который будет хранить результаты активностей и последовательно присваивать номера активностям.
Теперь мы должны добавить контекст в классы Workflow и WokflowStep.
Сделано. Теперь мы можем выразить композицию процессов.
Чтож, похоже мы смогли удовлетворить всем нашим требованиям из списка.
Теперь мы можем использовать этот движок в консольных приложениях, asp.net сайтах или даже интегрировать в SharePoint. И паттерн монады помог нам построить достаточно изящьное решение. Мы также можем добавить поддержку для циклов, условий(как), паралллельного исполнения, транзакций и многого другого. Полный код для этого поста.
P.S.
Просто вообразите возможность запускать вычисления поверх баз данных или веб сервисов без неудобностей типа IRepository, IService и т.п.