Microservices: What? Why? Why Not? When? How? And more

1. The What?
The microservices architecture is essentially a technique of structuring an application as a collection of loosely coupled services. These services are usually individually scalable, testable and deployable. According to AWS:
Microservices are an architectural and organizational approach to software development where software is composed of small independent services that communicate over well-defined APIs. These services are owned by small, self-contained teams.
The following diagram shows the difference between how a monolithic architecture looks like vs how a microservices architecture.

Each service is autonomous as well as specialized. In other words, each service should focus on solving a specific problem of the business. Like in the above example, each different microservice solves the problem of
2. The Why?
So if microservices are a bunch of little services rather than one large complex service, why is it so beneficial?
Some companies, when they scale, fall into the trap of just creating one massive application to deal with all their needs. This leads to numerous problems as the application starts scaling and serving multiple purposes. Teams need more time to debug and fix errors (not only because the application is so large but also because there are just more lines of code that could result in errors), new developers need to spend a considerable amount of time getting to know the application, and no one quite knows everything that is going on. Besides this, the deployment process gets extremely complicated when multiple teams are trying to deploy changes which affect similar areas of this large application.
Microservices are extremely useful in these situations when one application gets too complicated. They can help break down these large and confusing applications into smaller ones which teams can specialize in. Since each application is smaller and teams are specialized, new developers can quickly become productive and deploy processes can be independent of other services. Each smaller service is easily testable and much more easy to debug while also being individually scalable. This type of an architecture reduces the size of the code base and also protects from critical points of failure. One microservice going down is not fatal to your operations, if these microservices are perfectly decoupled (more on this later) then individual services going down have a lesser impact on the wider application. Overall, can make the development experience easier and more productive.
The simplest way to make a case for why microservices are good is to imagine a large company like Amazon. Amazon’s e-commerce website serves a lot of functions, there are millions of product pages on amazon, authentication services, integrations with multiple partners, gift cards service, amazon music, Alexa, kindle, Prime services etc to name a few, these also vary by country and regions. Imagine having every single one of those services in one massive application that handles every scenario possible for the multi national corporation. Now imagine if this application has an error or if you were put into the position of learning what the whole application does. The codebase would be huge, there would be unidentifiable errors and it would basically be impossible to work on.
Using microservices also allow different teams to have some technological freedom, since they are not bound to one single language that the original application started with.
3-4. The Why Not and The When?
Remember that thing about complexity being reduced when you break the monolith down into smaller services, that is true for the individual smaller services but the entire application as a whole can become more complex. There are different services talking to each other with different APIs and queues and so overall, the application can become more complicated.
Another product of breaking your application down to smaller services that communicate with each other is latency, if some services need to talk to each other, there will be some latency. Eventual data consistency between the individual services also adds to the complexity as a whole of this application. Developers also now need to deal with the complexity of testing interactions between services while implementing requests that span multiple services. Finally, the biggest reason for why not kind of ties into the when:
When is this added complexity of communication for the application as a whole worth it? There are a few points to keep in mind when deciding for/against using a microservice architecture:
Size of the monolith: According to this blog post from Runscope,
As it turns out, what works for large and complex programs does not always work at a smaller scale, and what makes sense when designing a new application does not always make sense when maintaining or updating an existing application.
Your application might be too small for being broken down into smaller microservices. If your application does not have complexity that warrants multiple services, obviously utilizing a microservice architecture is not going to help. But also trying to future proof your architecture and starting with microservices might cause a bunch of problems. For starters you wouldn’t know how to exactly cut the larger application down into smaller ones since the scope/use cases for each smaller application is bound to change as the application scales and services more users. It is always easier to break apart a large application into smaller chunks than to start with a broken application and then try to stick pieces together. This is shown in this blog.
Not to mention the additional complexities that your smaller app would have to deal with for data consistency across services and the communication that would have to be done inter-service. This added complexity to code and maintain could slow down releases and make the development process harder for a small team working on a young app. So the perfect time to consider a microservices architecture would be once your application gets too complex and too large for a single development team while also having clear cut use cases for each service.
5. The How?
So from the previous few sections, it seems clear that it is ideal to start with a monolith. But where does one go from here? After this monolith becomes too complicated, some time should be spent to outline exactly how to break it down into smaller services. A good reference for this might be the Atlassian blog, but I will try to summarize some key points of generic a microservice architecture.
Each small service of the microservices architecture serves a very specific and well defined purpose. Usually it is recommended that each service have its own persistent datastore/database since this not only increases performance but also ensures that there aren’t any conflicting writes/updates happening from other services within the architecture. Now one database per service can get expensive and might also pose some challenges that are difficult to get over, so it is better to keep in mind some of the other options like, table-per-service and schema-per-service (Detailed here). So at this point you would have pieces of working application and persistent storage for each one of them. These services would ideally have an API gateway on top of them to route queries to the exact service dealing with them. Finally, there is question of communication between these services. Communication between these services can be done either using HTTP REST callouts or through asynchronous messaging using a queue or a stream (The most popular tools to do this are Apache Kafka and RabbitMQ). In an ideal world, you would want to have communication between your services be asynchronous, but usually microservice architectures end up using both. The biggest reason for preferring async communication is that it helps to completely be autonomous which is the goal of the architecture. Having long chain HTTP requests that jump from one service to another are hard to track/debug, take a longer time, couple the services and are worse for the application’s performance.
According to this blog section from Microsoft on communication between microservices:
If possible, never depend on synchronous communication (request/response) between multiple microservices, not even for queries. The goal of each microservice is to be autonomous and available to the client consumer, even if the other services that are part of the end-to-end application are down or unhealthy. If you think you need to make a call from one microservice to other microservices (like performing an HTTP request for a data query) to be able to provide a response to a client application, you have an architecture that won’t be resilient when some microservices fail.

If you are looking for methods to transform your monolith to microservices, I would recommend this awesome blog from Qentelli detailing the Stragler, Lego and Nuclear pattern for converting to microservices.
6. And more!
Although microservices do give teams the freedom of choosing languages and frameworks specific to their use cases, it might still be a good idea to keep the use of different languages to a minimum. Having multiple languages would make switching teams for developers more difficult and would lead to them being less productive while doing so (initially atleast). Overall, microservices can be a powerful tool in your arsenal if you use implement them correctly and at the perfect time.
P.S: I am always looking for feedback to improve and learn. Please leave comments if something can be improved upon/if something isn’t accurate
References/Further Reading: