A practical explanation of why service decomposition remains difficult even for experienced teams, including hidden dependencies, ambiguous language, legacy systems, and organizational compromise.
Decomposition is hard because systems reveal their true shape only after teams start changing them under real pressure. On a whiteboard, many boundaries look clean. In production, the same system contains legacy tables, reporting jobs, undocumented dependencies, overloaded business terms, and workflow edge cases that cross the neat lines the diagram implied. That is why good decomposition is an investigative discipline rather than a one-time modeling exercise.
Experienced teams do not struggle because they lack theory. They struggle because the system contains several truths at once. One capability looks isolated in the codebase but depends on hidden reporting queries. One domain term sounds stable but means different things to billing, identity, and support. One service appears independent until the first incident reveals that three other teams still share its deployment assumptions.
The main sources of decomposition difficulty usually look like this:
flowchart TD
A["Ambiguous business language"] --> E["Unstable boundaries"]
B["Hidden dependencies"] --> E
C["Legacy data and workflows"] --> E
D["Organizational history"] --> E
E --> F["Iteration, revision, and review needed"]
A system rarely keeps the same priorities for long. New pricing models, new integration partners, new compliance expectations, and new internal ownership models all change what a good boundary looks like. A design that was reasonable when one team owned everything can become weak when several teams need local release control or when one workflow now carries stricter reliability requirements.
This means decomposition should be revisited as the business changes. Stable boundaries matter, but stability does not mean refusing to adapt when the reasons for separation have changed.
Hidden dependencies are one of the main reasons naive decomposition fails. Common examples include:
These dependencies matter because they are real coupling even when they are absent from the service diagram. Teams that ignore them often create “independent” services that break as soon as the first schema change or workflow refactor lands.
Terms like customer, account, order, and subscription sound straightforward until teams discover they mean different things in different contexts. Support may use account to mean a tenant. Identity may use it to mean a user principal. Billing may use it to mean a contractual relationship. If those meanings are not separated, the architecture drifts toward shared-everything services and confused APIs.
One simple way to make this visible is to capture domain terms with contextual meaning before drawing the final boundary.
1term: account
2contexts:
3 identity: login principal and authentication credentials
4 billing: contract and payment relationship
5 tenant_admin: organization workspace membership
6risk:
7 - false assumption that one service should own all meanings
This kind of note is not bureaucratic overhead. It is one of the cheapest ways to surface model confusion before it reaches code and schema design.
Legacy platforms make decomposition hard because they often encode business behavior in places teams do not fully control anymore: triggers, batch jobs, integration brokers, admin scripts, or hand-maintained reports. The existing system may have survived precisely because it blurred concerns and allowed local shortcuts.
That is why decomposition work often starts with discovery:
The work is partly technical and partly organizational. Systems often reflect the reorgs, mergers, and vendor accommodations that shaped them.
Because the truth emerges gradually, decomposition should be treated as iterative. A team might begin with a modular boundary, observe whether it reduces coordination, and only later decide whether that boundary is strong enough to distribute. That is better discipline than pretending the first cut must be final.
A simple discovery worksheet can make the iteration more concrete:
1candidate_boundary: fulfillment
2business_terms_clear: mostly
3hidden_dependencies_found:
4 - nightly-erp-export
5 - warehouse-read-report
6authoritative_data_clear: partial
7owning_team_clear: yes
8recommended_next_step: keep modular, remove hidden reads, review again
This kind of note supports better design because it turns vague discomfort into explicit review criteria.
A team wants to split a monolith by drawing service lines around the current package structure. They have not traced reporting jobs, external exports, or shared libraries, and they assume ambiguous terms like account will “sort themselves out later.” What is the main problem with this plan?
The stronger answer is that the plan treats decomposition as packaging rather than discovery. It risks freezing hidden dependencies and language confusion into distributed form, which usually makes correction harder rather than easier.