This post originated from an RSS feed registered with Java Buzz
by Geoffrey Wiseman.
Original Post: Facades: Loose and Tight Coupling
Feed Title: Furious Purpose
Feed URL: http://www.jroller.com/diathesis/feed/entries/rss
Feed Description: Thoughts and experiences on technology and software.
Integrating dependencies with your codebase is a fact of life in the modern
world of software development. Unless you're working extremely close to the
metal, odds are extremely high that you're integrating with dependencies on
a fairly regular basis.
In the case of third-party dependencies, you often have little or no control
over the code, either because you have no access to it (closed source) or
because you don't have the time to invest in it (for some open source projects).
In order to reduce 'vendor lock in' and to enable you to switch from the
current dependency to another, many people recommend wrapping the dependency
with your own layer (or facade).
Done well, this gives you access to the underlying functionality without tying
you to vendor-specific references that are difficult to replace once they are
intertwined with your own codebase. Done poorly, this can expose all of the
weaknesses of vendor lock-in without exposing all of the power of the underlying
API, and add a new, and often brittle layer between your application and the
underlying dependency.
I've seen some poor examples of this before, and it occurred to me this morning
what the difference often is -- it's possible to wrap a third-party library
with your own code without, in fact, making loose coupling. If your facade's API
mirrors the API of the third-party library, or fails to sufficiently abstract the
underlying details in a domain-specific or vendor-neutral way, then you
have wrapped the API but may well have failed to loosen the coupling.
Anti-Pattern: Tightly Coupled Facade
For instance, one project that came my way included a 'data access layer' on top
of Hibernate whose implementation naively wrapped many of the Hibernate classes
with a similarly named delegate class with almost identical methods. Hibernate's
session became a DataAccessSession. Hibernate's query became a DataAccessQuery,
and so on. As a result: all data access code had a new potential source for bugs,
within the delegate layer; some Hibernate functionality was concealed by the
facade because there hadn't been time or interest to expose all of Hibernate's
features; the application was as tied to Hibernate's API as ever, just with
minor indirection. Any attempt to replace Hibernate with, say, JDO or TopLink
would require a significant amount of work, and the refactorings that result
might well have propagated from the lower layers into the application itself.
That project would have been better off using Hibernate directly. Doing so would
have increased development speed, reduced potential for bugs, made Hibernate's
power more accessible and, likely, not significantly increased migration issues.
This does mean coupling the application to Hibernate, but I'd prefer that to a
poorly designed and tightly coupled facade.
Pattern: Loosely Coupled Facade
That's not to say that all attempts to loosen the coupling to a third-party
library that you know, expect or suspect needs to be replaced is a bad thing.
There are certainly merits to creating a loosely coupled facade. There are
at least two viable approaches that resolve the tight coupling: a
domain-specific API or a generalized functionality API.
If you have a clear understanding of the functionality that the API provides
and of the implementations that you might consider using underneath the API,
then you can give your facade a generalized functionality API. Using the
above example of O/R mapping layers, if you've had exposure to a number of
O/R mapping layers and a good understanding of how your applications tend
to interact with them, as well as a list of candidate implementations (say,
Hibernate, Kodo, TopLink and OJB), then you should be able to write an
API for your facade that exposes much of the general functionality of an
O/R mapping layer while keeping a level of abstraction that allows you to
replace the underlying implementation relatively simply. This approach
is advantageous because it allows you to use the same facade in a number
of different contexts. Its primary disadvantage is that you must have
a markedly stronger understanding of the candidate APIs and how you make
use of them to do this effectively.
Another viable approach is to create a domain-specific API that exposes
the services that your domain needs rather than exposing the functionality
as a whole. Rather than trying to abstract the notion of O/R mapping,
for instance, you might simply create a PurchaseOrderRepository that
allows you to save and load purchase orders as well as retrieve/query all
the purchase orders for a customer and for a particular date range. This
API may not be suitable for other projects, but it does offer loose coupling
to the underlying persistence without requiring you to have a firm
understanding of all the candidate replacements. This also has the
advantage of lending semantic value to your API (e.g. the Ubiquitous Language
of Domain
Driven Design.