December 4th: 'Microservice Architecture'

"Please build me a house with this hammer"

Author: Hans Bugge Grathwohl - Date: 2024-12-04

Microservices - The Architectural Pattern Without Trade-Offs

“When we build our product, we need to drive agile transformation and deliver value-added solutions while maintaining operational excellence through continuous improvement methodologies. Our platform needs to be future-proof and scalable. I expect best practices to be followed, so naturally we’ll use microservices!”

A Danish IT manager of an inhouse enterprise software project

Such non-sequiturs are unfortunately all too common in our industry. Around ten years ago, microservices was a hot and exciting topic. Books were written and many conferences were held about how large software companies found success with employing microservices to solve their problems - suddenly, they could do continuous delivery and zero-downtime deployments. There were so many success stories that a whole generation of IT managers now has internalized that microservices are a given - if you’re not using microservices, you’re not being Agile. (The tech community has long since moved on to newer and shinier things like “serverless architecture,” but it takes a while for our managers to catch up.)

Where did those success stories come from? Netflix, Spotify, Amazon, Uber. And what are you building — an internal HR portal?

Software Design 101

Software design is all about trade-offs and fending off complexity. There’s no such thing as a free lunch - whatever technical design choice you make, it always comes at a cost. But if you get the choice between simple and complex, it’s usually safe to go with simple, because complexity has a compounding effect, and it takes surprisingly few bad choices to get to a place where development velocity is approaching zero.

You want to know a good way to kill development velocity? Make it so that your developers cannot run the system on their development machines without setting up their own Kubernetes cluster. Make it so that your developers cannot use a debugger to follow the flow of a user request because it passes through multiple processes, some of which are in Go and some of which are in Python. Make it so that your developers are stuck with the first service boundary design they came up with before they understood the domain, because someone else is depending on your API and changing it requires too much coordination.

I’ve seen greenfield projects merely a few months old that have become so bogged down with operational complexity that 90% of the development time is wasted on excessively long feedback loops, and none of the developers ever test their changes end-to-end because it’s just too much effort to get the system up and running locally, and they’re not even sure what the system does anymore. All because someone had made a decision to use microservices early on, without weighing the trade-offs.

Sure, we have all seen the “big ball of mud” monolith, and we know how painful it is to work with. But if your team did not have the discipline to properly maintain one large code-base, why would you expect that they would have the discipline to maintain a large number of small code-bases? Software design is hard. Making your system distributed does not make it easier.

But What About Horizontal Scaling?

Your company acquires another company with hundreds of thousands of employees, and suddenly your HR portal needs to scale beyond your wildest imaginings. Thousands of requests per second! See, you should have gone with microservices from the start! Then you would only need to whisper a few words to Kubernetes, and all your problems would be solved in a zero-downtime instant with horizontal scaling!

Luckily, you are a good software engineer, so instead of worrying about management buzzwords, you instead go and profile your system to figure out where your bottlenecks are, and solve the problem by scaling the parts of the system that need scaling - that might mean factoring out a new service, or just adding more capacity to the existing one. You don’t need microservices up front.

Besides, when profiling, you are not unlikely to find that serialisation and deserialisation are some of your most expensive operations. Do you know when you don’t need to serialise and deserialise data? When you’re calling an in-process function in your monolith!

The Functional Programming Paradox

Something that has bothered me for a while about microservices advocates is that they love the idea of separated stateless services communicating with immutable data that facilitate parallelisation and scaling, but rarely are these the same people who advocate for using the functional programming languages where these properties come for free. Why can we think in functions and data on the system scale, but in-process data models have to be stateful classes? In my experience, if you build your system from functions and immutable data, it becomes easier to factor out services later.

Conclusion

The next time someone suggests microservices as the solution to all your problems, ask them if they’ve considered a helicopter for their commute. After all, helicopters solve transportation problems just like microservices solve architectural ones - expensively, with lots of moving parts, and with exciting new ways to crash and burn.

If you are an architect or a manager who is pushing a technology or architectural decision on your team, you should at least be aware of the trade-offs, no matter if you are pushing for microservices, AI™, or, god forbid it, serverless functions.

Here’s a humble suggestion for new software projects: maybe start with a monolith? I know, it’s boring. But consider this: a well-structured monolith with clear boundaries between modules can be far more maintainable than a poorly designed distributed system. Even Martin Fowler and Sam Newman say it: “The default option should be not to use microservices, unless you have a really good reason, go with the monolith”

Here’s another suggestion: don’t make architectural decisions if you are not actually the one building the system.