Consumer Contracts - Evolution Pattern
Consumer Contracts - Evolution Pattern
Consumer Contracts - Evolution Pattern
Evolution Pattern
This article discusses some of the challenges in evolving a community of service
providers and consumers. It describes some of the coupling issues that arise when
service providers change parts of their contract, particularly document schemas, and
identifies two well-understood strategies - adding schema extension points and
performing "just enough" validation of received messages - for mitigating such issues.
Both strategies help protect consumers from changes to a provider contract, but neither
of them gives the provider any insight into the ways it is being used and the obligations it
must maintain as it evolves. Drawing on the assertion-based language of one of these
mitigation strategies - the "just enough" validation strategy - the article then describes the
"Consumer-Driven Contract" pattern, which imbues providers with insight into their
consumer obligations, and focuses service evolution around the delivery of the key
business functionality demanded by consumers.
One of the most common ways in which we might evolve a service is to add an
additional field to a document on behalf of one or more consumers. Depending on how
the provider and consumers have been implemented, even a simple change like this can
have costly implications for the business and its partners.
In our example, after the ProductSearch service has been in production for some time, a
second reseller considers using it, but asks that a Description field be added to each
product. Because of the way the consumers have been built, the change has significant
and costly implications both for the provider and the existing consumers, the cost to each
varying based on how we implement the change. There are at least two ways in which
we can distribute the cost of change between the members of the service community.
First, we could modify our original schema and require each consumer to update its copy
of the schema in order correctly to validate search results; the cost of changing the
system is here distributed between the provider - who, faced with a change request like
this, will always have to make some kind of change - and the consumers, who have no
interest in the updated functionality. Alternatively, we could choose to add a second
operation and schema to the service provider on behalf of the new consumer, and
maintain the original operation and schema on behalf of the existing consumers. The
cost of change is now constrained to the provider, but at the expense of making the
service more complex and more costly to maintain.
A business can only fully realise these benefits, however, if its SOA enables services to
evolve independently of one another. To increase service independence, we build
services that share contracts, not types. Even so, we often end up having to evolve
consumers at the same rate as the service provider, chiefly because we've made the
consumers depend on a particular version of the provider's contract. In the end, service
providers find themselves adopting a cautious approach to changing any element of the
contract they offer their consumers; this, in part, because they cannot anticipate or gain
insight into the ways in which consumers realise this contract. At worst, service
consumers realise a provider contract and couple themselves to the provider by naively
expressing the whole of a document schema within their internal logic.
Contracts enable service independence; paradoxically, they can also couple service
providers and consumers in undesirable ways. Without introspecting the function and
role of the contracts we implement in our SOA, we subject our services to a form of
"hidden" coupling that we are rarely equipped to address in any systematic fashion. The
absence of any programmatic insights into the ways in which a community of services
has adopted a contract, and the lack of constraints on the implementation choices made
by service providers and consumers, combine to undermine the purported benefits of
SOA-enabling the enterprise. In short, the enterprise becomes burdened with services.
Schema Versioning
We can begin our investigations into the contract and coupling problems that bedevil our
ProductSearch service by looking at the issue of schema versioning. The WC3 Technical
Architecture Group (TAG) has described a number of versioning strategies that might
help us evolve our service's message schemas in ways that mitigate our coupling
problems. These strategies range from the excessively liberal none, which mandates
that services must not distinguish between different versions of a schema, and must
therefore tolerate all changes, to the exceedingly conservative big bang, which requires
services to abort if they receive an unexpected version of a message.
Both extremes bring with them problems that inhibit the delivery of business value and
exacerbate the total cost of ownership of the system. Explicit and implicit "no versioning"
strategies result in systems that are alike in being unpredictable in their interactions,
fragile, and costly to change downstream. Big bang strategies, on the other hand, give
rise to tightly coupled service landscapes where schema changes ripple through
providers and consumers, disrupting uptime, retarding evolution and reducing revenue
generating opportunities.
Our example service community effectively implements a big bang strategy. Given the
costs associated with enhancing the business value of the system, it is clear that the
providers and consumers would benefit from a more flexible versioning strategy - what
the TAG finding calls a compatible strategy - which provides for backwards- and
forwards-compatible schemas. In the context of evolving services, backwards-compatible
schemas enable consumers of newer schemas to accept instances of an older schema:
a service provider built to handle new versions of a backwards-compatible request, say,
could nonetheless still accept a request formatted according to an old schema.
Forwards-compatible schemas, on the other hand, enable consumers of older schemas
to process an instance of a newer schema. This is the sticking point for the existing
ProductSearch consumers: if the search result schema had been made forwards-
compatible when first put into production, the consumers would be able to handle
instances of the new version of the search result without breaking or requiring
modification.
Extension Points
Let's now roll back time and, from the outset of our service's lifetime, specify a forwards-
compatible, extensible schema:
This schema includes an optional Extension element at the foot of each product. The
extension element itself can contain one or more elements from the target namespace:
Now when we receive a change request to add a description to each product, we can
publish a new schema with an additional Description element that the provider inserts
into the extension container. This allows the ProductSearch service to return results that
include product descriptions, and consumers using the new schema to validate the entire
document. Consumers using the old schema will not break, though they will not process
the description. The new results documents look like this:
Note that the first version of the extensible schema is forwards-compatible with the
second, and that the second is backwards-compatible with the first. This flexibility,
however, comes at the expense of increased complexity. Extensible schemas allow us to
make unforeseen changes to an XML language, but by the same token, they provide for
requirements that may very well never arise; in so doing, they obscure the expressive
power that comes from a simple design, and frustrate the meaningful representation of
business information by introducing meta-informational container elements into the
domain language.
We'll not discuss schema extensibility further here. Suffice to say, extension points allow
us to make backwards- and forwards-compatible changes to schemas and documents
without breaking service providers and consumers. Schema extensions do not, however,
help us manage the evolution of a system when we need to make what is ostensibly a
breaking change to a contract.
Breaking Changes
Unfortunately, with our existing setup, if we remove a required component - in this case,
the InStock field - from our extensible schema, we will break existing consumers. To fix
the provider, we have to fix the entire system: when we remove the functionality from the
provider and publish a new contract, each consumer application will have to be
redeployed with the new schema, and the interactions between services thoroughly
tested. The ProductSearch service in this respect cannot evolve independently of its
consumers: provider and consumers must all jump at the same time.
Our service community is frustrated in its evolution because each consumer implements
a form of "hidden" coupling that naively reflects the entirety of the provider contract in the
consumer's internal logic. The consumers, through their use of XSD validation, and to a
lesser extent, static language bindings derived from a document schema, implicitly
accept the whole of the provider contract, irrespective of their appetite for processing the
component parts.
David Orchard provides some clues as to how we might have avoided this issue when
he alludes to the Internet Protocol's Robustness Principle: "In general, an
implementation must be conservative in its sending behaviour and liberal in its receiving
behaviour". We can augment this principle in the context of service evolution by saying
that message receivers should implement "just enough" validation: that is, they should
only process data that contributes to the business functions they implement, and should
only perform explicitly bounded or targeted validation of the data they receive - as
opposed to the implicitly unbounded, "all-or-nothing" validation inherent in XSD
processing.
Schematron
</schema>
Notice that this sample Schematron schema makes no assertions about elements in the
underlying document for which the consuming application has no appetite. In this way,
the validation language explicitly targets a bounded set of required elements. Changes to
the underlying document's schema will not be picked up by the validation process unless
they disturb the explicit expectations described in the Schematron schema, even if those
changes extend to deprecating or removing formerly mandatory elements.
Here then is a relatively lightweight solution to our contract and coupling problems, and
one that doesn't require us to add obscure meta-informational elements to a document.
So let's roll back time once again, and reinstate the simple schema described at the
outset of the article. But this time round, we'll also insist that consumers are liberal in
their receiving behaviour, and only validate and process information that supports the
business functions they implement (using Schematron schemas rather than XSD to
validate received messages). Now when the provider is asked to add a description to
each product, the service can publish a revised schema without disturbing existing
consumers. Similarly, on discovering that the InStock field is not validated or processed
by any of the consumers, the service can revise the search results schema - again
without disturbing the rate of evolution of each of the consumers.
At the end of this process, the ProductSearch results schema looks like this:
Consumer-Driven Contracts
The use of Schematron in the above example leads to some interesting observations
about contracts between providers and consumers, with implications beyond document
validation. In this section we draw out and generalize some of these insights and express
them in terms of a pattern we call Consumer-Driven Contract.
The first thing to note is that document schemas are only a portion of what a service
provider has to offer consumers to enable them to exploit its functionality. We call the
sum total of these externalized exploitation points the provider contract.
Provider Contracts
The definition of contract here is a little broader than the one we might usually offer when
talking about services, but from a service evolution perspective it usefully abstracts the
significant forces that impact our problem domain. That said, the definition is not meant
to be exhaustive in terms of the kinds of elements a provider contract might contain: it
refers simply to a logical set of exportable business function elements that are
candidates for including in a service evolution strategy. From a logical point of view, this
set of candidate elements is open, but in practice internal or external factors, such as
interoperability requirements or platform limitations, may constrain the type of elements a
contract can contain. For example, a contract belonging to a service that conforms to the
WS-Basic profile will likely not contain policy elements.
Consumer Contracts
In our example, we can say that the set of assertions generated by all consumers
expresses the mandatory structure of the messages to be exchanged during the period
in which the assertions remain valid for their parent applications. If the provider were
possessed of this set of assertions, it would be able to ensure that every message it
sends is valid for every consumer insofar as the set of assertions is valid and complete.
Generalizing this structure, we can distinguish what we have already called the provider
contract from the individual contractual obligations that obtain in instances of provider-
consumer relationships, which we will now call consumer contracts. When a provider
accepts and adopts the reasonable expectations expressed by a consumer, it enters into
a consumer contract.
Figure 3: Consumer contracts
◾ Open and incomplete Consumer contracts are open and incomplete with respect to
the business functionality available to the system. They express a subset of the
system's business function capabilities in terms of the consumer's expectations of the
provider contract.
◾ Multiple and non-authoritative Consumer contracts are multiple in proportion to the
number of consumers of a service, and each is non-authoritative with regard to the
total set of contractual obligations placed on the provider. The non-authoritative
nature of the relationship extending from consumer to provider is one of the key
features that distinguish a service-oriented architecture from a distributed application
architecture. Service consumers must recognize that their peers in a service
community are liable to consume the provider in ways quite different from their own.
Peers may evolve at different rates and demand changes of the provider that
potentially disturb the dependencies and expectations residing in other parts of the
system. A consumer cannot anticipate how or when a peer will disturb the provider
contract; a client in a distributed application has no such concerns.
◾ Bounded stability and immutability Like provider contracts, consumer contracts
are valid for a particular period of time and/or location.
Consumer-Driven Contracts
Consumer contracts allow us to reflect on the business value being exploited at any point
in a provider's lifetime. By expressing and asserting expectations of a provider contract,
consumer contracts effectively define which parts of that provider contract currently
support the business value realized by the system, and which do not. This leads us to
suggest that service communities might benefit from being specified in the first instance
in terms of consumer contracts. In this view, provider contracts emerge to meet
consumer expectations and demands. To reflect the derived nature of this new
contractual arrangement, we call such provider contracts consumer-driven contracts or
derived contracts.
The following table summarizes the characteristics of the three types of contract
described in this article:
Contracts may be expressed and structured in several ways. In their simplest form,
consumer expectations can be captured in a spreadsheet or similar document and
implemented during the design, development and testing phases of a provider
application. By going a little further and introducing unit tests that assert each
expectation, we can ensure that contracts are described and enforced in a repeatable,
automated fashion with each build. In more sophisticated implementations, expectations
can be expressed as Schematron- or WS-Policy-like assertions that are evaluated at
runtime in the input and output pipelines of a service endpoint.
Benefits
Of course, our ability to start with a minimal set of lean requirements and evolve our
service as and when consumers demand presupposes that we are in a position to
evolve, deploy and operate the service in a controlled and efficient manner. This is
where the Consumer-Driven Contract pattern provides a second key benefit. Consumer-
driven provider contracts give us the fine-grained insight and rapid feedback we require
to plan changes and assess their impact on applications currently in production. In
practice, this allows us to target individual consumers and provide incentives for them to
relinquish an expectation that is stopping us from making a change that is not currently
backwards- and/or forwards-compatible. By deriving our service providers from
consumer contracts, we imbue them with a repository of knowledge and a feedback
mechanism that we can draw on during the operations part of the system lifecycle.
Liabilities
In this article we've identified the motivation for introducing consumer-driven contracts
into the service landscape, and thereafter described how the Consumer-Driven Contract
pattern addresses the forces that determine service evolution. We will end by discussing
the scope of the pattern's applicability, together with some of the issues that may arise
while implementing consumer and consumer-driven contracts.
We've suggested that systems built around consumer-driven contracts are better able to
manage breaking changes to contracts. But we don't mean to suggest that the pattern is
a cure-all for the problem of breaking changes: when all's said and done, a breaking
change is still a breaking change. We do believe, however, that the pattern provides
many insights into what actually constitutes a breaking change, and as such may serve
as the foundation for a service versioning strategy. Moreover, as we've already
discussed, service communities that implement the pattern are better placed to
anticipate the effects of service evolution. Provider development and operations teams in
particular can more effectively plan their evolutionary strategies - perhaps by deprecating
contractual elements for a specific period and simultaneously targeting recalcitrant
consumers with incentives to move up to new versions of a contract.
Further Listening
You can listen to Ron Jacobs of Microsoft interviewing myself and Martin Fowler at the Microsoft Architect
Insight Conference in March 2006. We discuss Consumer Driven Contracts in the context of changing
architectures.
Acknowledgments
Ian Cartwright, Duncan Cragg, Martin Fowler, Robin Shorrock, Joe Walnes
Significant Revisions
12 June 2006: First published