January 31, 2012

Inverting Package Dependencies

Dependency inversion is an important concept to understand and apply to the code you write, as it neatly handles the coupling in your code, making it easier to grow and maintain.  I think many people have at least seen some form of dependency inversion, usually something like the following, where there is a domain class, ClaimsProcessor, that is injected with an interface for a repository:

public class ClaimsProcessor
{
public void ClaimsProcessor(ClaimsRepository claimsRepository)
{
this.claimsRepository = claimsRepository;
}

private ClaimsRepository claimsRepository;

...
}

public interface ClaimsRepository
{
...
}

public class HibernateClaimsRepository implements ClaimsRepository
{
...
}

However, there’s another form of dependency inversion that is often overlooked.  Part of the reason for it being overlooked is that it’s not the most intuitive; it took me a re-reading of Martin's article on the principle to get it.  But once I understood it, I wanted to kick myself for all the times I didn't apply it.

Going back to the example above, where is this interface located?  In Java parlance, what package is the interface in?  Let's say you have two packages, a domain package and a data access package.  The domain package contains the ClaimsProcessor class, and the data access package definitely contains the implementation of the interface, the HibernateClaimsRepository class.  But in what package should the ClaimsRepository interface be?

image1

It seems natural to place the interface in the data access package, as that's the one where the implementation of the interface resides.  This placement, though, is undesirable, as the domain package will then have to import the interface from the data access package.  Thus the domain package depends on the data access package, the consequence of which is that changes to the data access package will result in changes to the domain package.

image2

This is wrong.  Just because the ClaimsProcessor class uses the ClaimsRepository interface doesn't mean it should have to change because the underlying implementation of the repository changed.  It should only have to change if the interface itself changes - meaning how the repository is expected to function has changed, not just implementation details.  With this in mind, we should place the repository interface in the domain package, inverting the dependency relationship so that the data access package now has to import the interface in the domain package.  This makes the data access package rely on the domain package, allowing the domain package to be isolated from implementation changes to the data access package.

image3

With the repository interface in the domain layer, the domain is basically offering a contract stating that it needs some data in a particular way; anything that can fulfill this contract, regardless of how it does so, can be used by the domain.  The domain can change the terms of the contract (by modifying the interface), and anything trying to fulfill the contract will have to make changes to accommodate the new terms.  With our dependencies set this way, we could in theory swap out our entire data access layer without impacting our domain layer.  This isn't possible when the interfaces are in the data access package.

So is this really a big deal?  In reality, wouldn't the domain and infrastructure packages most likely both change during a release anyway?  Perhaps, but with the structure suggested above, they aren't required to.  This, for one, can help limit the scope of testing, as you can focus your efforts on the packages that have changed.  It could also allow your deployments to be more granular, offering flexibility in how you release things. I can't think of any downside to structuring your code this way, and the effort to do so is minimal.  So start inverting today!

No comments:

Post a Comment