Microservices architecture, or simply microservices, is a software architectural style that has grown in popularity in recent years due to high demands in performance and increasing usage of cloud platforms.
It’s an approach of developing an application as a set of independently deployed, loose coupled small-sized services that communicate with standard mechanisms, such as REST or SOAP.
Microservices vs Monolith
To understand microservices it’s useful to compare them to a monolith – an application designed as a single unit, usually consisting of a client-side, server-side, and database.
A monolithic application is a composition of modules being executed within a single process. A change to a single module requires the entire application to be redeployed causing a temporary service outage.
The monolithic approach is commonly used, but its drawbacks increase with system growth. Attempts to eliminate performance bottlenecks lead to the scaling of the entire application. The larger the codebase gets, the harder it is to maintain. Bigger development teams perform worse and are harder to manage.
Pros and Cons
The microservices approach solves common problems with monolithic applications, but it has several drawbacks.
Instead of scaling an entire monolith, you can only scale performance-critical microservices.
A set of services is harder to develop than a set of modules. Poorly designed, microservices are harder to refactor.
|Dev team distribution|
Different microservices can be developed by different, relatively small teams that only have to agree on a communication interface.
Each client request will result in multiple requests across microservices, each waiting for each other.
Each microservice can be redeployed independently, without taking down entire system.
|Data inconsistency issues|
Maintaining data consistency in a distributed system is a challenge.
Different microservices can be developed with different technologies, frameworks, programming languages and use different database engines.
Deploying and managing multiple instances of multiple services
As noted above, monolithic apps are hard to extend and maintain as they grow in size.
Microservices deal with it by splitting the system into relatively small components that can be modified or extended without affecting others.
The key challenge here is proper decomposition. Ideally, microservices have to fulfill a single business capability and be loose coupled so that they can be individually upgradable and replaceable. If done poorly, refactoring remotely communicating services will be much harder. Thus microservices don’t eliminate complexity but rather transfer it to the design level.
In around 2002, Amazon CEO Jeff Bezos issued a mandate stating:
- All teams will henceforth expose their data and functionality through service interfaces.
- Teams must communicate with each other through these interfaces.
- No other communication is allowed.
- It doesn’t matter what technology is used.
This approach largely contributed to Amazon’s huge success and greatly influenced microservices architecture.
The benefit is that each microservice can be developed by a small cross-functional team and deployed individually. It’s especially useful for companies that have employees spread across the globe. Another big advantage is that there’s no commitment to any technology stack, so an individual microservice can relatively easy migrate to different technologies and companies can involve developers with different skills.
The common pattern with microservices architecture is that each service has its own database and can access other services’ data only via API. This has many positive aspects as well:
- Services are loosely coupled, changes to one database don’t affect the others.
- Databases have a relatively simple structure and are easy to maintain.
- Ability to use different database engines that best suits the service needs, i.e., fast No-SQL databases for performance, relational ones for strong ACID support, etc.
However, there are some issues with this approach:
- Application-side joins. When data from multiple services is needed to serve a user’s request, the join will occur on the application level, instead of the database level. However, this is not necessary a drawback – many high-load systems, for instance eBay, tend to move a big part of the application logic from the database to the application. The reason is that applications are much easier to scale than databases: no worries about partitioning, replication, and maintaining consistency. Applications, being stateless by their nature, can simply spawn additional instances to improve the performance.
- Data consistency. Maintaining consistency is a challenge with database-per-service approach.
- Distributed transactions is a complex solution that’s not easy to implement and poorly (or not) supported by many DB engines.
- An alternative is event-driven architecture, where services publish events when their data changes, and dependent services subscribe to these events and update their own data. This leads to eventual data consistency.
- Services can have eventually consistent materialized views of other services data, which they replicate for performance reasons. This is a widely used approach: on social networks, when a post is added it doesn’t immediately appear in all news feeds.
The microservices architecture pattern arose as a result of companies trying to deal with their high-load solutions. The most notable examples include Amazon, Netflix, and eBay.
The question whether to use microservices or not is a rather tricky one. The thing is that microservices solve problems that you don’t usually experience when you begin developing an application. Microservices help to deal with an application’s growing complexity and load, which is not usually an issue in the beginning. Martin Fowler has come up with the following diagram illustrating microservice benefits in relation to the system’s complexity:
Some people suggest to go with a monolith first and then to migrate to microservices if necessary. Some try a hybrid approach, keeping a monolith as a base and adding new functionality around it with microservices.
On the other hand, when starting with a monolith it may be very hard to migrate to microservices, because good in-app interfaces may not become good service interfaces.
Generally, to get the most of a microservices-designed solution, an effort must be put into proper architecture design using clear domain knowledge – otherwise it can cause more problems than benefits.
Need more advice on microservices architecture solutions development or on refactoring your existing monolithic application? Feel free to reach out to me at [firstname.lastname@example.org, ATTN Ivan Shchudlo Microservices Architecture 101]