Modular monolith
Best when deployment simplicity and internal modularity solve the main problem.
A modular monolith can be a strong architecture when one deployment unit is acceptable and internal boundaries are clear. UMA addresses a different pressure: how to preserve one behavior when execution needs to cross runtime boundaries.
A modular monolith organizes code inside one deployable system, with clear internal boundaries between logical modules. UMA organizes portable behavior so it can remain coherent across multiple execution contexts. browser, edge, cloud, workflow, and beyond. These are different answers to different pressures.
The two ideas can coexist. A modular monolith may host UMA-style capabilities without abandoning its single deployment unit. UMA can also help identify which behaviors should leave the monolith first, giving teams a principled extraction path rather than a speculative decomposition.
The distinction matters most when a team is deciding whether the complexity of a portable boundary is warranted. A modular monolith is often the right answer. and the UMA model is explicit about when it is not needed.
Modular monoliths improve internal structure by making module boundaries explicit. enforcing that one part of the system does not reach into another's internals, that interfaces are intentional, and that dependencies flow in one direction. That discipline produces systems that are easier to reason about, test, and evolve within a single deployment.
UMA adds a stronger question: can this behavior survive outside the original host without being rewritten? A module with clean internal boundaries may still rely on shared database access, a common ORM, framework middleware, or in-process event buses. Those assumptions are invisible inside the monolith but become obstacles when the capability needs to execute elsewhere.
Internal modularity is not the same as runtime portability. A well-modularized system is easier to extract from, but the extraction still requires designing an explicit contract, removing host assumptions, and proving the behavior in a genuinely different execution context. UMA names that work and treats it as architecture.
The modular monolith usually keeps deployment simple by shipping one unit. That is a genuine advantage: one build, one release, one set of operational concerns. For a team with a single stable runtime and no requirement for behavior to appear elsewhere, that simplicity is worth protecting.
UMA accepts that some behavior may need to execute in different places and makes the surrounding runtime decisions explicit. This is not a claim that distributed deployment is always better. distributing for its own sake introduces coordination overhead with no return. It is a claim that runtime diversity needs a model when it appears, and that model should be designed rather than improvised under pressure.
The question is not monolith versus services. The question is whether any behavior in the system has begun to appear in two different execution contexts. If the answer is no, the monolith should stay simple. If the answer is yes, UMA's boundary model gives that duplication a principled resolution.
A module inside a monolith often assumes the host process, shared memory, local libraries, a common framework, and database access through a shared connection pool. Those assumptions are efficient and appropriate inside one deployment. They become coupling when the behavior needs to execute somewhere else. and that coupling usually only becomes visible when the extraction is already urgent.
A Universal Microservice has to make more of its boundary explicit so the behavior can be governed elsewhere. The contract defines what it accepts and what it returns. The adapter translates between the contract and the host environment. The core logic is free of assumptions about where it is running.
That extra discipline is useful only when the behavior needs that freedom. A module that will never leave the monolith does not need a UMA-style contract. and designing one for it would be unnecessary overhead. The skill is knowing which behaviors have already begun to move, or soon will, and applying the model there first.
A modular monolith can evolve into services when pressure justifies it. when a module needs to scale independently, when a team boundary demands clearer ownership, or when a capability is being duplicated into a second runtime. UMA gives teams a way to make that extraction deliberate rather than reactive.
The evolution path is: identify the behavior that is already under pressure to appear in a second context, design an explicit contract for it, remove the host assumptions, and prove the behavior is equivalent in both environments. That proof is the UMA adoption entry point. The monolith can continue hosting everything else while the extracted capability validates the approach.
This means UMA adoption does not require decomposing the entire system. One extracted capability is a meaningful starting point. and the extraction evidence either confirms that the approach is worth scaling or reveals that the pressure was not as real as it seemed.
A modular monolith is the right architecture when a single team owns the system, runtime diversity is low, deployment simplicity is a priority, and the product is still in early discovery. Those conditions describe a large number of healthy systems. Adding UMA complexity to a system that does not have a portability problem is waste, not engineering discipline.
An early-stage product that is still finding its domain model should not invest in runtime contracts. The module boundaries are not yet stable enough to extract. UMA assumes the business behavior is sufficiently understood that a contract can be designed and maintained, that understanding takes time to develop, and the monolith is often the right environment to develop it in.
The honest advice: start with a modular monolith, keep the module boundaries clean, and watch for the signal that one capability is being requested in a second runtime. That signal is the UMA trigger, not a general preference for architectural sophistication.
The extraction signal is behavioral duplication under runtime pressure. When the same business rule is being rewritten for a second execution context (a mobile app, an edge node, a partner integration) the duplication is not a development problem, it is an architectural one. Extracting that behavior behind an explicit contract stops the divergence before it compounds.
A module that is the most-changed part across releases is often a signal that its boundary is not stable. but also that it is the most important capability in the system. That importance is an argument for giving it a cleaner boundary and more deliberate governance, not for leaving it embedded in the monolith where its changes ripple into adjacent modules.
Extract when a capability needs to prove parity across environments, when a second team needs to consume it independently, or when the business cannot tolerate behavioral drift between surfaces. Those are concrete pressures with concrete costs. and the UMA extraction is a response to real pressure, not a preemptive architectural preference.
The modular monolith can be the starting point for UMA adoption, not an obstacle to it. The existing module with the clearest boundary and the most external pressure is the extraction candidate. Identify it, design a contract for it, and prove it runs identically in both contexts. That is the migration entry point.
The extraction should be conservative: the contract should reflect what the behavior already does, not what someone hopes it could do. The adapter layer should handle the new runtime without touching the business logic. The proof should be automated and repeatable: a test suite that runs the same behavior in both the monolith context and the extracted context and asserts equivalence.
Once the first extraction is proven, the team has a template. Subsequent extractions follow the same path. The monolith continues to host everything else while the UMA boundary earns its design cost through demonstrated portability. That is a migration, not a rewrite.
Best when deployment simplicity and internal modularity solve the main problem.
Best when behavior must cross browser, edge, cloud, workflow, or AI-assisted paths.
Both models value explicit boundaries over accidental coupling.
UMA treats runtime movement as a first-class architectural concern.
This page compares the architectural lenses. The book expands the decision model for when to keep behavior inside one system and when to extract it into a portable capability.