Application Auto Upgrade
We are constantly updating the applications, sometimes these updates include braking changes. We used to address the breaking changes using bash scripts. Recently we have adopted a new way of addressing these.
Using Spring Components
The idea is quite simple, we write a spring component in our applications for each time we need to address some breaking change. Each upgrade has a version number, we increment this version for each new upgrade component. We maintain a table in the database that stores the current version of the application.
Then there is the upgrade runner which runs at the start of the application and orchestrates the upgrade process. It gets the current version then finds and runs the pending upgrades. The whole upgraded process runs in a single database transaction. At the end of a successful upgrade, the application version gets updated in the database.
Pros
We get to use all the services, caches, and components of our application while upgrading, this makes the upgrade process quite simple and easy to understand.
The upgrades reside within the application itself, with no additional resources to maintain.
As the upgrades are just Spring components it is possible to test them quite easily (both unit and integration tests).
Wiring upgrades do not require extensive SQL or bash scripting knowledge, any developer can write them.
The whole upgrade runs in a single database transaction, so either it upgrades fully or fails.
Upgrading is done at the beginning of the application, and failure causes the application to fail, so the chance is low that the user will be using an inconsistent application.
Cons
Adding new required fields in entities maintained by Hibernate can cause a lot of exceptions, as Hibernate tries to create the field even before the upgrade runner runs.
Adding new required fields in entities maintained by Hibernate can also break previous upgrades, in case any of our previous is trying to fetch and use those entities using Hibernate.
Previous upgrades can break due to having an older database that is being accessed using a newer code base (for example the previous point).
Guidelines
The following are some guidelines that we should follow to overcome the cons listed above.
Write test cases along with the upgrades. Keep in mind that we might have to prepare the database according to that version before running the tests.
When adding new required fields always provide a default value.
When adding/updating/removing fields consider the existing upgrades. Compilation might point out a lot of the problems but still, there might be more. We might need to adjust the upgrades accordingly.
When adding new validations/constraints consider the existing upgrades. We might need to adjust the upgrades accordingly.
Write idempotent upgrades, so that running them multiple times does not cause any problems.
How to Handle Other Cases
Upgrading from within the application can solve most of the braking changes but there are some cases that can not be done from within the application. Sometimes we might have to make changes on the host machine or do something before the containers start. For example
Changing something in the .env
Upgrading Postgres, Mongo, or any other containers
The proposal is to use the same technique as our current upgrade process. We would have upgrade scripts (bash/python) with version numbers. The scripts will be in our git repository and will be included in the application package we generate on release.
Finally, there will be another script (bash/python) that will act as the controller to run the appropriate upgrade scripts on start. This script will also be included in our git and the application package.
Now the challenge is to keep track of the current application version. It can be done in several ways, like
Get as an argument to the controller script
Store in some file in the system
We already have an issue for this https://stiftelsenasta.atlassian.net/browse/AN-2184 .
Â
Â
Â
Â