Decomposition and the useful module
Complex engineered systems are organized into hierarchies whose elements interact strongly within groups and weakly across them. Simon (1962) described this near-decomposable structure as a recurring architecture of complexity: the reason large systems are tractable at all is that most interaction stays local to a subsystem, and only a thin traffic of interaction crosses between them.
Decomposition is what turns that observation into a design move. Cutting a system into parts can support parallel work, focused verification, replaceability, maintenance, reuse, change isolation, and fault containment (Parnas, 1972; National Aeronautics and Space Administration [NASA], 2016). None of those outcomes is automatic. Each depends on the criterion used to divide the system and on the quality of the interfaces that result. A module, in this sense, is a bounded part of a system with a coherent responsibility and a defined interface to the rest of the system (Parnas, 1972) — not simply a smaller piece of code or hardware.
| Clear responsibility | A reason for the elements to belong together, not an accident of where they were written. |
|---|---|
| Internal cohesion | Related work and related decisions stay close inside the boundary. |
| Limited dependency | Other modules need little knowledge of what happens inside. |
| Stable interface | Externally visible commitments change less often than the implementation behind them. |
| Replaceability | An alternative can satisfy the same contract. |
| Testability | Behaviour can be examined at the boundary. |
These qualities pull against one another. A boundary drawn to maximise reuse tends to need a more general interface; a specialised module can be faster or simpler but harder to replace. Choosing where to cut is therefore a judgement about which of these properties the system most needs.
Arbitrary subdivision can create many coordination points without creating coherent responsibilities or stable boundaries. More pieces is not the same as more modularity; a decomposition that ignores the criterion for the cut can leave a system harder to reason about than the monolith it replaced (Parnas, 1972).
Cohesion, coupling, and change propagation
A change is the most practical test of an architecture: when one decision has to change, how far do its consequences have to travel? Two properties predict the answer. Cohesion describes how strongly a module’s internal elements belong together around a single focused responsibility. Coupling describes the dependency between modules — what one must know about, receive from, or coordinate with another (Parnas, 1972).
High cohesion and low coupling are the same idea seen from two sides. When related decisions live together and unrelated ones live apart, a change tends to stay inside one boundary. When internal data is shared, call-order assumptions are hidden, and dependencies run in every direction, a single local change reaches several owners at once.
| Aspect | Tangled | Well bounded |
|---|---|---|
| State | Shared, mutable internals | State owned by one module |
| Assumptions | Hidden call-order assumptions | Explicit contract |
| Dependencies | Many, in every direction | Few and necessary |
| A local change | Reaches several owners | Stops at the boundary |
The failure mode is change propagation. When editing a payment provider forces edits in checkout, email, analytics, and inventory, the symptom is not badly written lines — it is a decision that should have stayed local leaking across boundaries.
Low coupling is not zero coupling. A system does its work through relationships between its parts; a module with no dependencies contributes nothing. The goal is deliberate, comprehensible dependency — the minimum a module needs and no more — not isolation.
Interfaces as contracts
An interface is the complete agreement two elements need in order to interact — not merely the connector drawn between them on a diagram. It defines what crosses the boundary and the conditions under which the exchange is expected to work (NASA, 2016).
| Data | Inputs and outputs, including units and data formats. |
|---|---|
| Physical | Fit, power, capacity, and environmental limits, where relevant. |
| Behaviour | Sequence, state, and timing. |
| Communication | Protocol and error handling. |
| Governance | Assumptions, ownership, security, and version compatibility. |
This is why two components can fit physically, or connect technically, and still be incompatible. If their timing, their meaning for a shared value, their allowable states, their versions, or their assumptions about errors differ, the connector carries data the other side cannot correctly use. A complete interface names all of it.
A connector is not a complete interface. A cable that mates, or an endpoint that responds, says nothing about timing, meaning, allowable state, error behaviour, or version. Treating the physical or technical connection as the whole agreement is one of the most common sources of integration failure (NASA, 2016).
Information hiding and abstraction
A strong boundary does two things at once: it reveals what another module needs in order to use it safely, and it conceals the design decisions that module should not depend on. Information hiding assigns the decisions most likely to change to individual modules and keeps them behind stable interfaces (Parnas, 1972). When a hidden decision later changes, the change stops at the boundary.
| Information hiding | Not the same as |
|---|---|
| Conceals replaceable implementation decisions | Secrecy from authorised stakeholders |
| Reduces dependence on internal representation | Withholding hazards or constraints |
| Still exposes behaviour needed for safe use | An excuse for undocumented behaviour |
Two mechanisms support information hiding and are often confused with it. Encapsulation is a means of enforcing a boundary; abstraction is a simplified view of something for a particular purpose. Both serve information hiding, but neither is a synonym for it (Parnas, 1972).
Hiding a likely-to-change decision protects the rest of the system from it. Hiding a hazard, a limit, or an undocumented behaviour endangers it. The two are opposite intentions that happen to share a word, and conflating them is how “information hiding” gets misused to excuse missing documentation.
Modularity across domains
The same reasoning appears wherever systems are built, though the constraints differ by domain. A replaceable battery pack has to meet mechanical, electrical, thermal, and safety limits at its interface. A software service exposes operations and data contracts while owning its internal storage decisions. A manufacturing cell groups equipment and work around a production responsibility. A team boundary assigns decision ownership but also creates a coordination interface. And a reusable UI component packages a set of visual and behavioural decisions (NASA, 2016; Parnas, 1972).
Software gives the most familiar examples. JavaScript modules use explicit export and import statements to declare exactly what a module makes available and what it consumes; browser modules get their own scope, run in strict mode, and execute once per module graph (Mozilla Developer Network, 2026). Atomic Design organises interface work as atoms, molecules, organisms, templates, and pages — a useful design-system methodology for composition and consistency, but a component hierarchy, not a complete system architecture or a universal decomposition method (Frost, 2016; International Organization for Standardization, 2022).
Event mediation is a structural choice inside this space. In direct communication a sender calls receivers it knows by name. With an event bus, a sender publishes an event contract and subscribers react without the sender naming them. That removes some direct dependencies and can isolate a change to one subscriber — but it introduces new operational contracts of its own: delivery, ordering, ownership, observability, and schema evolution all become things someone has to design and govern.
| Broader concept | Specific application |
|---|---|
| Module — a contextual system boundary | JavaScript module: a language and runtime unit (Mozilla Developer Network, 2026) |
| Architecture — elements, relationships, and principles | Design system: reusable UI decisions and components (International Organization for Standardization, 2022) |
| Interface — a cross-boundary contract | Atomic Design: a component-hierarchy methodology (Frost, 2016) |
A software function is not a physical replaceable unit, and a team boundary is not an API. The modular principle transfers across domains; the constraints do not. Use the shared idea without pretending a line of code and a bolted-on subassembly obey the same rules.
Tradeoffs and a worked decomposition
Every boundary that isolates change also adds an interface that has to be designed, integrated, governed, tested, and evolved. Modularity is therefore a trade study, not a score to maximise (Parnas, 1972; Simon, 1962).
Take a worked case: a smart-home security service that must detect conditions locally, apply rules, notify people, and stay useful during an internet outage. A useful decomposition works through three moves.
State what the system must do: detect, decide, notify, and allow authorised control. Purpose comes before any boundary.
Group sensing, policy, identity, notification, UX, and audit; mark what is likely to change — vendors, channels, rules, and storage.
Put each likely change behind an interface, and specify timing, offline behaviour, error handling, ownership, and versioning at the boundary.
| Interface overhead | Translation and contract cost at every seam. |
|---|---|
| Performance | Latency crossing boundaries. |
| Integration | Version compatibility across independently changing parts. |
| Fragmentation | Distributed ownership and coordination when parts multiply. |
| Boundary selection | Difficulty choosing stable cut lines early. |
| Reuse tension | General reuse competing with specialised optimisation. |
| Operations | Complexity created by independent deployment. |
A useful design does not maximise the number of modules. It protects the decisions most likely to change, supports the operating model the system actually needs, and keeps the cost of each interface proportionate to the benefit it buys. When a team splits one workflow into thirty tiny services and then spends most of its time coordinating releases, the fragmentation cost has overwhelmed the isolation benefit.
A quick test of a decomposition: trace a likely change through it. If replacing a notification vendor forces edits in the sensor logic, the boundaries are exposing avoidable coupling. A decision that should have been local has leaked, and the cut lines need to move.
References (APA 7th edition)
- Frost, B. (2016). Atomic design. Brad Frost Web. atomicdesign.bradfrost.com
- International Organization for Standardization, International Electrotechnical Commission, & Institute of Electrical and Electronics Engineers. (2022). Software, systems and enterprise — Architecture description (ISO/IEC/IEEE 42010:2022). iso.org/standard/74393.html
- Mozilla Developer Network contributors. (2026). JavaScript modules. MDN Web Docs. Mozilla. developer.mozilla.org
- National Aeronautics and Space Administration. (2016). NASA systems engineering handbook (Rev. 2, NASA/SP-2016-6105 Rev2). nasa.gov
- Parnas, D. L. (1972). On the criteria to be used in decomposing systems into modules. Communications of the ACM, 15(12), 1053–1058. doi.org/10.1145/361598.361623
- Simon, H. A. (1962). The architecture of complexity. Proceedings of the American Philosophical Society, 106(6), 467–482.