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
Nathan