How to handle major framework/dependency upgrades?
Looking for some best practices on handling a major dependency upgrades within a project, assuming the use of a dependency management tool(e.g., Maven 2).
Specifically, I'm interested in:
- How to get an inherited application up-to-date(e.g., Spring 1.2.x to 2.5.x)
- What practices can be put into place to after such an overhaul to keep applications somewhat up-to-date
Your own experiences or any articles/papers you've come across/found to be useful are welcome.
EDIT: Updating a dependency version number is trivial. I'm more looking for how you go about tackling the changes you need to make, based on changes to the dependency(deprecation, deletion, changes to types in parameters/return values, etc...). And if there is a good way to ease these changes in the future, as keeping your dependencies up-to-开发者_如何学Cdate should allow you to stay on top of changes and prevent a lot of time being wasted just to get feature 'more securer x 2.1'.
Good practices for handling dependency changes are the same as good practices for application design. You want to layer your architecture and minimize widespread coupling of your code on each dependency in order to keep changes isolated, so that upgrading a dependency doesn't break every part of your application. Code to interfaces and keep business logic separate from infrastructure code.
For minor upgrades (upgrading point releases of a dependency), it helps if you have a comprehensive set of unit tests to detect failures due to API changes. This is one big reason why it sometimes helps to write trivial tests that seem on the surface to always work. An example of this is writing a simple JDBC unit test to perform a simple query. This seems like a waste of effort until you catch a runtime problem with the JDBC driver after a database upgrade (it's happened to me).
For larger changes (like upgrading between incompatible versions of a framework like Spring), it helps if you have some automated functional or integration tests, or at least have a functional specification that your QA people can run through to verify high level functionality. Unit tests will likely no longer be relevant if the framework API you are upgrading is different enough to require broad code changes.
The actual tactical part of managing a migration from one version of a dependency to another incompatible one really depends on what you are doing. A mature library will provide some kind of migration path and hopefully won't require you to rewrite everything. It is a good idea to separate the code changes related to a framework upgrade from the changes related to implementing a new feature. This way if something breaks you will know it has to do with the framework upgrade and not something you broke while implementing a new feature.
Part of what makes this so difficult is that at runtime you can only have one version of a particular dependency in your JVM, so you have to update all of the code at once. OSGi addresses this particular problem by allowing different OSGi bundles running in the same to depend on different dependency versions, so you could depend on different dependency versions at runtime. This is how Eclipse manages dependencies for plugins without breaking other plugins.
In my experience, dependency upgrades are implemented due to a needed functionality owned by the dependency, as a fix to a bug in the dependency that directly affects your own code, to support a new Java release, or to keep your code compliant to a particular standard (with respect to dependencies that affect security). If your code does not fall into one of these areas of necessity, I wouldn't bother keeping them up-to-date, but instead only update them as necessary as updating from release to release may actually introduce bugs into your application.
I've always found that best practice begins with finishing production on your application for the cycle, releasing it as a stable build, and manually updating your dependencies in the next development iteration. Centralizing your versions in your parent POM will result in minimal dependency version modifications and increased extensibility. Assuming you're using Maven:
<dependency>
<groupId>your.dep.group</groupId>
<artifactId>your-artifact-id</artifactId>
<version>${desiredVersion}</version>
</dependency>
精彩评论