Sarah Savage logo

A fusion of development and business

In defense of the “monolithic microservice”

There are a plethora of articles online about how microservices are bad. And I tend to agree with many of them – microservices are often a solution in search of a problem, and when you have multiple services running and communicating with each other, you have an increase in the number of overall problems you face as a developer.

So why in the world would I recommend a microservices architecture to my client?!

The truth is that in some cases, individualized services (not microservices) is the solution to an otherwise complex and intractable problem.

But first, a definition.

Defining “Microservice”

microservice, according to Amazon, is “an architectural and organizational approach to software development where software is composed of small independent services that communicate over well-defined APIs. These services are owned by small, self-contained teams.”

There’s a lot to break down here, so let’s do so. The first part of this is the small, independent services that they’re talking about. Most of the microservices architectures I’ve encountered have a link to the Single Responsibility Principle in that they do one discrete thing, and that’s it.

The next aspect of the microservice that Amazon talks about is the fact that they are owned by small, self-contained teams. In other words, a team owns one or more microservices, and writes those microservices for the consumption of other teams using communication over well-defined APIs. So the design here is that there are multiple teams. Okay.

So in other words, microservices architecture is intended for larger organizations that have multiple teams working on discrete areas of the product and are large enough to merit specialization.

In contrast, I have a team of five, working on a bunch of different “microservices” and anyone is capable of working on any part of the system at any time. This seems to violate Amazon’s definition.

Monolithic Microservice vs. Microservice

In fact, I am not using a microservice architecture at all. I am using whatI am describing as an independent services architecture or what I call a “monolithic microservice”. It’s close to a service-oriented architecture but I want to make a clear distinction – these are individual applications running as discrete services that communicate over APIs – much like a microservice – but they do more than a single thing – unlike a microservice.

These services are fully contained applications with fully scoped business rules that apply to them. This is very similar to domain-driven design, except that in this case the actual applications break down on business needs and bounded contexts, rather than domains within the same application. 

For example, we have an identity service which is responsible for providing a user’s identity to the other services. It also knows about and owns the user’s preferences and is responsible for maintaining whether or not they own the product (and whether they have traded in their key for a store license).

There’s another service which is responsible for knowing about which products an individual user has purchased and is also responsible for signing downloads of that product. However, it does not know the manifest of the files to be downloaded – that belongs to a third service – and it knows nothing about payment for the product except that it has a known payment ID linked to a record in a payments service.

If these services were microservices, we’d have actually seven or eight services by now – one for identification, one for preference management, one for manifest, one for what you own, one for downloading, etc. However, I have grouped similar services together for the purposes of creating monolithic “microservices”. Each service communicates with the others over well-defined APIs, and is responsible for its discrete ownership of data.

Monolithic Microservices over Monoliths

The debate here is between the monolithic microservice and the monolith in general.

Obviously the monolithic microservice is more complex to build, maintain and ship than a monolith in general. So why did we select this architecture?

In short, because we have future plans that rely on aspects of the services being independent from other aspects.

While we’re building an addon store, we want to add future services that will rely on the identity component – which needs to run independently of the store and issue a token that can be used in these yet-to-be-designed services.

We want to accept payments for the product on our website, which necessitates having a user authenticate and set up a wallet with a credit card – independent the store itself. But we also want to sell the product within the store – necessitating the ability to communicate with a payments service independent the store.

And because the product is a desktop piece of software that requires the ability to install and update itself, it made sense to extract the signing of files to the CDN and verification of purchase to an external service that doesn’t require interaction with the store catalog.

Because these services do more than just a single task, they are closer to monoliths than they are microservices. And yet, because they own discrete information that’s relevant to them, inform other components about that information, and communicate over APIs, they are similar to microservices. Hence the name – monolithic microservices.

Aren’t you future-proofing/gold plating/violating YAGNI?

Perhaps a bit. We have no way of knowing if the store will be a commercial success until we launch it. But we do know, already, that we want to release certain additional features whether or not the store is successful. In other words, we already have expansion plans that require our identity services. Tying identity directly into the store would mean never being able to pivot as necessary.

Even though our team is small, we do have some separation akin to what Amazon envisions. We have a single front-end person who is responsible for the look-and-feel, and we have designed the applications in different frameworks to suit the tastes of various developers – we’re using Symfony and Mezzio. We could just as easily use Go or Python or .NET or any other language, since we’re communicating over REST API’s.

An argument can be made taht we’re future-proofing, but I believe it’s not a real argument: since we know we’re going to need it, we’re planning for the future we know we’ll have, rather than for a future that we might have one day. YAGNI teaches us to avoid including things we’re unsure about; in this case, we are sure we’re going to need it.

Doesn’t this add (unnecessary) complexity?

Whether or not monolithic microservices add complexity is a settled question – it absolutely does.

Whether the complexity is unnecessary is a deeper, more unsettled question.

In our case, the complexity is worth the benefit – the ability to log into the desktop product and share a universal token amongst our other applications – that it outweighs the costs. We were going to have to implement an API anyway for the application, as well as OAuth2 for application authentication. Extracting the identity services was the first step – and the driver of the monolithic microservices architecture.

Of course, this adds a variety of complex and challenging situations developing services that can talk to each other. But it also presents an opportunity for us to streamline, think through interactions, and develop APIs that we won’t hate using. And that’s not all bad.

Over the course of the next few weeks, I’m going to go over the tooling we use, the way we set up our architecture, and how we develop against services that are in various states of development.

In closing…

Microservices that do a single, discrete task are generally overcomplicating the problem space and should be avoided. But the monolithic microservice organized along lines of business and/or the specific needs of the use case are great. They offer the benefits of microservices – flexibility, discrete components and clear communication over standardized APIs – without the drawbacks. And they’re fun to work on.

Monolithic microservices might not be for every project: in fact, they certainly aren’t for every project. But for projects that are complex, involve many teams, or offer needs across multiple business functions or departments, they can make sense – and provide a middle ground between monolith and microservice.

One thought on “In defense of the “monolithic microservice”

  1. This is exactly the situation I am working with at the moment. We have a wide scope for the overall business domain, fairly well defined bounded contexts, and a tiny team. We are also expecting reasonable (but currently unknown) traffic levels and an expansion of the tech team over the next few years.

    With this in mind, I have set up an architecture almost exactly as you have outlined – split out technical services into dedicated services, that the other domain-oriented apps can leverage. Then each domain-oriented app is essentially one or more bounded contexts (each fully modularised to allow future splitting into dedicated apps) inside a single deployable unit. As the team grows, apps can be assigned as needed to different developers/teams without having to extract anything for a single monolith etc.

    Finally, from the very beginning, we have done a few things which I consider important for small teams managing a monolithic microservice architecture:

    1) All infrastructure is defined using Terraform, so the actual infra config is documented as Terraform code.

    2) Invest heavily in a CI pipeline even before app development is in full swing. The CI should update dependencies automatically, enforce coding standards, run the entire test suite including performance tests and more. Basically automate as much as possible so they developers can focus on building apps.

    3) Design apps for horizontal scalability from the very beginning – i.e. no local sessions, filesystems etc. Because we do not truly know what our traffic levels will be, we want to start with a few instances of each service and then just be able to add more without meddling with the application code at all. Because we have multiple applications, these can all be scaled independently so this optimises our cloud costs too.

    4) Use containers/serverless where possible. We have say 10 apps to look after. We do not want to be worrying about the underlying operating systems of server machines as well. Just use containers that can be ephemeral and you can lose this maintenance overhead completely.

    Anyway, please write more about your experiences on this topic it is very interesting as not so much has been written about this middle ground between monoliths and microservices, and to be honest this is where many apps actually should be in the current era.

Thoughts? Leave a reply

Your email address will not be published. Required fields are marked *