Sunday 25 September 2011

Detangling Dependencies Between Guice Injected Services

It is very easy to allow services to depend on each other, even when they are in different modules.  In fact Guice explicitly supports this behaviour with the AbstractModule#requiredBindings method.  I think this is very bad and in my experience has led to some complex interactions and strict requirements on order of creation.  Even worse, in some cases you may require a cyclic relationship to fulfill what you want to do which means you can't fully initialise the services via constructors.

Instead of a clever technical solution, I believe it is the software design that is incorrect.  The issue can be resolved using SOLID principles to identify and rectify the issue.

The services require the dependencies because they are trying to do too much, this is violating the Single Responsibility Principle (SRP).  I often think of SRP as a DON'T whereas the Interface Segregation Principle (ISP) is a DO: to fix an SRP issue apply ISP and move that behaviour out into new classes.

This still hasn't fixed the issue yet because those dependencies still have to be wired up in the modules, except you don't.  The new classes with dependencies can be instantiated via Guice when required with injected dependencies, the code to wire them up in the modules can be removed altogether.

In this way, layers of objects are being created.  The bottom layer of services are injected into the next layer of composite services which are then injected into the application.  This is the Open Closed Principle (OCP) where classes should be open for extension but closed for modification.  In other words, prefer to building on top of existing classes instead of modifying them.

I do suggest that all layers can talk to each other unless the software is huge enough to justify such abstraction.  Also, be sceptical of composite services which simply provide a layer over another service and perhaps wrap exceptions or even provide a something useful.  In this case a new API has been created to replace a more universal one.  Ask if the new API is actually better than what it is truly wrapping and it is worth the new layer being imposed.

No comments: