What was before Microservices ???
So before digging into the Microservices Architecture, we need to understand what was before Microservices and what are problems with that architecture. So before Microservices, there was Monolithic Architecture. Monolithic Architecture is like a big container where all the software components of an application are assembled and deployed together. That means your client-side user interface, the server-side application that handles HTTP requests and database, is deployed as a single executable component. Therefore the components are tightly coupled with each other in the application. For a lightweight application, a monolithic architecture suits better. But for complex, evolving applications, it will be a nightmare.
What is Microservices ???
In simple terms, Microservices is an architectural style that structures the application as a collection of small autonomous services modeled around a business domain. Each service is self-contained and implements a single business capability. Apart from that, Martin Flower, the founder of this architectural style, describes Microservices as below.
Microservice architectural style is an approach to developing a single application as a suite of small services, each running in its 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.
- - Martin Fowler, James Lewis - -
Why Microservices Better than Monolithic Architecture
Monolithic: Let assume a use case of an online shopping application. Where users can order and search for items. In the shopping season, the searching component of the application will have more traffic. But the developers cannot scale the search component of the application only because all the components of the application are deployed as one instance. Therefore new instances of the tightly coupled application have to be created every time to scale. Therefore, it will lead to the waste of resources in the server.
Microservice: Since Microservice is a domain-driven architecture, components are divided into a well-defined scope and a dedicated purpose with a single business capability. So in a use case like an online shopping application, components like listing items, order items, searching items will be deployed separately. Therefore when the developers need to scale the searching component during shopping seasons, they can perform Granular Scaling on that needed component.
Monolithic: Assume that developers want to make small changes in the application in the item listing component. Since all the modules are developed and deployed as one instance, the problem here is that the developers have to rebuild the code for every small change.
Microservices: Since each module is developed and deployed independently and if developers need to do any change in a single module or component don't need to redeploy the entire application just redeploy the module with the changes.
Monolithic: Imagine developers need to adopt new technologies like NestJS and GraphQL to fetch the item details from the backend, but since the whole application is developed and deployed as one big container, we have to stick to fixed technology stack when developing and can not use different technologies for each component.
Microservice: But in Microservice, we can develop each component like listing items, order items, searching items using different technology stacks since all the components are loosely coupled and independently deployable. Then they can communicate with lightweight mechanisms, often an HTTP resource API. Another advantage is that depending on the need of each component can utilize different technology stacks. Because some components might be CPU-intensive, and some components might be Resource-intensive.
Fault Tolerance (Reliability)
Monolithic: Let’s imagine the order item feature is not working in the application. Since the application is deployed as one compact module, the complete system goes down. To handle this, the application has to be re-built, re-tested, and also re-deployed.
Microservice: Since the application is deployed and developed as a set of small autonomous components, there are failover mechanisms and design patterns to use when developing the microservices to avoid defaults or bugs in one component bring down the entire application. (We will discuss how we can perform failover mechanisms in future articles).
The main difference between Monolithic and Microservice
🔸 The monolithic application put all their functionality in a single process and scales by replicating the monolithic on multiple servers
🔸 The microservice application puts each element of functionality into separate services and scales by distributing these services across servers replicating as need.
Some Characteristics of Microservices
Componentization via Services
Services as components rather than libraries. Services avoid tight coupling by using explicit remote call mechanisms. Services are independently deployable and scalable
Organized around Business Capabilities
Microservices are not organized around the technical capabilities of a particular product but rather business capabilities. Microservices are not divided into UI teams, database teams, and so on. There are cross-functional teams that work towards the fulfillment of one single functionality.
Products not Projects
The standard project model is to deliver pieces of software that are then considered to be completed. After that, hand over to a maintenance organization and disband the project team. The Microservices style is that a team should own a product over its full lifetime. For example, Amazon: You build, You run it.
Decentralized Data Management
Each Microservice component will have a separate database for its operation. When using decentralized storage Polyglot Persistence is frequent in microservices architectures. It manages inconsistencies via the business practices in place throughout the organization by using a common design such as reversal processes versus 2PC distributed transactions.
Design for Failure (Fault Tolerance)
A consequence of using services as components is that applications need to be designed so that they can tolerate the failure of services. Any service call can fail due to the unavailability of the supplier. Therefore can use design patterns like the circuit breaker to smoothly handle those failover incidents.
Best Practices of Microservices
🔸 When moving to microservices, you should have a fresh domain-driven design.
🔸 Don’t use hardcoded values for configuration parameters. Instead, use a service discovery mechanism to like Service Registry.
🔸 Logging: Logging is a vital part of microservices because it will help us to track the flow of requests as well as to track bugs or errors that will occur in microservices applications. When logging, we have to be mindful about logging at the correct level and position. Otherwise, it will be a mess. We should only log once for a particular error at the initial layer that will call multiple-layer like Service, Repository, DAO, etc. Therefore, log in to the place where we initiated the process, and we must log the stack trace or some information to understand where the error exactly happens on multiple service call layers.
🔸 Versioning: Use Semantic Versioning to maintain application versions. When our microservice application version is updated to 3.0.0 from 2.0.0, it is considered a breaking change. Therefore consumers using our services have to adjust their services depend on our new version. We can maintain both those versions parallelly and direct traffic to both of those versions. Then it will give some time to the consumer to update their service to consume our new service.
🔸 Dependency: Each service should be independently deployable. Therefore avoid any tight coupling between any services. Therefore we must maintain independence among services when developing as a best practice.
🔸 Executable Contracts: We should always make some test cases or scripts for services we develop. Therefore whenever we build any of our services CI tools will execute those tests. Then if those tests are passed we know that we haven’t broken our consumers.
🔸 Documentation: For documentation, we can use a tool like Swagger. It will allow us to write technical terms, and the swagger toolset will convert the technical terms we wrote into a proper documentation.
The above figure shows how different components of the microservices architecture work together. A brief functionality of each component is listed below.
- Clients: Different users from various devices send requests.
- Identity Providers: Authenticates user or client identities and issues OAuth Tokens using an identity provider service.
- API Gateway: Handles client requests.
- Static Content: holds static all the content of the system.
- Management: Balances load on services and identifies failures.
- Service Discovery: Helps in finding the way to communicate with other microservices.
- Content Delivery Networks: A distributed network of proxy servers and their data centers.
- Remote Service: Enables remote access to information that resides on a network.
Some Disadvantages of Microservices
- The monitoring of the microservices application will be difficult.
- Hard to test because services are isolated. If we need to run the integration testing, it would be a bit difficult since it depends on various other services.
- The debugging of microservices applications can be difficult.
For further more clarification check these resources;