In the previous chapter, we’ve traced a single Change through the Change Stream all the way back to its Source. We intentionally haven’t defined and explained exactly what the Source is, because we wish to keep our application agnostic to it. As it has no mind of its own, our application can not care and should not care whether a Change was Caused by the PM or the CEO.
We’ve seen the work of an Engineer on a single Cause-Change cycle. We’ve then seen the work of an Engineer on a Stream of Changes and the difference between the two. We’ve seen how his work relates to the erosive evolutionary processes and what defined his work to be eventually beneficial.
We’ve seen that what aids him is an application that matches the Change Stream via Directions, and what aids him more is an application designed to overcome the Stream’s unsteadiness. In the next few chapters, we are going to come to his aid and teach him how to do so. Starting with this one, with something that might seem as a contradiction to Software Engineering and Changeability.
Cohesion of Change
RapidAPI’s (2021) Monetization service was a server side application already evolved into a Monolith, and it relied heavily on Stripe. Everything related to subscriptions, payments and charges were managed by it. Stripe provides a Node.JS package, very well maintained and encapsulated enough, supposedly. Our service used about 30 of its exposed functions.
We were tasked with having Zero Errors in the Logs, which surfaced something wrong with our code. We needed to surround each of Stripe’s functions with a dedicated try/catch clause. They were called about 200 times by 40 different files that imported the package. Meaning we needed to make 200 Changes to a 5 years old code. 200 instead of just 30. That number had to be reduced in order to have any chance of success.
So we followed the principle of cohesion and coupling, better known as “things that would change together should be together”, and refactored our code just a little. First we created a wrapping class around Stripe’s package called StripeClient that exposed exactly the same 30 functions. We Changed our 40 files to import it, which was safe because it is a wrapper. After covering StripeClient with tests, we safely then made 30 Changes to all of the wrapper’s functions. We used another trick, and actually reduced it to only 10. At the end it was a total of 50 safe Changes instead of risky 200 Changes.
From this moment going forward, some future Changes required to Stripe’s code would be easier and safer to do. For example, if Stripe API’s initialization would Change, it would require a single Change made to the code at StripeClient instead of 40. Future inefficiencies prevented, which is the outcome of the practice of Software Engineering. However, consolidating Changes together is not news.
Egg before the Chicken
Let’s look at it through the Change Stream.
- Stripe API is The Source of Change
- Changes to Stripe API are The Causes
- The Changes derived from Causes will be made by our engineer, starting with StripeClient.
This is a tunnel in the Change Stream, a Direction of Change and a path for our engineer to follow as he makes the Change. Each Change coming through it will have a closed set of Causes and Intentions related only to Stripe. Other Changes, coming through other Directions would have a place to stop, at the StripeClient encapsulation. Stripe’s Direction is mutually exclusive from the others. Which is exactly the goal of cohesion and coupling.
Stripe API is also a clear and perfect independent cut of The Source. Changes of Stripe can only happen at Stripe, thus Causes of Stripe can only happen at Stripe. Meaning this closed set of Causes are mutually exclusive from any other Causes in the Source.
Finding mutual exclusivities within Causes and between Sources can lead towards a design, even before writing a single line of code. It is so because Stripe API existed before our code or any Change made to it. Let’s try to determine whether we ended up with a beneficial outcome.
As the application grows, every other Modules’ relationship with StripeClient (Module A) would not Change. More than that, the Direction has set the relationship to be dependent/used by. We have placed a correct intersection that does not Change through time. As a result, we have slowed down the evolution towards a Refactor and prevented future Inefficiencies.
As StripeClient grows, it would grow internally and independently. As it grows, it would expose more functions, enlarge the integration surface for the other Modules to consume which is a must. But most importantly, as StripeClient grows it will not be growing into other Modules. Its Direction of Growth is opposite to the Direction of Change. We have slowed down the evolution towards a Monolith and prevented both future Inefficiencies and future Instabilities by unintended consequences.
No matter how many Causes there actually are at the Source of Stripe, the Direction will remain the same. No matter the frequency of Changes that will go through it, the Direction will remain the same. And along with it being mutually exclusive, we’ve made this Direction not only match the Change Stream, but agnostic to its unsteadiness.
That was our first step into letting go of our binary thinking minds, and giving in to Change. We’ve managed to slow down the erosive evolutionary processes without stopping the Change that fuels them. Oh I do wish life was that simple, because the PM has another idea in mind. On this, in the next chapter.