Circling back to the theme of service definition and contracts, let's look at the principles that are important to keep in mind when designing "good" services. As with most design pursuits, there is rarely an "ideal" model or an absolute truth. It's frequently necessary to balance influences to achieve the most appropriate and valuable design. Striking the right balance requires good experience and the ability to apply effective judgment. Fortunately, there are a number of well-established principles and patterns that help encourage good decisions about services, interfaces, and relationships. Leveraging this shared perspective leads to better designs.
The principles discussed here build on the most fundamental characteristic of a quality service definition: explicit boundaries. Our service definitions must precisely define the interface syntax and semantics - what it looks like and how it behaves. The definition forms a "contract" - a commitment on the part of the service - that the specified interface and behavior will continue to be honored until which time the "contract" terms are explicitly modified.
The remaining design quality attributes build upon this contract foundation and make solutions easier to build, support, and enhance. We'll briefly define several of these attributes and consider their impact:
The Loose Coupling design goal encourages the design of an interface that makes as few assumptions as possible about the service consumer or other factors external to the component exposing the interface.
The objective is to encourage modular and flexible design by reducing the interdependencies among elements and reducing the risk that changes in one element will necessitate changes in another. Adhering to this principle reduces the overall complexity of the system and significantly enhances flexibility.
The encapsulation principle motivates us to hide behavioral characteristics and data constructs behind the elements interface. In a well-encapsulated service, the implementation is clearly distinguished from the service contract and hidden from view (or concern). Strong encapsulation is an important tool for creating loosely-coupled designs.
As described, encapsulation is strongly aligned with "information hiding". Some have argued that these are distinct characteristics, emphasizing the role of encapsulation as one of keeping the data packaged with the operations that can be performed on that data. There is a fair argument to be made, but I tend to think of it as stated and emphasize the latter concern as a distinct principle called Autonomy (see below).
While applied in somewhat of a different context, network protocols and the OSI model provide an easy way to think about encapsulation. From Wikipedia:
- In computer networking, "encapsulation" is to include data from an upper layer protocol into a lower layer protocol... This is a method of abstraction for networking by allowing different layers to add features/functionality.
The American Heritage Dictionary defines the word autonomous with expressions such as the following:
- Self-government or the right of self-government; self-determination.
- Not controlled by others or by outside forces; independent:
- There is full authority to change any internal aspect of the service implementation, so long as the external contract is strictly maintained
- There is no authority to change the responsibilities of another service
A service exhibits good orthogonality when it's interface provides:
- one straightforward way to accomplish a given task
- no redundancy
- no side effects
Orthogonality is a system design property facilitating feasibility and compactness of complex designs. Orthogonality guarantees that modifying the technical effect produced by a component of a system neither creates nor propagates side effects to other components of the system. The emergent behavior of a system consisting of components should be controlled strictly by formal definitions of its logic and not by side effects resulting from poor integration, i.e. non-orthogonal design of modules and interfaces. Orthogonality reduces testing and development time because it is easier to verify designs that neither cause side effects nor depend on them.There's an amusing and effective description of orthogonality in Pragmatic Programmer.1 I wont' go into the whole story, but Dave Thomas describes how the controls on a helicopter are anything but orthogonal (unlike the car mentioned in the Wikipedia description). If you adjust one of the controls to influence, say, the pitch, you must also adjust others to change things like the tail rotor and the throttle, or you won't achieve the desired results. Essentially, a change in one characteristic of the helicopter's flight has side-effects on others, and those side-effects must be anticipated and accounted for to obtain the desired behavior.
For example, a car has orthogonal components and controls (e.g. accelerating the vehicle does not influence anything else but the components involved exclusively with the acceleration function). On the other hand, a non-orthogonal design might have its steering influence its braking (e.g. Electronic Stability Control), or its speed tweak its suspension.
In designing systems, it's essential that we provide orthogonal controls that can be easily understood - by the consumers who will use the service and by the developers who must make future changes to its implementation. Orthogonal designs lead to significantly reduced development and test time, greater flexibility, and enhanced opportunities for reuse.
An idempotent design is one that has the same effect if an operation is used multiple times as it does if used only once. This is especially important in the types of highly-distributed systems we frequently build these days, where interdependent design elements may be isolated on separate networks, potentially positioned in different geographical locations (increasing the chances of communication failures).
There's a common maxim in organization (at least I hear it a lot on those home improvement shows):
"A place for everything and everything's in its place"I may be taking a bit too much liberty with the principle, but I think this is a reasonable way to describe high cohesion. When a service exhibits high cohesion, its responsibilities are intuitive, unambiguous and focused. They're also well-organized. The service offers the "right" set of related capabilities and stays focused - not trying to become all things.
High cohesion reduces complexity and increases reuse, in part because of the ease of understanding the role and purpose of the element.
Understanding these principles and energetically applying them when designing software systems will lead to products that are delivered more quickly with higher quality, exhibit greater flexibility and adaptability, encourage greater levels of reuse, hold up more reliably under the pressures of time and influences of evolution, and overall add greater value to the business.
1Hunt, Andrew, and David Thomas. The Pragmatic Programmer: From Journeyman to Master. Addison-Wesley, 1999.