Microservices have been a hot topic in the industry in the last few years. By this point, you've definitely heard of them. If not, there's no better way to describe them than this precise article by Martin Fowler.
We've been working with Microservices for a little over a year now on one of our fairly important projects. We've also been running native Kubernetes in production on that same project (stay tuned for an article regarding that!). By the way, we love production, you should love it too, in fact, you should go as early and as often as possible to production as you can. It's our favorite place on the internet.
Back to the point: running microservices in a native Kubernetes cluster is not easy. We've been there, we've seen it, and yes, it can hurt. This post aims to tell our experience and give ideas on why you should consider this architectural style from a backend developer's point of view, who also does a tad bit of DevOps.
Why you would consider it
There are many well-known reasons on why you should consider MSA (MicroServices Architecture, an architecture applying the Microservices style), and they're all over the internet. All things considered, it is the best possible solution to achieve a self-healing and scalable application. Let's take a look at how it might affect developer productivity and flexibility.
One of the most understated advantage that MSA has is that it enables a polyglot architecture. You're not bound to languages or technologies throughout your system's or project's lifecycle anymore. You can choose the best possible language or tooling for a given task, without making a system-wide compromise.
Microservices scale great. Both horizantally, vertically, and in terms of developer productivity. Let me explain the latter. By splitting your traditional large codebase into many small and independent components - or in our case, into separate git repositories - you're letting your team members understand the given task easier. Onboarding is not a hassle anymore, as the developers are working with a small, well-defined and grouped part of the system. Therefore, your new team members are able to bring up your sprint velocity quite early and effectively. The almost complete elimination of merge hell further improves developer productivity. We're not saying that these benefits can't be achieved with a traditional, monolithic approach, but it's definitely harder.
It is also effortless to support deprecated clients. This tedious chore is now bearable, as you can deploy the old, deprecated, but still used api separately, without slowing you down and messing up your new code.
So, if making your technologies and architecture flexible or your team scalable is a priority, you should have a glimpse at MSA.
What we learned
Now let's take a look at some of our most worthwhile learnings and their induced benefits.
Having a gateway is necessary. The clear reason is that it masks the different microservices and makes your api seem like that it is composed of one component. An api that hides all the underlying details from the outside world gives you flexibility, which is invaluable if an inner change is needed.
It's also easier to drop unneeded parts of the system. We have a few archived microservices which turned out to be not needed after a few months (e.g. when we switched to Kubernetes based config retrieval from a dedicated config server, etc.). Dropping them was a breeze, perhaps thanks to MSA and the hard and well-defined borders that it creates between different parts of the system - if designed well.
'Extract till you drop' is not a law that you should abide on an architectural level. One of our early mistakes were that we identified microservices by domain entities, thus introducing unnecessary fragmentation. Instead, you should define and group new microservices by the use cases that they're participating in and their respective users rather than the actual entities that they're representing. If you have microservices that change together, than they belong together. If you need to use distributed transactions, again, you have microservices that belong together.
MSA makes it mandatory to use CI/CD. CI/CD's relevance is clear nowadays, however some teams still might skip it in order to save time in the beginning. What they don't recognize is that they waste time in the long run. Testing, integrating, and deploying dozens of microservices by hand would be tremendous task without CI/CD, so you're left with no choice but to actually use CI/CD - which is great.
Do you really need Microservices?
So, we've concluded that MSA not only makes your application flexible, but it somehow guides you to practices that'll help you eventually. This might lead you to believe to:
Unfortunately, there're two sides to every coin and most of the time you don't really need microservices. A frequent argument for legitimizing MSA is that the giants of the world use it. Google, Netflix, Spotify are one of the many big companies that apply this style. They're successful, they use it, therefore MSA lets you succeed, right?
Wrong. MSA solves many problems that only arise when you're successful. Please don't solve problems that you don't have at hand. Don't aim for perfect scaling if you have a few hundred users in the first haul. You don't need microservices to have a well-defined and modularized application (however it definitely helps).
If you're a small startup that aims to deliver its first MVP, don't bother with MSA. In fact, MSA will very well slow you down, as it raises otherwise not needed questions and makes having a well-trained DevOps team necessary, when time and money is of the essence. These questions, amongst many are: log collection, monitoring, distributed tracing, configuration management, debugging between microservices. They're simply not present or vastly easier problems to solve if you design a traditional monolithic application.
Debugging inside a microservice is a breeze. You have a small and steady component, that you can understand in a few minutes, hopefully with well written unit and integration tests. However, things still can go wrong if your services are well tested, and debugging between microservices is a different kind of beast. You'll need to rely solely on logs, remote debugging, distributed tracing or other tools, and that'll significantly slow you down. This persuades the necessity of system-wide integration tests that are run at least nightly.
Microservices are great, however they come with a couple of notable disadvantages, amongst many far-reaching advantages. You should only choose them if you have the time, money, and willingness to actively research and evolve at the same very fast pace as the MSA community does. If you decide to do so, prepare for a thrilling and enchanting journey that'll broaden your problem solving and technical skills in a way that you never saw before.
I would like to thank my friends who proofread this post: Adam Tomko, Adam Berkecz, Marton Braun. Cheers bois! 🍻
Have you ever worked with microservices? Let me know in the comments below! Thanks for reading!