Protocol Extensibility and Versioning

| | Comments (0)

I've argued for a while now that extensibility and versioning are important topics, and it's incredibly important for data formats to plan for evolvability. I've already argued that you must have substitution mechanisms in place for V1, otherwise it's impossible to evolve formats. I've refined my argument that compatibility should be thought of at the the message level, and how to think about synch/asynch compatibility as combinations of compatibility of the messages that make up the message exchange pattern.

But what about protocols? Can we cast the same logic that applies to data formats to protocols? I think we can apply some of the rules for format extensibility to protocols but not as many and the effect isn't as useful as we'd like.

It turns out to be harder to plan for evolvability in protocols for a few reasons: the substitution rules for one protocol to another are much harder than format substitution, and we want earlier indication of whether a new protocol or protocol extension is understood than we can typically live with for formats. I will show how the advocated format rules of: Provide Extensibility, Must Ignore unknown extensions, Re-use namespace names in compatible evolution, and Provide Must Understand could be adapted for protocol evolution but then show the deficiencies in this approach.

I've regularly used the example of a Name containing a first name, then a second version that contains a last name, and then a third version that contains a middle name, when discussion data formats. Using a simple schema shortand, we can see the language evolves something like: Name (First), then Name(First,Last?), then Name(First,Last?,Middle?). The Name language specifies required formats and optional formats.

Now imagine a protocol for exchanging purchase orders. The message flow follows an exchange of messages, perhaps modeled in BPEL, that looks something like:
A -> B: Submit Purchase Order
B -> A: Acknowledgement | Rejection
B -> A: Order Fulfilled

You can create a language that describes this message exchange. It might look like: Submit, Rejection | ( Acknowledgement, Fulfillment )

Interesting how protocol messages, ie the state machine, can be modeled somewhat like a data format. Now can we apply the same extensibility and versioning rules to protocol languages? Can we specify extensibility, substitution rules like must Ignore or Must Understand, and namespace name re-use?

First off, we need to think about where the extensibility would happen. Maybe there would be new protocol messages after the acknowledgement or the fulfillment, expressed as Submit, Rejection | ( Acknowledgement, ..., Fulfillment, ... ). We could also specify a default Must Ignore Unknown extensions rule, which says that any message in the extensibility point that is unknown will simply return a fault. That means the process isn't terminated or broken, just that the method isn't supported. A better rule name might be Must not terminate process if unknown message, but let's keep it as Must Ignore unknown for now.

In the next version, the service decides to offer a check status request and response after the Acknowledgement and before Fulfillment. The language is:
Submit, Rejection | (Acknowledgement, (CheckStatus,Status)*, Fulfillment, ... )

Given these two languages, we can now examine the compatibility guarantees. The first case is where B deploys the new service. A will never send B a CheckStatus message, and B will therefore never respond to A with a status response. Therefore B is backwards compatible with A. We could have had a slightly different scenario where the interface uses a push model for pushing the status messages to A rather than a pull model. In this case, B would send a status message to A, but A would ignore it. In this variation, B is also backwards compatible with A. It is also worth noting that A is forwards compatible with B, that is A allows for new messages that it doesn't understand from B.

The second case is where A deploys the new service. A will send CheckStatus messages that B does not understand, and A will response with a Fault. As long as B ignores the Fault, then the service still works. The second variation - use of a push status - will have no affect on either side because B will not push messages as it doesn't use the new version. In both of these cases, A is backwards compatible with B and B is forwards compatible with A.

The same rules for determining whether an extension was compatible for a data format evolution have applied for protocol evolution. A change is compatible if something optional and thus ignorable is added. Any addition of a required element, such as a required Last Name or a required Status Message (say if the order can't be fulfilled), then both sides have to change.

One important area that protocol evolution differs from format evolution is the substitution rules. In format evolution, there are a variety of techniques for specifying that a new type is substitutable with on old type. In fact, there is a huge amount of computer science research on type system extensibility. Arguably the simplest substitution rule is to make an older type out of a newer type by ignoring the newer type's additional elements. More advanced systems like XSLT have a fallback mechanism where the new xslt type is transformed to an older xslt type by the xslt stylesheet testing for the new type and using the old type if the new isn't supported. XSLT effectively supports logic of the form: If new type supported then new type otherwise old type. Even more advanced systems are being deployed where a new type is sent with a transform - such as an xslt - to the older type by either value or reference (say in a UDDI registry).

Protocol evolution can approximate the Must Ignore Unknown rule by following the same rule and saying that the protocol continues when an error because of unknown message is generated. Moving to more complex substitution rules, it's very hard to see how a new message could possibly be transformed into an older message. The semantics of messages are typically not amenable to this transformation from a newer message into potentially multiple older messages. Following the XSLT example, it seems overly complex for a sender to send messages of the form: if new message supported then new message otherwise older message. Nor is it likely to send messages of the form: new message + transform to older message.

The good practice format rule of Provide a Must Understand model could apply for protocol evolution. If B is the authority over the protocol, then A cannot simply add messages into B's namespace or wsdl. If it adds messages, it has to put them into it's own namespace. This is typically what we consider extensibility, as opposed to versioning. In this scenario, A has extended a protocol defined by B. Now how does A indicate that B can't ignore A's extension? B has the means to indicate required changes by changing element names or namespace names but A doesn't have that ability. For B to allow A to add mandatory messages to B's protocol, B could provide a Must Understand model that A can use. In the case of a mandatory versus optional extension, we are effectively stating that failure to understand mandatory messages means the state machine should terminate, and failure to understand optional messages means that the state machine can continue.

However, the handling of unknown messages is quite different from unknown format extensions. With format evolution we are saying that unknown optional extensions are valid and should still be processed and mandatory extensions are not valid if unknown. Format extensions have 2 results: successful processing or failure. But any unknown - beit mandatory or optional - protocol messages are going to result in an error message. The service simply can't do anything with the message. The rule of provide must Understand for formats could be changed for protocols to: provide terminate message. This way the client can terminate the protocol if a mandatory message is not understood. It will probably have a terminate message for garbage collection reasons anyways. Arguably, the terminate because mandatory message not understood is a garbage collection message. Another option is that Must Understand model could be provided, and if a mandatory message is not understood - with the commensurate fault - then the protocol will be aborted, kind of the way individual message processing terminates when a mandatory but not understood part is encountered. This simply saves the transmission of the terminate message.

Unfortunately, none of these solutions really help provide the protocol in a compatibly evolvable manner. If a client has extended the protocol in a mandatory manner and the service doesn't support the extension, usually the client wants to know right away rather than waiting for an error message at some point in the future. Terminating the processing of a message because a mandatory extension is not understood typically happens far quicker than terminating a protocol because a format is processed far quicker. Conceivably, we could come up with a mechanism for stating the mandatory protocol extensions that a client will be sending and the server could fail early. But this seems far outside the 80/20 point of protocol design. Most systems are simply not going to allow the client to evolve the protocol in a mandatory way and not provide a Must Understand Model.

It's worth observing that failing on a mandatory protocol message versus a mandatory format extension are similar in that careful planning for failure is needed. In the format extensibility case, the format processor has to be prepared to rollback any work that it has done on the message when it encounters an unknown mandatory extension. This is why the SOAP 1.2 specification says that mandatory extension processing is atomic - either all mandatory extensions or none. In it's words "Therefore, for every mandatory SOAP header block targeted to a node, that node MUST either process the header block or not process the SOAP message at all, and instead generate a fault"

In reality, protocol evolution is usually done to represent a new state transition diagram in the protocol. The new messages and states are almost invariably required and not optional. Because the protocol changes are required, systems typically cannot evolve in compatible manners. This doesn't mean that providing for protocol evolution by specifying compatible evolution rules is unnecessary, it's just that there's less chance they will be used.

A common exchange pattern in protocols is negotiation message exchange patterns. An example of this is HTTP's content negotiation. A client can request certain media types for a given request, and the origin server can respond with the types it chooses. This model roughly argues that the requested media types are substitutable for each other under situations that the client or server controls. The combination of the server's view of substitutability (it might not offer all the types requested) and the client's view define the substitution of the media types. The protocol messages stay the same but the formats may change. Negotiation seems to offer a different type of extensibility mechanism than we've typically thought about. In most Web services, a given request returns a specific schema type, and schema extensibility mechanisms are used to allow variable type returns. When we want the pattern of passing in the types that are allowed, we roughly use the Factory Pattern as described by the gang of 4. I know that the factory pattern is specifically for construction, and I'll argue that creating a representation based upon a requested type is implicitly a constructor as a representation is a time sample of a resource. Content negotiation in HTTP is a factory pattern in the application space and handles format extensibility. Format or content negotiation can be treated as format extensibility rather than protocol extensibility for purposes of extensibility and versioning.

There are other types of negotiation message exchange patterns. Constraints and capabilities that effect protocol messages and content, as embodied by technologies such as WS-Policy, ebXML CPA, and CC/PP which provide for exchange constraints and capabilities. They invariably specify either protocol message or format rules. An example of a format rule is constraining the substitutability of formats. Imagine that the submit purchase order is sent securely. The client and service may exchange policy statements about what kind of security tokens and messages are allowed. The substitution rules for the messages and/or types are embodied in the policy statements, such as credential can be username/password OR x.509 certificates. This is a much more complicated substitution mechanism because the username/password and x.509 may be modeled as sub-types of credential, but the policy language also controls the substitution. An example of a protocol rule is specifying protocol messages. Imagine that the submit purchase order is sent reliably. The reliability protocol might specify that a createSequence message is required to enable ordered delivery of messages in the protocol. This is clearly an incompatible change as the createSequence is required. These are a couple examples of the substitutability of formats and protocols being constrained by languages in addition to the format or protocol language.

To summarize, I've shown that protocol evolution can be modeled similarly to format evolution and that the format evolution rules can be applied to protocol evolution but this has limited usability. I've suggested that underlying protocol evolution is different from format evolution because protocol state machines typically do not allow substitutability of a newer state machine with an older one, that the presence of a network results in desire for earlier failures. The result of this analysis is that protocol designers can and should provide format and protocol evolution but that they shouldn't have high hopes that they can regularly provide for compatible protocol evolution.

As an afterward, it may be worth pursuing trying to solve problems of protocol evolution by examining whether the use of a constrained protocol, ie HTTP, provides any greater evolvability for protocols. I've taken a first stab at showing how adding verbs to a state machine is hard to do in a compatible manner, so maybe reducing the verbs would help. At any rate, I think the examination should be done using fairly rigorous analysis and real-world use cases like purchase orders.

Leave a comment

About this Entry

This page contains a single entry by Dave Orchard published on June 14, 2004 1:50 AM.

Supporting Democracy: Fact or Fiction? was the previous entry in this blog.

Why putting extra structure in V1.0 is good is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.

Categories