In this section: Comparisons and Tradeoffs
Why Software Architecture Keeps Fragmenting
Distributed systems fragment for a structural reason, not because of bad engineering decisions. The same business rule ends up rewritten in multiple places because the tools that enable portability (frameworks, runtimes, queues) each impose their own coupling. Understanding the mechanism makes the fix legible.
The pattern every team recognizes
A pricing rule lives in the backend. Then the frontend needs it to keep the UI responsive (so it gets reimplemented in JavaScript. Then the edge layer needs it to cut latency) so it gets reimplemented again, this time in a worker. Then an orchestration workflow needs it (another copy. Then an AI-assisted path needs it inline) another copy.
None of these decisions are wrong in isolation. Each solves a real performance or availability problem. The system as a whole is wrong because there are now five versions of the same rule, each slightly different, each drifting on its own schedule, and no structural mechanism to detect when they diverge.
This is not a maintenance problem. It is an architectural problem. The structure of the system creates the incentive to duplicate, and then offers no way to enforce consistency after the duplication happens.
Why existing boundaries do not stop it
Microservices address team autonomy and independent deployment. They do not address behavioral portability. A microservice that handles the pricing rule on the backend cannot be moved to the edge without rewriting it. because it is coupled to the host: the framework, the HTTP stack, the database client, the configuration system. The service boundary marks where one team's code ends. It does not guarantee that the behavior inside can cross a runtime boundary.
Serverless reduces operational burden but increases this coupling. When a Lambda function or a Cloudflare Worker is the unit of deployment, vendor-specific invocation contracts, environment variable injection patterns, and SDK shapes all become part of the logic. Moving the behavior to a different host means untangling those assumptions, not just redeploying a module.
Container-based architectures manage packaging and scheduling well. They do not prevent behavioral drift. The container image is an operational artifact. The business logic inside can still be rewritten differently per environment, and the container boundary provides no mechanism to detect or prevent that.
The structural cause
Fragmentation happens because most service boundaries are defined at the deployment unit. not at the behavioral contract. When the boundary is the process or the container, it carries everything: the business rule, the I/O wiring, the framework assumptions, the runtime-specific error handling. None of that is separable after the fact.
When you add a new execution surface (edge, mobile offline, browser, AI agent) you cannot reuse the existing service. You reuse the logic only by reimplementing it. The only choice is whether to do it explicitly (accepting the copy) or implicitly (letting the two implementations drift apart silently).
The fragmentation is deterministic. It is the natural output of a system where the deployment unit and the behavioral unit are the same thing. Every new runtime becomes a new fragmentation event.
What changes when the boundary moves inward
UMA separates the behavioral unit from the deployment unit. The portable service (compiled as a WASM module with an explicit contract) is the thing that does not change. The runtime layer (adapters, placement decisions, trust policy, governance) is the thing that varies by environment.
When you add a new execution surface, you add an adapter and configure placement. You do not reimplement the business rule. The behavior stays in one place. The runtime diversity that previously triggered duplication becomes a routing and adapter problem instead.
This requires upfront structure: contracts, descriptors, explicit boundaries. The payoff is that fragmentation stops being the automatic outcome of adding execution surfaces. It becomes something that requires a deliberate violation, explicitly choosing to implement the rule again rather than routing to the existing portable service.
When this matters and when it does not
If your system has one runtime and one team, behavioral fragmentation is not a current pressure. The overhead UMA introduces (WASM compilation, contract authoring, runtime infrastructure) produces no return in a single-context system. A well-structured monolith is the right answer until runtime diversity is real and growing.
The question is whether runtime diversity is a permanent feature of your system or a temporary phase. If you are adding browser execution, edge nodes, workflow orchestration, and AI-assisted paths because your product requires them (not as experiments) then the fragmentation cost is not future risk. It is current debt accumulating with each new surface.
UMA earns its overhead when behavior must cross runtime boundaries and the crossing is structural, not occasional. That is the condition the model was designed for.