Formulation of the problem

One of the integral parts of any
ECM system is business process management, or workflow.
Business processes in each individual organization have many nuances. They are constantly changing due to changes within the organization, changes in legislation, etc. Therefore, it is cheaper and more logical to engage in the development of business processes either analysts or programmers who specialize in business logic. So, creating and changing business processes should be as simple and convenient as possible.
')
Also, when a process is changed, already running processes should work correctly. It is impossible to stop a long and complicated agreement negotiation only because now the agreed document should be printed out not by the initiator of the agreement, but by the secretary.
This dictates some of the requirements for the business process engine:
- Processes should be developed on the basis of high-level blocks. An example of such a block could be the creation of a task for the coordination of a document, the start of a subtask, the execution of an arbitrary piece of code, etc.
- When changing the process scheme, it is necessary to ensure the possibility of converting already running processes to the new version of the scheme.
When developing a new version of the business process engine, we decided to try
Windows Workflow Foundation (hereinafter referred to as WF).
Development on the basis of high-level blocks
Simplify business process design
Each high-level block of the route may consist of a large number of Activities (For example, for a task block, 68 activities are needed). This is due to the fact that each block has several events in which handlers you can write code. Also for each part of the block (events, internal logic of the block) error handling should work. This treatment does the following: if an exception was thrown, then it is analyzed, and in some cases it is necessary not to interrupt the process, but try again after a while. And the waiting time until the next attempt gradually increases from 5 minutes to 1 hour. This is necessary for situations where it was not possible to perform the operation due to communication problems, SQL server timeout, etc.
It would be possible to make blocks with composite activities, but WF does not allow doing activities with several outgoing arrows. For example, the block of the “Task for document approval” route should look like this:

And WF allows you to do this:

And you still have to make a variable and pass through it the result of the task.
The second problem is blocks running in parallel. The only way to do this in WF is to use the Parallel block. But then instead of the intuitive:

We get:

All this led us to the fact that we do not have enough WF activities as such, we need a higher order scheme that describes the route “from above”. When developing a route, our block classes are used (not related to WF), and only then the finished scheme is converted into an Activity. Process diagrams are stored in the form of XML, the generation of the Activity occurs at the moment of publishing the route to the application server. In addition to the blocks, the circuits contain links between the blocks (arrows from one block to another).
Block Conversion to Activity
For each block there is a pair class of the builder that generates activity. It looks like this:
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) },
We do not use compound activities in order to have no conversion problems.
The only difficulty in converting the route described by our blocks is in parallel branches. Such branches of the route are processed separately, then the result is combined into Parallel.
Converting an already running process to a new scheme
Convert to WF
The WF process is converted in several steps:
- For the old version of Activity, InstanceConverter.PrepareForUpdate is called. This call caches the current schema description in it itself.
- Activity is modified.
- For a modified Activity, DynamicUpdateServices.CreateUpdateMap is called. This call creates UpdateMap, a map of changes on the basis of which the running schema instances are converted.
- When loading a saved instance in WorkflowApplication, a map of changes is indicated.
The main problem here is the inability to create UpdateMap based on two Activities. Those. if version 1 is deployed on one server, version 2 is on the other, version 3 is on the third, then upgrading to version 5 will be problematic. It will be even more difficult to upgrade the first server from version 1 to version 4.
How do we solve the problem with conversion
Schemas on the server are stored in the form of XML, in which our blocks lie, and not the activity. Thus, it is necessary to convert from one version of our presentation of the route to another. It happens like this:
- For the old version is built Activity.
- For a built Activity, InstanceConverter.PrepareForUpdate is called.
- Diff is built between the old version of the route and the new one. It consists of added, removed, modified blocks and links. Each block and each link has its own unique ID for building the correct diff.
- According to this diff, the prepared Activity changes.
- The map of changes is under construction.
- Each route instance is loaded with this change map and immediately unloaded. This is done at once for all instances so that the change map is used exactly once.
Changing the Activity in clause 4 is as follows: the activity generated for the block is packed into FlowStep (if several arrows with conditions go out of the block, FlowDecision is generated after FlowStep). When you change / add / delete links, the values ​​of the properties of FlowStep.Next change.
Each block is stored in a variable in the schema in serialized form. When the block properties change, the default value of this variable changes.
When a block is added, the corresponding set of activities is generated and inserted into the right place of the scheme. Removing a block is simply cleaning up the FlowStep.Next that leads to it.
Conversion when changing generated activities
In addition to changing the business process, conversion may be required when changing the activities generated for a block. For example, if you need to add a new functionality to the block, or just fix the bug. We did it like this:
Each route map stores a version of the Activity generation algorithm.
When changing the activity generation logic for a block, the version increases, and the converter learns to convert the activity of this block from the old version to the new one.
When converting a route, the converter converts the activity of the blocks for which the generation logic has changed (determined by the version of the scheme).
The only feature is that the conversion should also take place in the form of a change in existing activities, and not generation from scratch, otherwise UpdateMap will not pick up.
Conclusion
After reading the article, you may get the impression that we have in vain used the Workflow Foundation - this is not so. Through the use of WF, we have received out of the box hosting, storage of process instances, all the logic for the execution of processes, including parallel ones.
The article describes only the solution to the problem of low-level WF. Behind the scenes were issues of hosting processes, the problems of converting some sets of the Activity and much more.