I started looking into Guice and how we could use it with Liquibase 4.0. After some research, I think it will not be enough of a help to introduce as a dependency. We need a way to support different subclasses of objects being used at runtime, and while the Guice @Inject support is nice I don’t think it fits well enough with what we need. What we mainly need is a good way to manage implementations which can be easily overridden with better implementations, but that logic can be complex and isn’t necessarily known at “bind” time.
For example, Liquibase ships with a bunch of Database implementations–one UnknownDatabase which supports all databases, and an implementation for each supported database that works better for that database. Furthermore, extensions can add new Database implemenatations and can override standard ones with even better implemenations (such as a new OracleDatabase that handles quoting based on customer-specific needs). When we connect to a database we need to find the right Database implementation based on the connection URL, connection version metadata etc. I’m not seeing how the Guice bind logic would be able to handle this.
Another example: there are the Change implementations which each have a name like “addTable”. Extensions can also provide better “addTable” implementations. When we are parsing a changelog we aren’t injecting Change instances to have Guice help with that, we need to look up the ones that can match and then find the best match.
Final example: the *SQLGenerator classes each can support a combination of Database+Statatement to generate the correct SQL to do a Change. We want to look up the correct SQLGenerator, but we don’t know what it is until we have the actual Statement and Database and we need to create one for each Statement.
There are similar examples with changelog parsers, diff logic, lock logic, changelog history logic, preconditions, etc.
Guice seems to work well when are wanting to construct a graph of objects and at graph construction time you know the pieces/dependencies you are going to use. For us, most of the dependency management is happening after objects have been created.
Another thing I was hoping to get out of Guice was a solution to the bugs related to classes not being found in different classloader setups. When jars are nested in other jars in wars, or stored on a network location, or running in OSGi etc. the existing ServiceLocator logic can break and be a pain to troubleshoot. Unfortunately Guice doesn’t seem to have any sort of Class Finding logic. I can understand it being out of scope of the project, I was just hoping it wasn’t…
Therefore, I’m feeling like the best approach for us is to stick with our more build-your-own extension manager than using Guice. We’ll still need the ServiceLocator logic to find implementations of classes at startup which populate the various ChangeFactory etc. classes which can use whatever logic they want to iterate over the found classes/instances to find what is needed.
Once change I am planning on making, however, is to manage that whole process better with the Scope object. Rather than global singletons, I would like to move the Factory etc. objects onto Scope-based singetons. So, instead of ChangeFactory.getInstance() you would call scope.get(ChangeFactory.class). This will give us some of the no-singleton advantages of Guice, even if it isn’t auto-setting dependencies.
Am I missing anythign in my Guice evaluation?
Nathan