Summary

Layering is how we keep a convenient change from becoming a permanent coordination problem. Runtime surfaces, stateful capabilities, deterministic policy, provider translation, data movement, executable jobs, infrastructure, and durable explanation each change for different reasons. The architecture makes those reasons visible.

Problem

Operational code tends to settle wherever the first request enters the system. A page needs a decision, so the decision lands in the page. A scheduled task needs durable state, so the state hides in the task. An external API returns an awkward payload, so its shape spreads through the rest of the application.

Each choice can be locally reasonable. Together they create a system in which the real workflow exists only across several callers and in the memory of the people maintaining it. A second surface then has two options: copy the behavior or reach into the first surface's internals. Both make the next change harder.

Principle

Each layer owns a kind of change, not a fashionable technology. We put new work where its responsibility will still make sense when another surface needs it, when a provider changes, or when a failed run has to be repaired without the original author present.

Platform Shape

app/ owns the runtime edges: routes, pages, workers, scheduler, and CLI. These surfaces turn requests into calls and state into something a person or process can use.

services/ owns reusable capabilities and persistence-backed domain behavior. libs/ owns contracts, deterministic transformations, and policy that does not need hidden state. integrations/ absorbs the vocabulary and failure modes of external systems so the rest of the platform does not have to. pipelines/ owns deterministic, rebuildable data movement. jobs/ makes scheduled and triggered sequences explicit without becoming the long-term owner of their logic. infra/ carries migrations, hooks, and deployment support. docs/ preserves the rationale and recovery knowledge that code alone cannot carry.

Consider a provider document that eventually requires human review. The integration translates the provider payload. A pipeline may normalize its claims. A service owns the resulting operational state and repair path. A job moves the workflow forward. An application shows the evidence and available action. The layers are not ceremony around the workflow; they prevent each handoff from becoming a fresh interpretation of what happened.

This is not a microservice diagram in waiting. It is a modular monorepo. We keep the pieces physically close because the company still benefits from coordinated changes, while making conceptual boundaries strong enough that a later split is an engineering decision rather than an excavation.

Tradeoffs

Layering creates more files, more naming decisions, and occasional friction when a change crosses several responsibilities. It can also become empty taxonomy if we split code without giving each piece a clear owner.

The alternative looks faster only from the first caller's perspective. We are optimizing for the second workflow, the failed run, and the maintainer trying to understand why a decision was made. The boundary earns its keep when those people can find the evidence and act without reconstructing the whole system.