ApplicationIntermediate

How do you enforce boundaries in a modular monolith?

“A modular monolith is only modular if the boundaries hold. What mechanisms actually keep the Order module from reaching into the Payment module's tables, and how does that prepare a later extraction?”

What this tests

  • Concrete enforcement mechanisms, not just intent
  • Understanding of data ownership inside one database
  • How in-process events replace direct calls
  • The path from module to service and what changes at extraction

Answers by level

Read the beginner answer first and notice what is missing.

Three mechanisms. First, a public API per module: a small set of exported functions or an interface; everything else is private, and a build rule (package visibility, an import linter, an architecture test) fails the build on an import from another module's internals. Second, data ownership: each module owns its tables, and no other module queries them — the Order module gets payment status through payments.getStatus(orderId), not through a join. Third, in-process events for consequences: when an order is placed, the Order module publishes OrderPlaced in the same transaction and the Notification module subscribes, so there is no direct call and no reverse dependency.

This sets up extraction. If the Payment module already has a public API and owns its tables, moving it behind HTTP means replacing the in-process implementation of that interface with a client, and moving its tables to their own database. The in-process events become messages on a queue. What changes is the failure model — the call can now time out — so the extraction is when timeouts, retries and idempotency get added, not before.

Green flags · Red flags

Strong green flag · Uses an outbox for in-process events so the consistency model does not change at extraction.
Green flags
  • Names build-time enforcement (import rules, package visibility, architecture tests)
  • Requires per-module table ownership with no cross-module queries
  • Uses in-process events for consequences to avoid reverse dependencies
  • Explains that extraction swaps an implementation for a client and moves tables
  • Notes that cross-module transactions are the hidden coupling
Red flags
  • "Folders per domain and code review are enough."
  • Allows cross-module joins because they are convenient
  • Thinks extraction is moving a folder to a new repo
  • Does not mention that the failure model changes when the boundary becomes a network

Follow-up questions

F1
The Order module needs the customer's email to send a receipt. How does it get it?
F2
What breaks when you extract a module that relied on a shared transaction?
F3
How do you find hidden coupling before extraction?

Scenario

A team calls their monolith "modular" but the Order module joins the payments table in four reports and the Inventory module updates order rows directly. They plan to extract Payments next quarter. Describe the enforcement you would add now and what the extraction will require.

Learn this topic