The Anti-Microservice Manifesto
A network boundary can isolate ownership, failure, or scale. Where a function call will do, that boundary adds cost without enough benefit.
A function call, then a network
Inside one process, a module can call another through an interface the compiler checks. Across a service boundary, the same interaction needs a message format, transport, discovery, timeouts, versioning, authentication, and a plan for partial failure.
That machinery can be necessary. It is never free. Much of it recreates properties that a local call already gets from the language and the process. The network adds delay and failure modes before it adds value.
How we got a distributed monolith
Microservices can solve organizational problems as well as technical ones. Their strongest benefit is often independent ownership. In a large shared codebase, teams can block one another's releases. Services answer this by giving each team its own code, deployment path, data, and schedule. That benefit can be real.
The technical cost arrives later. Interactions become harder to understand, debug, test, and operate as the graph grows. The mistake was turning a useful trade into a default.
Team autonomy needs clear ownership and interfaces. It does not always need a network.
Separate services can remain tightly coupled in behavior and release. One service needs another, which needs a third. A request crosses all of them and fails when any required link fails. The services may be independently deployable in theory. In practice, an interface change still requires a coordinated release. The system pays for network latency and partial failure without gaining independent change.
Diagnosing one user-visible error can mean reconstructing a path across processes and queues. Tracing and central logs rebuild what a local call stack would have shown directly. Segment described why it moved back toward one codebase when the operating cost outweighed the benefit.
Start local
We begin with a well-structured application in one process. Modules have clear interfaces and owners. Internal communication stays local and is checked at build time. Deployment moves the application as one unit.
Design still lives in modules and types, not in one file. Those remain language and ownership boundaries until a runtime need makes the network worth its cost. If one component later needs a different owner, location, security boundary, or scaling plan, it can be separated with evidence in hand.
Process boundaries can contain crashes and resource use. Independent deployment can matter when components have different owners or risk. Some workloads genuinely need separate scaling or geography. Those are reasons to split a system. Fashion is not.
Large organizations adopted microservices partly to create organizational autonomy. Applying the same topology to a small team can import the technical costs without gaining the organizational benefit.
Put a network boundary where it is required. Not where a function call will do.