Microservices are one of the most recent approaches to the development of enterprise software applications. The concept has been discussed since 2011, and the term was accepted for this architecture style around that time. Even though it has been a buzzword since 2014, its use is still not fully established across the industry. There is still a lot of questions regarding what really is a Microservice, when, how and where to use them.
In this post, we will describe Microservices through their main properties and explain how these properties can be assured when building an application this kind of approach.
Let’s start with the Microservices Architecture definition by Martin Fowler and James Lewis:
“In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.”
— James Lewis e Martin Fowler
This means that instead of developing an application as a big monolith, multiple services are built that when combined achieve the desired functionality.
But why use a seemingly more complex approach instead of the good old traditional way of building features as components in a single application?
There are three main motivations for the adoption of Microservices when building applications and they are both related to the adoption of Cloud Computing.
First, Cloud Computing favors application scalability as it implements the concept of dynamic elasticity. Microservices allow for an optimal adjustment of computing resources to better match fluctuations in demand.
In other terms, cloud computing permits the easy allocation and release of computational resources (for example, virtual machines or containers), which in turn allows applications to adjust to demand. Whenever there is peak utilization (for example, of a mobile application) all that is need is to allocate resources and fire up new application instances, releasing the same resources when utilization decreases.
With the monolith style, new instances replicate the entire application (all the code implementing its functionality). In reality, specific features are more accessed during peak demand. For example, checking the balance in a chequing account application is a much more requested feature than the detailed statement from the last 6 months.
When the application is built as a suite of Microservices where each of them is responsible for a specific set of features, only the services related to that feature need to be replicated.
Therefore, the microservices approach allows the reduced usage of computational resources – and cost – by more closely matching capacity cost with actual demand:
The second reason is related to how quickly businesses need to update applications. Last-minute feature changes, bugfixes, A/B testing, flash deals… Building and running automated tests on large applications can get slow and tiresome, a fact made more evident when working with automated build and deploy pipelines and running on cloud infrastructure. Microservices allow you to break this problem in smaller pieces, resulting in smaller builds for the affected service, while keeping the others intact.
Furthermore, as the application evolves it gets harder to maintain its structure, especially when it comes to modularity. Larger applications are harder to maintain and a lot of times fixing part of the code breaks it elsewhere, slowing down development even for small changes.
The third motivation for Microservices is the built-in fault tolerance of using a distributed model. Failure in a single feature can bring down an entire monolithic application. With distributed services, one of them crashing does not cause all features to become unavailable, minimizing the impact of failures.
Although there are no exact rules to define microservices, they are typically characterized by some fundamental characteristics:
The concept of cohesion is closely related to the Single Responsibility Principle introduced by Robert C. Martin in the early 2000s, which states that a class (or in our case, a Microservice) should only have a single responsibility and execute it well. Ignoring this principle results in software that is hard to maintain and reuse.
The degree of cohesion of a software component indicates how closely related are the features it implements and if they are part of the same problem domain. A highly cohesive Microservice means it has a single responsibility and fulfills it completely. It should not overlap responsibilities with other components, delegate its responsibilities to other services or try to execute tasks not related to it.
A Microservices coupling refers to how connected or related it is to other components. In a way, coupling indicates how a component relies on others in a system. It is desirable that microservices have a low (or weak) relationship of interdependence. This means that microservices are more independent, and therefore more reusable. It will also make them more maintainable because it brings a higher degree of functional isolation which restricts the propagation of changes to its internal behavior.
Microservices should be autonomous, as in they should be completely capable of managing and executing their tasks without depending on other external services. Even when there is an execution flow to be followed with chained services, the autonomous execution model should be followed. Microservices should be capable of orchestrating their communication and execution within the application.
Ideally, software should be constructed as a set of independent components that function without relying on other resources or external components. This is usually impossible or highly impractical. Nevertheless, this principle should be followed, and external dependencies minimized and thoroughly justified.
Naturally, these concepts are interrelated. For example, low coupling usually implies high cohesion. Similarly, independent microservices need to be cohesive. Autonomy and independence are overlapping concepts.
We can understand coupling as an external property of a component, referring to how it relates to other components. Cohesion, on the other hand, is an internal property of a component, given by how its parts relate to one another.
In this context, we can also consider another Single Responsibility Principle quote from Robert C. Martin: “Group things that change for the same reason. Separate things that change for different reasons”.
Regardless, Microservices are not isolated in an application. At some point, they need to communicate and coordinate to know when to execute. That’s when the Microservices Architecture comes into play. It is the model by which a set of microservices is grouped together in an organized manner to offer the features of a complete application coherently.
The Microservices Architecture defines the model to build each component of a system and its coordination to form a cohesive whole:
- Autonomy – Microservices should be created based on business features to minimize communication between other services or resources. Communication should happen through well-defined contracts that do not expose internal implementation. There must be complete freedom of choice as to what technologies to use to implement a service (like programming language and persistence mechanisms) and it should be possible to deploy each service independently from the rest of the application.
- Resiliency – With the application split in independently deployable microservices, potential failures are isolated, affecting smaller parts of the application and not the system as a whole. Unfortunately, we have now introduced multiple points of failure into our application, therefore it is critical to have the proper monitoring, failure detection, and recovery tools.
- Transparency – In order to have Resiliency, the system must be transparent and observable. Only then it will be possible to identify when a failure occurs so problems can be diagnosed. The main tools used for this purpose are application logs and request tracing.
- Automation – The complexity of a system based on microservices is naturally much higher than a monolithic system. There is the need to automate every step to make the system available (build, automated testing, deploy) and it should be possible and simple to update the system partially (isolated microservices). Eliminating manual tasks reduces errors and guarantees the correct implementation and functionality of the system. DevOps techniques are key to implement a Microservices Architecture.
- Alignment – Microservices must be based on the business needs the application is trying to address. The feature set must be adequately distributed between microservices ensuring high cohesion, low coupling, autonomy and independence as previously stated. Domain-Driven Design is extremely useful for this purpose. Ideally, development and operation teams should be organized around Microservices with each team responsible for one or more components independently.
What is the size of a Microservice?
The prefix “micro” seems to indicate a Microservice should be small, an idea reinforced by the Single Responsibility Principle and high cohesion that should characterize it. A Microservice should do one thing and do it well. In his excellent book “Building Microservices: Designing Fine-Grained Systems”, author Sam Newman states that “a Microservice should be small enough, and no smaller than that”.
There is no final definition of what is a Microservice, so we resort to its properties and characteristics to understand its concept. Likewise, there is no golden rule to define the size of a Microservice, nor the size of the team responsible for its construction, evolution, and maintenance. The size of a microservice is certainly not the most important factor of a system. It is much more important to ensure the properties of High Cohesion, Low Coupling, Autonomy, and Independence.
In the next posts on this series, we will dive deeper into understanding the limitations and main concerns that go into the project of a Microservice.