In the previous two chapters, we’ve had a look at our development workflow through Bottlenecks and Throughput. We’ve seen that a removal of a Bottleneck can potentially increase our Throughput. In order to see that effect, we’ve gone through the process of automating a deployment.
We’ve started to inspect the consequences of increasing our Throughput. One of which causes our development workflow to consume more User Stories made by our Product Manager. We called this effect a release of Back Pressure. But we speculated there would be another effect, of creating an Upstream Pressure on our workflow.
In the next few chapters, we will be hitting two birds with one stone and learn two things at once. We’re going to inspect the effects of Upstream Pressures through the action of splitting an application, as it is one way out of many to handle it. In this chapter, we’d start with our own first split. It would be the simplest one possible, to a too convenient single application.
In this series, we will be splitting multiple applications who share various and complicated relationships. But before we rush to it, we better start with something simple; A single application that actually holds two independent applications that for no reason ended up being together. That would be two groups of Modules inside an application.
One group of Modules could be an internal website to track marketing leads, the other group could be an internal website to track the companies’ projects. The two groups would not intersect in any way, they would share nothing. Not any business logic, not any code and not any flow or feature. Not sharing data, and not calling each other. It is actually the same with two independent Serverless Functions that were put together, which almost any company has. One would send android notifications, the other would send iOS notifications.
Although the two technically share nothing, they do belong to the same company and they share Throughput. If the same engineers own them both, their Throughput is divided between them. As such, both applications exist within the same Change Stream along with the engineers and the product managers. All of them within the same company with its Sources of Change.
As our two groups of Modules share nothing, they are mutually exclusive and within the same Change Stream, it entails there is a split within the Change Stream. There exists one Direction of Change per group, and both would never have a Cause to Change together. However, as each group internally has multiple Modules, the external Direction of Change might be further split into smaller internal independent Directions.
But to determine whether something is beneficial or otherwise, we need to explore the consequences of a split action.
As we actually have two mutually exclusive groups of Modules, nothing really prevents us from turning each group to an independent application. That would be splitting the one application into two.
The immediate effect would be that once split, there would be a technical boundary between them to cross. If by mistake we’d wish to entangle the two somehow, we’d have to make an effort for it. That would be a stop sign for us to think about it again before a blind execution, the expected outcome of a Direction of Change. That is a beneficial outcome as the two applications really shouldn’t Change together.
Let’s presume that we’ve introduced an Instability to one group of Modules, due to a Change. One so horrible that spins our application up and down, such as a memory leak of an unintentional DDoS attack. Once split into two, only one application would spin and the other one would be safe from harm. We have increased our reliability and availability, which is a higher uptime and a better SLA (Service level agreement). And we can further increase it, as now we can put the two into two separate Containers.
Splitting applications also had a lot to do with scalability and resource costs. Let’s do some quick math and presume that each application independently requires 1 CPU. Combined as a single application they would require 2 CPUs. As long as they are combined they can be scaled up only in multiplications of 2 CPUs.
To run two instances, one of each once split, would require the same resources as before with a total of 2 CPUs. But if we wish to scale out just one application by one instance, it would be only one additional CPU. We’d have a total of 3 CPUs, which would have been infeasible unless we made the split. As we pay our cloud provider per CPU or RAM, it could lead to potential cost savings. In the next chapter, we’d see it can also be achieved without actually making a split.
Let’s recall our applications are going through 4 evolutionary processes which are fueled by Change. Splitting applications have a potential effect on them.
As each one is independent, each one evolves independently towards a Legacy, which could ease us to keep up with technological shifts. For example, let’s presume one of our 3rd party Packages had gone through a major update without backward compatibility. This update is required for one application, but breaks the other. But it wouldn’t break the other as they no longer reside together within a single application. Each evolves slower towards a Legacy in comparison to being as one application.
Long term speaking, if we wish to replace an outdated framework, such as moving from Express.JS to Nest.JS, we can do it for just one and let the other rot until its end of life. Same goes if we wish to move it from Ruby to Node.JS, or any other tuple.
All by the way are forms of refactors. As each one independently has less code to refactor, each refactor would be easier. And as they are independent, there is no need to refactor them both at once. It would be two small refactors instead of a big one. That slows down the evolution towards a Refactor.
Instead of a single application bundling together 6 Modules, there would be two and each would be bundling only 3 Modules. As each grows independently, each would grow from 3 to 4 to 5 instead of from 6 to 8 to 10. We had significantly slowed down the evolution towards a Bundle. It would ease our efforts to revert it when necessary, and as long as they really share no relationship, tracing issues would be eased as well.
A split also has an effect on the most important evolution process, the one towards a Monolith, and it is not straightforward at all. As it has a lot to do with Bottlenecks and Throughput, it may actually speed up or even accelerate it. We will see that in the one chapter after the next.
The next chapter is about what technically allows all of the above, and that is to be able to independently deploy our two applications. And this has effects on its own.