Microservices: Are they good or bad? The truth is, it depends

Benjamin Cane
Level Up Coding
Published in
8 min readJan 12, 2024

--

Something can be both good and bad, this is a cat. Cats are both good and bad.

I've seen a growing sentiment in the industry that Microservices are bad and Monoliths are good. Of course, we've all seen the references to how Stack Overflow has maintained a Monolith architecture over the years.

But, just last year (2023), Amazon Prime posted an article about moving from Microservices to a Monolith.

The article reads Serverless to Microservices, but that's beside the point.

A company like Amazon, known for using Microservices and Serverless in the past, publishing an article about moving back to Monoliths holds a lot of weight.

The primary argument is that Microservices bring too much complexity.

While Microservices architectures are often very complex, my hot take of the day is that there are times when the benefits outweigh those complexities.

I don't think we as an industry will return to Monoliths, even though it might look that way. I believe we are close to embracing something else, something different.

But for now, let's explore Microservices and what is good and bad about them.

Complexities of Microservices

Why is there an argument that microservices are just too complex? Let's explore some common challenges of microservices.

Management Overhead

Kubernetes SRE Team Member Managing a Microservices Platform

One of the biggest challenges I've seen with Microservices is the management overhead of services.

For every service running in production, you must monitor, deploy, test, and establish documentation like runbooks, data flows, etc.

While automation helps, there is still quite a bit of work that goes into creating a service. The more services you have, the more there is to do to set up a platform.

Once you have a service established, there is also ongoing maintenance.

Keeping dependencies from software and a container/server level up to date might be easy for a single service, but when you have many services, this overhead adds up! It can be overwhelming.

Monorepos is an approach that can help reduce this overhead, but then your trading overhead with dependency management for a complex build process.

When you have a monolith, it's far less overhead, keeping software dependencies up to date.

Latency

A good rule of thumb, is assume every service a transaction traverses is at minimum 500 µs. Once you add business logic, the time increases from there.

Latency is a subject near and dear to my heart.

With a heavily Microservices-based architecture, expect processing time to be linked directly to the time spent in service-to-service calls.

Microservices experience a lot of latency, which worsens with Serverless or Functions-as-a-Service designs.

One implementation pattern that worsens this problem is when service-to-service calls span multiple availability zones.

Within an availability zone, your services have physical proximity; thus, it takes less time for a network packet to reach the other service.

But when you cross an availability zone, you are likely spanning multiple physical locations, which means a network packet must travel more distance, taking more time.

Not to mention latency added by load balancers, routers, firewalls, and other networking and security equipment/software between availability zones.

An excellent way to reduce latency time with an architecture requiring many service-to-service calls is by keeping those calls within the same availability zone. This localization can be done by following a Cellular Architecture or using Zone Aware routing capabilities available in some service meshes.

Resiliency

Everything fails, all the time — Werner Vogels

Like latency, the more network calls you make, the more need for resiliency.

With every request, there is a chance for failure. To build a reliable Microservices-based platform, you must focus more on non-functional requirements like handling request failures.

A good service mesh can reduce the need for applications to focus on resiliency. However, I've found that applications still need to deal with request failures, dependency issues, etc., and there is no quick escape from non-functional requirements.

A service mesh can blindly retry a service-to-service call, but is a blind retry the correct answer? Maybe, maybe not.

But what about the things that don't traverse a service mesh? Databases, Message Brokers, Distributed Configuration Management services, etc., as the number of services using these dependencies increases, the chances of these dependencies causing issues increases.

A Microservices-based platform that ignores the non-functional resiliency requirements will create a lot of operational overhead.

Distributed Responsibility

Overworked Engineer on the one microservice that everything relies on….

While distributing responsibility is often a benefit of microservices, this greatly depends on an organization and how that organization evolves.

Microservices are great when you want a specific small team to take ownership, but this can also be problematic if that team becomes a bottleneck.

For example, it is typical to have a single service, which is a core dependency for many services within a platform or enterprise.

When a single team manages a service that hundreds of other teams rely on and want changes to, that team cannot keep up.

This challenge is fixable with the culture of InnerSource, but it takes work to create and mature this type of culture.

It's far easier for a team to throw work over a fence than to contribute to an unfamiliar code base. Maintainers also feel far more comfortable with changes introduced by their teammates vs. people who have never contributed to this code base before.

You must have the proper testing, documentation, and culture to do distributed responsibility well.

Advantages of Microservices

While it's not that the previous complexities don't exist with Monoliths, it's just that large-scale Microservices make these complexities more challenging and apparent.

But even with these complexities, there are some advantages that Microservices bring to the table.

Let's explore these advantages.

Scalability

Supermarket Checkouts are a great example of auto scaling on prem. Because you can add only as many cashiers as you have registers.

The Microservices approach of breaking an application into small parts is a significant advantage for scaling a platform.

With each piece being independent, each component is scalable individually. Some services might benefit from scaling up; others might benefit from scaling out. With each service independent, you can scale them based on their unique needs.

A service that manages extensive data may need to adopt database sharding, whereas other services might only need a small persistent store and an in-memory cache.

With Microservices, you can scale how and when each platform component needs it.

Flexibility

Did you know that Octopuses have the most flexible appendages in nature?

Every platform changes over time as business needs change.

Some platforms are constantly evolving, adding new capabilities, and changing.

For these types of platforms, Microservices have the advantage of flexibility. With services running as decoupled independent pieces, adding new functionality can be much less challenging with a Microservices architecture.

How you design the Microservices-based platform is critical to this benefit, though. Adding capabilities may be as simple as adding and integrating a new service.

Often, it means changes to existing services when designed well with straightforward contracts and isolated boundaries of responsibilities. These changes can be simple; testing can be more accessible. However, with clear boundaries and integrations, testing can become simple.

Reusability

Microservices should be thought of as lego bricks, you can take one and reuse it anywhere.

Defining the right level of functionality per service can be very difficult, but if done right, it can encourage the reuse of Microservices.

Either as a central service offering a single capability to many platforms or a deployable service that many platforms can run a copy of.

Before Microservices came around, it was common to see enormous Monoliths with duplicative functionality.

At best, you'd find a library that gets reused.

I've seen success with simple Microservices being reused across numerous platforms.

The key is thinking of each service like a LEGO brick that can be reused anywhere that requires the same functionality and creating a culture of sharing and encouraging reuse and contribution vs. rebuilding and creating duplicated capabilities.

In Summary: Microservices vs. Monoliths

So which is better, Monoliths or Microservices? Frankly, it depends on what you are trying to accomplish.

Some systems need the scalability, flexibility, and reusability of Microservices. Software Architecture is about making trade-offs; the complexities that Microservices bring are a trade-off that may make sense. It also might need to be clarified.

Making overall statements like "Microservices Suck" or "Monoliths are terrible" is short-sighted. And the argument might be meaningless soon.

Nearing a moment of change

Several projects are trying to solve the complexities of Microservices by running components together without losing the flexibility around scaling and isolating functionality.

Service Weaver

Service Weaver Logo

Service Weaver promises the developer experience of building a Monolith but allows them to break out applications into their own small, scalable "services."

With Service Weaver, the recommendation is that each unique functionality is broken into its package. These different packages can be deployed together or as independent services.

It does this by abstracting the communication between each package from the developer. From a Software Engineer's perspective, it looks like calling another method.

But when Service Weaver splits a package into its service, it generates the communication code between packages. This generated code, of course, includes observability and resiliency.

For more information on Service Weaver, check out Robert Grandl's talk from GopherConUK.

The abstraction of the communications with the required non-functionals built-in is an exciting approach. However, the ability to build everything together as one service while splitting and deploying as separate services is intriguing.

WebAssembly

Logo for WebAssembly System Interface
WebAssembly System Interface Logo

WebAssembly and the WebAssembly System Interface (WASI) are foundational technologies that can drastically change how we build software.

Unlike Service Weaver, which is Go-based only, WebAssembly and WASI are supported by multiple languages (Go, Rust, Zig, Swift, Javascript, etc.), with many others actively working to add support.

Some are using WebAssembly to build Serverless applications; others are using it to address Edge Computing needs. Some use it to offer multi-language extensions to platform services (think plugins).

WebAssembly could be used to retain the isolation of functionality Microservices & Serverless brings but also enable the same flexibility of running components together or distributed that Service Weaver aims to achieve.

The WASI standard is still developing, but some exciting projects are leveraging the capabilities of WASM/WASI to change how we deploy applications.

Is the industry changing yet again?

Is the industry changing yet again? It might be.

Paying attention to these emerging patterns and frameworks is a good idea. Like how Microservices has taken over, these new technologies will likely enable a very different way of building software.

Will it be better? Maybe.

Will it solve all of the complexities of distributed systems? Probably not. But maybe some of them.

--

--