The code pyramid
Understanding software modularization
Four and a half thousand years ago the Great Pyramid for pharaoh Khufu was built. It is easy to understand why it is called like that. The building is made up of about 2.3 million stone blocks and they all lay on one foundation. A building that withstood the ravages of time for so long is surely one of the greatest engineering efforts of humankind.
Consider most electronics today. They are build to last a couple of years. A smartphone already shows battery decline and other malfunctions after a few years. But also on the software side things will rot like an Italian car of the seventies' rust in the rain.
Software gets rotten when it’s not properly maintained. Code that’s becoming unreadable, libraries that are outdated and the structure is getting a mess. Currently we haven’t figured out how to make it as rock-solid as the pyramids. But many principles are invented. This blog is about the building blocks for modular software.
Modular software
Software engineering of today is surely not at the level of the Egyptian engineering. But building the pyramids took the Egyptians also many centuries to refine. To build the correct foundations or to get the angle of the pyramid right.
Like the engineering of pyramids also software engineering is constantly evolving. All of this has the aim of making software more robust and flexible. And just like the pyramids the key to this is simplicity. To achieve simplicity while keeping functionality is one of the hardest things there is.
A pyramid is a simple structure, it has a square foundation and four triangles. A triangle is composed of three line segments. Software engineering is best simplified as three times:
- Design time: Writing the code
- Build time: Compiling the code
- Run time: Running the code
As the name design time shows, it’s not just about writing code. The code must be well structured otherwise it’s hard to cope with complexity. Writing code is just like writing a book. Why, because writing books is also not only about writing. Say we want to write a book on the history of pyramids. We can’t just put all sentences after each other. This doesn’t make a book, it must have some structure.
Our book on pyramids for example could consist of several parts. Each part covers a time period in Egyptian history. In every part there are chapters about the techniques the Egyptians used at that period. Matching sentences they are being placed in paragraphs. When all is structured and the story flows, it’s pleasant to read.
1. Design time
Writing software, it’s not so different from writing a book. The code must be well-structured and grouped together. As programming languages evolved, the structures became more sophisticated. In Java and other modern languages this is partly enforced on the programmer, so that it’s almost hard to write a bad structured program.
Comparing a book to an application looks something like this:
While writing a book makes no sense just by putting sentences after each other, writing complex programs works the same way. This is taught to students studying computer science or software engineering from the begining of their study.
As a self-taught programmer I learned this the hard way… I started to create scripts to automate stuff, but soon I also wanted to create a user interface and add some data storage. The things that still worked while writing a script didn’t work anymore while writing an application. Just like a bunch of emails don’t make a book.
A script executes the code from top to bottom, but sometimes you want to do the same thing multiple times. So the first thing I did (and I think this is a common mistake of programmers when they start) was to just copy the same code over and over. Then when you change one of the blocks, the others stay the same. Of course this isn’t what you want.
The old-skool way of solving this (also a bad practice in software design) is to use the “go-to” command. Just go to that line of code and execute it again. This creates a maze code where you can’t get out:
Like an archeologist discovered a new tomb, I discovered functions. From then on I think I started to write well-structured programs. On the top of this discovery, I learned about classes and packages in Java, so the structured approach still works with bigger programs.
In bigger applications functions are part of classes and classes are part of packages. Classes and packages group functionality (functions) that belong together. An end result of well-structured program in design time could be:
Structuring a program in design time helps other programmers. The ones that later read and modify your code. It shouldn’t be harder for them then to dechiper the hieroglyphs!
2. Run time
Once a program is build, it becomes a fixed structure. Just as when the building blocks of the pyramid are at their final position.
In software it is expected that application changes and functionality can be added all over the time. But this is only possible in design time. Ones the code is build it’s converted into ones and zero’s and the only thing we have are executable files like a .jar or an .exe file.
What long has been the holy grail of software engineering is to have the modularization of design time within run time. Partly within a single application this is achieved with settings. Either through a user interface or property files. But often an application only offers a small set of functionality. For a complete set we often need multiple applications.
Often we actually want that the applications are building blocks to create a complete building. Just like Lego blocks.
It’s not about the building blocks of a single application, but to create a whole application landscape.
One pyramid is just one building, but multiple pyramids are part of a landscape. Together with other pyramids they don’t just form a set of stones, but represent part of life (and death) of the Egyptian culture. There is even a theory that has the hypothesis that the position of the pyramids reflects the constellation of the stars.
I haven’t seen such a profound collection of applications in an application landscape yet. But an application landscape and how well they integrate together form the backbone of a modern organization.
There have been many constructions to make runtime modularization to run multiple software components together within an application landscape. This has been on software tooling level, software engineering and software architecture level. Let’s discuss each level. What solutions and design principles have been invented to create run time modularization?
1. Tooling level
On the tooling level there have been several efforts to use applications in a modular fashion. One of the first attempts were applications servers. Think for example of Websphere, Glassfish, Wildfly and Tomcat.
The idea of an application server is that you can put your application in a directory and that it gets automatically deployed. The application can share resources and some things like logging is done by the application server, relieving the burden of development.
Mostly these application servers only run for a specific programming language or runtime (like JVM). To get a more technology agnostic approach, the application and its dependencies were put into containers and then run on a container orchestration platform like Kubernetes.
This approach doesn’t share a lot of code, but shares a lot on network and operating system level. Through standardized protocols/tools (for example brokers or API gateways) the different applications or services can communicate with each other.
2. Engineering level
On this level, the modularization is build into the program itself. This is done in design time, but used in run time. In Java this can be done by OSGi. In this standard the code is put into bundles that work dynamically in runtime with each other.
Later a similar approach has been build into Java through project Jigsaw which eventually became the Java Platform Module System. Both efforts use modules which can be assembled in runtime to create applications.
This approach puts extra complexity in design time and only works for a specific language. Later approaches comply more with the tooling approach. They implement an API, so that these functions can be called by any other program over the internet through a standardized protcol like HTTP.
3. Architectural level
Software architecture defines the fundamental structures of a software system. But when we are not talking about one system, but about multiple systems than we need to define how they are interacting with each other. There have been several approaches that defines the design principles for modularization.
Over the time, it was realized that the building blocks to create runtime modularization must become smaller and smaller. Here are five approaches. The first one is the oldest and latest the newest:
- Monolith architecture: This architecture uses a single development stack. For example components that are developed on a mainframe.
- Enterprise Architecture Integration: Applications exchange and synchronize data with each other.
- Service Orientated Architecture: Design principles for reusable and loosely-coupled services.
- Microservice Architecture: Creating smaller services around a specific business compatibility, mostly with a well-defined API and part of a continuous delivery process.
- Lambda architecture: Serverless functions calling within a network.
With each design principle the software unit became smaller and smaller.
Comparing modularization at design time and runtime
A pyramid has three sides. Let’s assume that design, build and run time all represent one side. We discussed various levels of design time and run time to create modularized software. In this chapter we focus on these sides of the code pyramid.
To compare both sides is a bit comparing apples with oranges, but it gives more sense when each level of modularization fits.
The foundation of the pyramid is code. Just lines of codes. Just like the foundation of a book are sentences and the foundation of a pyramid is a stone square.
In design time reusable parts are functions. Using these in runtime we call them severless functions (like AWS Lambda or Azure functions). Functions that go together are classes (or libraries) in design time, in run time these can be compared to microservices. Packages can be compared to services. On the top of the pyramid is the application.
Final note
All of these analogies and comparisons hopefully helped to put the several approaches into perspective and understand modularization in software. Now let’s build software that lasts more than thousand years.