The myth of complexity: why microservice architecture doesn’t work for you

A line drawing of a sad face saying 'head hurts'; a swirly line above the head indicates confusion

When I started my company, it was very obvious that we were going with a microservice architecture from the get go. Even though we were just two people, it seemed like the right choice. Different pieces of your business have their own different rhythms after all, their own little cadence when things get busy or need urgent attention. It made sense to allow each bit of software to independently follow whichever cycle was dictated by the business part. We also didn’t know what parts, if any, we were going to keep long term, so having a more granular choice seemed like a good idea.

This was not a very popular choice among founders back then, and I’m sure it’s just as unpopular today. I kept hearing that it’s easier to start with a monolith and microservice architecture brings unnecessary complexity. Founders may say all sorts of things as a proxy for “I have my hands full and this is not on the critical path”, so I generally ignored these arguments. If architecture isn’t important at this point because you’re focused on selling, it’s the right call to ignore it and go with whatever choice doesn’t force you to think about it.

But then I keep hearing the same argument - microservice architecture brings complexity - in non-startup context as well and it breaks my brain. In fact, this statement seems utterly ridiculous to the point that I can’t treat it seriously, and yet CTOs repeat it as if it was some universal truth.

I’ve worked in software for something like two decades, always at the intersection of where the shit tends to hit the fan and from the systems perspective, I can say this with high confidence: microservices don’t increase complexity. Complexity is a reflection of your domain and design choices. If anything, microservices expose complexity inherent in your space. Once something is exposed, it can be managed or mitigated, and that’s been my experience with this type of architecture. In the end, managed pockets of complexity are easier to handle than hidden, surprise complexity of unknown scale that comes with monoliths.

So where does this misconception that microservice architecture increases complexity, come from? I found out two common root causes that often end up intertwined. But before we go there, I need to talk about the crux of the issue - perception of complexity.

Perception of complexity

I’m talking about the perception of complexity as opposed to actual complexity. Sometimes we look at something and it appears complex, but when we look at the same thing from a different perspective, the complexity disappears. That’s what happens with microservice architecture and it mostly affects senior leaders, architects, and executives.

When we want to consider our system architecture on a high level, we tend to look at black boxes that represent various semantic pieces and the connections between them. It’s the standard way to draw architecture diagrams - put a box around each component and show how they all communicate. In case of a monolith, such a picture is deceptively simple. In case of microservices, unless the diagram has been drawn by a seasoned architect fluent in applying something like the C4 model, we see a messy jumble of boxes.

Two panel drawing. First panel: single big black rectangle; a person is pointing at the rectangle with a comment 'Simple!'c Second panel: multiple small black rectangles with complex connectors; a person is looking at them confused

This is a very common mistake - trying to accurately represent every microservice while looking at the system as a whole. No one needs that. It’s the wrong level of granularity. It’s like trying to understand a human by analyzing all their internal organs, blood circulation, nervous system, etc., all at once. It may be helpful to have some insight into a particular part of a human being, like a digestive system when we’re trying to diagnose a problem related to that, but it’s stupid to look at it when we’re trying to simply get to know that human. Software systems created to solve complex problems aren’t any different in that regard. If we want to understand the system as a whole, we have to look at it from the appropriate perspective, which is not individual microservices, but larger units of business context. What these units are exactly depends on what questions we’re trying to answer with a given diagram.

Drawing a diagram which shows all our microservices at once is always a pointless exercise and I’m not sure why people do it.

Now, there is something to be said that microservice architectures put people in this mental space of finer granularity when reasoning about their systems. They encourage thinking on lower levels, which is great for individual teams who need right-sized chunks of work, but can be disastrous for a CTO. As tech leaders, we really need to be aware of this issue and consciously let go. Which brings me to the first root cause of the perceived increase in complexity that allegedly comes with a microservice architecture - the need for control.

Need for control

This is, of course, not a real need, more of a mental habit of inexperienced leaders that rarely gets coached out and consequently often gets entrenched over time. Many technical leaders get uncomfortable when they don’t know “what’s going on”. When multiple teams are involved, there is a lot going on. That’s the point of having multiple teams in the first place. And when those teams work efficiently and release their work at a high speed, there is even more going on. We very quickly reach the limit of what a single person can reasonably absorb, causing our technical leader of multiple teams to panic. They really want to know the details, obstacles, tradeoffs for every one of their teams.

With a monolith, we are forced to coordinate between teams. (I have to remark at this point that coordination is very different from collaboration; the latter means work together, while the former means work on the same thing at the same time, coupled.) That necessary coordination involves multiple meetings, discussions, maybe some elaborate work tracking tool - all of these provide our leader with opportunities to catch up on “what’s going on”. They feel like they are in the loop, because in a lot of ways they are. They coordinate, pass information on, act as a conduit of alignment. Doesn’t matter that this way of working means much slower delivery of whatever needs to be delivered to the end users. In fact, this slowness is what enables the leader to know “what’s going on”. This feels comfortable and simple, even though, on a system level, it is anything but.

Now with microservices, we no longer need to coordinate. Cross-team coordination is replaced with user interviews, occasional point to point meetings, asynchronous collaboration, and a lot of documentation. There are a lot of bits and pieces spread and dispersed across the organization, often invisible to the leader. Things are moving at a faster speed than the single leader can absorb. Teams themselves are talking about all those little details that don’t immediately translate into a bigger picture. If you have the need to know “what’s going on”, this feels like a nightmare. You can no longer view the whole thing, you only ever hear about the pieces and have to put the whole system together all at once in your own head. That naturally is going to feel like a complex task. And you may be mistaken to think that the architecture is complex.

But in the end, it’s all the same bits and bobs that are being implemented. The business problems didn’t change. The end user experience, hopefully, is the same regardless of the underlying architecture. And the teams have the same amount of coding to do. If anything, the amount of non-value added work is less in case of microservices, because we don’t need to coordinate as much or run expensive end to end tests all the time. Teams can focus on solving problems within their scope and don’t need to understand other teams’ tradeoffs. The only thing that’s different is the position of the tech leader managing all these teams, who no longer is the conduit for all information and therefore has no access to all the information. There is no such need. But if someone is used to swimming in all the details, they may believe the need is still there and the fact that the information is nearly impossible to collect to form a cohesive picture. They conclude this must be the result of increased complexity.

As leaders, and especially leaders of leaders, we really need to let go of this nonsense. We don’t need to be aware of every single microservice, just like we don’t need to be aware of every single module in a monolith or every single line of code in a class or every single html tag on our landing page. Our convenience of understanding all details of the whole system is less important than the ability for our teams to deliver. If we take our “architecture diagram” from before, and then zoom in to the level where our individual contributors operate - implementing individual pieces of functionality, we can see that what seemed like a jumbled mess may be in fact very orderly, and what seemed like a clean single black box could instead be filled with confusion.

Two panel drawing. First panel: big rectangle filled with jumbled mess of various components, spider webs, trashcans, representing the monolith; a person points to the rectangle, confused. Second panel: small rectangle box with a single component, with connectors going out of each side of the box, representing a single microservice; a person points to the rectangle, smiling with a comment 'Simple!'

As long as the teams that created the respective pieces are there and still working on the codebase, all we need to do is to make sure that said teams understand the business priorities and our product landscape. And that is some potentially complex work for an engineering leader. Notice how that has nothing to do with your architecture choices.

I just described how microservice architecture may encourage looking at the whole system through a lens of wrong granularity, which increases a perception of complexity. Before, I also mentioned that the complexity of your software is a reflection of your domain and design choices. Putting these two together leads us to the second main root cause of the claim that microservice architecture increases complexity - difficulties with breaking down the domain into manageable chunks. Or to put it more broadly, poor skill in breaking problems down.

Poor break down of problems

I remember when the Agile movement started. Breaking the work down into smaller chunks was the name of the game. Apparently this was a really hard problem, since it spawned all sorts of rituals to solve it - Scrum to name one (anyone remembers planning poker?) My young self was always confused with that, why make it so complicated, just divide the solution to the business problem into a series of small, doable steps, done! Now that I’m older and wiser, I understand that to a lot of people, it isn’t so simple to pull off. Surprisingly, it is the software engineers who tend to struggle with work breakdown the most. Something about thinking in algorithms and object orientation interferes with intuition for useful business boundaries. Experienced engineers tend to get better at this but they’re often still plagued by the tendency to pick unhelpful axes for the division.

What I’m talking about is focusing too much on “what this does”. When keeping monoliths modular, this sort of thinking is helpful and lets you keep your code clean. In fact, some of the best modular monoliths I’ve seen were organized along this principle. And this is how you shoot yourself in the foot when attempting to move between the modular monolith and microservice architectures.

To understand why, we need to take a small step back and remember what is the point of microservices. It is not to draw neat little boxes in our architecture diagram since it’s never neat, quite the opposite actually. It is also not to reach some pure architectural ideal, in fact purity stands in the way of good microservice implementation. We choose this type of architecture to decouple the lifecycles of different parts of our system. This comes with a number of benefits: we can release different bits at their own cadence, sunset or replace parts more easily, choose whichever technology stack suits a given problem best, our teams can work autonomously and therefore more efficiently, which in turn enables better scaling. So the goal here is decoupling the lifecycle of each microservice from each other. Now let’s remember that the lifecycle of any software is not dictated by technology or its implementation, it is dictated by the business reality.

Focusing on “what this does” encourages people to split the problem space along the lines that matter from a technology (or worse, particular implementation) standpoint. That’s ok if we have one big blob that lives as one thing, but if we want our tiny blobs to live their own lives, we’ll quickly run into problems.

For microservices, focusing on “what this does” isn’t helpful. At all. It is anti-helpful even. It makes you create complex, sad abominations. It’s much better to think about “what value does this bring” instead. The difference is subtle, almost indistinguishable at first (unless you come from a product background, in which case thinking this way is as natural as breathing). The main difference between what something does and what value it provides is that the value needs a recipient. This perspective forces you to also ask “who is this for” and “is it really necessary”. If you cannot point out who would the customers of this thing be (they don’t necessarily have to be paying or even external) and what sort of benefit they would gain from this thing, this thing isn’t a microservice.

Splitting the problem space along the lines of “what this does” is a bit like splitting a group of humans into a bag of eyes, bag of legs, bag of heads, etc. (I have a feeling we’re all going to regret this metaphor…) This may be a useful way if we want to specialize in, e.g. eyes, but assembling a whole human out of limbs is not straight forward (ask Dr Frankenstein… see already regretting it!) In fact, when getting our individual human assembled out of the separate eyes, legs, and heads, we may question why we split it in the first place, and rightfully so. When I see people complaining about microservice complexity and choosing to go back to a monorepo, it is almost always because they’ve split their domain along the implementation lines and not business context lines, much like splitting a group of humans into bags of individual organs that then have to be put together in various ways to provide any sort of value.

We can, however, split the same group of humans into individual humans, each with their own head, legs, eyes, and so on. But if we do that, we’ll end up with multiple copies of eyes, heads, and legs! Oh no, duplication!

And that’s what often breaks software engineers’ brains. If we optimize for business value and decoupling of our services’ lifecycles, we end up with some necessary duplication. Engineers are taught to avoid this at all costs (looking at you, DRY!) In a microservice context, it leads to overengineering and design choices that result in extra complexity. Instead of avoiding all duplication, we should accept some of it as inevitable. Distinguishing the inevitable good duplication from the stupid dangerous one is not that hard, as long as we keep thinking “what value does this bring”.

Let’s get back to my creepy metaphor. In a real world, there is little value in a bag of eyes as a service. Every person needs their own eyes, and nature happily decided to duplicate this element everywhere, it is a reasonable duplication. But let’s imagine we live in the world of Cyberpunk and there is a market for cybernetic eyes and legs. In that case, it absolutely makes sense to have a bag of eyes as a service, because there are customers getting value out of it. In that situation, even though we may have split a human into individual organs based on “what this does”, a service gets born because there is value delivered to multiple customers. But this only works because there is a market and interested customers, and not because we’ve decided that having a bag serving eyes would be the “cleaner” architecture choice. Ok, ok, I’ve exhausted the usefulness of this metaphor long ago.

The point is, breaking our domain space into microservices only works when we take the customer-centric point of view, rather than simply modularizing our architecture. Creating service boundaries based on “what this does” leads to increased complexity in coordinating those services and almost always results in more microservices than we need. They also often end up being smaller than necessary. It pushes the teams towards overengineering and wasting time pondering how to avoid all duplication. That is, indeed, complexity, but it’s down to design choices rather than the architecture type.

Now imagine if a team doesn’t break down their domain space into reasonable chunks and goes full on duplication avoidance with a sprinkle of overengineering, then add a manager who feels out of control when looking at all the tiny boxes in the architecture diagram and you have not only a strong perception of complexity, the complexity is genuinely there. No one will be happy in that scenario. What’s more, it will likely be difficult, if not impossible, to realize the benefits of microservice architecture in that case - decoupling of service lifecycles, removing the need for coordination, or increasing teams’ autonomy and speed. It would be fully justified to question the point of this architecture in this case, because there really isn’t one. But the same could be said about poorly arranged monoliths or really any other pattern that was incorrectly applied.

In the end, it boils down to a question - is it easier to have a clean microservice architecture, or a clean monolith? And that is a much better framing. Instead of bringing up complexity, which is a red herring, it’s ok to say that a microservice architecture doesn’t suit our organizational priorities. It’s fine to say that we don’t want to force our engineers to think like their customers, or that we hired people who are pure technologists and we want to shield them from everything else. It’s just a different way of working. Your software architecture is inextricably linked to your organization structure and the communication style encoded in your company culture. You can’t just change one without the other. Which suggests there are some prerequisites, independent from the technology, to be able to adopt the microservice architecture without it becoming a complex train wreck. But this article is already way too long, so I’m going to leave it to another time.