Item 2: Prefer loose coupling across component boundaries



Item 2: Prefer loose coupling across component boundaries

Loose coupling, like so many other terms in our world, is at once familiar and foreign—familiar in that we all know it's a goal we should be striving for, foreign in that we're not quite certain what loosely coupled code looks like. The fact that many programmers refer to code written by using Reflection (the java.lang.reflect libraries) as being "loosely coupled" code doesn't help matters. Before we get too far, we need to draw the distinction between tightly coupled, late-bound, and loosely coupled code.

To repeat our Definition of Tight Coupling from Item 1: a given "thing" is tightly coupled to another "thing" if one has to change in response to changes in the other.

Tightly coupled code is code that intrinsically depends on whatever it's using, whether that use be through a method call or a more rigorous protocol. For example, consider the following code:






Person p = new Person();

int age = p.getAge();


This code is tightly coupled to the definition of the Person type, in that if Person changes such that getAge changes its signature, our client code will break and will need editing and recompilation. Similarly, the Servlet API is tightly coupled to the HTTP protocol specification: if HTTP ever changes its rules, the Servlet API (at least the javax.servlet.http package) will need to change with it.

It's not quite as easy as that, though—the presence of an API (methods to call and such) doesn't necessarily indicate the presence or lack of tight coupling. Again, some have suggested that the following code is loosely coupled code, by virtue of the fact that the Person type and its getAge method never really appear directly in the code—therefore, the compiler won't complain if the getAge method on the Person type changes signature.






Class c = Person.class;

Object o = c.newInstance();

Method m = c.getMethod("getAge", new Class[] { });

int age =

   ((Integer)(m.invoke(o, new Object[] { })).getIntValue();


Unfortunately, even though we're not syntactically bound to the signature of the Person type, we're still semantically bound, in that this code will fail miserably if the getAge method, taking no parameters, does not exist or is not accessible on the Class object referenced by c. Worse yet, the error won't be caught until runtime, rather than at compile time, meaning that if you don't have a really good set of unit- or integration-test suites running to catch these errors, you're just begging to be embarrassed at the worst possible moment. This is not loosely coupled code. In point of fact, this is loosely bound code, meaning the method calls, or more technically the bindings behind them, aren't resolved until runtime. (This is why Java's dynamic method invocation mechanism is sometimes called late-bound—you don't know which method you invoke in an inheritance hierarchy of types until runtime, when you know the exact object you're working with.)

One way to think about loose coupling is to ask yourself if you're truly ignorant of what the "other party" is really doing. For example, JNDI uses a generic mechanism to convey initialization settings to the InitialContext (if it can't infer them from system properties) by asking for a Hashtable or Properties object to be passed in to the constructor—in other words, a simple collection of name-value pairs. From there, the InitialContext will look at the keys and values in that name-value tuple collection and figure out where to go next. This is so that you, as a programmer, don't need to worry about "keeping up" with all the possible configuration objects that JNDI Providers might require. JDBC operates in much the same way, using an arbitrary string to contain a "JDBC URL" that conveys all of that information.

Unfortunately, in this particular case, we're not really decoupled from the JNDI layer—if you don't hand in the exactly correct strings as part of the Hashtable, JNDI throws exceptions at best or silently accepts the strings and then fails with NamingExceptions when you try to do lookup calls later. It's up to you to hand in an object whose values match what the JNDI Providers are expecting—and you don't get any help from the compiler to make sure it's all correct. Once again, we're back in a loosely bound rather than loosely coupled scenario; if the Provider were to change its specific settings, our code would break.

Back up a second, though—what, precisely, is wrong with tight coupling?

In itself, nothing. Tight coupling permits a measure of efficiency in both the development and execution of code that can't be matched by loosely coupled code. Consider the Reflection-based example, above, and all the possible things that could go wrong in the execution. In fact, the code as written won't even compile because there are about a half-dozen different checked exceptions that need to be handled in those four lines that I've blithely ignored in the interest of keeping the code sample down to less than a page in size. None of those exceptions require handling in the first snippet because the compiler can, by virtue of the tight coupling of caller against target, ensure that everything's good long before we execute this code. And, as a result, the just-in-time (JIT) compiler can do a tremendous amount of work to make the call to the getAge method as efficient as possible.

In fact, to a certain degree, tight coupling is not only desirable but also absolutely necessary, such as within component boundaries. To create an Iterator that can navigate across a Collection without intrinsic awareness of the Collection's implementation is a terribly difficult thing to do as well as a waste of your time and mine. I care only about the Iterator's interface, not its implementation—so long as you write your Iterator implementation to obey the implicit contract defined by the Iterator interface, I can use it without worrying about how it works. In other words, I can remain loosely coupled to your implementation (of both the Iterator and the Collection itself, in fact) by sticking to the established contracts defined by the interfaces.

See how hard this is to nail down? No Reflection necessary, yet we're more protected against change than the Reflection-based code was.

Enterprise systems have a particular need for loose coupling due to the nature of the beast: enterprise systems are, by definition, systems that need to access and be accessed by other systems, both inside and outside of the company. While you might be able to control, for the first release, all the points of possible choice within the system, such as the client browser, by the time the second release is on the way, you'll find you're missing that exact level of control you used to have. Components tend to version independently of one another—that's what they're intended to do, as a matter of fact—and as a result, trying to keep a tightly coupled relationship when one side changes and the other can't is a great recipe for an early heart attack.

Put it in concrete perspective. Imagine that you've created a domain logic layer—whether exposed via EJBs, WSDL, or POJOs (plain old Java objects, as coined by Martin Fowler and company), the example still works in any case—that demands you pass three parameters to a paySalary method: a Person, an Account, and a comment. In Java interface definitions, this would probably look something like this:






public interface MyBusinessLogicInterface

{

  public void paySalary(Person p, Account a, String comment);

}


For the first release, everything's peachy, and several other departments get wind of the usefulness of your system and write clients to call in to pay employee salaries. Success!

After the initial euphoria, however, you find that changes need to be made. In particular, you need to take an additional parameter, the total amount the person should be paid, since some employees (contractors, perhaps, or other hourly workers) have a variable amount they're paid at each payday. How do you change this without breaking your clients?

Some have suggested that a new interface could be created, call it MyBusinessLogicInterface2 or MyBusinessLogicInterfaceEx, which inherits from the old interface and provides the new paySalary method with the overloaded signature. Clients that want the new interface will explicitly look for it; those that want to stick with the old functionality will continue to use the old interface. Never mind that you'll have to be very careful to avoid duplicating common code in the implementation of these two methods if they're in two separate beans—after about the third or fourth revision, you're going to start running out of names: MyBusinessLogicInterfaceEx3 is not an intuitive interface name. Think about any new partners who want to use the system, confronted with five interfaces that all seem to be identical in nature: What differentiates the most recent one from the previous four versions?

And please don't suggest that refactoring is the answer. Refactoring implicitly assumes that you control both ends of the spectrum, the caller and the recipient side. You can't arbitrarily force your clients to change their code, or you will be a very unpopular person after you tell them. Refactoring within a properly encapsulated component is absolutely your business—refactoring across component boundaries is almost always a Really Bad Idea unless all parties involved agree to it.

One approach would be to do as many legacy systems already do: subvert that comment field to your purposes by passing the additional parameter inside of it. In fact, you might even be tempted to use it as a mini hook point, passing the additional data inside the comment field for the recipient to parse out and process as necessary. "If the comment starts with 'amount,' then parse in all the digits you find until you reach the first semicolon...." If this sounds like a good way to build maintainable, scalable code to you, please put down the book and check into the nearest mental health facility.

The problem here is that RPC-style interfaces, while convenient and easy to build (there's that efficiency argument again), create really brittle contracts that can't tolerate much change in the system. Anything that changes the contract between client and server requires new deployment to both sides, creating a ripple effect that can usually be found all across the enterprise. It's even possible that you won't be permitted to deploy the new code if it breaks too many (or too visible to upper management or the outside world) client systems.

One way to avoid the tightly coupled nature of remote components is to prefer data-driven interfaces rather than behavior-driven ones (messaging versus RPC, essentially), as described in Item 19. Critics will argue that this breaks the object-oriented nature of the component, but, as described in Item 5, that's not necessarily a Bad Thing. Keeping communications context-complete (as described in Item 18) will also help promote loose coupling.

For local components, it's less of an issue to be data-focused or context-complete because the cost of making local method calls is far less expensive than in the remote case. For example, the Servlet Specification can safely establish a Context object between the Web application and the container as a way to keep the container and component at arm's length from one another; for example, I don't have to assume anything about the container's deployment model if I can ask the Context for a file resource relative to my component (getResource and getResourceAsStream).

The late U.S. Supreme Court Justice Potter Stewart once said that he couldn't define pornography, "But I know it when I see it."[2] In many respects, loose coupling is the same. It's not a clear distinction that follows a set of precise rules; it's more of a philosophical and stylistic approach. Usually it's pretty easy to spot systems that were tightly coupled—they're the ones that, after a few years, nobody wants to work on and everybody wants to rewrite from scratch. The goal is clear: to allow previously unrelated systems to interoperate with a minimum of technology adjustment on the part of any one system or program. You'll know you've reached a loosely coupled component state when another project/department/company can start using your components without having to worry about how you're doing what you do, sometimes even across technology boundaries. Or, from the opposite angle, with a loosely coupled component, you can completely change your implementation (perhaps moving from Java to .NET or vice versa) without clients being aware of the shift.

[2] From Jacobellis v. Ohio, 378 U.S. 184, 197 (1964), as quoted at http://caselaw.lp.findlaw.com/scripts/getcase.pl?court=US&vol=378&invol=184.