One behavior
Choose a rule or normalization path that is repeated across environments: a validation function called in three places, a pricing rule embedded in two services, a flag evaluator reimplemented for each deployment target.
UMA does not require a rewrite. It can start with one portable behavior, one contract, or one runtime-governed boundary inside a system that still has legacy services and ordinary deployment infrastructure.
The practical first step is not replacing your platform. It is finding a behavior that is duplicated across services, environments, or teams (a validation rule, a pricing calculation, a feature flag evaluator) and moving that behavior behind an explicit contract and a runtime-evaluated boundary.
The rest of the stack does not change. The legacy services still run. The existing deployment infrastructure still operates. The only thing that changes is that one behavior, previously embedded in the host, is now expressed as a portable service with a declared contract, wrapped by a runtime that validates, binds adapters, and records lifecycle evidence. Adapters let existing hosts participate without modification: the host that was previously calling an internal function now calls through an adapter that the runtime selects.
This is incremental adoption in the most literal sense: one boundary moves, the rest stays.
Choose a rule or normalization path that is repeated across environments: a validation function called in three places, a pricing rule embedded in two services, a flag evaluator reimplemented for each deployment target.
Define what the behavior accepts, returns, emits, and depends on. The contract is the source of truth for what the portable core does. not documentation, not convention, but a machine-readable descriptor that the runtime enforces.
Add validation, adapter binding, and lifecycle evidence around the portable core. The runtime selects the right adapter for the host, validates input before side effects happen, and records what ran and why.
Keep legacy internals where needed while governing the boundary. The first milestone is not eliminating legacy code: it is making one boundary explicit, which creates the proof that the pattern works in your context.
Most teams find one of three behaviors easiest to move first, because each is already isolated in intent even if not in implementation:
A shared validation rule. Input validation logic that is duplicated across services, with slight variations that cause inconsistencies at the edges. The rule has clear inputs and outputs, no side effects, and its inconsistency is already causing bugs. Expressing it as a portable service with a single contract eliminates the drift immediately. The existing callers each get a thin adapter. the rule itself becomes testable in isolation against the contract.
A pricing engine or rate calculator. Business logic that must produce the same result regardless of whether it runs on a server, in a browser, or at an edge node. This is the canonical UMA target: behavior that cannot afford inconsistency across contexts. Wrapping it as a portable service with a runtime-selected adapter for each deployment target makes the equivalence provable rather than assumed. The Chapter 6 portability lab demonstrates this pattern directly: one contract, two execution targets (native and WASI), parity checked from emitted events.
A feature flag evaluator. Evaluation logic that determines which code paths run based on rules, user attributes, or rollout percentages. This is the entry point used in Chapter 4 of the book. deliberately chosen because it is small enough to implement completely, complex enough to have meaningful contract semantics (country matching, percentage rollout, default fallback), and important enough that inconsistency has visible consequences. The WASI CLI built in Chapter 4 can be called from any host with a one-line adapter without changing the evaluator.
The first milestone in incremental UMA adoption is not a complete platform migration. It is a before-and-after proof: one behavior that was previously embedded in a host, now running as a portable service behind a contract, with a runtime that produces lifecycle evidence, and a parity check that confirms the output matches the previous implementation.
That proof serves two purposes. Technically, it validates that the adapter model works for your specific host and deployment context, that the runtime can select the right adapter, that the contract semantics match what the host needs, and that the portable core passes the same cases the legacy implementation handled. Organizationally, it demonstrates that the approach is not speculative. The smoke script runs, the CI passes, and the output is inspectable.
From that first milestone, adoption proceeds incrementally: each subsequent capability that crosses runtime boundaries is a candidate for the same treatment. The system does not need to commit to a full rewrite. It accumulates portable boundaries at the rate that makes sense for the team, while the legacy surfaces continue to function through adapters.
Incremental adoption introduces a risk: if each team that adopts UMA independently develops its own contract conventions, its own adapter patterns, and its own runtime configuration, the result is a new form of fragmentation. not the fragmentation of duplicated logic, but the fragmentation of incompatible portable boundaries.
The antidote is shared contract definitions and a consistent runtime governance model. When capability descriptors are defined in a shared location and referenced by multiple services, the runtime can enforce compatibility across the system rather than just within each service. Chapter 11 in the book covers this directly: how to structure adoption so that each new portable boundary integrates with the existing governance model rather than creating a parallel one.
Chapters 5 and 6 show the first runtime and portability steps: the smallest complete examples of an incremental boundary move. Chapter 11 addresses the governance model that prevents fragmentation as adoption grows.