Make Jar not War!

Why are we still building monoliths?

Is it laziness, ignorance or just convention?

Coming to terms with terms

In the context of this article we will define a monolith as a single, unified unit consisting of three parts:

  • a client-side user interface (a browser or mobile app).
  • a server-side application that provides the application's logic.
  • a data persistence mechanism.
Additionally, a monolith is usually built from one massive code base. Server side-application logic, front-end client, data persistence, background jobs, etc., are all maintained within a single code base. When a developer fixes a defect or adds a new feature, they need to build, package, test and deploy the application as a single unit.

Why Rage against the Monolith?

Any non-trivial application eventually encounters problems with scalability, tight coupling, fragility, degraded performance, change management, and slow development cycles.

Lack of Scalability

Monoliths suffer from scalability along two dimensions:

Load Scalability

  • Vertical scaling - As the need to support larger loads grows, most organizations start by throwing more powerful hardware at the problem. This brute-force approach often buys time early in the application lifecycle. Unfortunately, they hit an upper bound where either the non-linear cost of increasingly more powerful hardware exceeds the return on investment, or there is no longer a more powerful machine to acquire.

  • Horizontal scaling-Here we deploy multiple instances of the same application to handle the increasing load fronted by a load balancer. Horizontal scaling is by far the most common form of scaling as applications move out of private data centers and onto cloud platforms. Unfortunately, this approach is often inefficient in its use of resources. Application load is usually not evenly distributed across the application. Instead, it is usually caused by a small fraction of the application's features. Horizontal scaling ignores this fact and forces us to scale all features evenly. This coarse-grained scaling wastes resources on idle features and increases the aggregate resource requirements beyond what is required to support the actual load.

Team Scalability

A second scaling problem also exists with monoliths. This scaling problem relates to team scaling. As the monolith's feature set grows in size and complexity, the team size usually expands to support it. For large applications, the team is usually boken up into distinct groups of developers aligned across functional areas. As the number of developers grows, the communication and coordination overhead grows non-linearly. As this overhead grows, the pace of development naturally slows.
"organizations which design systems ... are constrained to produce designs which are copies of the communication structures of these organizations."

—Melvin Conway


The implication of Conway's law suggests that a change to the organization of the application's development teams would manifest as a structural change to any system they build. As companies like Amazon and Netflix succeed built around many small semi-autonomous teams building microservice, the benefits of this approach become difficult to ignore.

Managing change

As mentioned previously, any change to a monolithic application requires the entire application be rebuilt, retested, and redeployed. To accomplish this usually results in the application being taken offline for some period during every deployment. The cost of this downtime often means lost revenue, lost productivity or both. As a result of this, organizations often delay deployment as long as possible to avoid the downtime caused by frequent redeployment resulting in an increasing collection of features and fixes in each release. This Big Bang Release approach attempts to balance the benefits of new features and fixes against the cost of downtime. Unfortunately, this approach further slows the application's release cycle as the management of an ever-increasing collection of changes increases the number of dependencies that must also be managed.

Tight Coupling

A well-designed application uses clear interfaces to hide implementation details from its consumers and decouples dependencies between classes. This rigor is often lacking In monolithic applications due to the increased design, implementation, and testing costs this approach incurs. While the application benefits significantly from the additional rigor, the application can often be realized more economically without it. Trading short-term savings for long-term flexibility usually proves to be a false economy. This lack of rigor often causes the application to become a convoluted and complicated to maintain Big Ball of Mud", which slows development as developers struggle to understand the side effects of any change.

" Big Ball of Mud" is haphazardly structured, sprawling, sloppy, duct-tape and baling wire, spaghetti code jungle..."

— Brian Foote and Joseph Yoder, Big Ball of Mud. Fourth Conference on Patterns Languages of Programs (PLoP '97/EuroPLoP '97) Monticello, Illinois, September 1997

As applications increase in size and scope, avoiding the "Big Ball of Mud" becomes increasingly difficult.

Architectural Divergence

It is extremely rare that an application once developed is never enhanced with new features. It is also equally rare for the original architects and developers to implement these changes from inception to sunset. As the application matures, it is common for the implementation to include multiple architectural styles reflecting the different tastes and industry trends popular when these features were implemented. This divergence in architecture makes the application more difficult to maintain as developers must contend with these inconsistent implementation patterns.

Monolithic Architecture Warning Signs

As monoliths age and accumulate features, they tend to exhibit three classes of problems: fragility, degraded performance, and slow development cycles.

Fragility

When we speak of application fragility, we are referring to the applications ability to resist destabilizing forces. Fragility can manifest in several forms.

Load-induced Fragility: As the load on the application increases beyond some critical threshold, the application's performance is significantly negatively impacted. This fragility usually manifest as significant performance slowdowns from severe bottlenecks or worse still, failure.

Runtime Fragility: A defect in one component of the system can affect other parts even when they’re logically unrelated. If the defect causes the operating system process to crash, the defect causes the entire application to crash.

Load and runtime fragility are usually a strong indicator that your architecture is no longer adequate for your needs.

Decelerating Development Cycles

As a monolith grows, feature implementation and release velocity decelerate as teams are forced to coordinate their efforts to manage changes. This decelerating velocity has a negative impact on the time it takes to release any defect fix to production. As the release velocity decreases, users must wait longer for new features and find workarounds for long-lived defects. This decreased release velocity increases external pressure on the company as competitors with faster development cycles release features to market quicker and potentially lure away customers. If the development and release times are increasing, it may be time to consider an alternate architecture that shortens these cycles.

Slow CI builds

One of the earliest symptoms that a monolith is becoming bloated manifests with exceptionally long build times. Build times longer than a few minutes should be a warning flag. Long build times negatively impact productivity as the team is forced to wait for the build to complete.

An unfortunate side-effect of long build times is the impact this may have on continuous integration. Often teams confronted by long build-times, disable the common build on commit feature of their CI system to avoid piling up an ever-growing queue of build requests. By increasing the interval between builds, we undermine one of the core benefit of CI which is to exposing source code conflicts at the earliest point when they are easier to remediate. Instead, the build process runs less frequently, and we are now forced to resolve more code conflicts. If the application build time is slowing down the team's productivity, it may be time to consider an architectural alternative.

Slow deployment

As monoliths grow in size, the number of dependencies and assets to be deployed often grows as well. This growth negatively impacts the speed of deployment as each asset must be deployed along with the application. This situation is exacerbated further in scaled environments as each of these dependencies and assets must be deployed to each instance in the cluster.

When containerized application deployment is implemented, this situation may worsen as the application must be first, built, packaged into a container image, uploaded to a registry, and then retrieved by each member of the cluster. If the time it takes to deploy your application is continuing to increase, it may be time to consider an architectural change.

Team dynamics

As the size of the codebase increases in size and complexity, it can often take months for a new developer to become comfortable making non-trivial contributions to the project. This delay in productivity adds additional load on the existing developers to shoulder the workload until the new developers have been able to wrap their heads around the application. Poorly isolated dependencies and tactical coding decisions further convolute the codebase making the new developer even more dependent on the veteran developers to pass along any requisite tribal knowledge to the initiates. If it takes an increasingly long interval for new resources to understand and make meaningful contributions to the application, it may be time to reconsider your architecture.

Technology change is difficult

Most modern monolithic applications are built around a popular framework to leverage the framework's codebase for application infrastructure. Third-party frameworks are usually better documented than in-house solutions, and there is usually a large pool of experienced developers from which to recruit, who already understand the application's plumbing.

Frameworks allow applications to get off the ground quickly but can pose problems as they age. Since the monolith is built on top of a framework, it can be quite difficult to migrate the application to newer frameworks that provide better support for newer use cases. Additionally, as frameworks age, they may no longer be supported. This lack of support often forces application developers to dig into the framework code to fix any defects or security problems. If you find that your application has outgrown its original framework or language, it might be time to consider another architectural approach.

Verdict

So to answer the question we posed at the beginning of this article- The answer is all three.

  • Monoliths are easy to understand. If we are lazy and want to choose the simplest architectural option regardless of the long-term consequences, then we build monoliths.

  • There are individuals in the software industry who are ignorant of any other architectural approach. They keep building monoliths unaware of the well-documented alternative.

  • Finally, it has been the conventional approach for such a long time that many industry professionals are hesitant to break from tradition and explore its alternatives.


Coming up

Now that we have identified some of the problems with monoliths the obvious question we should ask ourselves is "what is the solution?" In the following articles, we begin to explore a different approach called Microservices. Each article is intended to distill some aspect of this new approach into a comprehensible form. We start with some of the theoretical aspects and then we will implement a microservice based architecture that serves as both a real-world example and a starter application for your own projects.