Building a monolith has always been the default Architectural Style. In the very beginning, there were single-file applications. Then we started having applications with several files. And after that, we began seeing applications composed of other applications.
But first, what is a monolith?
A monolithic application is a unified unit, self-contained and independent from other applications. A monolithic architecture is a singular, large computing network with one codebase that couples all business concerns together.
If we want to introduce changes to this sort of application requires updating the entire stack by accessing the code base and building and deploying an updated version of the service-side interface, making updates restrictive and time-consuming.
Monoliths can be convenient early on in a project's life for ease of code management, cognitive overhead, and deployment, allowing everything in the monolith to be released at once.
So, the monolith started evolving. When applications began being with multiple files, there wasn't much reasoning about them, nor a need for that reasoning. The applications were relatively simple. As the applications got more complex, there was a need for some rules for file relations.
Modular programming, in the form of subsystems and software libraries, started as a way of reusing subsystems, in the late 1960s and 1970s. The architecture evolved from classes to a more coarse-grained explicit definition of code units. Programming languages implemented modularity with different grades of explicitness.
One of the first languages designed from the start for modular programming was the short-lived Modula. Another early modular language was Mesa. Wirth drew on Mesa and the original Modula in its successor, Modula-2, which influenced later languages, primarily through its successor, Modula-3. Modula's use of dot-qualified names, like
M.a to refer to object a from module M, coincides with the notation to access a field of a record (and similarly for attributes or methods of objects) and is now widespread, seen in C#, Dart, Go, Java, OCaml, and Python, among others.
With modular programming, modules perform discrete functions, interacting through well-defined interfaces. Often modules can form a sort of a graph, a directed acyclic graph (DAG) to be precise, where modules are arranged as a hierarchy. The lowest-level modules are independent, depending on no other modules, and higher-level modules depend on the lower-level ones. A program or library is a top-level module of its hierarchy but can be a lower-level module of a higher-level program, library, or system.
Componentization is an approach to software development that involves breaking software down into identifiable pieces that application developers independently write and deploy. These components are then stitched together with network connections and workflows.
Componentization aims to facilitate software development using reusable components that connect using standard interfaces. The components must conform to a known model that dictates how the components connect. Common componentization models include SOA, CORBA, JavaBeans and COM+. A recurrent example of this style is the pipes and filters architecture, extensively used in Unix systems and that allows us to do something like “_ps -ef | grep cs“. Another example is the usage of microservices as components of composite applications, like Netflix.
The concept of componentization has been around since 1968 when Douglas McIlroy gave a presentation titled Mass Produced Software Components at the NATO conference on software engineering. However, it wasn’t until the 1990s, when IBM and Microsoft released their software component models, that componentization became mainstream.
The Modern Monolith
Nowadays, having a monolithic Architectural Style means that all the application code is deployed and run as a single process on a single node. We assume it uses modules and components, although it is often not the case.
The keywords here are deployed and node. Deployed means that it doesn't matter where the code is physically stored if it is organized in one or several repositories, but how it is organized at runtime. Node means that it is still a monolith when all of the modules in a monolith assemble to the same memory image, which runs as a single process on a single node. Communication is achieved through standard procedure calls through the same stack and heap. The single memory image makes the application monolithic.
When developing using a monolithic architecture, the primary advantage is fast development speed due to the simplicity of having an application based on one code base.
The advantages of a monolithic architecture include:
- Easy deployment – One executable file or directory makes deployment easier.
- Development – When an application is built with one code base, it is easier to develop.
- Performance – In a centralized code base and repository, one API can often perform the same function that numerous APIs perform with microservices.
- Simplified testing – Since a monolithic application is a single, centralized unit, end-to-end testing can be performed faster than with a distributed application.
- Easy debugging – With all code located in one place, it’s easier to follow a request and find an issue.
As you can see, monoliths can work very well even for large applications. But if you want independent scalability of different domain components, independent deployability of different components, or modules written in different languages, you will start hitting a wall.
In 2009 Netflix faced these growing pains. Its infrastructure couldn’t keep up with the demand for its rapidly growing video streaming services. The company decided to migrate its IT infrastructure from its private data centres to a public cloud and replace its monolithic architecture with a microservices architecture. The only problem was, the term “microservices” didn’t exist and the structure wasn’t well-known. At that point, we started need to segregate our monolith into different applications, but this is a story for another time.
The Big Ball of Mud
A Big Ball of Mud is a haphazardly structured, sprawling, sloppy, duct-tape-and-baling-wire, spaghetti-code jungle. These systems show unmistakable signs of unregulated growth and repeated expedient repair. Information is shared promiscuously among distant elements of the system, often to the point where nearly all the important information becomes global or duplicated.
The overall structure of the system may never have been well defined.
If it was, it may have eroded beyond recognition. Programmers with a shred of architectural sensibility shun these quagmires. Only those who are unconcerned about architecture, and, perhaps, are comfortable with the inertia of the day-to-day chore of patching the holes in these failing dikes, are content to work on such systems.
Big Ball of Mud is an anti-pattern that usually arises from monolithic architecture. You certainly worked, work or will work on a project with these characteristics. The sad truth is that every day Big Balls of Mud are created.
All too many of our software systems are, architecturally, little more than shantytowns. Investment in tools and infrastructure is too often inadequate. Tools are usually primitive, and infrastructure such as libraries and frameworks is undercapitalized. Individual portions of the system grow unchecked, and the lack of infrastructure and architecture allows problems in one part of the system to erode and pollute adjacent portions. Deadlines loom like monsoons, and architectural elegance seems unattainable.
Factors such as financial and time pressure, unqualified or inexperienced developers, changing requirements, changing developers and many other factors contribute to the creation of Big Balls of Mud.
Top comments (0)