bLOGical has posted some Principals of Loosely Coupled APIs which provides a table of distinctions for tightly coupled and loosely coupled architectures as well as referencing an excellent article by Bill de Hora on Foundations for Component and Service Models.
bLOGical's table, reproduced here, is one of the best one page descriptions of loose coupling I've seen. I've made a few additions of my own, in red.
Tight Coupling | Loose Coupling | |
Interface | Class and Methods | Fixed verbs (i.e. RESTian) |
Messaging | Procedure Call | Document Passing |
Typing | Static | Dynamic |
Synchronization | Synchronous | Asynchronous |
References | Named | Queried |
Ontology (Interpretation) | By Prior Agreement | Self Describing (On The Fly) |
Schema | First-order |
Higher-order |
Communication | Point to Point | Pub & Sub/Multicast |
Interaction | Direct | Brokered |
Evaluation (Sequencing) | Eager | Lazy |
Motivation | Correctness, Efficiency | Adaptability, Interoperability |
Behavior | Planned | Adaptive |
Coordination | Centrally Managed | Distributed |
Contracts | By Prior Agreements, Implicit | Self Describing, Explicit |
I view this table as a continuum rather than a black or white distinction. A particular implementation might pick a feature from one column or the other, mix and match as it were, to get a desired result and the completed system will be more or less loosely coupled depending on which features are selected.
I should explain the change I made on Schemas in the table. I view Schemas in both styles being largely based on grammars, context-sensitive grammars to be precise. The question is more about what can you describe within the Schema and that's a question of what kind of predicate language you'll allow as your type system. For more details on this idea, you may want to look at Cardelli and Wegner's classic "Understanding Types".
The de Hora principals (with my commentary) are:
- Avoid changing or extending the interface methods. This corresponds to "Interface" line in the table. HTTP is a perfect example of an interface with a small set of fixed verbs (i.e. GET).
- Control change by using a dictionary interface. Used when a fixed set of verbs won't cut it, a dictionary interface (Python example) provides a set of verbs for finding and executing right method. This is akin to data-driven programming in the Lisp world.
- Calls should return documents not objects. The issue here is largely one of granularity. In a loosely coupled system, where latency is a real issue, getting back a pointer to an object isn't very useful.
- Avoid binary compatibility. Systems that require binary compatibility aren't loosely coupled since an upgrade on one end requires an upgrade on the other.
- Don't confuse an API with a contract. This emphasizes the difference between a protocol and an API. A protocol is a sequence of document exchanges that is required for compatibility. An API is more of a one-way, this is how we're doing things at the moment declaration.
- Version the contract. Versioning can be difficult to do. The task is made easier using using intermediaries. If you don't version, you're back to the tightly coupled upgrade issue again.
- Don't build an API for data transfer. The point here is pretty simple: we already have an API (protocol) for data transfer. Its called HTTP. Don't invent another one. See my paper on service-oriented data architectures for more about this.
de Hora doesn't like a lot of things in Java very much. Reading it reminds me of Paul Graham's quip that "Object-oriented programming ... lets you accrete programs as a series of patches." There's a lot of truth to that.