Supporting Several Versions of JIRA in Your Add-on: Journey to Multiple Modules and Back
In a galaxy far far away, in the age of JIRA 4, we started developing a cool add-on for JIRA…
As stated, the API changes were incompatible so the best solution we saw at that moment was to maintain several versions of code. We’ve performed some refactoring and split the add-on code into several modules (Maven projects), basically 2 modules for each JIRA version. There was the ‘shared’ module which contained the code able to work on all supported JIRA versions (this was most of the code actually). And, for each supported version family of JIRA (4.2.x, 4.4.x and 5.x.x) at that time there was its own ‘individual’ Maven project implementing some interfaces and abstract classes defined in the “shared” module. Each ‘individual’ module also contained its own version of velocity macroses (they were different due to differences in 4.4 and 5.0 UI). This approach had its pros and cons.
- Supported Product Range. We were able to support the necessary wide range of JIRA versions for the add-on.
- Isolation. If there was a fix to be created for a problem only occurring in, say, JIRA 5.x and the fix was done in the ‘individual’ module — we knew that we only have to test this fix on JIRA 5.x and build a new patch release only for that JIRA. Otherwise one had to test the change on all JIRA versions to make sure that it works across all of them.
- Increased Complexity. We now had multiple modules for the JIRA Add-on. If one wanted to develop a new feature for the add-on (say, only for JIRA 5), one had to work with two modules — the ‘shared’ module and the JIRA 5.x ‘individual’ module. In some time it turned out into very annoying stuff. Often the developer had to put and test the same code in every JIRA version ‘individual’ module. It happened when there was a fix or a new feature done for all JIRA versions which couldn’t be implemented in the ‘shared’ module. In that case, one had to have the same (or similar) code in every JIRA 4.2.x, JIRA 4.4,x and JIRA 5.x ‘individual’ modules. Hence — an annoying overhead, code duplication, increased number of man-made mistakes done while developing and testing for each JIRA version module.
- No FastDev (or QuickReload). For the same reason (multiple maven projects) we couldn’t use the benefits of the FastDev. With such add-on structure, one couldn’t use atlas-cli (provided by Atlassian Plugin SDK) to reload the add-on after small dev changes. So, instead of reloading the add-on the developer had to frequently restart JIRA which ate huge amount of time during the development process.
- Management Overhead. Additional overhead in the continuous integration and release management to support individual releases for each JIRA version range.
At some point, it became clear that this approach seems to do more harm than good. We started searching our way out. By that time it appeared that we have less than 7% of customers using some of the oldest JIRA versions like 4.2.x. So we’ve figured out that we are not getting any profit by supporting these JIRA versions as the development cost was high and the number of users for these versions was small. We tried to persuade our customers to update their JIRA servers and stopped supporting the add-on for JIRA 4.2.x. Most of the customers moved to newer versions eventually. The fact that Atlassian was going to stop supporting these JIRA versions also helped.
Next, we decided to move back to the classic one-module structure. We created one central class to solve the JIRA API compatibility problems. It implemented the ‘problematic’ set of methods which had the different signature and/or return type due to discrepancies in JIRA APIs. The class was hiding behind the interface the difference of the implementations of the same call in different JIRA APIs. The implementations of the methods were dealing with compatibility problems mostly with Java Reflection. An interesting fact is that when we first started working on the compatibility problems we didn’t like the Reflection solution and ended up with multiple modules. The idea to invoke some methods with Reflection seemed an evil. Too ‘hacky’, dangerous and hard to maintain. In the end, it appeared to be the lesser of evils for the following reasons:
- it allowed to switch back to one-module project structure
- once (well) implemented, it didn’t actually require any further attention or support. The changes to API happen not so often, so if your code is well covered with tests (ours is), you are on the safe side even with the Reflection-based implementation of some (small) parts of it. Should the signature of some Reflection-invoked methods be changed by Atlassian, your tests fail immediately way before you release the new version of the add-on.
At the end, our team learned some lessons:
- Know your users. Research the market. We do not try to blindly support the whole existing version range of product for the add-on anymore. If there is only a small percentage of customers using your add-on on JIRA X, maybe it makes sense to stop supporting this JIRA. Instead, the resources needed to maintain this version could be allocated to develop new features and make the majority of your users happy.
- Look to the future. Keep an eye on the End Of Life Policy. If Atlassian is going to stop supporting JIRA X in 5 months, you probably shouldn’t take this JIRA version into account when developing new features for your next release in 2 months. Use all available info to assess if supporting of the add-on for JIRA version X.Y is worth the effort and if your users really need it.
- Write tests, maintain your CI. This is a known truth, but we developers still ignore it too often. With this add-on, we didn’t ignore it and it paid off. Before the first refactoring effort we already had a wide range of unit tests, Wired Tests, and even UI tests. We also had a CI maintained in Bamboo with several builds configurations. Should we not have all of this, our refactoring efforts on the journey to multiple modules and back would be much much more expensive if possible at all.
As an add-on vendor and/or developer, it is up to you to decide how exactly are you going to maintain compatibility with a range of Atlassian products. Be it by using Java Reflection or by maintaining several code bases or … you name it. Please make your decision wisely. I hope that the lessons learned by our team can be of some use to you.
Author: Andrey Kozynets