Microservices architecture is dominating both the Developers’ chitchat and, more recently, managerial agendas. How come? Because this approach to software architecture serves as the backbone for high-performance digital platforms such as Amazon, Netflix, and eBay, among others.
So What is Microservices Architecture?
Microservices is 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.
Here are several other defining features of microservices:
- Each microservice has a separate codebase and data storage, managed as an independent product by a small development team.
- Microservices deployment can be done independently. You can re-configure/upgrade each service without melding with the entire application.
- Services can have different technology stacks, frameworks, or libraries. Meaning that you can use the best-suited tech for the purpose.
- To function as a complete product, microservices use well-defined APIs (synchronous, asynchronous, and event-driven) for communication.
As of 2020, microservices architecture has been used by 61% of Oreilly survey responders for at least a year and almost 30% — by 3+ years. What’s more important though is that among those who are currently migrating or implementing new systems, 50% plan to use microservices. Why so? Due to the following host of microservices advantages.
The Benefits of Microservices
- Higher system’s resilience through isolation and decoupling. If one microservice is down, it won’t drag the complete application.
- Faster deployments — rapid deployments of microservices are possible due to the usage of containers (more on them in a bit!).
- Diverse technological portfolio — the ability to use an array of programming languages and supporting technologies is among the main advantages of microservices for platform development.
- Improved scalability. Microservice architecture allows rapid provision of extra resources to services, experiencing peak usage.
- Better product maintainability. Fixing and upgrading individual microservices is more sustainable than attempting legacy modernization.
Monolith vs Microservices Applications: What are The Differences?
To understand microservices benefits (and shortcomings), it makes sense to compare them to monolith architecture.
A monolith is an application designed as a single unit, usually consisting of a client-side, server-side, and database. It’s a composition of modules being executed within a single process. That’s why if you want to make a change to a single module, you’ll need to re-deploy the entire application, causing a temporary service outage.
The monolithic approach is still used a lot, but its drawbacks increase as your software system grows. The larger the codebase gets — the harder it becomes to maintain. Also, larger development teams are harder to manage, and thus the productivity falls. Finally, monolith applications typically experience major performance issues.
Given the above, it makes sense why microservices are gaining the upper hand as a preferred architecture pattern. Yet, there are certain disadvantages of microservices too.
Microservices Pros and Cons
The microservices approach solves common problems with monolithic applications, but it has several drawbacks.
Advantages |
Drawbacks |
Scalability: Instead of scaling an entire monolith, you can only scale performance-critical microservices. |
Additional complexity: 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. |
Communication excess: Each client request will result in multiple requests across microservices, each waiting for the other. |
Independent deployment: You can deploy each microservice independently, without taking down the entire system. |
Data inconsistency issues: Maintaining data consistency in a distributed system is a challenge. |
Technology independence: Similarly, you can develop different microservices using different technologies, frameworks, programming languages, and even database engines. |
Operation complexity: Deploying and managing multiple instances of multiple services. |
Now let’s zoom in on microservices vs monolith comparison through the next three dimensions — design, development, and data management.
Design
As noted above, monolithic apps are hard to extend and maintain as they grow in size.
In microservices, you deal with this issue by splitting the system into relatively small components that you can modify or extend without affecting others.
The key challenge here is proper decomposition. Ideally, microservices should fulfill a single business capability and be loosely coupled. So that they can be individually upgradeable and replaceable. Proper microservices composition also makes their performance more reliable.
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.
Development
In 2002, Amazon CEO Jeff Bezos issued an infamous 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 you can deploy each microservice individually, with the help of a small cross-functional team. Such an approach is 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 easily migrate to different technologies and companies can involve Software Developers with different skills.
Data management
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 NoSQL 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 necessarily 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 inconsistency. Maintaining consistency is a challenge with a 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 an 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 popular approach. For example, when you add a new post on social media, oftentimes it doesn’t immediately appear in all news feeds.
How to Design and Develop More Stable Microservices
Generally, to get the most of a microservices-based solution, you’ll need to place a lot of emphasis on proper architecture design, using relevant domain knowledge. Otherwise, this type of architecture can cause more problems than benefits.
Why? Because microservices add up to the system’s complexity. This diagram by Martin Fowler perfectly illustrates how the microservice benefits change in relation to the system’s complexity:
What can you do to make microservices more stable?
There are several approaches that you can use to make sure your system is reliable and fast.
Request Isolation via Threads
This means that each microservice call will be performed in a separate thread, and later the result of its execution will come back to the main calling thread for further processing.
Pros. More control over a microservice execution. For example, you can stop a microservice thread after a certain timeout, e.g., in case the underlying network call takes too long. Or you can run parallel execution of different microservices requests. Doing so allows you to speed up your application performance and decrease the response delay.
You can also use isolated thread pools so that even an excessive load upon one microservice will not cause a lack of threads for others.
Cons. Additional threads will consume a certain amount of memory and require more processor resources to manage their execution. It will inevitably slow down the system. But this drawback is outweighed by the increased reliability and performance gain provided by concurrency.
Timeouts
If the microservice call takes too long, you can return a response to a client immediately with default or cached value, and stop waiting for the actual response by the microservice. You can configure timeouts of different duration for each microservice depending on its importance and expected delay.
Pros. Enhanced user experience due to elimination of the need to wait for a response.
Cons. Data returned to a user will not be accurate, so you need to consider if the speed of the response is indeed more important than its accuracy.
A Circuit Breaker Design Pattern
This design pattern allows you to stop calling a microservice at all if it fails too often, and instead return some default response to a user immediately. It does not mean that a microservice will never be called again – you may also configure a time delay upon which the system will try calling it next time to see if the microservice is back to normal and if so then resume using it. Such an approach will lower the pressure in your system caused by the unhealthy microservice.
Pros. Reduces response delays for the user, since the system does not wait for a timeout during each call to the broken microservice.
Cons. Data accuracy may suffer severely because when a circuit breaker is activated the microservice is not called at all.
Request Collapsin
You may want to gather several requests on a microservice into a package and deliver them all at once, rather than perform resource-consuming network calls every time.
Pros. Lower network load and lower threads utilization in your application.
Cons. Usually, the system waits for a certain amount of time to gather a package of requests or just limits the amount of data to be retrieved inside such a package. Therefore, some requests will have to wait until a package is collected, which means you’ll need to balance network load and response speed.
Request Caching
If you send the same request on microservice several times, why not save its result and reuse it later? Fortunately, existing frameworks allow us to configure caching relatively easily.
Pros. A microservice response is consistent throughout the user’s requests, and no time gets wasted on unnecessary network calls.
Cons. If the results are to be used by the same user then data accuracy won’t usually suffer. But the caching approach can become problematic if applied to requests for multiple users. Additionally, you will need more memory to hold cached values.
Stabilizing Microservices Architecture
Let us summarize both pros and cons of each approach mentioned above, assuming that you use a default static response if a microservice becomes unavailable.
As you can see, the main purpose of the suggested approaches is to make the environment as stable as possible, so that the end-user will not notice outages of a particular microservice or increased delays in the network and will receive the responses quickly.
From another perspective: it ensures that a faulty system fails quickly and without impact on others. This is an advantage but it doesn’t come for free. Usually, the main sacrifice is data accuracy.
So when building your system with the use of microservices, you should ask yourself what is more important for your particular situation: always receive up-to-date data? Or to be able to receive software feedback quickly and without those “Please come back later” annoyances?
In many practical cases, the latter is more justifiable. As a quick example: let’s suppose you rely on a microservice to retrieve prices for a non-binding comparison by a user of your website. If this microservice becomes unavailable, it can ruin the user experience. In this situation, you may be better off ignoring the very latest price updates and provide the cached response back to the user in a smooth presentation, at a small risk that some of the shown prices may be slightly outdated.
All approaches outlined above can be either implemented by the development team from scratch, or used as a part of third party libraries, like Hystrix, Akka Toolkit, or Apache Commons.
To Conclude: How to Approach Microservices Adoption
Some people suggest going with a monolith first and then migrating 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 tough to migrate to microservices, because good in-app interfaces may not become good service interfaces.
Generally, to get the most of a microservices-based solution, you’ll need to place a lot of emphasis on proper architecture design, using relevant domain knowledge. Otherwise, this type of architecture can cause more problems than benefits.
Need more advice on microservices architecture solutions development or on refactoring your existing monolithic application? Contact Edvantis!