hebel. today · 2 November 202644 / 69
Technology · Software Architecture Patterns
Start with a modular monolith before distributing
Engineering teams often gravitate toward microservices to avoid the technical debt of a "big ball of mud," but premature distribution introduces severe operational complexity. A modular monolith provides the necessary middle ground, offering the…
From 6 newsletters · 1 min read · one thing to try
Engineering teams often gravitate toward microservices to avoid the technical debt of a "big ball of mud," but premature distribution introduces severe operational complexity (System Design Nuggets, System Design Nuggets). A modular monolith provides the necessary middle ground, offering the deployment simplicity and transactional consistency of a single codebase while enforcing strict boundaries between business domains (System Design Nuggets, Level Up Coding System Design Newsletter). By defining clear interfaces and ensuring each module owns its specific data, you maintain the ability to refactor boundaries in hours rather than weeks. This approach keeps development cycles fast, as changes to related modules remain within a single repository, build process, and deployment pipeline.
“The most expensive architectural mistake in software engineering is decomposing a system into services before you understand the domain.”
System Design Nuggets
Transitioning to microservices is only justified when specific components face concrete, measurable pressure to evolve independently (Level Up Coding System Design Newsletter). For instance, if one service requires specialized hardware like GPUs or scales at a rate ten times higher than the rest of the system, a network-boundary shift becomes a rational engineering choice. Once you cross that network boundary, you must account for new failure modes, including network latency, partial service unavailability, and the need for distributed consistency patterns like sagas to replace local ACID transactions. Do not treat microservices as a default; treat them as a response to specific scaling or organizational bottlenecks that a monolith can no longer accommodate.
When you do design for scale, focus on loose coupling through well-defined interfaces such as APIs or message brokers (The System Design Newsletter). This decoupling allows components to interact without deep knowledge of internal implementations, facilitating independent updates and deployments. For asynchronous workflows—such as processing orders where inventory, payment, and notification services must react to the same event—event-driven architecture prevents slow consumers from blocking the entire pipeline (The System Design Newsletter, React Java). However, these systems require careful handling of eventual consistency and idempotent consumers to ensure that late or duplicate messages do not corrupt business state. Always prioritize clear domain ownership and interface discipline before introducing the overhead of distributed infrastructure.
Try today · 120 min
Define a module boundary in your existing monolith.
Pick one domain, move its logic into a dedicated folder, and replace all direct database access with a single, clear interface.
You need
- A clear domain boundary (e.g., 'Payments' or 'Inventory')
- A defined interface (API or service layer)
- Access to your current codebase
Steps
- Identify a domain that needs to be independent.
- Move all related code into a single, isolated directory.
- Restrict all other modules from accessing this module's internal database tables.
- Expose only the necessary functionality through a formal interface.
- Update all external callers to use this new interface instead of direct calls.