Most backend systems do not collapse because of traffic. They collapse because complexity grows faster than understanding.
The pattern is predictable. Business logic bleeds across layers. Transaction boundaries become fuzzy. Orchestration happens in ways that cannot be traced. Eventually, every change feels dangerous, forcing a choice between rewriting everything as microservices or living with the mess.
There is a better path. This post presents an architecture that stays maintainable as monoliths grow, providing clear ownership without forcing premature distribution.
What Matters Most
This is not about minimizing layers or adding abstractions. Those are tools, not goals.
What actually matters:
- Clear ownership of business outcomes
- Explicit orchestration that can be traced and debugged
- Controlled side effects that do not surprise
- Predictable transactions that make rollback straightforward
The structure described here exists to serve these principles. When structure stops serving them, it should change.
Five Core Roles
Systems can be organized around five distinct roles. Each has a clear purpose and specific boundaries.
API Handlers
Handlers live at the edge of the system. They are translators.
Their job:
- Accept and parse input
- Validate request-level concerns (auth, rate limits, basic format checking)
- Immediately delegate to the appropriate workflow
What they do not do:
- Make business decisions
- Touch the database directly
- Contain domain logic
Handlers are thin adapters. They translate "HTTP request with JSON body" into "execute this workflow with these parameters." Nothing more.
Workflows
A workflow owns exactly one business outcome. When creating a post, processing an order, or following a user—that is a workflow.
Workflows are responsible for:
- Coordinating the steps needed for their outcome
- Applying business rules and validation
- Owning transaction boundaries
- Deciding whether the operation succeeded or failed
The key rule: if something needs to commit or rollback, it belongs in a workflow.
Transactions represent business outcomes. When debugging a partial state or race condition, the responsible workflow must be immediately identifiable. Diffuse transaction management creates debugging nightmares.
Orchestrators
Sometimes one business outcome requires multiple workflows. That is when orchestrators become necessary.
Orchestrators coordinate workflows when:
- Multiple workflows must execute in sequence
- The ordering matters
- No single workflow naturally owns the full story
What orchestrators do not do:
- Contain business rules
- Make business decisions
- Manage database transactions
Orchestrators are about flow, not logic. They specify "first do A, then do B, then do C" but they do not decide what happens inside A, B, or C.
Services
Services provide capabilities. They are the interface to external systems and datastores.
Services handle:
- Database queries and writes
- External API calls
- I/O operations
- File system access
Services do not:
- Orchestrate multiple steps
- Contain business flow logic
- Own transactions
If a service calls another service for business reasons (not just utility reasons), that coordination should move up to a workflow.
Helpers
Helpers are pure functions. They transform data without side effects.
Characteristics:
- Deterministic (same input → same output)
- Stateless
- No I/O
- Trivial to test
Helpers can be called by anyone. They are utilities, not domain logic containers.
How Dependencies Flow
Dependencies move in one direction:
Handlers → Workflows or Orchestrators
Orchestrators → Workflows
Workflows → Services and Helpers
Services → Helpers
Helpers → Helpers
This unidirectional flow prevents circular dependencies and makes orchestration traceable. When understanding how data flows through the system, following the arrows provides a clear path.
If a workflow needs to call a handler, or a service needs to call a workflow, that signals a problem. Either the boundaries are wrong, or a different solution is needed (like events or message queues).
Who Owns Database Sessions
The most important rule for keeping transactions manageable:
Only workflows create database sessions.
The component that opens the session owns the transaction. It decides when to commit, when to rollback, and when to close the connection.
Everyone else—handlers, orchestrators, services, helpers—receives a session as a parameter. They use it, but they never:
- Create sessions
- Commit
- Rollback
This makes transactional behavior explicit. When examining a workflow, the transaction boundaries are immediately clear. When debugging a partial write or deadlock, the responsible workflow is obvious.
This rule also prevents nested transaction complexity. If services could create their own transactions, the result would be implicit nesting, unclear commit points, and mysterious partial states.
Why This Approach Scales
This architecture scales because it provides clarity at every level:
- Business outcomes have clear owners. Each workflow owns one outcome. No shared responsibility, no confusion about ownership.
- Orchestration is explicit. The composition of complex operations from simpler ones can be traced.
- Side effects are controlled. I/O happens in services, pure logic lives in helpers.
- Transaction boundaries are obvious. The workflow defines the transaction scope.
The pattern works for:
- HTTP APIs serving synchronous requests
- Background job processors
- Event-driven systems processing messages
- Systems gradually evolving toward distribution
When extracting a service eventually becomes necessary, the boundaries are already clear. A workflow and its services can move together. No untangling of spaghetti required.
The Role of Structure
File organization and naming conventions reinforce these concepts, but they're not the point.
Use structure as:
- Guardrails during development
- Documentation for new team members
- Review aids during code review
The architecture comes first. Structure enforces it, but if your structure stops serving the architecture, change the structure.
When This Breaks Down
This pattern does not fail because of scale. It does not fail because of user load.
It fails when discipline erodes.
If business logic starts leaking into handlers, or services start orchestrating other services, or transaction ownership becomes implicit again—complexity creeps back in.
The architecture only works when boundaries are maintained. That is not a limitation of the pattern. It is the nature of all architecture.
Final Thoughts
A scalable monolith is not about avoiding microservices forever. It is about staying in control while growing.
By maintaining clear roles, explicit orchestration, and obvious transaction ownership, systems can:
- Scale with the team
- Evolve as requirements change
- Split cleanly when distribution becomes necessary
Code written today should be understandable tomorrow. That is what makes systems last.
