The core idea behind microservice development is if the application is broken down into smaller independent units, with each group performing its functionality well, then it becomes straightforward to build and maintain an application. The overall application then just becomes the sum of individual units. Let's begin by debunking microservices.
Today's world is evolving exponentially, and it demands an architecture that can satisfy the following problems that made us rethink traditional architecture patterns and gave rise to microservices.
There is a great need for technological independence. At any point in time, there is a shift in languages and adoption rates change accordingly. Companies such as Walmart have left the Java stack and moved towards the MEAN stack. Today's modern applications are not just limited to the web interface and extends its need for mobile and smartwatch application too. So, coding everything in one language is not at all a feasible option. We need an architecture or ecosystem where multiple languages can coexist and communicate with each other. For example, we may have REST APIs exposed in Go, Node.js, and Spring Boot—a gateway as the single point of contact for the frontend.
Today's applications not only include a single web interface, but go beyond into mobiles, smart watches, and virtual reality (VR). A separation of logic into individual modules helps to control everything as each team owns a single unit. Also, multiple things should be able to run in parallel, hence achieving faster delivery. Dependencies between teams should be reduced to zero. Hunting down the right person to get the issue fixed and get the system up and running demands a microservice architecture.
Applications need to constantly evolve to keep up with an evolving world. When Gmail started, it was just a simple mailing utility and now it has evolved into much more than that. These frequent changes demand frequent deployments in such a way that the end user doesn't even know that a new version is being released. By dividing into smaller units, a team can thus handle frequent deployments with testing and get the feature into customers hands quickly. There should be graceful degradation, that is, fail fast and get it over with.
A tight dependency between different modules soon cascades to the entire application and it goes down. This requires smaller independent units in such a way that if one unit is not operational, then the entire application is not affected by it.
Now let's understand in depth about microservices, their characteristics, their advantages, and all the challenges while implementing a microservice architecture.
There is no universal definition of microservices. Simply stating—a microservice can be any operational block or unit, which handles its single responsibility very efficiently.
Microservices are modern styles to build autonomous, self-sustaining, loosely coupled business capabilities that sum up as an entire system. We will look into the principles and characteristics of microservices, the benefit that microservices provide, and the potential pitfalls to keep an eye out for.
There are a few principles and characteristics that define microservices. Any microservice pattern would be distinguished and explained further by these points.
A microservice is just another new project satisfying a single operational business requirement. A microservice is linked with business unit changes and thus it has to be loosely coupled. It should be that a microservice can continuously serve the changing business requirements irrespective of the other business units. For other services, it is just a matter of consumption, the mode of consumption should not change. Implementations can change in the background.
Microservices promote basic, time-tested, asynchronous communication mechanisms among microservices. As per this principle, the business logic should stay inside the endpoint and not be amalgamated with the communication channel. The communication channel should be dumb and just communicate in the communication protocol decided. HTTP is a favorable communication protocol, but a more reactive approach—queues is prevalent these days. Apache Kafka, and RabbitMQ are some of the prevalent dumb communication pipes providers.
While working with microservices, there is often a change of failure. A contingency plan that eventually stops the failure from propagating to the entire system. Furthermore, each microservice may have its own data storage need. Decentralization manages just the need for that. For example, in our shopping module we can store our customer and his transactions-related information in SQL databases, but since the product data is highly unstructured, we store it in NoSQL-related databases. Every service should be able to take a decision on what to do in fail case scenarios.
Microservices should be well defined through service contracts. A service contract basically gives information about how to consume the service and what all the parameters are that need to be passed to that service. Swagger and AutoRest are some of the widely adopted frameworks for creating service contracts. Another salient characteristic is that nothing is stored and no state is maintained by the microservice. If there is a need to persist something, then it will be persisted in a cache database or some datastore.
Microservices, being lightweight, help to replicate a setup easily in any hosting environment. Containers are more preferred than hypervisors. Lightweight application containers help us to maintain a lower footprint, thus by binding a microservice to some context. Well-designed microservices should perform only one function and do that operation well enough. Containerized microservices are easily portable, thus enabling easy auto-scaling.
Everything is abstract and unknown behind the service API in microservice architecture. In the preceding example of shopping cart microservices, we can have our payment gateway entirely as a service deployed in the cloud (serverless architecture), while the rest of the services can be in Node.js. The internal implementations are completely hidden behind the microservices and the only concern to be taken care of is that the communication protocol should be the same throughout.
Now, let's see what advantages microservice architecture has to offer us.
Adopting microservices has several advantages and benefits. We will look at the benefits and higher business values we get while using microservices.
Microservices architecture enables us to scale any operation independently, have availability on demand, and introduce new services very quickly without zero to very few configurations. Technological dependence is also greatly reduced. For example, in our shopping microservice architecture, the inventory and shopping module can be independently deployed and worked upon. The inventory service will just assume that the product will exist and work accordingly. The inventory service can be coded in any language as long as the communication protocol between inventory and product service is met.
Failure in any system is natural, graceful degradation is a key advantage of microservices. Failures are not cascaded to the entire system. Microservices are designed in such a way that microservices adhere to agreed service level agreements; if the service level agreements (SLAs) are not met, then the service is dropped. For example, coming back to our shopping microservice example, if our payment gateway is down, then further requests to that service are stopped until the service is up and running.
Microservices make use of resources as per need or effectively create polyglot architecture. For example, in shopping microservices, you can store products and customer data in a relational database, but any audit or log-related data you can store in Elasticsearch or MongoDB. As each microservice operates in its bounded context, this can enable experimentation and innovation. The cost of change impact will be very less. Microservices enables DevOps to full level. Many DevOps tools and techniques are needed for a successful microservice architecture. Small microservices are easy to automate, easy to test, contaminate the failure if needed, and are easy to scale. Docker is one of the major tools for containerizing microservices.
A well-architected microservice will support asynchronous event-driven architecture. Event-driven architecture helps as any event can be traced into—each action would be the outcome of any event, we can tap into any event to debug an issue. Microservices are designed with the publisher-subscriber pattern, meaning adding any other service that just subscribes to that event will be a mere task. For example, you are using a shopping site and there's a service for add to cart. Now, we want to add new functionality so that whenever a product is added to a cart, the inventory should be updated. Then, an inventory service can be prepared that just has to subscribe to the add to cart service.
Now, we will look into the complexities that microservice architecture introduces.
With great power comes greater challenges. Let's look at the challenging parts of designing microservices.
It is one of the topmost challenges while adapting microservice architecture. This is more of a non-functional challenge wherein new organizational teams need to be formed and they need to be guided in adopting the microservice, agile, and scrum methodologies. They need to be simulated in such an environment that they can work independently. Their developed outcome should be integrated into the system in such a way that it is loosely coupled and is easily scaled.
Creating the perfect environment needs a proper team, and a scalable fail-safe infrastructure across all data centers. Going to the right cloud provider (AWS or GCP or Azure), adding automation, scalability, high availability, managing containers, and instances of microservices are some of the key considerations. Further microservices demand other component needs such as an enterprise service bus, document databases, cache databases, and so on. Maintaining these components becomes an added task while dealing with microservices.
Being completely independent testing out services with dependencies is extremely challenging. As a microservice gets introduced into the ecosystem, proper governance and testing are needed, otherwise it will be a single point of failure for the system. Several levels of testing are needed for any microservice. It should start from whether the service is able to access cross-cutting concerns (cache, security, database, logs) or not. The functionality of the service should be tested, followed by testing of the protocol through which it is going to communicate. Next is collaborative testing of the microservice with other services. After that is the scalability testing followed by fail-safe testing.
Locating services in a distributed environment can be a tedious task. Constant change and delivery is the dire requirement for today's constantly evolving world. In such situations, service discovery can be challenging as we want independent teams and minimal dependencies across teams. Service discovery should be such that a dynamic location can be provided for microservices. The location of a service may constantly change depending on deployments and auto-scaling or failures. Service discovery should also keep a lookout for services that are down or are performing poorly.
The following is a diagram of shopping microservices, which we are going to implement throughout this book. As we can see, each service is independently maintained and there are independent modules or smaller systems—Billing Module, Customer Module, Product Module, and Vendor Module. To coordinate with every module, we have API Gateway and Service Registry. Adding any additional service becomes very easy as the service registry will maintain all the dynamic entries and will be updated accordingly: