This is a mobile version, full one is here.
29 January 2019
My Recipe Against Dependency Hell
Do you specify exact versions of your dependencies? I mean, when your
software package depends on another one, do you write down, in your
Gemfile, or what have you, its version as
1.13.5 or just
1.+? I always thought
that it was better to use exact version numbers,
to avoid the so called dependency hell,
and I was not alone.
However, very soon I realized
that dynamic versions, like
1.+, give more flexibility.
Just a few weeks ago I realized that neither approach
is right and found myself a hybrid formula. No suprise, I again saw that
I wasn’t alone.
First, let me explain what’s wrong with fixed dependencies.
Say I create a library X, which depends on, for example, a logging
facility, which is a third-party library, not mine. Thus my library X has
a dependency. The logging library has a version number, such as
1.13.5. I put this text into the
pom.xml file of X:
<dependency> <groupId>com.example</groupId> <artifactId>log-me</artifactId> <version>1.13.5</version> </dependency>
Many advocates of fixed dependencies argue that it’s very important
to stick to the version
1.13.5, instead of using a more flexible dynamic
1.13) (i.e. any version, provided it is
1.13 or newer).
Why? Because future versions may introduce something that
will break the build of X. They may change interfaces, rename classes or methods,
or delete something that I’m using. You never know what the authors of this
log-me library might do. That’s why it’s better to hard-wire ourselves to
call it a day.
This is true.
What if the library X is used by another library Y, which also depends on
log-me but needs version
1.14.1. Boom! There will be a conflict
in library Y: Maven, the package manager, won’t be able to decide
which version to use. It will be necessary to resolve the conflict somehow.
In the case of Maven it’s resolvable, in the case of, for example,
Rake, it’s not (to my knowledge).
To resolve this problem, library Y will have to explicitly say which version
has to be used. But it can’t be sure that
1.14.1 will work correctly with
library X. To confirm that it does, it would have to be tested by the authors of library
X. So the best the creators of library Y can do is to try it and hope
for the best. With other build tools, like Rake, the authors will have no choice,
but to ask the authors of library X to upgrade to
1.14.1 and release
a new version. Then, library Y will be able to use library X.
This problem would not exist if library X depended on
But, as I mentioned above, in that case its authors would be planting a time bomb—eventually
one of the future versions will definitely break the build.
So what is the solution?
Here is my formula: If you trust the authors of the library, use dynamic versioning; if you don’t, use a fixed version.
What I mean is, do you trust that they are professional enough to think about backward compatibility and to follow the principles of semantic versioning? If they are careful enough to not delete or modify something that may affect future versions without also changing the major number of the version, then you can trust them. How do you know who to trust? I don’t trust anyone, except my own libraries and a very small number of other libraries which I have reviewed in GitHub and checked the quality of their repositories.
Of course, you can’t fully trust anyone, but this is the formula I
have for myself now. You can see how it works in this
Gemfile, for example.
Pay attention to the version numbers. Those that start with
>= are dynamic,
while others are fixed. It’s a hybrid approach, but it works for me.
Maybe it will help you too.