The Granularity Conundrum: Finding The Right Size of a Microservice

Rohit Singh
Level Up Coding
Published in
6 min readJun 11, 2023

--

Prologue

In the microservices architectural style, a microservice is usually designed on SRP (Single Responsibility Principle), as a separately deployed unit of software that does one thing really well. And we as developers are tempted to make services as small as possible without considering why we are doing so! This subjectiveness related to what is and isn’t a single responsibility is where we developer do mistakes with regard to service granularity. To overcome this dilemma faced by development teams in sizing the microservices, it is vital to understand the drivers of granularity.

Granularity

We have two concepts in microservices — modularity which concerns breaking up systems into separate parts, and other one is — granularity which deals with the size of those separate parts.

Determining the right level of granularity — the size of the service — is one of the many hard parts of a microservices architecture that we as developers struggle with. Granularity is not defined by the number of classes or lines of code in a service, but rather by what the service does — hence, there is this conundrum to getting service granularity right.

The granularity of service is divided into two opposing forces — granularity disintegrators and granularity integrators.

Granularity Disintegrators

When should I consider breaking apart a service into smaller parts?,

whereas,

Granularity Integrators

When should I consider putting services back together?

Granularity Disintegrators

Since we are living in the era of micro-services and nano-services, most development teams do mistakes by breaking services arbitrarily and ignoring the consequences that come with it. In order to find the right size, one should carry out the trade-off analysis on different parameters and make a calculated decision on the context and boundary of a microservice.

Granularity disintegrator drivers provide guidance and justification for when to break a service into smaller pieces. Let’s look at how these drivers affect the sizing of a microservice with an example.

Example: Consider a typical Notification Service, that does three things: notifies a customer through SMS, Email, or a Printed postal letter that is mailed to the customer.

Let’s analyze this scenario on the disintegrator drivers and find the right size. We start with:

Service scope and function

Is the service doing too many unrelated things?

The scope and function mainly depend on two attributes — first is cohesion, which means the degree and manner to which the operation of a particular service interrelate. The second is the overall size of a component, measured usually in terms of the number of responsibilities, the number of entry points into the service, or both.

Scenario: Looking at the notification service, one might say to break this service into three separate single-purpose services. But is this the right move? The answer is No!!! Because this one has a relatively strong cohesion i.e. all of these functions relate to one thing which is, notification, and have one single purpose. So, no need to break the service as it should be one service doing three things.

Next is:

Code Volatility

Are changes isolated to only one part of service?

Code volatility is the rate at which the source code changes. We have to measure the frequency of code changes in a service which leads to a good justification for breaking apart a service.

Scenario: Let us consider we have the below metrics for the service functionalities:

So, now if we go by metrics of change, frequent changes on the Postal Letter Notification part require also testing SMS and Email too, and thus as a single service, it increases the testing scope and deployment Risk. So, how do we solve this?

If we break this service into two separate services, Electronic Notification, and Postal Letter Notification, the frequent changes are now isolated into its own service, and thus testing scope is reduced and deployment risk is lower.

Scalability and Throughput

Do parts of the service need to scale differently?

The scalability demands of different functions of a service can be objectively measured to quantify whether a service should be broken apart.

Scenario: Considering again the notification service example, measuring the scalability demands of the single service reveals as follows:

Now, in this case, as a single service, Email and Postal letter functionality must unnecessarily scale to meet the demands of SMS notification, impacting cost and elasticity in terms of MTTS (mean time to startup). This perfectly justifies breaking the Notification service into separate services — SMS, Email, and Letter as highlighted below, as it allows each of these services to scale independently to meet their varying demands of throughput.

Fault Tolerance

Are there errors that cause critical functions to fail within the service?

The ability of an application within a particular domain to continue to operate, even though a fatal crash occurs (such as OOM).

Scenario: Considering our Notification service scenario, let us say, if the email functionality continues to have problems with OOM errors and fatally crashes, the entire consolidated service comes down, including SMS and Postal letter processing.

Separating this single consolidated notification service into three separate services provides a level of fault tolerance for the domain of customer notification. Thus, a fatal error in the functionality of Email doesn’t impact SMS or postal letters.

Further: Now, one question could arise here, since Email is the only issue with regard to frequent crashes, why not combine SMS and Postal letter functions? It's a valid question. If we remember, when we discussed the code volatility scenario, we separated Postal letters from Email, and SMS and thus combined these two into one — Electronic Notification. If we could do that there, we could also do it here. So, why not?

Because Email and SMS are related, they are both electronic modes of notification. But here, SMS and Postal notification have nothing in common to merge these. In other words, there is no cohesion here.

Note: Remember this, if a service is too hard to name because, it is doing multiple unrelated things, then consider breaking apart a service. And second, whenever breaking apart a service, regardless of the driver, always to check to see if strong cohesion can be formed with “leftover” functionality.

So, disintegrating the notification service into three separate services only makes sense here.

Last but not least driver is:

Extensibility

Is the service always expanding to add new functions?

The ability to add additional functionality as the service expands.

Scenario: Let us say we have got new functionalities to add to the Notification service — like Mobile Push Notifications, Desktop Notifications, Social Media Notifications, and so on. These new functionalities could certainly be added to a single consolidated notification service. However, every time a new notification is added, the entire notification service would need to be tested, and the functionality of all the notifications needs to be deployed into production unnecessarily.

Note: Apply this scenario only if it is known ahead of time that additional consolidated functionality is planned, and desired as part of the domain.

Recommended Practices:

  1. If a service is too hard to name because, it is doing multiple unrelated things, then consider breaking apart a service.
  2. Whenever breaking apart a service, regardless of the driver, always check to see if strong cohesion can be formed with “leftover” functionality.
  3. Break apart a service on business capabilities rather than technical capabilities.
  4. Use Single Responsibility Principle (SRP) while designing the microservices but keep the strong cohesion picture in mind.
  5. And lastly, analyze the trade-offs of breaking apart a service using disintegrator drivers.

--

--

Passionate technology professional, enthusiastic developer, and a devoured reader