4 min read

Why I don't like MicroServices

Why I don't like MicroServices

ok, I don’t just not like them, I actually hate them. I will go through my personal experience with them and explain why I believe we just need to move on

Scalability

Biggest lie ever, having 30 endpoints and 60 functionalities in 1 service versus 30 micro services with 2 functionalities each has a big impact on resources. The only differences between both is the size of the binary (most of the binary is common stuff, the compiled code is barely noticeable), until traffic comes in. Basically, the code would maybe be 70MB for a 60 functionalities but 55MB each for the 30 microservices, that quickly ads up, that seems insignificant until each one of them need to run in a containerized (with possible side containers when running on kubernetes, each). Then when they need to scale, multiply each one of those by 3, 6, 9 nodes, etc + global availability. Not insgnificant anymore.

Now the resources each of those is using, on “stand-by” let’s say each of the pod requires 250MB of RAM, versus the bigger app needs 1GB of RAM. the 1GB of RAM will be multiplied by the number of nodes, but the micro-services will be multiplied by the number of micro-services and probably at least by 3 for availability (without forgetting the global availability, so all of this multiplied by the number of zones)

TODO: Add real code examples with perf-tests

Maintainability

Duplicate code? yes everywhere. Want to solve the duplicate code issues? Introduce templating, introduce shared modules, now discuss mono-repos vs multi-repos, it’s a deep rabit hole that doesn’t really solve any real-life problem.

All your apps will need code that’ll be used in many or most microservices, e.g: authentication, logging, handlers, middlewares, test suites, pipelines, scripts, etc. All that needs to be maintained forever, the code will need to be hosted somewhere to be reused and shared to all microservices. How that would look like in MicroServices:

  • Add the dependency in your micro-services and scripts — Make sure you tag it properly because you won’t guarantee backwards compatibilty (or are you?)
  • Import your own code where you need to reference it
  • Build your code to test, that will pull your shared code and compile it into your application

Now there’s a change that need to be done on the shared library

  • Bump the version and run the unit tests of the shared library
  • Reference the new version in the micro-services
  • Test and update yet another unit test

Do the above with all micro-services

For Monolithic:

  • Change the code of that specific implementation, find and replace with your editor
  • Run the unit tests
  • recompile and ship

There’s much more to say about this but this was the most annoying part for me, the rest is trivial but still annoying (maintaining same formats, not diverging from the built templates, having to maintain in-house frameworks to be completely compliant — 0 business benefits there)

Visibility

I believe that micro-services have little visibility, it’s hard to find a piece of code that you would need or re-use the utilities that constantly change. So most of the time you just end up with duplicates, functions and classes or structs that are not worth maintaining for backwards compatibility so we keep in the helpers folder.

No one sees your PR so not much review going on, projects patterns are not respected and it is hard to not diverge, we all have different coding styles which is OK, but having 1 app with 4 different coding styles is hard for the SRE to come debug, he’ll find the loggers instantiated somewhere in this module and somewhere else random in that different service. In monolithics you can’t go wrong, someone opens a PR there’s instantly a bunch of eyes on the PR, simple requests like “can you move the logger into the logger module” are normal and won’t be missed. Over time with micro-services you won’t be able to come back and refactor everything, so you will have multiple coding styles, etc.

Debugging

Debugging these apps is the worse, you often have to open 7 tmux panels and read the logs when the request goes through, debugging a Class in a different codebase because something went “wrong” (because maybe nothing went wrong, who knows you’re still debugging) is a nightmare.

In Monolithics (new pattern for 2023, let’s go) you just call the struct directly instead of going through monkey travelling of grpc and complex api calls (oh yeah you also need to generate dozens of clients — we forgot about that), you just do: user = users.Get(name)

Such good times

Readability

Since there’s less architectural layers using Monolithics, it’s much simpler to understand what’s happening from top-down. The features have the same entrypoints, 1 main then a bunch of handlers, the code check and code smells are centralized and easy to maintain, etc.

The infrastructure is much simpler, you only have to maintain a few pods, probably a reverse proxy with very basic configuration, 2 or 3 databases, an authentication server and you’re ready to roll.

Open Discussion

Sometimes Micro-Services are used to do some specific jobs that might be time or resource intensive: That can be replaced by a simple worker that the monolithic sends working somewhere else. This design is great because you can just send it somewhere that’s not kubernetes and costs less (e.g your home server that has 256GB of RAM but still costs less monthly than a small EC2 machine)

Conclusion

There’s much more to say about this, but to be frank there’s a use-case for each. I still didn’t see a use-case for micro-services 🤷‍♂, but it must be there if so many people are hyped about it.

It’s a nightmare to debug networks and infrastructure, complexifying SREs and DevOps teams jobs is unnecessary most of the time, a Monolithic is easier to deploy, easier to run locally, requires less expertise into debugging or maintaining, and is fun to code in.

What do you think?