Microservices: How many are too many?

Rodrigo Luque
6 min readDec 31, 2024

--

At which point in software architecture did we start to have to make 3, 4, … X deployments for just a simple functionality? Not many years ago, the concept of breaking down your system into small different pieces (called microservices) started to become really popular, but I think we took the term “micro” too literal.

Microservices brought two game-changing benefits. First, they let different teams work independently on their own pieces, so that work could be parallelize without many conflicts. Second, when problems pop up (and they always do), you can fix just the broken piece without having to touch the rest of the system.

In this article, I’ll show you how to know when it’s the right time to split your system into a new microservice — and when you’ve gone too far and need to merge things back together.

2 different concepts: Modularity vs Granularity

When splitting your system into different components, you are applying modularity. Meanwhile, the size of those new smaller components are gonna define the granularity of your system.

This are the key terms to understand from this article because we will be explaining, not only the benefits of these 2, but also how to find the right granularity or size of your components.

  • Modularity: It is about the breaking apart of systems into different components.
  • Granularity: The size or degree of abstraction of those components.

Benefits of Modularity

I guess we all know, more or less, what are the benefits of splitting your system into different components, so I am just going to list them here:

When are we making pieces too small?

Okay so you grab your system and you decide that you could be splitting it following your own criteria. Once you start splitting it, you realize that there are many reasons to split your newly created components into even smaller pieces. You continue going into that rabbit hole and end up with components that are just a couple of hundred lines of code, and now you have dozens of services that need to communicate with each other, that need consistency, etc.

The symptoms that you are going to see when splitting your system into very small components are:

  • You have an excessive communication overhead.
  • You end up with complex deployment pipelines.
  • It is becoming very hard to manage transactions.
  • There is an increase in operational complexity.

Basically, you end up loosing most of the benefits that we listed before that modularity provided us.

When are we making pieces too big?

The same problem can happen in the other way around. You read what I listed before and you think “Oh well, maybe it is just better not to split any of your system”. Well, let me tell you that you might be missing out on many benefits and that you will end up with these problems:

  • You have slow deployment cycles (slower than having your system splitted).
  • There will be challenges in terms of team coordination (conflicts when touching the same part of the code).
  • Your system will be harder to maintain.
  • Harder to isolate problems.
  • Inefficient to scale (there might be some parts that don’t need to be scaled).

Let’s see a real scenario

Let me present you a very simple scenario that can happen at any company and that we need to analyze to see what decission to make.

So imagine that you have an app and you decided to add the feature of notifications. You are going to have two types of notifications: transactional emails sent to your users when they sign up, and then SMS notifications for implementing 2FA in your app.

Do you create 1 service encapsulating all the notifications stuff or do you create 2 services (1 for each method: SMS and email). The second way doesn’t sound too good, right?

But what if I tell you that you are going to have tons of SMS sent as all users will be receiving an SMS every time they log in meanwhile you might send fewer emails as users only get one when they sign up. This means that they might scale differently growing over time.

Also, how often are you going to modify the content of the SMS sent or the logic itself for the 2FA? I guess it is a pretty straightforward message with a code and 3–4 more words. On the other hand, you are going to have emails with a certain template and you might want to add emails for other actions such as payments, etc.

Now you are thinking on 2 services but then, if you analyze it closely, is it really worth having 2 services just for this? In the end, both serve the same purpose (notifying) and both are cross-domain.

The answer is… it depends (like everything in software architecture). But you need to have certain criteria to decide wether to split your notifications or not, and this is what we are going to see next.

Disintegration Drivers: Aspects for splitting your system

We are going to call disintegration drivers to those aspects and forces that would cause us to break a service apart making it more fine-grained, more single purpose. These drivers are:

  • Service Functionality 👉 Breakdown code by behavior identifying single responsibilities.
  • Code Volatility 👉 The rate of change for different parts of your code. If your code is going to change too much compared to others, that can be an indicator. You can measure it with Git’s history.
  • Scalability & Throughput 👉 As we saw before, we might have different scaling needs per component. Also, we might need certain performance requirements for certain flows.
  • Fault Tolerance 👉 Isolating failure points or reducing cascade failures.
  • Security & Privacy 👉 We might need to protect certain sensitive parts of our system. Or it might need certain user permissions different from others.

Integration Drivers: Aspects for combining components

As you might have assumed, integration drivers are going to be the opposite. That is, aspects that encourage combining services into fewer, more cohesive units to optimize performance, consistency, or operational simplicity.

These are:

  • Transactions 👉 Consider when there is a need for data integrity, ACID requirements, a need to avoid distributed transactions, a business rule for atomicity, or a hard mechanism to rollback in case of an error.
  • Data Dependencies 👉 Consider when data consistency becomes challenging.
  • Communication Coupling 👉 Have in mind the 8 fallacies of distributed computing. There is a very interesting article about it that explain them really well https://medium.com/geekculture/the-eight-fallacies-of-distributed-computing-44d766345ddb

Conclusion: Best practices

After seeing all these, I hope that, even though you know that the answer is always “it depends”, you now have the tools and knowledge to decide wether to split your system or not and how big/small should those components be.

As a way of summarizing what I have explained before, I want to show here what the experts recommend to do when facing a challenge like this one:

External Resources

If you really liked this topic, I recommend you checking out the following resources where they dive deeper into this topic (they also explain it better than I do 😅), but I hope you found this article useful.

📔 Software Architecture: The Hard Parts from O’Reilly.

📔 Fundamentals of Software Architecture from O’Reilly as well.

📔 Head First: Software Architecture from O’Reilly (3 out of 3 😂).

🎙️ Software service granularity: Getting it right that you can listen here.

--

--

Rodrigo Luque
Rodrigo Luque

Written by Rodrigo Luque

Software Developer based in Madrid, Spain 👨🏻‍💻. I consider myself a curious person in constant desire of improvement.

No responses yet