Microservices Architecture, The Hard Parts : Service Granularity

Naresh Waswani
Simpplr Technology

--

In a Microservice Architecture, a microservice is defined as a single purpose, separately deployable unit of software which does one thing and does it well. Microservice Architecture has a big advantage of Team Autonomous — the team owning the microservice can decide the features they want to release, when to release, etc. without coordinating with other teams.

They still need to ensure that the independent deployment of the microservice should not break any existing functionality.

But one thing which teams generally struggle with is — what should be the size of a microservice? Too fine grained service could lead to high inter service communication, leading to coupling and performance issues. Too coarse grained service could lead to the same issues as that of a Monolithic Architecture.

A microservice should be micro in nature but what does that mean — less number of lines of code or less number of classes and interfaces in a service??? There is no correct answer to this question, so how do you decide the granularity of a service?

Discussion around a business context always helps to understand things better, hence I plan to use one such well known business requirement where a team is forced to think — One composite microservice or multiple smaller microservices?

Notification Service — deliver messages over Email, SMS and In-App Notifications. This is one use case which you will find in many of the applications. And if you ask a team to implement the Notification service, you would get two possible solutions —

  1. A composite, coarse grained Notification microservice — handling all notification channels
  2. Multiple smaller fine grained microservices, one per notification channel type
Notification Service Design Alternatives — Single Coarse Grained Service Vs Multiple Fine Grained Services

If you see, logically the functionality of the Notification service is to deliver messages to users. The feature seems highly cohesive in nature, and hence a single coarse grained microservice, following the principle of Single Responsibility should suffice here

On the other hand, because you need to deliver messages to different kinds of channels/integrations which may have their own challenges, multiple fine grained microservices, one per integration type also seems logical here.

The situation is definitely not easy, so how do you decide if it should be a single composite microservice or multiple smaller microservices?

Well, there are ways to smartly derive to a decision on this, and listed below are some of the main decision drivers which can help us decide the granularity of the service —

Driver #1 — Service Functionality

The functionality offered by a microservice should be Single Purpose and Highly Cohesive in nature. For many of the cases, it is very clear but for some situations, it might get tricky, like in our Notification requirements above.

Having a single service or having multiple services (per notification type) both seem fine for implementing Notification requirements. It really depends on the perspective of what a Single Purpose functionality or Cohesion means. And at times Single Purpose functionality could be subjective in nature.

Another use case from a typical Food Delivery Platform where it is relatively easy to make decision because of low cohesion could be —

A user having its profile in the platform, is allowed to configure the preferences of delivery notification or type of food for recommendation and once the food is delivered, can provide feedback on the delivery experience and food quality. In this case, all these requirements revolve around the User but these functionalities are quite different in nature and have low cohesion. Hence, it is easy to break these set of requirements into multiple services like — Feedback Service, User service and may be a Preference service — each following the principle of Single Responsibility and having high cohesion.

Driver #2 — Scalability

In a Microservices Architecture, the features may demand for different Scaling needs and in that case, it would make sense for us to break a microservice into multiple smaller microservices to save on cost and increase User Experience.

Consider the Notification use case again— if the Email functionality expects 10 x load compared to SMS functionality and In-App notification hovers around 3x to 5x load of SMS then having a single composite service for all these features would impact the Mean time to Start (MTTS) when more instances of the service is needed to support more load. And, to run multiple instances of a single Notification microservice, we need more resources in terms of Compute and Memory, resulting in more Cost.

So, just to support high throughput and scalability for Email notification, should we scale other services? For sure No. Hence, it makes sense to have dedicated individual services for each delivery type as individual feature scalability is one of the key requirements.

Driver #3— Fault Tolerance

In a Microservices Architecture, the overall system is expected to be Fault Tolerant. If one service goes down, other services are expected to work as is. So if something goes wrong with the Email functionality in our example, the expectation is that the rest of the Notification functionality should continue to work as expected.

Let’s see the Fault Tolerance level with a single Notification Service — If there is a bug in the Email functionality which crashes the overall microservice because it was not handled properly, it impacts other Notification delivery types as well. So, the Fault Tolerance is low here.

For a high Fault Tolerant, it would absolutely be needed to break this single Notification service into multiple services.

Driver #4 — Code Volatility

One of the drawbacks of Monolith Architecture is — a single change in the application happens, and you are forced to test the entire application, increasing the scope of testing, the time for testing and also the availability of the system if deployment needs a downtime.

If you happen to make changes in a specific area of the application again and again, then it absolutely makes sense to carve out that functionality from the monolithic application and host it as an independent microservice. This helps you reduce the testing scope and time and increases the stability of the application.

In our Notification case, if In-App Notification service has a very high rate of changes compared to rest of the notification types then it would make sense to have Email Notification as one dedicated service and rest of the notification types can be supported via a single microservice.

Driver #5 — Extensibility

The ability to add more functionality to the service without impacting the availability and reliability of the existing features is referred to as Extensibility.

If the Notification service is implemented as a single consolidated service and down the line we are going to add more notification delivery types like Browser Push Notifications and Native Mobile Push notification, then for making these changes in the consolidated service, the testing scope would increase, and deployment risks might be involved depending on the deployment pattern followed.

Additional Notification Delivery Channels to be supported in the future

So, if we know in advance that the service will have to support more notification channels in future, then breaking the service into multiple services will be helpful.

These decision drivers are equally applicable for situations where you are creating a new microservice or thinking of breaking an existing monolithic application into microservices.

Hope this blog has given you some idea of deciding the granularity of a microservice given a situation you are in.

Do share this blog with your friends if this has helped you in any way.

Happy Blogging…Cheers!!!

#MicroservicesArchitecture #MicroserviceGranularity #Microservices

--

--

Naresh Waswani
Simpplr Technology

#AWS #CloudArchitect #CloudMigration #Microservices #Mobility #IoT