Boundaries, cohesion & dependency direction
Separate responsibilities around changes and contracts, then decide which boundaries need a network.
BY THE END, YOU CAN
- Identify cohesive responsibilities and coupling
- Separate logical modules from deployment units
- Compare a modular monolith with independently deployed services
Diagram description: HTTP handler → Queue policy (calls); Queue policy → Storage port (uses); SQLite adapter → Storage port (implements)
Begin with what changes together
Relay has submission policy, scheduling policy, execution, and result reporting.
If introducing a job type requires edits everywhere, the boundaries may be working against you. Group behavior around a clear responsibility and expose a small contract. Hide details that callers should not depend on, such as a storage schema or retry representation.
Good boundaries are discovered by examining change scenarios and ownership, not by making every file the same size. A small cohesive module can still contain several functions and types.
Keep policy independent of replaceable details
The scheduler can depend on a queue interface instead of calling a database driver throughout its policy code.
A storage adapter implements that interface. Tests can provide a controlled adapter and exercise scheduling without a network. This is dependency inversion: the high-level policy defines what it needs, and technical details conform.
Interfaces have a cost, so introduce them at meaningful seams. Do not create an abstraction for every line. A useful question is whether a probable change becomes local and whether failure behavior remains visible.
A network boundary changes the contract
Logical separation does not require separate services.
A modular monolith can keep clear interfaces while sharing deployment and transactions. Independently deployed services may support separate teams or isolated scaling, but calls can time out after work happened, schemas evolve asynchronously, and cross-service transactions need coordination. Evaluate the team’s operational capacity and the domain boundary before extracting a service.
For Relay, separating long-running workers from the API can be valuable even if both come from one repository and share a carefully managed data model.
COMMON FAILURE MODE
Calling a system loosely coupled because its parts use HTTP. Shared database tables, synchronized releases, and chatty request chains can create strong coupling across the network.
TRY IT YOURSELF
List the changes required to replace Relay’s storage adapter. Then list what changes when a job type is added. Use these two scenarios to critique your module boundaries.
Make the call in the laboratory.
The two-question checkpoint, your notes, and your saved progress are in the laboratory. Free, with no account.
Open in the laboratory ↗CONNECT IT TO RELAY
Draw Relay’s logical modules. For each boundary, write the contract, owner, dependencies, and what change it is intended to isolate.