Strategy for Continuous Delivery of Service Tier
There are a few aspects each architect should take into consideration when designing Service Tier. There is not much difference if your service tier is small or large, if it is hobby or mission critical project, in almost every case you will need update some service, sooner or later. Some updates are simple and have zero impact in direction to clients, but there are also those nasty which change service API or service behavior in the way some client is not compatible with.
If you want to have deployment of you changes smooth you have to deal with compatibility issues. If you want to guarantee SLA and have your services highly available you have to think how to keep business running. And if you want to release in cadence of continuous delivery and often put changes into production you need to take backward compatibility seriously. Client applications will release on their own cycle, so when you release a change application still needs to work.
In this article I will ruminate on some common sort of changes having impact on client functionality, how to deal with them and consider some of best practices to mitigate service update impact. I will summarize some known solutions which are often forgotten in development rush.
Preliminary to Service Tier
Many multi-tier application architectures in basic 3-tier application has this middle layer defined as Service Tier. It is an abstract layer for one or more application with less or more responsibility of data sources aggregation and company’s common business logic. This layer usually
- serves right amount of data to applications in the way they need them to fulfill recent task.
- stores data sent from applications in defined business flow.
- provides computational algorithms solving complex problems or dynamic data sources.
Service Tier is set of one or more independent services, usually one per business area or problem (this independent adjective is important and architect should take it seriously and do not allow developers to implement direct call of service from another service). Each service has one or more functions liable for specific business task.
Service has Application Program Interface (API) thru which it exposes its functionality to general clients. API is set of public functions and their input parameters and output result.
Service Tier could be implemented in several technologies, for the next text I will use RESTful service, using standard http protocol for communication.
This is my favorite best practice and if you do not implement it yet, I highly recommend you do it just now. This can just simplify any future change and transform deployment nightmare into pleasant task.
The version could solve some compatibility issues we will talk later about. Every time you make incompatible change in your service you must increase a version, so non-updated clients are not failed when calling your service. If versions are implemented well you can calmly deploy service with new updated functionality and all application will work as before.
The API version can be implement in the several ways. One way is to deploy a new instance of service every time API is changed and this instance has accessible thru URL.
You can see the number in the URL which is indicator of version of service. There are a few disadvantages, increasing number of deployed and supporting services (what could have an impact when deploying some critical fix across the versions), and thus increasing requirement to infrastructure, versioning on service level for all its functions, etc.
I favored having version as mandatory input parameter (parameter with default value could cause an unpredictable results) for each function, also for those parameter less. This could increase service implementation complexity, but if well designed in code reusable way it could be manageable.
I have designed several service tiers using this API version parameter and it is just awesome. The all code is in one place and all versions are deployed as one instance (what could be testable in the nice automation way). There is also interesting idea of changing version number to data in format yyyy-mm-dd and you get some level of information how API is changing over time.
The API versioning is good if it is internal private service and you know who clients are. On other hand if you expose the public service your API should probably maintain all versions you implemented during the time. So be careful to change considerations, but not so much conservative to be resistant to any change.
There is nature requirement to change or extend service functionality as business changed. It is often ended up by changing its public interface, usually by adding some parameter, or extending result data structure. But it could also mean removing input parameter as well as removing data from result.
All such changes could be well consider in terms of backward compatibility. I believe many API changes could be done in way they have zero impact to compatibility. Let’s say I am extending parameter list by new parameter. If it is possible I will define this parameter as optional with the default value, or implement of fallback functionality if parameter is not provided. And it is very similar if you extend result data by adding new item. It will be simply ignored in old application.
However there are situation which require change of function signature. Let’s imagine the business is changed in the way some parameter is meaningless. Of course we could change this parameter as optional, but our API will look inconsistent. Over time everyone will forget why it is there. For the result data it could be much worse, because if you will mark some data as optional and return some default value, it could causes unpredictable issues on application side, which functionality could relay on that value. Do not forget you never know what application has implemented and how it works with the data you return from service.
So again we have several solutions, worse or better. One of them is creating a fresh new function. Well it is a way, however as time flies the number of such functions will grow and API of your service will become a mess nobody understands.
The nice way is increasing the version number of service API. Your service should accept at least previous version of function with unchanged functionality and the new version, where the change is implemented.
Service functionality change
There is another sort of changes which could have an impact to client applications. they are not as obvious and noticeable as change in API, but those changes still can cause an application issue.
Let me start with some simple example. Although it does not change API in exact way this change could be sometimes sorted in the same category. It is error handling and how service implements it. Remember we have RESTful service and thus we can return error as a special JSON with error code and error message but from http protocol point of view it will be OK response 200. There is a second way and returns error as http error code 4xx, 5xx. If you will want to make change and returning different set of error codes, it could have an impact on application error handling.
The second example is more theoretical. Let our service returns list of customers according to given filter. It was implemented in the way service returns always empty list if there was not a customer found. The customers data store is shard according to first letter of last name. There is a requirement by one application they want to distinguish the situation where there is no user which last name starts on given name or there is no customer with given name. Technically it means there is no such customer or the database shard is totally empty, no customer starts on given letter. The development team decided to change result as empty list if no customer or as null if shard is not exist.
Bright reader can observer that even API was not change it could have an impact on application, if it does not implement null reference check.
I have described a few inconveniences service tier could have during its lifetime. There is not silver bullet solve every single issue, however good architecture and software design could help having service layer resilient and flexible to change and staying code base simple and maintainable. If you incorporated API version into all services you get strong feature and solution how to change whole service tier architecture in future. You can just move in such crazy idea as changing current service tier into proxy layer, which will route service call to proper instance of service according to API version. Just dream big.