Copyright © 2003 W3C® (MIT, INRIA, Keio), All Rights Reserved. W3C liability, trademark, document use, and software licensing rules apply.
This document focuses on providing information on how a language can be designed for forwards compatible versioning, often the hardest type of versioning to plan for. It also provides motivation for versioning and some discourse on incompatible and backwards compatible versioning. Separate documents contain the terminology definitions and XML language specific discussion.
This document has been developed for discussion by the W3C Technical Architecture Group. It does not yet represent the consensus opinion of the TAG.
Publication of this finding does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time.
Additional TAG findings, both approved and in draft state, may also be available. The TAG expects to incorporate this and other findings into a Web Architecture Document that will be published according to the process of the W3C Recommendation Track.
Please send comments on this finding to the publicly archived TAG mailing list www-tag@w3.org (archive).
1 Introduction
1.1 Why Do Languages Change?
1.2 Kinds of Languages
2 Versioning Strategies
2.1 Why Have a Strategy?
2.1.1 Identifying Languages
2.1.1.1 Version Numbers
2.1.1.2 XML Namespaces
3 Big Bang/Incompatible
4 Backwards compatible
4.1 Replacement
4.2 Side-by-side
5 Forwards Compatible
5.1 Must Accept Unknowns: 3 variants
5.1.1 Ignore all or only unknown part
5.2 Fallback Provided
5.3 Understanding unknown version identifiers
5.4 Supporting functionality
6 Mixtures
7 Conclusion
8 References
9 Acknowledgements
A Change Log (Non-Normative)
The evolution of languages by adding, deleting, or changing syntax or information is called versioning. Making versioning work in practice is one of the most difficult problems in computing. Arguably, the Web rose dramatically in popularity because evolution and versioning were built into HTML and HTTP. Both systems provide explicit extensibility points and rules for understanding extensions that enable their decentralized extension and versioning.
This finding describes general problems and techniques in evolving systems in compatible ways. The terminology definitions used throughout are expressed in [Versioning]. A number of design patterns and rules are discussed with a focus towards enabling language changes such that newer version(s) of a language are processable by software that only understands the older version(s) of the language, aka forwards-compatibility. There are a few crucial good practices that enable forwards compatible versioning in a language:
the language should be extensible;
any extensions in a text of the language should have a well-defined default meaning (which often is that the extension conveys no information and can be ignored);
if the texts of the language contain version identifiers, then a given language version should define a set of compatible future version identifiers.
There are many reasons why a different version of a language may be needed. A few of them include:
Bugs may need to be fixed. Production use may reveal defects or oversights that need to be fixed. This may involve changes to texts of the language or changes to the information of existing texts.
Changing requirements may motivate changes in the language. For example, a middle, prefix, suffix, common may be added to a name structure.
Different variations of a language may be desirable. For example, the XHTML 1.0 Recommendation defines strict, transitional, and frameset schemas. All three of those schemas purport to define the same namespace, but they describe different languages.
And additional languages may be defined by other specifications, such as the XHTML Basic Recommendation.
Whatever the cause, over time, different versions of the language exist and designing applications to deal with this change in a predictable, useful way requires a versioning strategy.
Whether ten, a hundred, or a million resources have been deployed, if a language is changed in such a way that all those applications will determine that texts of the new language are invalid, a versioning problem with real costs has been introduced.
Ultimately, there are different kinds of languages. The versioning approaches and strategies that are appropriate for one kind of language may not be appropriate for another. Among the various kinds of languages, we find:
Just Names: some languages are just list(s) of acceptable names. Using names to identify words in the WordNet database, for example, or the names of functions and operators in XPath2 are examples of "just name" languages.
Markup: SGML, HTML, and the non-SGML variants of HTML are all markup languages. XML languages are described in Versioning - XML
Non-markup Text: languages designed with a text format. These may be programming languages such as Java or ECMAScript, or data formats like CSS or Comma Separated Values. Typically these are intended for humans to author and or view.
binary: languages that are not in a text format. These may be image formats like GIF, JPEG, or even binary encoded XML.
This is by no means an exhaustive list. Nor are these categories exclusive. Many languages have mixed modes. For example, XQuery has a text mode and an XML mode.
Languages may be composed of different languages, including different kinds of languages. If a language is composed of other languages, then languages are considered nested. There may be different versioning strategies for each nested language, and they all combine together into the overall versioning strategy.
In broad terms, the strategies to versioning fall into a number of classes ranging from "none" to a "big bang":
None. No distinction is made between versions of the language. Applications are either expected not to care, or they are expected to cope with any version they encounter.
Compatible. Designers are expected to limit changes to those that are either backwards or forwards compatible, or both.
Backwards compatibility. Applications are expected to behave properly if they receive a text of the "older" version of a language.
Forwards compatibility. Applications are expected to behave properly if they receive a text of the "newer" version of a language.
Big bang. Applications are expected to abort if they see an unexpected version.
There's no single approach that's always correct. Different application domains will choose different approaches. But by the same token, the approaches that are available depend on other choices or constraints. One very important constraint is the whether the language can be evolved by distributed parties such that parallel evolutionary development can occur. The dependencies makes it imperative to plan for versioning from the start. If versioning is not planned from the start, then the possible versioning strategies may be constrained by decisions that have already been made.
A language commonly goes through a lifecycle of iterative development followed by deployment followed by deployment of new versions. The point in the lifecycle of the language may also affect the selection of the versioning strategy for the language
Just as there are a number of strategies, there are a number of designs for implementing a strategy. The internet - including MIME, markup languages, and XML languages have successfully used various strategies, either singly or in combination. Summaries of strategies and requirements have been produced for earlier technologies and guided XML Namespaces and Schema, such as [Web Architecture: Extensible Languages].
Different kinds of languages and different versioning strategies expose different problems. Attempting to deploy a system that provides no versioning mechanism puts the burden of version "discovery" on consumers and is often impractical in anything except a closed system.
At the other end of the spectrum is the "big bang" approach which is also problematic.
"Big bang" is a very coarse-grained approach to versioning. It establishes a single version identifier, either a version number or namespace name, for an entire text.
The semantics of the "big bang" are that applications decide on the basis of the text version whether or not they know how to process that text. If the version isn't recognized, the entire text is rejected. Typically, when introducing a new version using the big bang approach, all of the software that produces or consumes the texts is updated in a sweeping overhaul in which the entire system is brought down, the new software deployed and the system is restarted. This big bang approach to versioning is practical only in circumstances where there is a single controlling authority, and even in that case, it carries with it all manner of problems. The process can take a considerable amount of time, leaving the system out of commission for hours if not days. This can result in significant losses if the system is a key component of a revenue generating business process and the cost of coordinating the system overhaul can also be quite costly as well.
The "big bang" approach is appropriate when the new version is radically different from its predecessor. But in many cases, the changes are incremental and often a consumer could, in practice, cope with the new version. For example, it might be that there are many messages that don't use any features of the new version or perhaps it is appropriate to simply ignore components that are not recognized.
Consider a producer and a consumer exchanging messages of a particular language. Imagine that some future version of the language defines a new component. Because producers and consumers are distributed,vit may happen that an old consumer, one unprepared for a new component, encounters a message with a new component sent by a newer producer.
If big bang versioning is used, old systems will reject the new message. However, if the versioning strategy allowed the old consumer to simply ignore unrecognized content, it's quite possible that other components of the system could simply adapt to the previous behavior. In effect, the old system would ignore the new component and so it would "see" a message that looks just like the old message it is expecting.
For the producer, the result would be that the request is fulfilled, though perhaps not quite the way it may have hoped. For example, a request that results in a response may return a text of the language without the new component. In many cases this may be better behavior than receiving an error. In particular, producers using the new language can be written to cope with the possibility that they will be communicating with older consumers.
If the new system needs to make sure that the new component is understood, then it can change the language in a way to indicate that the new behavior is not considered optional, aka backwards compatible.
Often, what is needed is some sort of middle ground solution.
As part of a strategy for language design, it is often necessary to be able to determine the specific language of a given text. This is often done by providing an identifier of the version of text, such as a version number or some other structure such as an XML Namespace. The basic rule for languages and version identification strategies is:
Good Practice
Language Identification rule: Any Languages intended for versioning SHOULD have a version identification strategy
The difficult, important, and often overlooked part of a version identification strategy is specifying the meaning and interpretation when a consumer encounters a version identifier it does not know about. A typical problem with version identifiers is that it is unclear what is being identified with the version identifiers. The common misconception is that a version identifier, suchy as a number, indicates the version of the document. That may be true, but in many cases it is an incomplete and unusable assertion.
In the scenario of documents, there are many possibilities for what a version identifier identifies. For a document that is potentially in many different versions of a language, an identifier that is a number could be:
The highest version of the language the document is compatible with
The lowest version of the language the document is compatible with
The lowest version of the language that has all the features the language uses
The range of versions of the language the document is compatible with
Imagine a name language version 1 is first, last, and extensions; and version 2 is first, last, optional middle, and extensions. If a name contains first and last, should the identifier be version 1 or 2? The previous options yield answers of: 2, 1, 1, 1-2. If a name that has first, last, middle then the previous options yield answers of: 2, 1, 2, 1-2?
Usually, the document contains the version number of the latest version of the language that the producing application understands. Thus the "newest" version of the identifier is used, even if the document itself is valid under older versions of the language. This usually works fine if the producer and consumer are at the same version, or even if the consumer understands the older and the newer version. But forwards compatibility requires that a consumer that doesn't understand the newer version must somehow treat the document as if it was an older version. Approaches for using version identifiers to enable forwards compatibility is covered in 5 Forwards Compatible
As a side note, version identifiers are often used in protocols that exchange documents. One scenario is that the version number identifies the highest version of the language the document is compatible with AND the highest version of the language that can the producer will understand when it is treated as a consumer. In this case it is a protocol version identifier, not just a format identifier. HTTP is a good example. The HTTP specification says "The protocol versioning policy is intended to allow the sender to indicate the format of a message and its capacity for understanding further HTTP communication, rather than the features obtained via that communication.". Because HTTP is a request-response protocol, the capacity for further HTTP communication is the crucial "version" information conveyed. Most documents that might have version #s do not fit that "future capacity" use case, they are just documents that do not have a protocol.. There are cases where a document format is combined with a protocol, such as Atom. These combination protocol/format case are fairly rare and not generally applicable.
Version identification has traditionally been done with a decimal separating the major versions from the minor versions, ie "8.1", "1.0". Often the definition of a "major" change is that it is incompatible, and the definition of a "minor" change is that it is forwards- and/or backwards - compatible. Usually the first broadly available version starts at "1.0". A compatible version change from 1.0 might be identified as "1.1" and an incompatible change as "2.0".
The version numbers can be contained in the texts, in the protocol messages containing in the text, or the address for the protocol messages. Some examples are shown below:
<name version="2.0"> <given>Dave</given> <family>Orchard</family> </name> <span class="fn20">Dave Orchard</span> urn:nameschemev2:given:Dave:family:Orchard <?XML version="1.1"?> GET /name/123456789 HTTP/1.1 GET /name/v2/123456789/ HTTP 1.1
It should be noted that associating version number changes with compatibility changes may be idealistic as there abundant cases where this system does not hold. New major version identifiers are often aligned with product releases, or incompatible changes identified as a "minor" change. A good example of an incompatible changed identified as a minor change is XML 1.1. XML 1.0 processors cannot process all XML 1.1 documents because XML 1.1 extended XML 1.0 where XML 1.0 does not allow such extension.
Unfortunately, version numbers often wind up looking very similar to the big bang approach. In many approaches, each language is given a version identifier, almost always a number, that's incremented each time the language changes. Although it's possible to design a system with version numbers that enables both backward and forward compatibility - for example XSLT - typically a version change is treated as if that the new language is not backwards compatible with the old language.
Some efforts, such as HTTP, try to have the best of both worlds by allowing for extensibility (in HTTP's case, via headers) as well as version numbers that explicitly identify when a new version is backwards compatible with an old version.
One argument in favor of version numbers is that they allow one to determine what is a 'new version' and what is an 'old version'. But in practice this is not necessarily true. For example, RSS has 0.9x, 1.x, and 2.x versions, all being actively developed in parallel. In effect the version numbers, even though they appear to be ordered, are simply opaque identifiers. Using version numbers does not gaurantee that version 1+x has any particular relationship to version 1.
Version numbers typically work best when versioning and extending a language is done in a centralized and linear manner. The makeup of each version can then be consistent and well described.
There are many cases where decentralized and non-linear versioning is desired. The desire for decentralized and non-linear versioning and extensibility was a large motivator for XML and for XML Namespaces. The self-describing and extensible nature of XML markup, and the addition of XML Namespaces, provides a framework for developing languages that can evolve in a decentralized manner. XML Namespaces [ XML Namespaces 1.0] provide a mechanism for associating a URI with an XML element or attribute name, thus specifying the language of the name. This also serves to prevent name collisions.
As desirable as compatible evolution often is, sometimes a language may not want to allow it. In this model, a consumer will generate a fault if it finds a component it doesn’t understand. An example might be a security specification where a consumer must understand each and every extension. This suffers from the significant drawback that it does not allow compatible changes to occur in the language, as any changes require both consumer and producer to change. As this finding focuses on compatible versioning, we provide no more focus on incompatible evolution.
Backwards compatibility evolution of a language means that producers of texts in a language should be able to produce texts that consumers that have been updated with a newer version of the language will understand. It generally means supporting previous versions of text in a newer consumer. There are are two significant ways that backward compatibility can be supported.
In the replacement design, a new version of software replaces the old and the new version of the software supports the old and the new version. The producer may or may not need to distinguish between the old and the newer consumer or the texts produced. For example, a web resource that supports additional Name Information as input may not need to change the URI of the resource.
As our definition of backwards compatibility specifies that the newer language's Defined Text Set must be a superset of the older language's Defined Text Set, the typical change is the addition of optional content into the newer version of the language. The older producer simply won't produce texts with the newer content. It is possible to reduce the Defined Text Set by removing items and achieve backwards compatibility, as long as the newer Language's Accept Text set contains all the texts originally in the Defined Text Set. One mechanism to do this is to replace the content with a construct that allows the removed construct.
In the side-by-side design, the new version of the software and the old version of the software are deployed "side-by-side". One variant of the approach is offering both versions of the system, for example by using different URIs for the old and new with a particular focus on enabling older versions of applications to operate on inputs that make use of newer language features.. The request to one resource gets mapped to the other resource behind the scenes using a proxy or gateway. This "alternative" approach works when the intermediary can completely handle or generate the new information (for backwards compatibility) or accept the new information (for forwards compatibility). For example, adding SSL security to a resource changes the URI but a Web server can typically handle mapping the https: URI to the older http: URI. If both URIs are maintained, then the addition is a compatible change. Another example is where new information is required, such as the priority, and the intermediary can apply a default value to provide the required priority. However, this too has its costs as multiple versions of the software must be supported and maintained over time and there is the added cost of developing the proxy or gateway between the two environments. Further, this does not work in scenarios where the intermediary cannot generate the new required content. For example, if a middle name is required in V2, a middle cannot be generated from just a family and a given name.
Forwards compatible evolution of a language means that producers of texts in language should be able to produce texts in a revision of the language without consumers having to change existing implementations that know of only the original language. The most common characteristic of a compatible change is the addition of syntax and/or features in a language, usually using the original language's extensibility mechanisms. However, languages may change in a forwards compatible way through the removal of features or syntax. This finding deals with the common case, in which features and/or syntax are added not removed, and in which the goal is to maximize compatibility when making such changes.
A supreme example of the benefits of extensibility is HTML. The first version of HTML was designed for extensibility; it specified that "unknown markup" may be encountered. An example of this is the addition of the IMG tag. This is a great example of a language designed for extensibility. Perhaps the opposite example of extensibility is XML 1.0. The first version of XML was designed for language authors to create their own elements and attributes, but it did not allow for new unknown content. It did not allow for new characters in element or attribute names, new punctuation such as different quote characters. This made it very difficult to move to XML 1.1 and adding new characters to element names for internationalization and other purposes.
The first rule introduced in this Finding relating to extensibility is:
Good Practice
Be Extensible rule: Languages should be Extensible.
A language that allows additional syntax also requires a specification of what happens when the additional syntax is in a text. By the definition of Extensibility, there is a mapping from all texts with additional syntax to texts without. If the extensibility is used in a forwards-compatible way, then by definition the software consuming the extension does not know about the extension and we call such extension an unknown extension. If the software consuming the extension "knows" about the extension, then it has been revised and uses the revised language that incorporates the extension. The behavior of software when it encounters an unknown extension, that is the mapping from texts with additional syntax to texts without, should be clear.
The simplest model that enables forwards-compatible changes is to require that a language consumer must accept content that is unknown. This rule is:
Good Practice
Must Accept Unknowns Rule: Consumers MUST accept any text portion that they do not recognize.
This is sometimes called the "MUST Ignore Unknowns" rule. HTML 4.01 follows this approach "If a user agent encounters an element it does not recognize, it should try to render the element's content. ". Under HTML 4.01, a user agent is free to remove or preserve an element that is not recognized. The Must Accept Unknowns rule for XML was first standardized in the WebDAV specification RFC 2518 [6] section 14 and later separately published as the Flexible XML Processing Profile [3].
An extension that affects an existing component is an incompatible change because it changes the mapping of the text with the extension to one without such that a consumer that is unaware of the extension will produce Information from the text that is incompatible with the intended Information (more work...). An additional rule is required:
Good Practice
Preserve existing information Rule: An Extensible Language MUST require that any texts with extensions MUST be compatible with a text without the extensions.
There are two specific variants of the Must Accept rule that qualify what kind of handling beyond accepting is required. One model is to remove the unknown:
Good Practice
Must Accept and Remove Unknowns Rule (Must Accept variant 1): Consumers MUST accept and remove any text portion that they do not recognize.
There is a history of usage of the Must Accept and Remove Unknowns rule. HTML 1, 2 and 3.2 follow the Must Accept and Remove Unknowns Rule as they specify that any unknown start tags or end tags are mapped to nothing during tokenization.
Another model is to preserve the unknown. This rule is:
Good Practice
Must Accept and Preserve Unknowns Rule (Must Accept variant 2): Consumers MUST accept and preserve any text portion that they do not recognize.
HTTP 1.1 [7] specifies that a transparent proxy should accept and preserve any headers it doesn't understand: "Unrecognized header fields SHOULD be ignored by the recipient and MUST be forwarded by transparent proxies."
Ignoring content is a simple solution to the problem of substitution. In order to achieve a compatible evolution, the newer texts of a language must be transformable (or substitutable) into older texts. Object systems typically call this "polymorphism", where a new type can behave as the old type.
In tree based languages, which includes all markup languages, there are two broad types of Must Ignore rules for dealing with extensions, either ignoring the entire tree or just the unknown part of the tree. The rule for ignoring the entire tree is:
Good Practice
Must Accept All Rule: The Must Accept rule applies to unrecognized texts and their descendents in tree based formats.
This variation on must accept requires the consumer to accept the text and any children it does not understand. Most data applications, such as Web services that use SOAP header blocks or WSDL extensions, adopt this approach to dealing with unexpected markup. For example, if a message is received with unrecognized elements in a SOAP header block, they must be ignored unless marked as "Must Understand" (see Rule 10 below). Note that this rule is not broken if the unrecognized elements are written to a log file. That is, "accepted" or "ignored" doesn’t mean that unrecognized extensions can’t be processed; only that they can’t be the grounds for failure to process.
Other applications may need a different rule as the application may want to retain the content of an unknown component, perhaps for display purposes. The rule for accepting the component only is:
Good Practice
Must accept Container Rule: The Must accept rule applies only to the smallest portion of the tree.
This variation on must accept requires the consumer to accept the smallest part of the text that is ignorable. For markup languages, this could be just an element or attribute that it does not understand, but in the case of elements, to process the children of that element. The Must accept Container practice was described in [HTML 2.0]
This retains the element descendents in the processing model so that they can still affect interpretation of the text, such as for display purposes.
A language can provide mechanisms for explicit fallback if the text is not supported. [MIME] provides multipart/alternative for equivalent, and hence fallback, representations of content. [HTML 4.0] uses this approach in the NOFRAMES element. In XML, the XML Inclusions specification [XInclude] provides a fallback element to handle the case where the putatively included resource cannot be retreived. There are many variations on where the fallback content can be found. For example, a schema language could specify that fallback content is found in a text, in a schema, or even in the schema for the schema language.
Providing forwards compatibility often requires more than a substitution model for texts, it must also provide a substitution model for any version identifiers. The fundamental problem with most designs of a single version identifier in a document is that it usually doesn't provide for a given document to be valid under more than one version. For forwards compatibility, the version identifier must specify a "space of versions" that a given document is valid under, whether that's a list or regular expression or some other algorithm. In particular, the version identification strategy must specify how unknown versions are dealt with.
Good Practice
Provide Version Identification substitution model: Languages MUST provide a substitution model for version identifiers for forwards-compatible evolution.
The use of a version identifier requires a substitution from an unknown version to a known version for a consumer that doesn't understand the version identifier.
There could be an algorithmic approach. For version numbers, one could say that version numbers will only have a "major" change if there is an incompatible change. For example, version 1.1 of a language is by definition compatible with version 1.0 and version 2.0 is incompatible. Then, when the producer puts 1.0, 1.1, or 2.0, a consumer at any level will know whether it can process the content. This also means that there is a choice about which version number to put in, the lowest or the highest. A document that contains "1.1" means that any 1.X processor can process it. A "2.0" document means that a 1.X processor cannot process it, but any "2.X" processor can.
Then the language have wording about processing unknown version numbers. Sample wording for a substitution model for version identifiers: "A processor of this version MUST not fault if it receives a document that contains the same major version number." This rule would be in conjunction with forwards-compatible design for the texts, such as "Must accept Unknowns".
HTML handled unknown version numbers in a forwards compatible way because browsers ignored the version numbers they didn't understand. On the other hand, XML 1.0 did not specify what an XML 1.0 processor should do when an XML document identified as XML 1.1 or XML 2.0 was encountered. As such, many XML 1.0 processors faulted when encountering XML 1.1 documents, whether those documents contained XML 1.1 content or not. Note that XML 1.1 specified that documents should only be specified as XML 1.1 documents if they had XML 1.1 content. Perhaps if XML 1.0 had specified that any document marked XML 1.X should be processable as an XML 1.0 document, then the migration to XML 1.1. would have been easier.
Additional functionality can be provided in a language for determining the capabilities of the system that the text is being interpreted in. A language can provide a mechanism for explicit testing. The XSLT Specification provides a conditional logic element and a function to test for the existence of extension functions. This allows designers of stylesheets to deal with different consumer capabilities in an explicit fashion.
Languages can choose a mixture of approaches. For example, XSLT provides both an explicit fallback mechanism for some conditions and explicit testing for others. The SOAP specification, another example, specifies Must accept as the default strategy and the ability to dynamically mark components as being in the Must Understand strategy.
This Finding is intended to motivate language designers to plan for versioning and extensibility in the languages from the very first version. It details the downsides of ignoring versioning. To help the language designer provide versioning in their language, the finding describes a number of good practices for using in language construction and extension. The main goal of the set of rules is to allow language designers to know their options for language design, and make backwards- and forwards-compatible changes to their languages to achieve loose coupling between systems should that desirable.
The author thanks Norm Walsh for many contributions as co-editor until 2005. Also thanks the many reviewers that have contributed to the document particularly David Bau, William Cox, Ed Dumbill, Chris Ferris, Yaron Goland, Rhys Lewis, Hal Lockhart, Mark Nottingham, Jeffrey Schlimmer, Cliff Schmidt, and Norman Walsh.
Who | When | What |
---|---|---|
DBO | 20070518 | Incorporated Rhys' comments, added version identifier story to forwards compatible evolution, split part 1 into terminology and strategies documents. |
DBO | 20070518 | Incorporated WG comments from May f2f which involved many updates to 2.2.2.2, made 1 table for case studies. |