practice

Module Dependency Enforcement

Automated checks that prevent one module from importing another's internals, giving a monolith the boundary discipline of separate services.

The modular monolith's premise is that boundaries and distribution are separate decisions. You can have well-defined modules with explicit interfaces in one deployable, and gain most of the structural benefit without network calls, partial failure or operational multiplication.

What makes it work is enforcement, because module boundaries in a single codebase erode by default — any developer can import anything, and under deadline pressure they will.

The mechanisms, in ascending strength: language visibility (package-private, internal, module systems) where the language supports it; architecture tests in CI asserting the dependency graph — the most widely applicable option and cheap to add; build-tool module separation, so a forbidden import does not compile; and separate repositories or packages, which is the strongest and reintroduces some coordination cost.

The rules worth enforcing: modules communicate only through published interfaces; no shared database tables between modules, each owning its own schema; and cross-module data obtained through the interface, not by joining across tables.

Done properly, extraction to a service later becomes a deployment change rather than a redesign — which is the strategic argument for starting here. Boundaries are cheap to move inside one deployable and expensive to move once distributed.