Every developer of the last decade knows that coupling in software is bad. It is an anti-pattern, an example of what not to do. Yet if you ask developers what loose coupling is they mostly give examples, not a definition. I myself couldn’t create a clear definition until I first wrote on the subject, and as you’ll see it’s still not that clear.
Part of what makes coupling so difficult to understand or spot in your own projects is that there are actually many different types of coupling. The following are all examples of coupling:
- Platform (should be less of an issue in SOA, but it’s still there)
Some of these are obvious: transport and location represent clear concepts that Services are designed to help alleviate. Others are a little more complex. Consider contract coupling. Contracts can couple in obvious ways, like types, and less obvious ways. Recall that one of the principles of a good service that it have an explicit contract. XSD does a great job making contracts explicit and enforceable: it allows for strong type definitions, complex types, and type validation. One thing XSD cannot address, however, is implicit coupling. I often see two distinct forms of implicit coupling: untyped messages and implicitly ordered contracts.
Untyped messagesThis is one of my favorites as there is almost a good reason behind it: namely flexibility. Generally it is believed that by using an untyped message (string or xs:any) a service interface can be changed at will without the normal planning (or pain) required. This anti-pattern service will often expose a method called Execute or something similar and take a string parameter of the XML payload or just xs:any.
This sounds like it might actually provide easy changeability and low maintenance, but there are two fundamental flaws with untyped messages: first consumers have no way to know what is legal to send. They have to ask for example payloads or a separate schema file outside of the service definition. This means the service is not well encapsulated. Worse they only know if their message is correct once they actually send it.
The second issue is that untyped messages accomplish nothing. Just because you can change this vague implicit contract without the cooperation of your consumers does not mean they can now use this service. Remember, they’ll be sending what they thought was the correct message before this contract update. Now they will just be sending the incorrect, old format and not know why it doesn’t work the way they expect.
I’ve seen some reputable software companies take this approach, and I’ve never seen it work out well. There is a difference between extension points in a schema and untyped messaging. As my friend Phil
Boardman pointed out ‘Untyped messaging is basically an angle bracket delimited text file’ (Phil does admit he didn’t think of this, but I heard it from him).
Implicitly ordered operations
This is a much more subtle, and perhaps more dangerous, type of coupling. This is where implicit rules or restrictions work their way into a service. Imagine a service with operations like these:
- Add Item to Basket
- Validate Order
- Place Order
This should look funny to begin with for a few reasons. First of all this looks a lot more like an API than a service, and second you can see there is probably some sort of order expected for these operations, but it is not clearly defined. You would need a separate read me document to tell you that you must first call the Login operation, then Add Item to Basket, then Validate Order before calling Place Order. WS-Policy is one way to solve the order of operations, but it would only mask the problem (mostly because it is not widely adopted or understood).
This example service also violates other principles: Autonomy and Statelessness (these will be covered in a later post). By requiring multiple separate operations to be sent in a specific order this service implicitly contains state within it (if it didn’t it wouldn’t know about your shopping cart or the items in it) and it forces consumers to understand its internal working (the order of operations). These are bad signs as changes to the service will almost certainly impact the consumer. Let’s suppose your service now needed to calculate tax or shipping. This type of service would have no real place for them.
This service should really exist as one operation: Place Order. In the implementation of this service the validation and login should all take place together in one unit of work or transaction script.
The continuum of coupling
Service design is a series of tradeoffs; the impacts of which are not always immediately clear and thus coupling is really a continuum and where your services fall on this continuum will vary with every implementation based upon these tradeoffs. For instance the goal of flexibility is in direct opposition with the goals of validation and control. This is why service design deserves careful attention before you begin implementation. This is the essence of contract-first development, and it is a good idea both in code and in services. Test Driven Development really shines here because you will get to know what your services are like before you are stuck with their legacy implementations.
Definition of Loose Coupling
When I alluded to that definition of loose coupling I would probably break it down like this: Something is loosely coupled if its interactions are separated by abstractions of type, transport, platform, and state.
So how can you tell if a service (or code for that matter) is loosely coupled? This part is strangely simple. If it is difficult to test a service, class, or method then it is probably not loosely coupled. Inversely, code that can be easily tested without major setup and teardown is loosely coupled. To be clear when I say test, I mean effective, completely automated tests. It’s very easy to make tests that are almost useless without realizing it. A test suite for even a single service should cover all the major expected scenarios for valid and invalid service invocation.
This is one of those “Code Smells” Martin Fowler writes about in Refactoring and you should be aware of it. When you start to realize your setup and teardowns are very large and cumbersome your code is slipping into tight coupling. This is the same for services and code in general (which is normally service implementations). This is probably the last warning sign that you’re about to have major maintenance problems; unless you don’t have adequate tests, in which case you’ll end up with major production problems.