Why I Chose a Clean Go Monolith Over Microservices Complexity

Published August 7, 2026

Published on August 7, 2026 • Updated on August 27, 2026

By Denny Eka Saputro (16 years in IT)

Why I Chose a Clean Go Monolith Over Microservices Complexity 1

When I set out to build a personal website after sixteen years in IT, one of the first decisions was architecture. In enterprise development it is easy to get swept up in high-end trends. We talk about microservices, event-driven message buses, Kubernetes clusters, and distributed tracing before writing a single line of code. But for my site I asked a plainer question: what is the cleanest, most maintainable architecture that delivers production performance without unnecessary overhead? The answer was not a web of microservices. It was a clean, modular Go backend paired with a React frontend.

The microservice trap for small projects

Throughout my career I have watched projects get over-engineered right out of the gate. A team splits a straightforward application into half a dozen services, then realizes it now spends most of its time managing network latency, API contracts, deployment pipelines, and local Docker setups.

Microservices solve an organizational problem. They let hundreds of developers work on separate domains without stepping on each other. But when you are running a single application, they introduce network boundaries where a simple in-memory function call would do the job better. For my site, splitting blog posts, portfolio items, and photo galleries into separate services would have added real complexity for zero practical gain. The problem microservices exist to solve is one I do not have.

Why Go fits a modular monolith

I chose Go because I wanted speed, low resource use, and a straightforward code structure. Its concurrency model and fast compilation make it ideal for lightweight REST APIs, and the whole backend ships as a single compiled binary. The important part is the word modular. A monolith does not have to mean a mess. Mine is organized into clear layers:

internal/
  handlers/      // HTTP layer: parse the request, call a service, write the response
  services/      // business logic, one boundary per domain
  repositories/  // data access, talks to DynamoDB
  models/        // the domain types everything shares

Each layer only knows about the one beneath it. A handler never talks to the database directly, and a repository never knows it is being called over HTTP. That separation is the same discipline you would enforce across service boundaries, except here it lives inside one codebase with no network in the middle. Adding a feature or refactoring a data model stays fast, because I can see exactly where each responsibility lives.

Modular now, splittable later

The usual argument for microservices up front is "what if I need to scale a piece independently later?" A modular monolith answers that without paying the cost today. Because each domain already sits behind a clean service interface, if one part genuinely needed to become its own service down the road, the seam is already there. I would be extracting a well-defined boundary, not untangling a ball of spaghetti. You get the option value of microservices without buying the operational tax before you need it.

The atomic-change payoff

There is a benefit I feel constantly. When a feature spans several domains at once, touching the frontend form, the API handler, the validation, and the storage layer in one go, it is a single pull request and a single deploy. The change is atomic. Either all of it ships together or none of it does.

In a microservices setup, that same feature becomes a coordinated dance across services, each with its own deploy, its own pipeline, and a version-compatibility window where the pieces are briefly out of sync and something can break in the gap. For a solo developer, that coordination is pure overhead with no upside. Atomic beats distributed every time when there is exactly one person holding all the versions in their head, and that person is me.

Keeping deployment lean on AWS

One of my goals during the AI-assisted build was keeping deployment simple and cheap. Because the backend is a single Go service backed by DynamoDB, with the frontend served as static assets from S3 and CloudFront, I avoided the headache of orchestrating multiple container instances.

The single-binary choice pays off in a specific place: the backend runs on AWS Lambda, and a small Go binary boots in milliseconds. That matters because cold-start time is a real cost on serverless, and a heavy runtime would tax every cold request. A compact Go service keeps those cold starts short and the memory footprint low, which keeps the bill low. And local development is just as lean. Running docker-compose up spins up the entire stack, the Go API, the React frontend, and local DynamoDB, in seconds.

Pragmatism over hype

It is tempting to reach for complex patterns because the big tech companies write engineering blogs about them. But those posts describe their scale and their constraints, not yours. Good engineering is not about using the most sophisticated tools available. It is about picking the right tool for your actual scale.

Building a clean Go backend reminded me how much power there is in simplicity. Write clear code, keep the architecture modular, and spend your energy delivering value instead of managing infrastructure. The monolith is not the unsophisticated choice here. For one person shipping a real site, it is the sophisticated one, because it solves the problems I actually have and refuses to invent the ones I do not.