As a professional software engineer, you have probably encountered an architectural stumbling block known as a monolith. A monolith is essentially a single-tier application in enterprise architecture. Forget about the traditional breakdown of Java EE applications, where they break down an Uber-WAR file into presentation, business-logic, and database tiers. That is just gloss hiding the truth from our eyes. A single WAR file deployed to an application server is, at best, a monolith.
In my contracting experience, I have witnessed many types of monoliths at various stages of software development lifecycle. Unfortunately, I have been around toward the latter end of the software development lifecycle, where the monolith application is already in maintenance mode or has been reiterated several times. It is the nature of contracting that the conditions of bit rot have already been set and are irreversible.
A monolith has the following characteristics:
- The application code is generally unfathomable because the modules are inextricably interlinked, making analysis absurdly complex.
- The time of development cycles gradually increases through each milestone release.
- There is a vast swathe of code and modules. I would say more than 500,000 lines of code and more than 250 modules is de-rigour.
- The ratio of unit test classes to regular code is alarmingly very low, sometimes much less than 5%.
- Usually, there are only one or two persons in the organisation who have any clue how the whole monolith hangs together.
- There is a culture of resistance to change. Sometimes that culture is political.
- People have passed through the eye of the needle many times over.
How do monoliths come to being? Who is responsible? I have my suspicions about the origin. In my experience, they are started when a business generates and builds software from a brownfield environment.
How does a monolith actually start? It starts with a basic requirement, or what appears to be fundamental, and then deviates into the asylum from the start date. The weird thing is that it does not matter if you have average engineers or a crack team of skunk workers, with at least 10 years’ experience and, say, everyone with technical architectural leadership experience in the past. Software, unfortunately, is very subjective. We have all opinions on the best framework, the best library, and we are limited by the constraints we have in our knowledge at the time of the monolith’s conception.
Why do I call the initial conditions brownfield? Because there is rarely a case in a prototype or starter version where we build everything from scratch. A monolith starts with a dependency like Java EE, if the code is written in Java. It will be different if your monolith is written in PHP, Python, Groovy, Scala, or Ruby.
Here is an extract of an old proposal document that I have:
Universal Trade Desk Add-On
What is UTD and why are we building it?
How is it being implemented?
When will it be delivered and what will be included?
Where are we now?
Summary and future development / roadmap
“This new application will be a near real-time Trading Desk Add-On to provide consolidated single source of Trade Data in a format understandable by X-QuantLib Exposure Library (X-QEL). The contents of UTD will be updated upon trade lifecycle events and will be available via an API. Traders will be migrated from LEGACY-Y to UTD in progressive steps. UTD will be available across the Web and also Desktop profiles.”
From the above sample description, which was written in 2009, I have changed some terms. You can see exacting business knowledge [jargon] is required. If I went back in time from 2016 and looked at this application, it would likely be a monolith, and I doubt that I would find the original document that described the start of the journey. In fact, I have engaged in a few contracts, and the client can no longer find the original BUFD.
A monolith is characterized by the culture that creates it. As soon as code is written, some say you already have technical debt. In effect, writing code, even with unit tests, is like taking an option on the future. If you know that code is just a prototype, then you can afford to cut corners and deliver the fast working example of the application that impresses the stakeholder.
Monoliths rarely get thrown away or restarted. There is an early day scenario where the majority of the application should be rebuilt from scratch, but because of business pressures like cost, time-to-market, and finance, nobody ever does this. Over time, the monolith keeps growing and stretching further into the distance.
After person years, this mountain of code keeps growing every day, every month and every year. Why? Because a monolith is nothing except a transformation of a viable business model, in other words, as long the business makes a profit, then it will self-sustain.
Monoliths to date are also a victim of software engineering trends. We can look at the technology and the libraries. The biggest Java monoliths were probably created long before Spring Framework, Guice, and CDI. Java did not have annotations or even generics. Even best practices change with time. By 2016, the J2EE design patterns are woefully past their sell-by date. I would add controllers written against Apache Struts version 1.0 to mix.
At best, the monoliths were never modularized or designed for best practice of components.
Conway’s Law asserts that organizations are constrained to produce application designs that mirror their communication infrastructures. No matter how adaptive an organization tries to break out beyond the law, the result always is unintended communication breakdowns between management, departments, and silos; which all require resolution.
Unsurprisingly, monoliths will exhibit the structure of the people that created and also maintain them.
A monolith is also affected by staff turnover. If a new business team has an original idea of an application at the very beginning, after a few years, the direction of the application will change. Old staffers leave the business and original consultants and contractors leave the project as new people arrive. The new staff must learn new concepts, and they are rarely handed over in a fashion that shows maximum knowledge transfer.
Because the business relied on selling their software to customers as a bespoke monolith, there were most likely promises to get extensions to the monolith finished by a certain date. I suspect that the extension and final application were underestimated and, like the majority of software projects in the industry, finally arrived considerably over the expected budget.
The problem is that monoliths have within themselves undue complexity in terms of logic. An extension of the application is dependent on several factors:
- Comprehensive view of the monolith’s internal working components.
- Sufficient translation of new business requirements to technical requirements.
- Adapting old components and developing new components.
- Dealing with the stress of mixed capable staff.
- Unknown impedances in the code, framework, and databases.
These above are the forces acting on an aging monolith. At the very beginning of the SDLC, these forces are still there. However, the complexity of the code and initial requirements are smaller.
Time to Market
Lack of Unit Tests
Depending on the age of the monolith, they demonstrate a shocking minimal degree of unit test coverage. The worst monolith that I ever saw had a ratio of 0.2% test coverage, according to a SonarQube analysis.
The lack of unit tests revealed that time-to-market was a crucial factor in the past. Some application monoliths were written before dependency injection had taken a foothold in the industry. There were so many static class factory singletons that it made testing very difficult. While there was one client that I saw had various degree of success with Power Mockito, even those monoliths that had tests that invariably were not verifying or validating enough outputs against expected inputs.
Lack of Know-How
With dependency injection and modules in general, the industry has shown over the past decade a lack of genuine know-how. It takes a while to design a modular application system effectively, and most people need experience.
We have searched for shortcuts in building systems. We have struggled over 25 years attempting to build object-oriented applications and systems. It is hardly a surprise that monoliths come into being because there is plenty of evidence of what not to do: anti-patterns.
Failure to Keep Up-to-Date
As we add more features and functions to a monolithic application, and in particular, one that we never throw away, we end up layering complexity on more complexity. It is a bit like having an open wound. The sensible thing would be to put a bandage on it because we think it is the quickest way to alleviate the pain and suffering. However, an open wound is not a straightforward cut. It requires antiseptic, cleaning, minor or major surgery, and then proper bandages followed by recovery. Software engineers don’t clean bad code or change the code smells. It stays infected and becomes worse.
Meanwhile, software development continually finds a brand new way. The Java language introduces annotations one year. In the following years, it introduces functional interfaces, lambdas, and streams. Yet with a huge mass of monolith with severe complexity and incomprehensibility, it is nearly impossible to use any of those recent features. For one, the management or the stakeholders that fund the monolith and keep the good ship afloat are petrified of the risk of any advancement. For another, politics and the business model mean that they deliberately fail to keep the monolith up to date with technology.
Can a Monolith Be Beautiful?
DHH describes his views on the hype regarding microservices oriented architecture (MSOA). He thought that we were blinded and prone to over-engineering new systems like microservices. Instead, he pointedly described Basecamp as a “majestic monolith” that benefited from a dynamic programming language; and over the past decade, optimizations in operating system functionality and CPU performance. DHH argued against going for distributed computing applications at the outset, especially in a small setup.
I found his views reassuring for the monolithic application. However, I observed that Basecamp, and I have never seen the code, has been under his complete control since the beginning. Every check-in of code into the version control system was most likely validated. DHH and Jason Fried are unusual because they are the architects who have stayed with their software. Architects often drop out of coding activities and mostly move on to other software systems and join other companies. Additionally, there is no way to see the “beauty” of Basecamp without examining the application code, infrastructure and, of course, the people culture at 37 Signals. So while I think a monolith can be magnificent and even majestic, it is a very tall order to ensure that it remains beautiful as time marches on.
There is a severe downside to monoliths in commercial companies. They are not normally open source and, therefore, they cannot be subject to independent critical reviews, not even of the structural engineering, security, and compliance types. I suspect that the beauty of a monolith is in the eyes of the architectural beholder and can be used as leverage — like a big stick to beat the development staff with it. “This is how it works here, this is what we have and this is how we are going to do it in order to get the delivery out of the door for milestone X.” In other words, beauty does not pay the bills. So in the end, we eventually finish with ugly monoliths, and they persist until a critical juncture, which might be business model failure and disruptive entries from the competition.
A Java EE Monolith is broken when it exhibits the following characteristics:
- It was not written from the beginning (five years ago) as a modular application where modules are self-contained with high cohesion and reduced coupling.
- Original architects have left the building and stage; and the current owners have no clue about deep internals.
- It is a representation of the political infighting status, managerial power, and intrigue.
- The organization is resistant to changing the monolith — worldly consultants and contractors have a tough time getting management to listen.
- There is a distinct lack of unit tests, sometimes with less than 10% coverage, and integration testing is hard because it falls one or two Scrum sprints beyond the current one.
- It is extremely hard to get around hard-coded dependencies, such as customer detail, test databases, and limited environments. In other words, testing the simplest code turns into a nightmare.
- The monolith has severe dependencies on legacy libraries from a decade or more years ago: Struts 1.x, WebWork, Tapestry, Spring Framework 1 or 2, and XML configuration.
- The standard build chain is complicated and takes a very long time.
- Even if management suddenly gave the green light to the entire development team today, there would be a huge mountain to climb to adopt Java SE/JDK 9, and the application code has not evolved much beyond Java SE 6 (2007).
All of these problems reflect the company’s origin, the culture, and people organization. And there you have it, ye olde monolith.