Skip to main content

The Scalable Monolith

· 6 min read
Vipul Sharma

A scalable monolith is where most systems should begin.

Before scale, before traffic, before teams grow large, the real challenge is clarity. When clarity fades, systems slow down even while they continue to function. Changes feel heavier. Understanding takes longer. Confidence erodes.

Many teams respond by adopting microservices early. The motivation feels reasonable. Smaller units appear safer. Deployments seem isolated. Boundaries look cleaner.

Premature microservices usually amplify the original problem. Function calls become network calls. Debugging crosses process boundaries. State spreads across systems. Ownership becomes harder to trace.

The issue was never distribution. The issue was structure.

A monolith with strong internal boundaries can grow far longer than expected. It can scale in features, contributors, and responsibility while remaining predictable and understandable.

This post describes a scalable monolith architecture built around strict responsibility separation, downward dependency flow, and a single place where state changes occur.


What Actually Matters

This architecture optimizes for clarity.

Specifically:

  • Responsibilities remain explicit
  • Execution flow stays visible
  • Side effects stay contained
  • State changes live in one place

Everything else reinforces these goals.


Understanding the Layers

Rather than treating layers as rules, it helps to understand how authority increases as we move upward. Each layer adds responsibility while protecting the layers below it.


Utils

Utils sit at the bottom of the system.

They are focused units that interact with the outside world or perform low level operations.

Utils may:

  • Perform I O
  • Call external systems
  • Interact with infrastructure
  • Be synchronous or asynchronous

Utils do not make business decisions. They do not define execution flow. They do not coordinate multiple steps.

Utils are terminal. They do not call other utils.

Any higher layer may call utils directly.

Utils exist to isolate system boundaries and low level details.


Helpers

Helpers group domain logic that is meaningful but self contained.

They provide:

  • Domain calculations
  • Complex validations
  • Rules that operate on data

Helpers execute entirely in memory. They do not perform I O. They do not interact with external systems. They do not manage database sessions.

Helpers compose utils.

Higher layers call helpers whenever domain logic is required.

Helpers exist to keep workflows focused on execution rather than computation.


Workflows

Workflows sit at the center of the system.

A workflow defines the execution of a single cohesive operation. It controls the sequence of steps, applies rules, and determines completion or failure.

Workflows are responsible for:

  • Execution flow
  • Validation and decisions
  • Database session lifecycle
  • Commit and rollback

A database session exists only inside a workflow. The workflow that creates it closes it. The session is never passed as an argument and never escapes the workflow boundary.

Workflows may call helpers or utils directly.

Every state change in the system originates here.


Integrations

Some flows span more than one workflow.

Integrations exist to coordinate those cases.

They define execution order and data handoff between workflows.

Integrations:

  • Call workflows
  • Define sequencing
  • Contain no domain logic
  • Manage no database sessions

Integrations never call helpers or utils directly. Their responsibility ends at coordinating workflows.


API Handlers

Handlers live at the system edge.

They translate incoming requests into internal calls.

Handlers handle:

  • Input parsing
  • Request level validation
  • Authentication and rate limits

A handler calls either a workflow or an integration and returns the result. All meaningful execution happens deeper in the system.

Handlers remain thin by design.


Dependency Rules

The dependency model is strict and simple.

  • Higher layers may call any lower layer
  • Lower layers never call higher layers
  • No communication occurs within the same layer

This creates a clear downward flow. Control always moves in one direction. Knowledge never leaks upward.

This rule removes a large class of architectural ambiguity.


Database Sessions and State

Database sessions follow one rule:

They exist only inside workflows.

A workflow creates the session, uses it, and closes it. No other layer sees it. Sessions are never passed, wrapped, or shared.

This keeps transaction scope explicit and debugging straightforward.

When state looks wrong, the responsible workflow is immediately clear.


Schemas and Boundaries

Schemas exist only where boundaries matter.

Schemas appear in:

  • Handlers
  • Workflows
  • Integrations

Helpers and utils rely on simple function signatures. Structured contracts appear only where data crosses architectural or execution boundaries.

This keeps the system expressive without becoming schema heavy.


File System as Reinforcement

Folder structure reinforces responsibility.

  • api Transport layer and schemas
  • handlers Request translation
  • integrations Workflow coordination
  • workflows Execution and transactions
  • helpers Domain logic
  • utils System boundary utilities
  • models Data structure only

Models define shape. Behavior lives elsewhere.


Example Structure

app/
├── api/
│ └── auth/
│ ├── auth.py
│ ├── auth_schema.py
│ └── auth_handler.py

├── integrations/
│ ├── user_auth_integration.py
│ └── user_auth_integration_schema.py

├── workflows/
│ ├── google_sign_in_workflow.py
│ └── google_sign_in_workflow_schema.py

├── helpers/
│ ├── auth_token_helper.py

├── utils/
│ ├── google_oauth_client_util.py
│ └── token_verifier_util.py

└── models/
└── user_model.py

Execution remains centralized. System boundaries stay isolated. Domain logic stays pure.


Premature Microservices Revisited

Microservices address organizational and operational scaling. They do not repair unclear internal structure.

Weak boundaries multiplied across services create distributed confusion. Strong boundaries inside a monolith make later distribution mechanical.

A scalable monolith earns the right to split later.

When distribution becomes necessary, workflows and their supporting layers move together with minimal friction.


When This Breaks Down

This architecture relies on discipline.

When logic drifts upward, when helpers start controlling flow, or when state ownership becomes unclear, complexity returns.

Boundaries exist to protect understanding.


Final Thoughts

A scalable monolith prioritizes control before distribution.

Clear layers, downward flow, isolated system boundaries, and visible transaction scope allow systems to grow without losing shape. Teams move faster because the system stays understandable.

That quality determines whether software survives growth.