Consumer-Driven Contracts was first and foremost an attempt at a contract vocabulary – though you’d be forgiven for thinking otherwise: the vocabulary definitions are buried away in a wealth of implementation detail. The original article is based around a simple, XML-based example – that of evolving an Order message schema in response to the changing needs of both a service provider and its consumers. On top of that, I discuss the Must Ignore convention as it is applied in XML Schema, and the use of languages such as Schematron to implement alternatives to XML Schema validation.
Looked at from the point of view of these details, the article appears to be nothing more that a summary of some reasonably well-known validation techniques as they apply to specific schema, validation and contract languages. But I’m not sure that’s all there is to it. For me, the core focus has always been on a simple separation of concerns. Consumer-Driven Contracts was motivated by a desire to unpack the term “loosely coupled;” to turn a spotlight on the several facets of the contractual relations between services, and to name each part. I wanted to create a vocabulary for describing the roles of the participants in a loosely coupled relationship, so that we might better understand who does what when a service changes, and so articulate the knotty problem of service evolution.
Services collaborate to satisfy a business process. Where two services communicate, at least one assumes the role of provider, the other of consumer. To the extent that the consumer uses something of the provider, it is coupled to that provider. In the interests of autonomy, process agility, reuse, and so on, we seek to reduce the degree of coupling between providers and consumers. But no matter how successful we are in loosely coupling services, we recognize that for a system to do something useful, some small amount of coupling must remain.
What does it mean to be coupled, one service to another? What relations or forces are at play between provider and consumer? Between a provider and all the consumers who depend upon it?
We can begin answering these questions by reviewing our understanding of service contracts. Service contracts are often described as what the requestors and providers of a service must agree on. In the WS-* universe, service contracts are expressed using commoditised, standards-based XML vocabularies:
- WSDL (Web Service Description Language) descriptions of a service’s operational behaviour – including structural definitions of the operations a service supports and the messages that go in and come out of a service – together with details of where to find a service and how to bind to it;
- XML Schema descriptions of the messages sent and received by the service;
- WS-Policy descriptions of any constraints on accessing and using the service, such as security requirements that a consumer must adhere to, or acceptable transports over which the service can be contacted. Policies separate these constraints from the service’s behaviour: if a service is relocated, perhaps to a new address or transport, its policy may change whilst its behaviour remains the same.
Such service contracts promote loose coupling by encapsulating the provider’s execution environment, thereby increasing the platform independence of the several parties in a distributed system.
A key point to note about Web Service contracts is that they are almost always explicit, being published using one or more of the contract formats described above. With the standardisation of these formats, there arose widespread tool support for generating language-specific clients and message representations. Looked at in one way, this appears to be the natural maturation of the Web Services platform; looked at another, the volume of tool support hints at the development burden imposed by a service’s relative freedom to implement an illimitable number of operations and message schemas. Operations proliferate because there are no intrinsic constraints on the number or names of operations exposed by a WS-* service: your service exposes a GetCustomerDetails operation; mine supports HandleCustomerDetailRequest. Message schemas proliferate in large part because of the de facto predominance of XML. If all you have is XML, then the choice – if you can call it a choice – of media type adds nothing to the interpretation of a message. This leads us to innovate at the level of the structure of the representations we make using that one media type: all too often, your Customer schema ends up looking nothing like my Customer schema.
In short, the Web Services platform trades standardised, commoditised and constrained contract metadata in return for an unconstrained set of operations and message schemas. From the point of view of a consumer wanting to invoke operations on and exchange messages with service providers, each service differs from every other, thereby requiring a service-specific proxy per consumer. Hence the need for explicit contracts on the provider side and toolset support for generating proxies on the consumer side.
Unlike WS-* services, RESTful services typically eschew explicit contracts: but that doesn’t mean that RESTful contracts don’t exist. Stefan Tilkov discusses this very issue in his Addressing Doubts about REST. The implicit RESTful contract is in large part captured by the HTTP specification, which describes a set of operations that can be performed on the resources exposed by a service, together with a set of response codes that guide a consumer’s interactions with those resources. Format-wise, REST encourages us to choose an appropriate IANA-recognised media type, such as XHTML, Atom, JSON, form encoding, or even XML, to structure a representation in the most economical and widely recognized manner possible. This focuses us on identifying potential structural similarities in our data, rather than essential semantic differences. A set of configuration parameters, for example, appears quite different from a customer’s contact details; leading us, perhaps, to create a Config schema for the one, a Customer schema for the other. But looked at in terms of their structure, both resources might be thought of as comprising sets of name-value pairs, making it practical to represent them in a widely recognised form-encoded format.
Whether implicit or explicit, service contracts to date have been overly provider-centric. In contrast to this monadic focus, Consumer-Driven Contracts distinguishes three types of contract: provider contracts, consumer contracts and consumer-driven contracts. I suggest that these contractual relations exist whether we like it or not. In a sense, we don’t opt to implement these contracts, least of all the consumer and consumer-driven contracts: they exist from the moment a service is up and running and being used by one or more clients. We’re not obliged to design and build to these contracts, but having named them and surfaced them as distinct from one another, we can leverage their characteristics if we so choose.
In my next post I’ll describe each of these three contracts in more detail.