2012-08-06
Optional Dependency Strategies for Java Libraries
Dependencies only exacerbate this problem. With each dependency you add, you pull in more code, more APIs to learn and more potential bugs.
Transitive dependencies take this problem to 11. You now have the possible issue of conflicting dependencies! And before you know it ... you have landed in JAR Hell! So much for laughing at our Windows friends stuck in DLL Hell...
And yet, dependencies are a necessary evil. If you want to avoid reinventing the wheel or if you want to integrate with the outside world, there very often is no way to avoid them. In the spirit of doing the simplest thing that could possibly work, strive to reduce the number of dependencies to the absolute minimum required. Every single dependency you add should be weighed carefully: does the functional benefit outweigh the complexity cost?
Special case: Libraries and Frameworks
A library or framework in broad general use (like Flyway) usually supports a wide range of scenarios for a very diverse set of users. In Flyway's case this currently means dependencies on:
- 10+ Jdbc Drivers
- Spring Jdbc
- OSGi (Equinox)
- JBoss VFS v2
- JBoss VFS v3
- Ant
- Maven
Nobody in their right mind would be keen on pulling all these dependencies in just to use the subset they need. And in fact, it wouldn't even be possible as some of them are conflicting! Yes, JBoss VFS v2 and v3, it's you I'm looking at!
- Create a separate module which depends on the base module and the optional dependency
- Create a module per optional dependency which the base module depends on.
- Mark the dependency as non-transitive and activate the functionality at runtime if present
- Use reflection and activate the functionality at runtime if present
- Use a Service Provider Interface and call the correct implementation at runtime
Each of these strategies has its pros and cons. Let's look at them in turn.
Separate Module
Can only make use of one of the optional dependencies at a time.
Module per Dependency
Largest amount of modules to manage overall.
All access to the code of the optional dependency support module must be carefully guarded to avoid NoClassDefFoundError.
Non-transitive Dependency
Advantage for the End-User:
Can depend on multiple optional dependencies at the same time.
Reflection
Can depend on multiple optional dependencies at the same time.
No transitive dependency support. Must manually reference optional dependency in own project.
Advantage for the Library Developer:
Service Provider Interface
Support various implementations of the same interface. In Flyway's case, this applies to the Jdbc drivers. Flyway supports many of them, and yet they are all accessed through the same API (Jdbc).
Smallest amount of code in KB as only code to support the common SPI is pulled in.
May have to configure which SPI implementation to use.
Should test the library with the different SPI implementations to ensure they behave as expected.
Checking if a dependency is present at runtime
A number of these strategies depend on being able to check whether a certain dependency is available at runtime and guard against using its related features if it isn't.
This sounds complicated, but it turns out to be relatively easy on the Java platform:
public static boolean isPresent(String className) {
try {
Class.forName(className);
return true;
} catch (Throwable ex) {
// Class or one of its dependencies is not present...
return false;
}
}
...
if (isPresent("com.optionaldependency.DependencyClass")) {
// This block will never execute when the dependency is not present
// There is therefore no more risk of code throwing NoClassDefFoundException.
executeCodeLinkingToDependency();
}
Conclusion
The 5 different strategies each deal with different scenarios and different forces that must be balanced. Even in a relatively small library like Flyway, it wasn't possible to simply rely on a single one. Know them well and know when to use them. But if there has to be a single most important guideline to remember, let it be this one:
About Axel Fontaine
Axel Fontaine is the founder and CEO of Boxfuse the easiest way to deploy JVM and Node.js applications to AWS.
Axel is also the creator and project lead of Flyway, the open-source tool that makes database migration easy.
He is a Continuous Delivery and Immutable Infrastructure expert, a Java Champion, a JavaOne Rockstar and a regular speaker at many large international conferences including JavaOne, Devoxx, Jfokus, JavaZone, QCon, JAX, ...
You can follow him on Twitter at @axelfontaine
Two day intensive on-site training with Axel Fontaine
Upcoming dates
Iasi, Romania (May 10-11, 2017)
Oslo, Norway (Oct 16-17, 2017)