Issues with Process Definition versioning
When you deploy a new process definition, Camunda BPM will, by default, keep the old definitions in the database and allow your existing process instances to continue on these models. This is good and necessary, since automated migration is not always possible. It does, however, raise some issues:
First of all, your process definitions will be versioned, but your Java-based implementations of listeners, delegates or called beans won’t be. Which means, they may not change, in their naming or method signature, ever. Say you want to rename a Java Delegate, for whatever reason. Changing the reference to that Delegate in your process definition will not suffice: your old process definitions will retain the old reference, so when an old process instance arrives at the point where it tries to call the referenced delegate, a delegate with that name will not be found, resulting in a RuntimeException. Or let’s say you want to change the method signature of a called bean method: your new process definitions will be able to call the altered method, but the process instances running on your older models will still be looking for that old signature. In short: whenever you refactor the Java side of things, you have to be very cautious.
There are of course some solution attempts to that, like Bean aliasing or keeping deprecated methods for backwards compatibility. But that’s not something for the long run. It will force you to maintain an API between your ever changing process definitions and your Java code, trying to support all old versions in your code – no exceptions allowed. Depending on the average runtime of a process, this may mean keeping a lot of deprecated code for a long time. Can you even know for sure there are no more process instances running that require those deprecated methods? Can you remove those methods in good conscience, ever?
Another issue comes with implementing features in an agile way: if you do, as you should, “release early, release often”, you will have to regularly release a system that is in no way feature-complete. If your process definition is missing features (or, god forbid, contains errors), it should not be too big of an issue, since those features will be added/the errors may be fixed with the next update. But, alas, if your process instances are never migrated, the new features will only ever be available to newly instantiated processes and the old process instances will remain incomplete or faulty.
Challenges of automated Process Instance Migration
So automatically migrating existing process instances upon deployment might be better than just allowing them to keep running on their old process definitions. So why not do it by default? Because there are some obstacles:
- Depending on your project setup you are likely to deploy to your various environments in different frequencies. Let’s say you deploy to the development-environment five times a day, to your testing-environment once a day and to your productive environment once every two weeks: your productive environment will obviously see way fewer deployments than your development environment. It follows that the artifact in charge of migration will have to simultaneously be able to handle the small increments on your development environment as well as the aggregated changes in between deployments on your productive environment.
- Although Camunda BPM does offer support for automated process instance migration out of the box, that support is limited to migration scenarios that allow for “simple” migration via matching of task-IDs. One scenario in which this would not be possible is the deletion of a task: where should the token of an existing process instance go upon migration, if it is currently sitting on said task? This means that you will always be forced to identify changes where simple automated migration is not possible and will have to provide a mapping for those migration-scenarios.
Now of course it’s possible to migrate process instances via the cockpit if you’re on the enterprise version of Camunda BPM. But you’re gonna have to do it manually, for all stages separately and – quite possibly – for a lot of different process definitions. With every change, small as it may be.
The automated solution
In our current project at the SüdLeasing GmbH, my colleague Tobias Schäfer and I developed a Spring Boot Component that will attempt to migrate all running process instances whenever the application with it’s embedded process engine is launched. You can check out the code on GitHub.
Introducing proper versioning
The key to the solution was introducing a proper version tag to our process models. Since both process definition versions and IDs vary from environment to environment, the version tag has proven to be a more reliable identifier. By structuring this tag using the pattern major.minor.patch (i.e. 2.4.1), we are now able to differentiate between “easy”, “difficult” and “unwanted” migrations:
- Patch version should be increased whenever there is a change that allows for simple migration using task ID matching. This includes most common changes, like changes to documentation, bpmndi or the addition of new bpmn elements.
- Minor version should be increased whenever a change is made where migration via task ID matching is not possible. This includes changes like the deletion of tasks or changes to the IDs of existing tasks. Increasing the minor version will require a migration mapping for that specific increment.
- Major version should be increased whenever changes are made that are so fundamental that a migration might be too complicated to make sense or a migration is unwanted for whatever other reasons. In this case, no migration ought to happen.
It should be noted that skipping patch versions shouldn’t be an issue since automated migration ought to be transitive: if you can automatically migrate from 1.4.2 to 1.4.3 and from 1.4.3 to 1.4.4, then migrating from 1.4.2 to 1.4.4 should pose no problem. Skipping minor versions may also be possible: since every minor change will consequently require an activity mapping and those mappings may be “added” to each other, this migration will also be transitive and we will be able to go from 1.2.8 to 1.4.3 assuming the correct mappings from 1.2.x to 1.3.x and from 1.3.x to 1.4.x are provided. This is important, because, as mentioned before, the migration component will have to handle larger version increments on the production environment.
Migrating without mapping
Once versioning was established, implementing the actual migration was pretty straightforward: the most common case, the migration from patch to patch does not require a specific migration plan:
As it turned out, this alone got us very far. In eight months of development we did not happen upon a single situation where the introduction of a higher minor version was even necessary.
So when would you even need a mapping for a migration? Really only if an activity got deleted/renamed that is also a wait state. Because if it isn’t, there will never be a persisted instance of this activity and it will therefore never be up for migration.
In this case, there would be no way for Camunda BPM to know what to do with Process Instances, that are currently sitting at an Activity that no longer exists in the newer Model. The tokens will have to be moved to a different activity. But which one? This is where a mapping has to come into play:
It’s simple enough. But, as mentioned, we never even got to a point where we had to utilize this. It turns out that renaming or deleting wait states is not really a common thing to do when working with process models. After all, they tend to grow, not shrink. And renaming is almost always purely for internal purposes and can, for the most part, be avoided.
This is why, to date, the migration of minor versions was not implemented in the GitHub project. There is a pull request pending that will add this functionality (check it out here), and it will work as described: developers will be able to register migration instructions for a given minor upgrade, the migrator will take these into account, „add“ them together if necessary and execute the migration accordingly.
And that’s it. Automated process instance migration that will be attempted on each deployment. I write „attempted“, because these things may be tricky to test in an environment of Continuous Integration. Migrations may fail, migration plans may be faulty. This is why we stressed logging and error handling in our solution, so that if something goes wrong, we may still fix it manually.
Allowing existing process instances to continue running on older process definitions had us facing a number of issues in an agile project at SüdLeasing GmbH. So we implemented a process instance migrator which automatically migrates existing process instances to newer process definitions upon deployment. The migrator runs without any need for configuration as long as only patches are applied. As soon as minor changes are implemented, a migration plan will have to be specified for those changes, but this functionality is still pending.
If you’re interested in this tool, stay tuned. We plan to release an out-of-the-box-implementation to maven central at some point.
And as always: we’re happy to support you with our BPM Technology Consulting.
Special thanks go out to Martin Busley, the team leader of the project in SüdLeasing GmbH, who agreed to Tobias Schäfer and myself publishing our solution.