To microservice or not to microservice
Distributed systems have been around for a while. Since like 1970s we’ve been building applications with large scale in mind, and large availability. We went even more in that direction since the Internet Bubble around ‘95 and we still carry on. Then, at the beginning of 2010s, the microservices happened. “Fine grained SOA”, how Adrian Cockcroft, former director for the Cloud Systems at Netflix described them, took over the development world.
Now, almost 10 years ahead, a microservice-based system is something ordinary to see, many of us have worked on them. But how many of us look at them and really sees distributed systems rather than a bunch of decoupled, orchestrated, micro-applications? What are the issues that we don’t expect to find the yet we stumble across every day?
What are distributed systems?
They are systems that have their components networked and that communicate by passing messages. The components are concurrent, independent, and lack a clock synchronization. Components of distributed system might be homogeneous or heterogeneous depending on the implementation. The main idea behind them is that for a bigger problem it’s easier to employ a large number of low performance machines, partition the problem to smaller chunks and parallelize the execution. An example of distributed systems is SOA-based systems, in which functionalities (services) are provided within an application via separate application components that communicate over the network. Each service represents an encapsulated functionality.
What are microservice systems?
Those are a subgroup of SOA-based systems, where the application is composed of loosely coupled, fine-grained services that communicate via lightweight protocols. Each microservice aims to encapsulate a layer, a domain or a functionality of the system and tends to be as independent of others as possible. They usually have significant benefits over monoliths that we will talk about in a moment, but also have some drawbacks and complications that should not be omitted.
Main benefits of microservice approach
By design, microservices are independent. They know only what they need or care about. By making them as hermetical as possible each service could be written in a different language, independently from all others. This characteristic makes them easier to understand, develop and allows easier testing. In case of service failure only a small part of the application functionality is not available and can be fixed and deployed independently from the rest of the system. In general the more modular they become, the better the microservice landscape is.
Following modularity, and somehow being its consequence, scalability comes from each microservice independency in implementation and deployment. Higher momentary load on single point in the system doesn’t necessarily clog the performance if that loaded point is able to scale horizontally. Even if this scalability isn’t unlimited, it’s a lot easier to manage and develop since the scaled service is (in theory) small and simple.
Application, especially those that require microservice architecture, usually are not implemented all at once. Its modularity allows to build consecutive components independently and integrate them with the rest of the system. It’s usually simpler to pull off then implementing a layer/component in a monolith. Moreover, any possible future changes could be implemented and deployed in a single microservice.
Distributed and parallel development
As a consequence of the latter, once designed and specified, parts of the system can be developed independently. That is both in the sense of concurrent development of different services, as a possibility of distributing the implementation geographically. It’s especially beneficial for large-scale applications and bigger organizations with multiple teams working on a single system.
Main drawbacks of microservice approach
System architecture and communication
It’s often complicated how the components of the system should communicate. You find that in many cases simple rest won’t be enough and a queue, a stream, or a topic would be required. This adds to the overall complexity, but also in most cases increases system resilience.
Resources, infrastructure and maintenance
Considering the amount of additional infrastructural components that might be needed, as mentioned in previous point, the raw amount of resources needed to run all of them is very high. Main things that should be considered for each service could be: repository, configuration, CI/CD pipeline, deployment scripts. Also, maintenance must be considered here, since with more complex infrastructure comes more difficult modification and keeping it operational.
Even if component testing becomes easier when using microservices, additional tests should be performed to assure quality – especially integration tests and end-to-end tests. Integration tests purpose is to expose faults in the interactions between microservices. It’s difficulty is not only the sheer amount of component integrations to test, but also the technical aspect of setting up a test environment facilitating such tests. End-to-end testing for microservice-based application depends greatly on its specification and implementation. Even though, considering multiple system components and data flows, it’s generally not an easy task.
Development, debugging and tracing
This conversation was marked as resolved by baszo When developing a component, developer would often want to deploy it locally or to an environment, connect with debugger and be able to debug it life. It’s a lot more complicated in case of multiservices because of the number of components that the data flow goes through, complexity of the infrastructure and limitations of the deployment environments. The same goes for tracing and logging, luckily there are now tools to trace the flow between the microservices.
It’s generally considered that microservice systems are not something that small companies would like to get involved in. Some tools try to remedy this, but considering multiple small applications, asynchronous deployments, infrastructural issues and complex testing, the cost required is often too great for smaller organizations.
For a monolithic application, transactions are usually a straight forward task and can be implemented quite easily. When it comes to microservices, transactions become a little bit more difficult. One of possible approaches is implementing distributed transactions mechanism, but it’s very complicated, adds a lot of overhead to the system and usually does not play nice with scaling. It’s benefit is maximizing consistency throughout the system. Another approach is building a system with eventual consistency. In this case we agree for momentary inconsistencies of the system, usually in terms of data, and we prepare for it. The second option tends to be much better suited for scaling, high throughput applications, but is more prone to errors and requires careful coding around inconsistencies.
Introducing changes to the code in microservice system can take different turns. If the change is internal on single microservice level then it’s pretty straightforward. But when a change is system-wide, then a couple of issues arises. First, the change has to be simultaneously introduced to a number of components, contracts between them must be assured. Then the deployment of the changes must be done in a coordinated manner, and often the whole system must be taken offline.
Common microservices mistakes
There are two frequent yet incorrect approaches regarding proper partitioning of a microservice-based system. First of them – when a layer of the system is distributed across multiple components. It’s vital to encapsulate the knowledge in the system only on need-to-know basis and linking the data as loose as possible. Second mistake is when a single component has too much knowledge, system awareness or responsibility. It’s then a clear sign that it could be divided into two or more smaller, more specialized components.
Even though it’s supposed to be one of the main advantages of microservice-based system, scaling is often overlooked and the microservice are constructed the way they cannot scale. It tends to happen i.e. when a ‘core’ microservice is implemented to link all the other parts, it then becomes the bottleneck and a single failure point of the system.
Overall architecture awareness
When implementing microservices it’s not enough to partition and encapsulate the logic, tie the parts and fire away. There are dataflows to consider, consistency issues to solve, accessibility to assure, partitioning to manage. It’s the most difficult aspect of microservice-based systems by far, yet people deciding to go the ‘microservice way’ are often not aware of it.
The hype for microservices continues. This article is not aimed at scaring or discouraging anybody. It’s goal is to show that choosing microservices comes with consequences. In many cases those are outweighed by the multiple advantages of this approach. But in equally many cases sticking with old-but-good and simple monolith would be good enough.