Domdhi logoDOMDHI.OS
TECH

The Case Against Microservices for Solo Builders

13 modules. 126 API endpoints. 310,360 lines. One Cloudflare Worker. Microservices solve an org-chart problem -- they let teams deploy without coordinating. A solo builder has no coordination problem, so all you buy is the cost.

DDominic BacaSOLO ENGINEER · DOMDHI.OS2026.10.294 MIN READ
TECH// FIG.01

Domdhi.OS is 13 modules, 126 REST endpoints, 378 route files, and 310,360 lines of TypeScript.

It deploys as one Cloudflare Worker.

Not because I haven't heard of microservices. Because I know what problem they solve, and I don't have it.

They Solve An Org Problem, Not A Technical One

This is the part that gets lost.

Microservices exist so that teams can deploy independently. That's the payload. When you have forty engineers across six teams, the bottleneck isn't compute — it's coordination. Every shared deploy means a release train, a freeze, a merge queue, and six teams arguing about whose change broke staging.

Split the system along team boundaries and each team ships on its own schedule. That's an enormous organizational win, and it's worth every technical cost it imposes.

Now count the teams in a solo project.

One. There is no coordination problem. There is no release train. There is nobody to deploy independently from. You're buying an expensive solution to a problem that structurally cannot exist at your headcount.

Conway's Law cuts both ways: your architecture mirrors your communication structure. A solo builder's communication structure is a single person. The architecture that mirrors it is a monolith.

What It Actually Costs You

The costs aren't theoretical and they land hardest on the person with the least capacity to absorb them.

Every function call becomes a network call. In-process, calling a function is nanoseconds and it either works or throws. Across a service boundary it's milliseconds, and now you have timeouts, retries, partial failures, and the question of what happens when service B is up but slow. You've converted a language feature into a distributed systems problem.

You lose the type system at every boundary. This is the one that hurts most in TypeScript. Inside one codebase, my loader's return type flows into my component's props and the compiler catches the mismatch. Across an HTTP boundary it's unknown on the other side, and the contract is now maintained by hope, documentation, and runtime validation you have to write by hand.

Transactions stop being free. "Insert a post and its distribution record atomically" is one BEGIN/COMMIT in a monolith. Across two services it's a saga, with compensating actions for every partial failure. That's a genuinely hard distributed-systems problem you've volunteered for.

Local development degrades. One npm run dev becomes docker-compose with six containers, and the day one of them won't start is a day you don't ship.

Debugging requires infrastructure. A stack trace tells you everything in a monolith. Across services you need distributed tracing, correlated request IDs, and log aggregation just to answer "where did this break." You now operate a platform in order to maintain an app.

Each of those is affordable when a platform team absorbs it. Solo, every one comes directly out of your feature budget.

What I Do Instead

The alternative isn't a big ball of mud. It's enforced modularity without distribution.

Modules are directories with rules, not deployments:

  • Each of the 13 modules owns its routes, its data layer, and its schemas.
  • Data access goes through typed lib/<domain>/*.server.ts functions. Raw database calls never appear in a route handler — that's the real boundary, and it's enforced by review rather than by a network hop.
  • An 800-line ceiling on any file forces extraction before anything becomes unnavigable.
  • Every write path ships a data function, an API endpoint, and an agent tool. Consistency by convention.

That gives me the thing microservices are actually selling — clear ownership, replaceable parts, boundaries you can reason about — with none of the distribution tax. The boundaries are real. They're just compile-time instead of runtime, which means the compiler enforces them for free instead of me enforcing them with integration tests.

And when a module genuinely needs to be its own service, extracting one that already has a clean data layer is a week of work. Merging two services back together is a quarter. The asymmetry says start joined.

The Honest Counter-Case

Three situations where I'd split, and I want to name them because "monolith always" is as dumb as "microservices always."

Wildly different scaling profiles. If one component needs 50× the resources of everything else — video transcoding, ML inference — isolating it is correct. You're separating for resource shape, not for team autonomy.

A genuinely different runtime. My whole stack is edge-compatible TypeScript. Something needing long-running Python with GPU access isn't a module, it's a different machine.

A hard blast-radius requirement. If one subsystem failing must not be able to take down the others — payments, say — isolation buys real safety.

Notice what's absent from that list: "the codebase is getting big." Size is not a reason. Size is a reason to modularize, which is free. Distribution is a separate, much more expensive decision that people keep making for size reasons.

Start Joined

The whole stack is one Worker, one deploy, one log stream, one stack trace. git push and it's live in ninety seconds.

I can hold the entire system in my head. Not because it's small — 310,360 lines isn't small — but because there's exactly one of it, and every boundary inside it is checked by a compiler rather than by a contract test I forgot to write.

Microservices are a solution to having too many people. If you don't have too many people, you don't have the problem, and you shouldn't buy the cure.

GET_TRANSMISSIONS

Raw signal on finance, freedom, fitness, and tech. No spam. Unsubscribe anytime.

D
Dominic Baca

One engineer building Domdhi.OS in public — money, freedom, and the occasional 2am migration. Every number live, every commit public.

FOLLOW THE LOGS →
// NEXT IN THE LOGS