Skip to content
matrab.
  • Technical
  • Architecture
  • Microservices
  • Solo Developer
4 min read

Boring Technology for Solo Developers: One-App Default

Ship one modular application. Split it into services only when independent deployment and scaling actually pay for the operational bill.

Modular monolith versus microservices: three teal modules inside one deployment boundary on the left, and the same three modules split into separate services on the right, wired together by crossing amber calls.

Microservices turn function calls into network calls, where the upstream service can block and the downstream one can simply decline to answer. If deploying and scaling the whole app is still fine, ship one modular application. Split it only when separate deployment and scaling repay the delivery and distributed-systems work you are signing up for.

Boring means you know how it breaks

Boring technology is not obsolete technology. The original argument separates boring from bad and favors tools whose capabilities and failure modes you already understand. You have seen this database fall over. You know what it looks like at 3am.

Every component you add has to be monitored, tested, understood well enough to modify, and operated in production for as long as the product exists. If it removes a week of construction pain and adds three years of operational homework, that is not a trade. That is a subscription.

One deployable, with walls inside it

Use a modular monolith while one release and whole-application scaling still fit. The modules stay collectively deployed, and the boundaries follow business subdomains instead of slicing everything into controllers, services, and repositories.

One deployable application

  • product module
  • order management module
  • notification module

Those boundaries come from an e-commerce decomposition. It is one possible domain split, not a required package layout or database schema.

The unstructured monolith is what people usually mean when they say monolith: every part of the application is allowed to reach into every other part. Microservices solve that by moving calls out of process and putting service data behind public interfaces, which hands you network, query, and transaction coordination problems in exchange. The modular version keeps the boundaries and keeps the single deploy.

Modular monolith vs microservices is an operations choice

The modular monolith vs microservices decision turns on what has to vary independently. Microservices buy independent deployment and scaling. The invoice covers service discovery, dependency testing, data consistency, centralized logs, tracing, metrics, API compatibility, security controls, and mature delivery operations. Every line of it lands on somebody's pager, which is where this choice turns into the on-call arithmetic that decides your evenings.

The microservices invoice

What you buy

  • independent deployment per service
  • independent scaling per service
  • independent reliability and ownership profiles

What it bills you

  • service discovery
  • cross-service dependency testing
  • data consistency across service-owned stores
  • centralized logging
  • distributed tracing
  • metrics
  • API compatibility and versioning
  • security controls between services
  • mature delivery operations
Three lines of credit against nine of debit. The right column is not optional once you cross the network boundary.

A modular monolith keeps one collective deployment and in-process calls. Microservice calls cross a network boundary, and service-owned data turns cross-service queries and transactions into design problems you have to solve on purpose.

CriterionModular monolithMicroservices
DeploymentApplication deploys as one unitServices deploy independently
CallsIn-processAcross network protocols
ScalingApplication scales as a unitServices can scale independently
DataIn-process transactions remain availableEach service owns data; cross-service work needs coordination
OperationsOne application releaseDiscovery, compatibility, tracing, metrics, and separate delivery operations

Stay in the middle column while deployment and scaling can stay application-wide. Move right when independent release, scaling, reliability, or ownership pays for everything in the right column.

The distributed monolith: all the costs, none of the autonomy

A distributed monolith pays the network and coordination bill while routine changes still cross service boundaries. Separately running components, coordinated releases, shared data changes, chatty cross-process calls. The autonomy test is whether services can be developed and deployed independently. The costs show up either way: network and data coordination, plus versioning and observability work.

one routine change

  1. deploy service A
  2. deploy service B
  3. coordinate shared schema
  4. trace calls across both services
One routine change inside a distributed monolith.

Same change, more steps, still no independent deployment. Keep the boundary inside one process until the proposed service can own its release, interface, data, and operating profile — including the rota that answers for it at 3am.

The call boundary is where the semantics change. Service Weaver demonstrates it cleanly: Go component calls execute locally in one process, then become RPCs once you place them separately, and now you are making decisions about retries, partial execution, concurrency, replicas, and persistence. Same code, brand new category of problem.

Extract from the edge, not the middle

Start with an edge module that has few dependencies and genuinely needs a different release, scaling, or reliability profile. Microsoft's e-commerce decomposition starts with notification, then points the monolith's notification call at the external service.

Service extraction from a monolith: interconnected teal modules inside one application boundary, with a single amber notification service outside on one call path.
Service extraction from a monolith: interconnected teal modules inside one application boundary, with a single amber notification service outside on one call path.

The extracted service owns its public interface and its data. Service-owned data makes cross-service queries and transactions problems that need explicit designs. If extraction only changes which process the code runs in, put it back. You bought network failure handling and got no independence with it.

Extraction test

  • its own release — ships without coordinating with the rest
  • its own scaling profile — scales on its own demand curve
  • its own public API — a stable interface others code against
  • its own data — no shared tables, no shared transactions

All of themExtract it. It can carry a service.

Any one missingIt is a module. Leave it in the application.

No request rate, revenue figure, repository size, or headcount appears anywhere in this test.

There is no magic threshold here. Not requests per second, not revenue, not repository size, not module count, not headcount. The test is whether the thing can own its deployment, scaling, reliability, API, and data. If it cannot, it is a module. Leave it in the application.