CONCEPT Cited by 1 source
Separation of concerns¶
Definition¶
Separation of concerns is the practice of organising a system so that each component has a single, well-defined responsibility, and components interact only through explicit interfaces. A change inside one concern should not ripple into others. The term is due to Dijkstra (1974) but the principle is older than the word.
At practical altitudes it surfaces as:
- Module-level: a function or class has one reason to change.
- Service-level: separate control plane from data plane (concepts/control-plane-data-plane-separation), frontend from backend, compute from storage (concepts/compute-storage-separation).
- Pipeline-level: build code, release code, setup code, and application code each have distinct lifecycles and concerns.
"Separation applies to the whole system"¶
A recurring insight in production-engineering retrospectives is that separation of concerns applies to everything, not just application code. Slack's Quip/Canvas team articulated this as the core lesson of their 60 min → 10 min build refactor:
Once we understood the problem through the lens of separation of concerns, it became clear we couldn't succeed with changes to the build system alone. We had to cleanly sever the dependencies between our frontend and backend, our Python and TypeScript, our application and our build.
— Slack, Build better software to build software better
And:
The whole system is more than our application code. It's also our build code, our release pipeline, the setup strategies for our developer and production environments, and the interrelations between those components.
Slack identified three couplings that each violated separation of concerns and together produced a 60-minute build:
- Backend ↔ frontend: the Python backend's built artifacts were transitive inputs to every TypeScript-frontend bundle, so every Python change invalidated every frontend cache entry.
- Python ↔ TypeScript toolchains: Python scripts orchestrated
tscandwebpack, fusing two unrelated languages' build pipelines and forcing them to share state. - Application code ↔ build code: build logic was in Python, imported application modules, and ran inside the same process — so a refactor of an application module could break the build, and vice versa.
Each of the three couplings had both correctness and performance consequences.
Why separation of concerns improves performance (not just maintainability)¶
Slack's refactor is a good case study because the performance lesson is often underweighted relative to the maintainability lesson:
- Cache hit rate is higher when fewer inputs leak into each unit of work — separation of concerns makes explicit which inputs actually matter. (See concepts/cache-granularity.)
- Parallelism is higher when units of work don't share mutable state — separation lets a scheduler run them concurrently.
- Blast radius is smaller when a change to one concern cannot reach another — engineers can reason about the consequences of their changes, which itself accelerates velocity.
The "whole system" inventory¶
The Slack framing is worth lifting verbatim as a reminder of which concerns deserve separation discipline:
| Concern | Typical artefacts |
|---|---|
| Application code | business-logic source, app binaries |
| Build code | BUILD files, Makefiles, build rules |
| Release code | deploy scripts, rollback procedures |
| Setup code | Terraform, Ansible, provisioning scripts |
| Test code | fixtures, harnesses, mocks |
| Observability code | instrumentation, dashboards, alerts |
Each should be authored, versioned, and evolved with its own lifecycle; couplings between them should be explicit and minimal.
Anti-patterns¶
- Build code imports application code. Tempting when both are in the same language; creates a coupling where app refactors break the build. Slack's rewrite-in-Starlark is a defence against this.
- Deploy scripts with embedded business logic. The release pipeline should not know why a deploy is happening; the application should not know how it's being deployed.
- Test fixtures referencing production configuration. Makes tests brittle to config changes unrelated to what they verify.
- One repository, no boundaries. Monorepos are fine, but monorepos without explicit intra-repo separation of concerns (via build graphs, visibility rules, ownership files) collapse into balls of mud.
Related¶
- concepts/service-coupling — the axes along which separation fails at service level.
- concepts/layering-violation — a specific shape of separation-of-concerns violation (one layer doing another layer's work).
- concepts/control-plane-data-plane-separation — the canonical separation at distributed-systems scale.
- concepts/compute-storage-separation — the canonical separation at data-system architecture.
- concepts/backend-frontend-network-separation — the canonical separation at client architecture.
Seen in¶
- sources/2025-11-06-slack-build-better-software-to-build-software-better — canonical articulation that separation of concerns applies to build code, release code, and setup code, not just application code; three specific couplings (backend↔frontend, Python↔TypeScript toolchain, application↔build) producing a 60-minute build and quantitative impact on blast-radius reasoning.