In general, Microservices are meant and designed to be consumed by multiple independent or interdependent systems. With current standard development methodology (Agile) in place, these Microservices will continue to evolve rapidly. But practically, not all consumers will be evolved at the same rate. This leads to a situation where a given Microservice should be able to exhibit multiple behaviors at any given instance to support different consumer needs. This is where the need for some kind of version management comes into the picture. In this blog, I will be covering, general perceptions I observed around Versioning, different ways of implementing versioning and best practices for versioning.
An illustration of the conversation between “Backward Compatibility and Versioning Management System”
Versioning Management System
|Hey Buddy! How are you? Happy to see you.|
|Hi Veteran, I am good. Nice to meet you!|
|You acknowledged that I am a veteran. I have been solving problems for a long time. I know you are new and difficult to adopt; you ask the team to keep the separate code for every change. I handle all with a single code base.|
|I know, you are getting old. You ask the team to write shitty code with multiple if else conditions everywhere. Most of the code become obsolete over a period of time. Yes, I ask for separate code base but the code is clean.|
|You ask the team to fire up multiple sets of VMs to support different versions, which is very costly and time-consuming effort. In my case, one set of VMs is enough.|
|I understand, I am adding cost for VMs, but I save a good amount of maintenance effort. Developers will have a quick turnaround with my code, I think you are aware of the fact that these days computation costs are lot cheaper compared to development costs.|
|You are constantly telling that with my approach we are adding maintenance effort, Can you detail it out?|
|Sure. Thank you for asking.
When service code is modified to support backward compatibility, you come up with code if this parameter is passed then do this way, if not then do another way. Like this, you will have code in many places. Sometimes you will have private methods which will be invoked only when a specific condition is true and this condition is not for business use case but primarily added to support backward compatibility. With this, the code is the mix of business conditions and Backward compatible conditions which is difficult for a developer to understand and modify.
|I see. But with your approach, you end up making integration complex, clients need to keep a track of what version they are going to invoke.|
|I see this as a clean way of doing. Client will maintain the version of Microservice they invoke and upgrade the version as part of client upgradation to invoke a new version of services.|
|In your approach, you end up running VMs specific to old versions forever.|
|This is not true. With auditing in place, we can track how many requests are coming for given service instance belong to a certain version. For a continuous period if there are no requests then you can safely retire instance. Retiring instance is a lot cheaper and easy, compared to retiring obsolete code.|
|Is it not so complex to keep track of who is calling what version of service?|
|We can have the version of given service as one of the standard log output. With log pooling and unique transaction id combination, we should able to track who is invoking what version of a given service.|
|I am the darling of many systems that were built so far. I was embedded into product development processes, without backward compatibility check, releases won’t be approved.|
|I agree that was the case. You did a fantastic job for Monolithic applications, but now we are in the age of Microservices. We need a new age solution for new age challenges.|
|Thank you. I hope people will remember my contributions!|
|Definitely. You did a great job so far. People will remember you as long as monolithic applications exist.
I need to go. A lot of people are waiting for me. Thank you very much for your time.
|Ok Bye. It seems I am less occupied now. Keep visiting me!|
General Versioning Techniques
Irrespective of the architecture pattern i.e. whether Microservices or Monolith, there are two standard version techniques which are popular in the software community. We will briefly cover what are they and when to use what.
What is it?
Semantic versioning represents each release with the combination of three non-negative integers (MAJOR.MINOR.PATCH ) and each integer implies certain meaning.
- MAJOR: Increase this number when the previous version is incompatible with the newer version.
- MINOR: Increase this number when the previous version is compatible with the newer version, but internal business logic is changed.
- PATCH: Increase this number when the previous version is compatible with the newer version, but a bug is fixed as part of the newer version.
The increment in the either of the three integers automatically implies certain specific meaning for the consumers.
When to use?
When you are working on a project with many modules and each module is interdependent on another module, then semantic versioning will definitely be a savior.
More Details: You can find details about semantic versioning at https://semver.org/
What is it?
Calendar versioning is another type of semantic versioning, but instead of using non-negative integers it is primarily driven by calendar dates i.e. the combination of YEAR, MONTH and DATE. Rather the fixing the specific date format, it allows you to choose various combinations listed below.
YYYY – Full year
YY – Short year
0Y – Zero padded year
MM – Month
0M – Zero padded month
0D – Zero padded day
We can choose any of the above Year, Month and Day combinations for versioning specific to your application.
When to use?
When the application is time-bound, this versioning is very much useful. There are some leading operating systems who tie their support cycle based on the calendar version. For major versions, they provide a couple of years of support and for minor versions couple of months support and their support periods are directly derived from the corresponding version. Ubuntu is an example of this.
More Details: You can find details about calendar versioning at https://calver.org/
For enterprise business application who uses Microservices, I would recommend the Semantic versioning approach. Calendar versioning is more suitable for the products where support and availability period is duration driven rather than usage driven.
Microservices Versioning Approaches
Specific to Microservices, we can use either of the following approaches for versioning:
URL based versioning
This approach is primarily about adding the version number to the service URL. As version number is part of the URL itself, it is easy to identify the specific version that is being invoked without getting into details. At the server level, if you want to redirect service requests with no version mentioned to default version, then this type of versioning is quick and effective.
Header driven versioning
In this approach, version is maintained at the header of the request. HTTP protocol has content-version as one of the header attributes and in this approach, this attribute is used to hold the version number for a given service endpoint. Academically this approach is recommended for versioning as the resource and its version are represented as per the REST specification. Configuring load balancer to support multiple versions of the same service will be relatively complex and runtime also load balancer may take more time as it has to inspect header for each service request to route to the correct version.
Recommended Best Practices
- Service Versioning and documentation must work hand in hand. Make services documentation mandatory for each version to reap the best possible benefits of the versioning. If documentation is not maintained, then integrators will get confused and may end up spending a lot of time in unproductive tasks.
- At the client level, a logic of appending the version number to a service must be in common code rather than for each service.
- At the client level, make service URL and version number configurable so that developers will focus only on business level integration.
- At the server level, make sure version number of service is part of standard logging output.
- Create a delivery pipeline so that possible version mismatches are verified and fixed at an early stage of change and compatible version mappings are smoothly propagated to production with minimal scope for human errors.
In this article, we covered the difference between backward compatibility and versioning management systems, general versioning types, different approaches we can follow for Microservices versioning and recommended best practices for an effective versioning management system. After reading this blog, I hope you will have a better knowledge to take conscious decision for the specific problem you are going to address in this area.