Introduced as a counter to these so-called monolithic architectures, the microservice architecture splits the business process into multiple independent services.
For example, in the context of an air-ticket booking, the monolithic approach involves building a single software that has a process “book ticket.”
“Book ticket” involves a number of individual processes. Maybe reserve a ticket with the airline, bill the customer’s credit card and send out confirmations to the customer if the ticket is successfully booked.
In a microservice architecture, the individual processes are broken out into independent services. In the example above, the services could be ticket booking, card payment and confirmation. Now the independent services communicate with each other through defined interfaces.
To say microservice architectures have become mainstream would be an understatement. In my news feed, I barely see monolithic architectures anymore — only in articles about going back from microservices to monolithic architectures. In this article, we’ll pit these two against each other.
Two software architectural styles enter the ring, one will leave as a winner.
There is a fundamental law of physics at play when it comes to microservices. Whenever a microservice calls another service over the network, bytes sent over a network. This involves turning bytes into electrical signals, or pulsed light and then turning these signals back into bytes. According to this link, the latency of a microservices call is at least 24ms. If we assume that the actual processing takes about 100 ms then the total processing time looks as below:
Network Latency — Microservices vs. Monoliths
Ideally, all call execution can happen at the same time and don’t depend on each other. This is called the fan-out pattern. Below is a diagram that shows, how the total time goes down as more and more calls execute at the same time.
Executing more calls concurrently means total execution time goes down
Executing all calls in parallel means the service will return to the consumer after the longest call finishes. A monolith has no network latency, as all calls are local. Even in a perfectly parallelizable world, the monoliths will still be faster. The way to fix this issue is reducing call chain length, using fan-out and keeping data as local as possible. Also using the fan-out pattern can significantly improve performance, as seen above. But in the end microservices can’t outrun physics when it comes to latency.
This is a clear win for monoliths.
There are a number of factors at play when considering complexity: The complexity of development, and the complexity of running the software. For the complexity of development the size of the codebase can quickly grow when building microservice-based software. Multiple source codes are involved, using different frameworks and even different languages. Since microservices need to be independent of one another there will often be code duplication. Also, different services may use different versions of libraries, as release schedules are not in sync. For the running and monitoring aspect, the number of affected services is highly relevant. A monolith only talks to itself. That means it has one potential partner in its processing flow. A single call in a microservice architecture can hit multiple services. These can be on different servers or even in different geographic locations.
In a monolith, logging is as simple as viewing a single log-file. However, for microservices tracking an issue may involve checking multiple log files. Not only is it necessary to find all the relevant logs outputs, but also put them together in the correct order. Microservices use a unique id, or span, for each call. This allows tools such as Elasticsearch to find all the relevant log output across services. Tools such as Jaeger can trace and profile calls across multiple microservices.
When running microservices in a Kubernetes cluster, complexity further increases. While Kubernetes enable capabilities such as auto-scaling, it is not an easy system to administer. To deploy a monolith a simple copy operation may be enough. To start or stop the monolith often a simple command is enough. Kubernetes on the other hand is not for the faint of heart. Transactions also add complexity to running microservice architectures, compared to a monolith. Across service borders, it is difficult to guarantee that data is in sync. For example, a badly implemented call retry could execute a payment twice. Microservice architectures can manage this by using techniques such as a central coordinator. However, in a monolithic architecture, transactions are easy to handle or even transparent to the developer.
For complexity, another win goes to the monolith.
Since microservices need to be independent of one another there will often be code duplication. A single call in a microservice architecture can hit multiple services. These can be on different servers or even in different geographic locations. In a monolith, logging is as simple as viewing a single log-file.