Backward compatibility
Nowadays in the industry the number of libraries, frameworks, external API's we used to develop our solutions is so high that we basically…
Nowadays in the industry the number of libraries, frameworks, external API's we used to develop our solutions is so high that we basically depend on things we cannot control. If we are the creators of anything public, we should respect our clients, backward compatibility should be always a requirement.
Open source is one of the keystones of software companies. Without the Open Source movement and the idea of sharing code will be so expensive creating code that majority of companies today will not do it.
But in software we are not understanding that for anything we create we are creating a contract between two parts, the client and the producer. Even if the software is free, it’s completely irresponsible to break continuously that contract.
The API of a library or a framework represents the experience the clients of that library will have, so we should preserve that API as much as we can. Breaking changes have colossal impacts in the clients, but also on the usage of the library. So breaking changes should be the last option for us, we should avoid them as much as we can. If we decide to take the risk, we need to minimize the impact. Maintaining intermediate code to adapt from the old version to the new one and creating a sunset process to give clients time to migrate are examples.
The reaction of teams using so many libraries and frameworks should be to protect their business from the changes required by these external behaviors. This is one of the main reasons of using clean architectures.
If the most relevant parts of your code (your business) are not infected by tons of libraries, then any breaking change will have a limited impact in your work.
Like in ships, we can minimize the impact of a problem with a third party library:
In a ship, bulkheads are partitions that, when sealed, divide the ship into separate, watertight compartments. With hatches closed, a bulkhead prevents water from moving from one section to another. In this way, a single penetration of the hull does not irrevocably sink the ship. The bulkhead enforces a principle of damage containment.
Release It!, 2nd Edition by Michael T. Nygard
Internal Services Changes
In this crazy microservices world (I would call it distributed monoliths world) services that expose APIs to other services breaks frequently their API’s.
This kind of systems are very fragile, if one service breaks the API all the upstream services will fail. In monoliths, this problem is not so bad, because your tests or your language will warn you about the problem. The problem is easier to solve in a monolith application, because you have closer clients and producers, your tests or your language will warn you about changes introduced in producers.
But in distributed apps the problem is not easy, trying to avoid it, companies introduce big E2E tests, contract tests or manual checks to verify that changes are secure.
Sometimes we use branches to isolate those changes and creates environments to check them manually, creating big batches of changes.
The complexity introduced by having a fragile architecture hard to be automatically tested are exposed in process and environments created to solve those engineering problems.
These solutions are related to this bad idea of trying to add quality when a product (feature) is created, usually a waste of time with few benefits.
Cease dependence on inspection to achieve quality. Eliminate the need for massive inspection by building quality into the product in the first place.
Demmings
But software is full of trade-offs, sometimes you can prefer to define internal API’s to reduce duplicity of code, to reduce cognitive load of teams. But taking a conscious decision is different from underestimate the cost of introducing internal API’s, in form of a service or internal library.
We will focus on service API’s, if you want to know more about libraries and their problems, take a look at this blog post.
In the case you prefer to introduce that Internal API, a good decision to shift left quality and reducing the problems of introducing that public internal API is deciding to make your changes thinking on backward compatibility.
Making big changes in multiple services and deploying all those services once, just introduce big risks. What happened if you have to roll back, you need to roll back all the services, in which order?. What are the side effects of all of those changes?. Do we need to roll back the data saved?.
A much less risky alternative is to use the “Expand and contract technique”. Instead of doing breaking changes, you introduce new changes that maintain backward compatibility and allows you to deploy the “server” service without changing your clients.
For example, if you need to change the body of one of your resources, you can create a new controller with the new body but maintain the old one. And you can migrate one by one each client calling the new endpoint, but non migrated clients would still use the old one.
Once all the clients have been migrated, the old endpoint can be removed.
Practicing Dark launching will help us to reduce deployment complexity by deploying multiple times with fewer consequences. Even more we can check the performance of our new solutions without affecting users, we can call our new endpoints and the old ones and monitoring how the new ones behave vs the old ones. We can also discard the answers of the new endpoints, so this help us to simplify how to optimize our systems with information from production but with fewer consequences for clients.
This approach helps us to deploy small changes continuously, reducing the risk of introducing bugs and the consequences of those bugs.
Architectural decisions
But if we know that’s hard to test automatically the junction points between services, why not reducing those junction points as the rule to follow.
Why not putting this idea as the chore of our architecture?. This is a trade-off sometimes is better to have duplicity (in code, in db’s) than too much cooperation, too much coupling.
Creating vertical autonomous slices of the business in charge of a team and reduce communication between those services as much as we can (those teams), will help us to create a simpler architecture. Easier to monitor, easier to expand, etc.
For anything coupled backward compatibility is a principle we need to have in our minds, it will help us to live with less stress.


