When a code change is required, the main consideration is how to reduce the impact of the new implementation on the existing code, thus reducing the possibility of introducing new bugs and instead keeping or improving components' high cohesion/low coupling relationship. That's when design patterns come to the rescue, offering proven solutions for almost all recurrent development needs.
The Context
To get everyone on the same page, let's first talk about the problem.
Over the last few months, I've been working on a project that is pretty much backed by an aggregator/orchestrator for a bunch of data sources. The main idea is to provide users with assembled records from these sources and to allow them to iterate and take actions like excluding or confirming the requests they represent.
The main problem we currently face with it is that, since we must rely on other services, every time a contract for their output changes (the objects and attribute types they provide as responses), we need to follow up with our integration clients. But, that's not all! Deploying those changes requires time and planning, so we need to be on the same page so we can publish all of the changes at the same time. This requires a feature toggle strategy, allowing us to have all of the code available and in place for both versions of the contract, at the same time, and to switch back and forth between them when required.
For languages without type constraints, like Javascript or Python, for instance, this kind of solution is not exactly a big challenge. But for Java, which is the language we're using in our project and the one which I believe is the best for any scalable application, things become more nebulous when two different data types must work together with the same data source.
The First Idea
When I initially took a look at the story requirements, my first thought was to have two versions of the contract and use the required logic for the feature toggle to select between them when sending data back to the client application. This, however, would work only for unsafe languages. Otherwise, it would create lots of duplicate code across the codebase, and the post-code refactoring would require an excessive amount of work. Of course, besides this un-DRY (don't repeat yourself) line of thinking, lots of tests on both ends would fail because of the contract change, which would require more and more working time when coding and refactoring. The reason for this was that the contract about to be changed was the base for the one the aggregator was exposing to the client application.
Let's try to make this more intelligible with a small diagram:
As we can see in the above sequence diagram, we have three contracts for all of the data that matters. In our current case, we are talking about the C2 contract returned by Service 1.
It's easy to see now that a change to C2 would be reflected in the next layer, breaking everything. So, I decided to take two days off from coding to find a better solution.
The Final Solution
If we don't want to broadcast a change to other parts of the application, we should decouple its components as much as we're able to. One way to do this is to use a GoF (Gang of Four) Structural Pattern. In this case, I found that using a Facade pattern "gangster" would be the best approach. The objective of this pattern is to abstract the use of more than one class to its clients. That is, the class that is using the Facade does not need to know how to interact with others on a low level, so we use an intermediate component to handle any underlying complexity with an easier-to-use interface.
The solution now is easy to understand. What I needed to do was to create a new version of the C2 contract, but abstracted by the Facade object. That way I could choose from two service calls, with the respective return value based on the contract version, and then translate the new contract to the old one. Changing the client object to call the Facade instead of the gateway object (which provides the service calling methods) meant that the impact was now minimal and easy to refactor when the contract became fixed.
Finals Thoughts
The experience obtained with this solution is about more than just understanding a well-established design pattern; it's also about understanding that a poor solution requires more time, or at least the same amount of time, to implement, leaving behind a footprint to be cleaned up after it. So, if you don't understand a problem deeply enough to provide the best possible solution, work first on the problem and not on the code. Time is always the scarcest resource we have, so use it wisely and look around for information that can help you produce a solution, even if it comes from a "gangster."
The sooner you start to code, the longer the program will take.
— Roy Carlson
Happy coding!