status
this chapter is in active development
expect live edits and rapid iteration (except for when i am really busy with other stuff) while this material is written.
service decomposition
what to split, why, and what it costs you.
status
this chapter is in active development
expect live edits and rapid iteration (except for when i am really busy with other stuff) while this material is written.
what to split, why, and what it costs you.
service decomposition is the act of taking what was a single deployable unit and breaking it into multiple services that communicate over a network. it solves specific problems. it creates specific new ones. understanding both sides determines whether the split is worth making.
the business case for service decomposition is almost always one of three things:
independent deployability. teams that need to ship independently without coordinating with every other team that touches the codebase. a change to the billing service should not require coordinating deployment with the search service.
independent scaling. components with genuinely different resource requirements. a video transcoding service needs GPU instances; the user profile service needs none. coarse-grained scaling wastes resources when requirements diverge sharply.
organizational clarity. at a certain team size, ownership gets ambiguous in a shared codebase. a service boundary forces a clear ownership conversation: this team builds this service, owns its API contract, and is responsible for its operation.
these are legitimate reasons. "we want to use microservices" is not a reason, it is a description of the outcome.
the benefits of decomposition are real:
none of this is free:
network latency and failures. every service-to-service call crosses the network. it is slower than a function call, can fail, can be slow, and requires retry logic, timeouts, and circuit breakers to handle correctly. the distributed systems failure modes from the previous chapter now apply to every inter-service call.
distributed transactions. if your monolith relied on a database transaction to ensure consistency across multiple operations, that transaction no longer works across service boundaries. you need to choose between 2PC (two-phase commit, complex and slow), saga patterns (compensating transactions, complex and error-prone), or redesigning the operation to not need cross-service atomicity.
operational complexity. every new service is a new deployment pipeline, a new monitoring configuration, a new on-call rotation scope. you need service discovery (how does service A find service B?), load balancing, distributed tracing, and centralized logging. the overhead per service is real.
testing complexity. integration testing now requires multiple running services. local development requires either running all dependencies or mocking them. contract testing (ensuring the API you consume matches what the producer provides) requires tooling that a monolith does not.
data ownership gets hard. if service A and service B both need user data, where does it live? each service owning its own data model (the microservices ideal) means synchronization is needed. a shared database violates service independence. this tension does not resolve cleanly.
the "strangler fig" pattern is useful: instead of rewriting the monolith, incrementally extract services at the seams. identify a piece of functionality with clear boundaries, an API that can be defined, and a team that will own it. extract it. leave the rest.
characteristics of a good service boundary:
bad service boundaries:
the operational investment in a distributed system, tooling, observability, deployment infrastructure, happens immediately. the benefit (independent team velocity) accrues over time as the teams grow and the feature surface expands.
for a small team with a limited codebase, the overhead of maintaining a distributed system often exceeds the benefit. for a large team with a sprawling codebase, the coordination overhead of a monolith often exceeds the operational overhead of services.
this is why starting with a monolith and decomposing at the seams is often the right answer: you defer the operational investment until you can see the specific problems that decomposition solves, then solve those specific problems.