Git, Linux & DevOps
Microservices Architecture
A microservices architecture breaks an application into a set of smaller services, each responsible for one area of functionality, each with its own codebase, and each deployed independently, usually communicating over the network through APIs or messaging. It's the alternative to a monolith, where all functionality ships as one deployable unit. The appeal is independent deployment and scaling of each piece; the cost is that problems which used to be a function call are now a network call, with everything that implies.
Why it matters
- It lets teams deploy independently
- A team owning the payments service can ship a change without coordinating a release with the team owning search, as long as the API between them doesn't break.
- It allows scaling only what needs it
- A service under heavy load can be scaled up on its own, instead of scaling an entire monolith to handle load that's concentrated in one part of it.
- It changes failure from a single point to a distributed one
- One service crashing doesn't necessarily take down the whole application the way an unhandled exception in a monolith can - but only if the rest of the system is built to handle a dependency being unavailable.
- It's common enough that its vocabulary shows up in most backend job descriptions
- Even teams running a monolith today often describe their target architecture, or their reasons for staying monolithic, in these terms.
Why split a monolith at all
The usual motivations are independent deployability (one team's release doesn't require coordinating with every other team), clear ownership boundaries (a service maps to a team that owns it end to end), and the ability to scale one part of a system without scaling all of it. None of these matter much for a small team or a small system - the benefits mostly show up once an organization is large enough that coordinating releases across a single shared codebase becomes the bottleneck.
What used to be a function call is now a network call
Inside one process, calling a function is fast and close to guaranteed to either succeed or throw an exception you can catch. Calling another service over the network introduces latency, the possibility the call never arrives or the response never comes back, and a whole category of partial failure that doesn't exist in a function call - the request may have been received and processed even though the response was lost.
# order-service calling inventory-service over HTTP
curl --max-time 2 \
-X POST https://inventory.internal/api/reservations \
-H "Content-Type: application/json" \
-d '{"sku": "ABC-123", "quantity": 2}'
# if inventory-service is slow or down, order-service must decide:
# retry, fail the order, or fall back to a cached stock estimateDefining service boundaries
The useful boundary is usually a business capability - orders, payments, inventory - owned end to end by one team, including its own data. A common mistake is splitting by technical layer instead (a 'validation service', a 'database service'), which recreates a monolith's tight coupling, just now over a slower, less reliable network. Each service should own its own data; two services reading and writing the same tables directly can't really be deployed independently, whatever the deployment diagrams suggest.
Mistakes people make here
- Splitting services by technical layer instead of business capability
- a 'database service' or 'validation service' that every other service must call for basic operations recreates a monolith's coupling, just now over the network and slower.
- Sharing one database across services
- if two services read and write the same tables directly, they can't actually be deployed or changed independently - a schema change for one risks breaking the other, which is the coupling microservices were meant to remove.
- Not planning for partial failure
- in a monolith, a bug is one process crashing; in microservices, one dependency being slow or down is a normal, expected condition that every caller needs a plan for, not an edge case.
- Adopting microservices before there's a team or scaling problem to justify the coordination overhead
- the operational cost - many deployments, many repositories, distributed debugging - is real and constant, while the benefits only pay off once a team or system is big enough to need them.
- Building chatty services that make many small synchronous calls to answer one request
- each hop adds latency and a chance of failure; a request that fans out to a dozen internal calls is slower and more fragile than the equivalent monolith call, and often signals the service boundaries need rethinking.
Strengths and trade-offs
Where it is strong
- Services can be deployed, scaled and released on independent schedules, which removes a lot of cross-team release coordination.
- A team can own a service end to end, choosing its own internal technology and release cadence within the agreed API.
- A failure can be contained to one service rather than taking down an entire application, if the rest of the system is built to tolerate it.
- Individual services stay small enough that a new engineer can understand one of them fully, even if the whole system is too large for anyone to hold in their head.
The trade-offs
- Distributed systems problems - network latency, partial failure, eventual consistency - now apply to what used to be simple function calls.
- Operational overhead multiplies: many services means many deployments, many logs, and real investment in monitoring and tracing to keep it debuggable.
- Data consistency across services is genuinely harder than a single database's transactions; maintaining correctness usually means new patterns instead of a simple transaction.
- Local development gets harder as the number of services grows, since running 'the whole system' on a laptop means running many services at once.
Who needs this
Backend and platform engineers at companies operating, or considering, more than a handful of services need this well. It's also worth understanding conceptually even at a company running a monolith, since 'should we split this out' is a recurring, real discussion.
Questions about microservices architecture
- Is microservices always better than a monolith?
- No - it's a trade of coordination overhead now for deployment flexibility later, and that trade only pays off past a certain size of team or system. Many successful companies run a monolith by choice for a long time, and some that adopted microservices early found the operational overhead outweighed the benefit until they were actually large enough to need it.
- How big should a microservice be?
- There's no fixed size in lines of code; the useful boundary is a business capability owned by one team, with its own data, that can be deployed independently of the others. Splitting purely by technical layer is the common mistake, because those pieces stay tightly coupled to everything that calls them.
- Do microservices need Kubernetes?
- Not strictly - they need some way to deploy, network and scale many independent services, and Kubernetes is a common answer to that once there are enough services to make manual management impractical. Smaller microservice systems run fine on simpler setups.
- What's the biggest hidden cost people underestimate?
- Observability and debugging. In a monolith, a stack trace usually tells you what went wrong. In microservices, a single user request can touch several services, so understanding a failure means tracing a request across all of them, which requires investment in logging, tracing and monitoring that a monolith doesn't need nearly as much.