Service Versioning Illustrated

Hogarth painting the muse

My last post, “No Structure Services”, generated some discussion on LinkedIn regarding service versioning and how an application’s architecture can enable exposing services that are well-defined, stable, internally simple and DRY. I’ve discussed these topics in the past: “Strict Versioning for Services – Applying the Open/Closed Principle” detailed the versioning process I use to design and maintain services that can evolve while maintaining backwards compatibility and “On the plane or in the plane?” covered how I decouple the service from the underlying implementation. Based on the discussion, I decided that some visuals would probably provide some additional clarity to the subject.

Note: The diagrams below are meant to simplify understanding of these two concepts (versioning and the structure to support it) and not be a 100% faithful representation of an application. If you look at them as a blueprint, rather than a conceptual outline, you’ll find a couple SRP violations, etc. Please ignore the nits and focus on the main ideas.

Internal API Diagram

In the beginning, there was an application consisting of a class named Greeter, which had the job of constructing a greeting for some named person from another person. A user interface was created to collect the necessary information from the end-user, pass it to Greeter and display the results. The input to the Greet method is an object of type GreetRequest, which has members identifying the sender and recipient. Greeter.Greet() returns a GreetResponse, the sole member of which is a string containing the Greeting.

And it was good (actually, it was Hello World which until recently was just a cheesy little sample program but is now worth boatloads of cash – should you find yourself using these diagrams to pitch something to a VC, sending me a cut would probably be good karma 😉 ).

At some point, the decision was made to make the core functionality available to external applications (where external is defined as client applications built and deployed separately from the component in question, regardless of whether the team responsible for the client is internal or external to the organization). If the internal API were exposed directly, the ability to change Greeter, GreetRequest and GreetResponse would be severely constrained. Evolving that functionality could easily lead to non-DRY code if backwards compatibility is a concern.

Note: Backwards compatibility is always a concern unless you’re ditching the existing client. The option is synchronized development and deployment which is slightly more painful than trimming your fingernails with a chainsaw – definitely not recommended.

The alternative is to create a facade/adapter class (GreetingsService) along with complementary message classes (GreetingRequest and GreetingResponse) that can serve as the published interface. The GreetingsService exists to receive the GreetingRequest, manage its transformation to a GreetRequest, delegate to Greeter and manage the transformation of the GreetResponse into a GreetingResponse which is returned to the caller (this is an example of the SRP problem I mentioned above, in actual practice, some of those tasks would be handled by other classes external to GreetingsService – an example can be found here).

Internal API with External Service Adapter Diagram

Later, someone decided that the application should have multilingual capability. Wouldn’t it be cool if you could choose between “Hello William, from your friend Gene” and “Hola Guillermo, de su amigo Eugenio”? The question, however, is how to enable this without breaking clients using GreetingsService. The answer is to add the Language property to the GreetRequest (Language being of the GreetingLanguage enumeration type) and making the default value of Language be English. We can now create GreetingsServiceV1 which does everything GreetingsService does (substituting GreetingRequestV1 and GreetingResponseV1 for GreetingRequest and GreetingResponse) and adds the new language capability. The result is like this:

Internal API with External Service Adapters (2 versions) Diagram

Because Language defaults to English, there’s no need to modify GreetingsService at all. It should continue to work as-is and its clients will continue to receive the same results. The same type of results can be obtained using a loose versioning scheme (additions, which should be ignored by existing clients, are okay; you only have to add a new version if the change is something that would break the interface, like a deletion). The “can” and “should” raise flags for me – I have control issues (which is incredibly useful when you support published services).

Control is the best reason for preferring a strict versioning scheme. If, for example, we wanted to change the default language to Spanish going forward while maintaining backward compatibility, we could not do that under a loose regime without introducing a lot of kludgy complexity. With the strict scheme, it would be trivial (just change the default on GreetingRequestV1 to Spanish and you’re done). With the strict scheme I can even retire the GreetingService once GreetingServiceV1 is operational and the old clients have had a chance to migrate to the new version.

Our last illustration is just to reinforce what’s been said above. This time a property has been added to control the number of times the greeting is generated. GreetingsServiceV2 and its messages support that and all prior functionality, while GreetingsService and GreetingsServiceV1 are unchanged.

Internal API with External Service Adapters (3 versions) Diagram

As noted above, being well-defined, stable, internally simple and DRY are all positive attributes for published services. A strict versioning scheme provides those attributes and control over what versions are available.

2 thoughts on “Service Versioning Illustrated

  1. Gene: as usual, you’ve created a clear and well reasoned post. And in some contexts, I think I’d make the same tradeoffs you’re advocating. However, as time goes by in my career, I find myself less and less convinced of the virtues of strict versioning.

    Part of my discomfort stems from the fact that engineers have to write a lot of code to implement it. Have you read Steve Yegge’s rant about the size of code being our worst enemy? Yes, you’re accomplishing DRY in one sense, but in another, you’re creating enormous duplication, when successive versions of an interface are only slightly different. It might not be duplication that hurts the coder quite so much (delegation takes care of that)–but a tester and a consumer of the interface don’t get any free pass… Proliferating interfaces is not a good thing, as anyone who has coded COM against a core Windows service (or CORBA against java, or SOAP against the wsdl-provider-du-jour) can tell you.

    Also, I have a long-standing discomfort with the facile assumption (pervasive in the whole industry) that an interface and a method signature are effectively equivalent. Certainly, they have a lot in common–but they also diverge. If I can only call function A under certain conditions, and I code to satisfy those conditions, and then the conditions change, I have a different interface, even though function A’s signature may remain totally identical. These changed conditions might be things like whether my API key gets throttled in a new way by a provider, whether the cost in $ or time or processing per method invocation changes dramatically, whether exceptions are raised for new reasons (even though the method still throws the same exception *type*), and so forth.

    I also feel like we spend too little time understanding how strict versioning of interfaces works when producers and consumers of an interface are on a different schedule. In your examples, you assume that evolution happens in tidy jumps. That can happen. However, I have frequently observed engineers trying to evolve a new version of an interface (which should theoretically not have any consumers, yet), and being forced to freeze and release stuff prematurely because a consumer has to go live. This has at least two undesirable effects: it increases the number of interface versions, and it may perpetuate bad design decisions, because the open/closed principle says once you’ve released it, it’s there forever.

    A final beef that I have with strict versioning is that it’s usually not granular enough, and as a result, it can break consumers unnecessarily. Usually, an interface isn’t versioned on a single-method basis; you lump dozens of functions/classes together under one API version, and if any of them change, you release API version 2. If I only call one of the 57 functions on one of the 6 classes in the API, and that function hasn’t changed, but the API gets revved, I often don’t have the option of not reacting. Your example doesn’t manifest this problem, because it’s so simple; but in more enterprise-ish scenarios, I see this problem often (maybe even “always”).

    In other words, I feel like strict interface versioning promises more than it truly delivers, by the time the rubber meets the road. Yet the cost of implementing it doesn’t decrease just because its value erodes.

    I wrote a long series of posts about “semantic interface versioning” a few years ago. You might not agree with some of my assumptions, but you might find it interesting:


    • Daniel,

      Thanks for the comment – great stuff in there (and I’m not talking about the compliments, though they are appreciated).

      First of all, thanks for mentioning the word “context”, something I failed to do in this post. I absolutely agree that there are contexts where strict versioning is less appropriate and in some cases, potentially counter-productive. The context I deal with most often when doing service work is application to application. In that context, I find loose versioning to be more problematic than not.

      Regarding code size, I agree that the safest code is that you don’t have to write. That being said, the redundancy in creating the various similar message classes is less problematic than the complex internal code needed to parse out an incoming message into something intelligible. Having a finite set of defined messages seems like less work for testers – for each interface there’s only the associated message and not the associated message. For a more dynamic service, the number of permutations could be significant. Additionally, if you’re using WCF, the defined service is self-describing, making life easier for consuming applications.

      Interface proliferation can be a problem if it’s not managed. Back in the day (VB6 & COM), I ran into several cases where people either had tons of interfaces or routinely broke other code because they didn’t take the time to understand and manage how it worked. Likewise, published service versions need to be managed – when, why, and what changes along with who is using what becomes a product management concern.

      Someone else brought up a similar point re: semantic/behavioral contracts versus structural ones. It’s a valid point that I don’t know if I’ve seen anyone address definitively. Unit tests can help, but do little toward documenting behaviors for consumers.

      The issue of forcing a version out the door prematurely is a valid one. When I’ve had to work in parallel with a consuming team, I always do so in a non-production environment with the caveat that whoever is forcing the accelerated schedule takes on the liability for any re-work caused by that acceleration. Success there depends on your organizational situation and YMMV. One thing I do like about the scheme I outlined above is that if a version does make it to production and really needs to die, I can make it go away after a reasonable period for client migration.

      Packaging methods into services is a study in its own right (perhaps more dark art than science). I find myself preferring very focused ones these days – that’s one of the things that I definitely like about the microservices style. I’ve come to believe that more methods per service either indicates chattiness or lack of focus. Lack of focus can absolutely lead to the situation you mention where a small change to a single concern has a big impact on consuming apps.

      Lastly, I very much enjoyed your series on versioning. I think we agree that context is king and where it’s appropriate, I agree with your take. Thanks again for the comment, you’ve added immensely whatever value was there in my post!

      Liked by 1 person

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.