A monolith is “a large single upright block of stone, especially one shaped into or serving as a pillar or monument.”
Oxford Living Dictionaries, s.v. “monolith,” https://en.oxforddictionaries.com/definition/monolith , accessed October 1, 2018.
Docker provides a solution to some of the problems posed by containers. But why did Docker become so successful only in recent years? Let’s delve into that a bit.
In the previous chapter, you learned about the evolution of Docker and the reasons for its wide adoption by the software industry. You also learned some basic use cases of Docker and its components.
In this chapter, I will consider the evolution of the microservices architecture. You’ll see how challenges posed by a monolith system, such as difficulty in continuous deployments, testing, scalability, etc., were solved by adopting a microservices architecture. You will also learn about the challenges of a microservices architecture and how application isolation enabled by Docker can come to the rescue.
Before we get into microservices, however, let’s first understand how microservices and service-oriented architectures are related. Both are architectures based on distributed systems, but there are some fundamental differences.
Microservices architecture is a kind of service-oriented architecture. In both architectures, services have a certain responsibility. These services can be developed independently on different tech stacks, and in both architectures, developers must deal with the complexity of a distributed system. However, microservices architecture splits an application into multiple different services that can be independently developed, scaled, tested, and deployed, whereas in a service-oriented architecture, services are provided to other application components. A service-oriented architecture must be deployed as a monolith, and all services must follow the same communication protocol.
Now let’s look at how microservices evolved.
Evolution of Microservices
Before we go into learning how microservices evolved, let’s first look into challenges presented by monoliths, because that is what contributed to the need for microservices architecture.
A monolith application is a single, self-contained software application in which all components of the application, including the user interface and the data access code, are all tightly coupled into a single program.
It becomes more and more difficult to test different pieces of the application independently.
Continuously deploying the entire application becomes tedious.
If you change a piece of code in a certain area, you will have to deploy the entire service, which could seem quite long and unnecessary.
A software bug in any module can bring the entire service down. Monoliths have single points of failure, which are very difficult to debug.
As the size of a monolith application increases, the startup time of the application keeps increasing with it.
To adapt new frameworks and technologies in your monolith app that uses a single stack, you must rewrite the entire application.
To mitigate all of these potential pitfalls, microservices architecture was born.

Microservices architecture in which an application is broken down into multiple services, and each service uses its own independent database
Microservices have many advantages over monoliths. A microservice architecture deals with the complexity issue of a monolith, for which it helps in dividing a single application into multiple components. This makes understanding as well as maintaining the code base a lot easier. Because the services operate independently, they can be developed using a framework that best suits the need. This gives developers a lot of flexibility, as they are free to choose what works best. Different modules can be deployed independently of one another. Services can also be scaled, as required. Testing independent services becomes easier as well, owing to the modularity that comes with a microservices architecture.
Comparing Monoliths and Microservices
Differences Between Monolith and Microservices
Monolith | Microservices Architecture | |
|---|---|---|
1. Maintenance | Maintenance grows in complexity as the application does. | It is easier to maintain microservices, as they are modular and independent. |
2. Deployment | Continuous deployment becomes very difficult as the monolith keeps growing. | Deployment of individual services is easier, and services can be deployed as and when required. |
3. Testing | Testing the entire monolith becomes a pain. | Testing individual components is much easier. |
4. Startup time | As the monolith grows in size, the startup time increases with it. | Startup times of individual services are much faster, because they are smaller in size. |
5. Adoption of newer technologies | A monolith is written in a single language, uses a single database, and is averse to adopting newer technologies. | Developers are free to choose the technologies to build their microservices. Each microservice can also use a database that best suits its needs. Microservices architecture allows you to take advantage of the latest available technologies. |
6. Scalability | It’s much harder to scale a complex monolith. | Microservices can be scaled on demand, as and when needed. |
Challenges with Microservices
While microservices address many issues with monoliths, they introduce many other kinds of problems that present a challenge. With a microservices architecture, you are dealing with all challenges that come with a distributed system. For example, because services in a microservices architecture are interconnected, inter-service communication must occur, and for that, a single, reliable, and consistent communication channel must be established, for example, using HTTP.
Multiple services mean more management of those services. All of these must be independently managed for their health and maintenance. These services have to be frequently updated and upgraded to meet the newest versions of the dependencies they use.
Microservices might have their own logging mechanisms. This might result in lots of unstructured and potentially unmanaged data. Retrieving logs can become confusing with gigabytes of available logging data.
Finding the root cause of a failure in a certain workflow might be very tedious to debug. In order to debug an entire workflow, you might have to get multiple services up and running and then test them end to end, in order to know where the bug exists, because the logic is distributed, as is the data. There could also be cyclic dependencies between services, which can be very difficult to deal with while debugging the root cause of a failure.
Last, the most significant issues are those related to versioning. When more than one service depends on certain libraries or packages, but only different versions of those libraries, it becomes tricky to get these services up and running. How can you have two versions of the same dependency on your machine? If you can’t have that, how can you manage getting these services up and running, either in a production system or in your debugging environment?
For example, imagine a spellchecker application with three different microservices: service A, service B, and service C. When the user enters a word to check the spelling of, the request is sent to service A, which depends on JavaScript version 1.8.5, Python version 2.7, and Flask version 0.12.4. Service B takes the request from service A, checks the spelling against a dictionary, and sends it to service C. In order to get service B up and running, you need Flask version 0.10.3. Service C takes this spelling and writes it to a database for records. Service C depends on Python version 2.1.
Service dependencies
Service A | Service B | Service C |
|---|---|---|
JavaScript v1.8.5 | - | - |
Python v2.7 | - | Python v2.1 |
Flask v0.12.4 | Flask v0.10.3 | - |
As you see, getting service A and service B running on the same machine is practically impossible, because they both require a different version of Flask. Similarly, getting service A and service C running successfully on a single machine is also impossible, owing to the different versions of Python.
This is one of the most prevalent and widely seen problems in the software industry. A common solution might be to update your services to use the same version of a certain dependency. But in a complex application with thousands of microservices, this becomes extremely difficult to keep track of. So, what is a good solution here? Docker.
In the preceding example, if you isolate service A, service B, and service C in their own environment and let them run independently and, at the same time, enable inter-process communication between them, they would not conflict with one another. Docker enables exactly this!
In the next few chapters, I will delve into how exactly this problem can be solved with the help of Docker, in addition to the many other advantages of using Docker to solve related problems.
Summary
In this chapter, you saw how the microservices architecture was born and how it evolved. The many challenges that came with monolith services were solved by the microservices architecture.
You also saw the differences between a monolith and a microservices architecture. You saw how as an application grows in size and complexity, a monolith poses many problems, such as difficulties with continuous deployments, testing, scalability, startup, etc. These are elegantly solved by a microservices architecture.
Last, you saw that with a microservices architecture come all the challenges of a distributed system. You saw how getting multiple services up and running can be quite challenging, if they rely on different versions of the same dependencies. Application isolation comes in very handy here. And Docker can help us with that.
In the next chapter, I will get into the basics of Docker and explain the nitty-gritties, including related terminologies, its architecture, how to install Docker, and some basic commands to use to get started.