Don't start your system with microservice

Nguyen EngineerNguyen Engineer
4 min read

The idea is simple: you have yourself and probably a few devs, you should focus on functionality instead of scaling. Don’t let the fear of a million users on day one open the door and try your website at the same time. Even with that, the vertical scaling, stateless server, and a good old proxy like Traefik are enough to hold the door.

I worked on a few projects that later became a maintenance disaster, some of them luckily rescued by a ton of time and money, but the technical debt haunted them for years. We don’t have to live like that.

Here is how we should build a new system.

Let’s start with a majestic monolith, key requirements:

  • Follow clean architecture, where your business logic has nothing to do with the infrastructure.

    • Why? That’s the separation of concerns. Later, you can easily split it into microservices, only the infrastructure changes, the business logic is the same, regardless you run it on a VM, a k8s pod, a lambda function, a cloud function…
  • Run your app in one single server at first, written in any language that can maxed out all the server resource. I would say Go, or C#, Java, and never Node.js or Python. Never build your monolith with Node.js, please. Ask yourself, can this Node.js app max out 64 cores CPU and 196GB RAM, running for 6 months without interruption? If the answer is No, just don’t use JS for the backend.

    • Why? I came a long way here to tell you not to use Node.js for your monolith because I felt all the pains from this language and its ecosystem over 7 years. It may be fine with a microservice, serverless, or small-scale project. But in the long term, the thing we scale is not the business, but the JS problem.
  • Use Cloud cloud-based database. So you don’t have to think about scaling your database (yet). Postgres with Supabase, MongoDB Atlas is mostly enough for all the use cases.

    • Why? You don’t have to think about sharing a database between microservices, or each microservice has its database, because there is no microservice at all. Cloud provider ensures the scaling for us, if we stress a table, it won’t take down the whole database.
  • Stateless server

    • Why? We already have a single-server monolith. It doesn’t mean we can’t have many instances. Here is the beauty of it. If you need to split a hot spot (like the whole user domain) to a microservice, simply use a proxy like Traefik, a load balancer, then spin up a few servers with the exact code base, configure the proxy to re-route the traffic in that domain to those servers. Ka-boom, you still have microservices when needed.

      • We should not use the sticky session or similar stuff; let’s use token-based authentication, so the request can be sent to any of your servers.
  • Use message bus for async work

    • This is the fun part where the debate gets hottest. You don’t need a swarm of Kubernetes pods to pull the message from the message bus and call a lot of microservices to complete the work. Even when you can, and several companies have already gone this way, it is still not recommended to go that way.

    • Solution: The business logic of the async work is still in the core, just expose it to an API. Pub/Sub needs to be configured to push instead of pull. When you publish a message to the message bus, it will push to the API. And these APIs allow for the long-running task, which will process the task and ack the message. It works well with the message ordering, and you can adjust the processing rate too. If your service is stressed, the Pub/Sub will know and decrease the rate of messages sent to your server. So beautiful.

    • If the tasks are intensive, spin up another server just for the async work.

Later, you will realize most of the microservices are unnecessary. The internal function call is much faster than the API call, not to mention the network cost, latency, and a million things that can happen in the network.

The database will stress anyway in any architecture, I rarely see the code stress; most of the API work is just some data manipulation, waiting for the database query, or waiting for a 3rd party service. Time spent on scaling microservices should be spent on the database scaling.

When you need a microservice

Things that use CPU or RAM intensively need to run in a separate service to not affect the rest of the system

  • Compose email, process HTML, and PDF

  • Image/video processing

  • AI model

Assume you are a CTO or a tech lead, you need many people to work on the same project. You still don’t need to start with microservices on day one. You will start to separate the team by the domain (the concern). Once a domain is assigned to a team, we don’t care if internally it monolith or whatever architecture inside it, we only care about the interface they expose.

Conway's Law

The thing we should focus on here is the delivery of the value with the least effort. While enjoying its simplicity and cleverness.

1
Subscribe to my newsletter

Read articles from Nguyen Engineer directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Nguyen Engineer
Nguyen Engineer

👋 Hi, I’m Nguyen, or you can call me Finn 💾 Vimmer ❤️ Gopher 📍I'm based in Da nang, Vietnam ⚙️ I love working with Go and Typescript ⚙️ I love both building distributed systems and the artistry of creating a single binary that efficiently uses minimal resources to accomplish as much as possible.