Pakkit.net
← Back to blog

Engineering Practice

Files Are Not Modules

Splitting a monolith into separate files feels like modularity, but if they still share global state and ship as one deployable, you've just rearranged a monolith — real modularity is isolation of state, build, test, release, and ownership.

  • Engineering Practice
  • Architecture
  • Modularity
  • Refactoring

There’s a comforting illusion in a tidy file tree. One concern per file, neat folders, clear names — it looks modular, so we call it modular. But I’ve worked in systems where every logical concern lived in its own file and the whole thing was still a monolith in every way that mattered: shared global state, one deployable, edited and released together. Files are an organizing convenience. Modularity is something stronger, and confusing the two leads to architectures that have the appearance of separation with none of the benefits.

Separate files, shared everything

The system that taught me this was a big policy codebase split across many files — one for each flow, each integration, each method. Reasonable on the surface. But those files all shared global state, all lived in a single deployable unit, and all got released as one artifact. You could not change one concern without reasoning about its effect on every other, you could not test one in isolation, and you could not let different people own different parts cleanly. The file boundaries were real; the module boundaries didn’t exist. It was a monolith wearing a folder structure as a disguise.

Putting concerns in separate files separates the text. It doesn’t separate the system. Shared state and a single release glue it right back together.

What actually makes something a module

A module isn’t defined by having its own file. It’s defined by isolation across the dimensions that matter:

  • State — it owns its data; other parts can’t reach in and mutate its internals. Shared global state is the single biggest tell that “modules” are fiction.
  • Build — it can be built (or validated) on its own, not only as part of the whole.
  • Test — you can exercise it in isolation and trust the result without standing up everything else.
  • Release — it can ship on its own cadence; changing it doesn’t force a lockstep release of unrelated parts.
  • Ownership — a person or team can own it end to end without negotiating every change with everyone.

If a unit doesn’t have most of those, it’s a file, not a module — no matter how clean the directory looks. The real question isn’t “is this in its own file?” but “can I change, test, and ship this without dragging the rest along?”

The monolith’s real cost is coupled blast radius

Why does the distinction matter beyond pedantry? Because the thing you actually want from modularity is bounded blast radius. In the file-split monolith, a small edit to one flow could affect any other through shared state, so every change carried the risk surface of the whole system. You couldn’t reason locally. The rewrite that fixed it broke the system along genuine boundaries — each piece its own deployable with its own state, its own pipeline, its own owner — so a change to one could be understood, tested, and shipped without holding the entire thing in your head. That’s the payoff: not prettier files, but the ability to think about a part as a part.

Decomposition creates a recombination problem

One honest caveat: real modularity isn’t free. The moment you split a monolith into truly independent units, you’ve created a new job — putting them back together into something deployable and coherent. Independent pieces need a defined way to recombine, version together, and ship as a set. That integration work is real, and it’s the tax you pay for isolation. But it’s a better problem than the monolith’s: recombination is explicit and designable, where the monolith’s coupling is implicit and everywhere. I’ve written separately about refusing the monorepo-versus-polyrepo binary to get both independent lifecycles and one coherent checkout — that’s the recombination problem handled deliberately.

Test your “modules” honestly

So when someone (including me) claims a system is modular, I push on it with one question: can you change, test, and release one piece without the others? If the answer is “well, they all share this state” or “it all ships together,” then the files are organized but the system isn’t modular yet — and that’s fine to say out loud, because naming it is the first step to fixing it. Clean files are a good start and a poor finish. The boundary that counts is the one around state, build, test, release, and ownership — and it’s also what lets you eventually migrate a piece without touching its callers. If you’ve turned a file-split monolith into something genuinely modular, I’d love to hear how you drew the lines.