Case file — A4

Common Closure Principle

Things that change together belong together.

Code that changes for the same reason belongs in the same component. Code that changes for different reasons belongs in different components. Group by axis of change, not by category of object.

ByAdam LewisPublished3 May 2026Reading12 minVersionv1.0ConfidenceHigh
§0b

Opinion

I've had this argument with senior architects more times than I can count, usually when a folder labelled shared/ shows up in twenty-three pull requests in a single sprint. The folder is not shared; it is a dumping ground where two unrelated reasons-to-change have been hiding under one name. Robert C. Martin spelled this out in 2002 and again in 2017: classes that change together belong together, classes that change for different reasons belong apart.1Robert C. MartinClean Architecture (Pearson, 2017), Ch. 13 “Component Cohesion”. Pairs CCP with REP and CRP — a component should have one reason to change, one unit of release, and no operations its consumers don't use. The corollary is the part most teams miss: a component that does not change for the reasons its neighbours change should not live in the same component as them.

The confusion with the Open-Closed Principle is the word “closure.” OCP's “closure” is Bertrand Meyer's (closed against modification, open to extension): a class-level rule about how to add features without editing the base.2Bertrand Meyer / Robert C. MartinOpen-Closed Principle: Meyer, Object-Oriented Software Construction (1988); Martin, “The Open/Closed Principle” (2014). The two “closures” are different: Meyer's is class-level (closed against modification); Martin's CCP is package-level (classes that change together). CCP's “closure” is Martin's (classes that close, i.e. change, together share a component): a package-level rule about how to group. Same word, different scale, different object. The two rules are not even adjacent.

The lever that makes CCP visible is the same one that makes A1 Screaming Architecture visible: the folder tree. If every PR touches three folders, the folders are wrong. If most PRs touch one folder, the folders are matching the axes of change, which is what CCP is asking for. Co-locate by reason-to-change and the diff stops looking like a tangle.

Copy a note and link

Grab this short comment and drop it into a PR comment or an LLM chat to prompt the right change.

Things that change together belong together. A component should have one reason to change — when a single folder appears in PRs for two unrelated requirements, split it. Group by axis of change (orders, billing, identity), not by category (controllers, services, models).

/tenet/common-closure-principle/A4
§0c

AI eyes only

Rule: things that change together belong together. Group by reason-to-change, not by type.

Reject: top-level controllers/, services/, models/ that scatter one feature across three folders. Reject: extracting by category when the requirement is a single closure.

Generate: per-feature folders that contain the controller, service, model and tests for that one feature. Refactor by axis-of-change before refactoring by type.

Diagnostic: when a single requirement changes, the diff confines to one folder. If the diff spans three category folders, the architecture has split a single closure across them.

§0d

Why?

  • Feature PRs touch one folder. Code review reads one folder. The diff stops looking like a hairball spread across four category-shaped directories, and the merge conflict surface shrinks with it.
  • Caps the blast radius of a release. When a component has one reason to change, a deploy of that component changes one capability — the rest of the system isn't even rebuilt. Continuous deployment becomes a smaller risk decision.
  • Pairs cleanly with F1 Single Responsibility: SRP at the function, CCP at the package. The same instinct at two scales, mutually reinforcing. A package full of single-purpose functions naturally has one reason to change.
  • Surfaces axes of change explicitly. The team learns which capabilities move together and which move independently — which is exactly what you need to plan a release train, a rollout window, or a service split.
  • Falls out of A1 Screaming Architecture for free. When folders name domains, the domain's axis of change is the folder's axis of change by construction. The two rules are one rule wearing different hats.
  • Surfaces the split signal early. When a single component starts appearing in PRs for two unrelated requirements, CCP says split it. The rule turns a vague “this folder is getting big” intuition into a concrete refactor cue.
  • Coding agents reach for category-shaped folders because the training data is loud with MVC tutorials. CCP forces the question that matters — what reason-to-change does this code belong to? — and gives the agent a place to put the answer.
The receipts
Origins, quoted passages, evidence, the strongest counter-argument and the reply.
§1

Origins

Robert C. Martin coined the Common Closure Principle in the 1996 C++ Report series “Granularity” and codified it in Agile Software Development: Principles, Patterns, and Practices (2002).6Robert C. MartinAgile Software Development: Principles, Patterns, and Practices (Prentice Hall, 2002), and the 1996 C++ Report series “Granularity”. The 2002 book is where CCP, REP, and CRP are codified together as the cohesion triad. The single load-bearing line: classes that change together are packaged together. Martin paired CCP with REP (Release-Reuse Equivalency) and CRP (Common Reuse Principle) as the three Component Cohesion principles — each one rules out a different way of getting package boundaries wrong.

The book-length restatement is Chapter 13 of Clean Architecture (Pearson, 2017).1Robert C. MartinClean Architecture (Pearson, 2017), Ch. 13 “Component Cohesion”. Pairs CCP with REP and CRP — a component should have one reason to change, one unit of release, and no operations its consumers don't use. Martin makes the SRP-at-scale claim explicit: CCP is the SRP for components. A component should have only one reason to change; when it has two, it should split. The chapter pairs CCP with REP (a component is the unit of release) and CRP (don't force consumers to depend on things they don't use), and the three together form the package-cohesion triad.

Mathias Hartl's Principles of Package Design (Apress, 2018) is the practitioner companion.7Mathias HartlPrinciples of Package Design (Apress, 2018), Ch. 8 “The Common Closure Principle”. The single best practitioner walkthrough — “reasons to change” as the package-design unit, with worked PHP examples. Chapter 8 walks the CCP rule into worked examples; the chapter's teaching framing — “reasons to change” as the package-design unit — is the sharpest version of the rule in print. The book also pairs CCP with the SDP/SAP rules from A3 Stable Dependencies — the same component principles run together as a single package-design system.

The instinct is older than the name. Larry Constantine's cohesion taxonomy (1968, published 1974) had functional cohesion at the top of the hierarchy: a module exhibits functional cohesion when its parts contribute to a single, well-defined task — which is what “changes for one reason” is when applied to the function rather than the package.8Stevens, Myers & Constantine“Structured Design”, IBM Systems Journal 13(2), 1974. The cohesion taxonomy that predates CCP by twenty-eight years; functional cohesion at the top of the hierarchy is what “changes for one reason” looks like at function scale. CCP is the package-level form of the same instinct, given a metric and a name twenty-eight years later.

§2

Quotes

Gather into components those classes that change for the same reasons and at the same times. Separate into different components those classes that change at different times and for different reasons.

Robert C. Martin · Clean Architecture (2017), Ch. 13

The CCP is the SRP rephrased for packages.

Robert C. Martin · Agile Software Development (2002)

Duplication is far cheaper than the wrong abstraction.

Sandi Metz · The Wrong Abstraction (2016)

Cluster the entities and value objects into aggregates and define boundaries around each. Choose one entity to be the root of each aggregate.

Eric Evans · Domain-Driven Design (2003)
§3

Evidence

Twenty external sources, ranked by author authority. The first five are the canon; expand to see the rest, including the qualifiers and the named opposers. Each links out to its primary source.

  1. 01
    Robert C. Martin · 2017
    The single canonical chapter. Pairs CCP with REP and CRP into a triad: a component should have one reason to change, one unit of release, and no operations its consumers don't use.
  2. 02
    Robert C. Martin · 2002
    Source of CCP, REP, CRP, and the framing that CCP is the SRP for components. The 2002 book is the bridge between the 1996 C++ Report papers and the 2017 Clean Architecture restatement.
  3. 03
    Robert C. Martin · 2002–
    Online compendium of all SOLID + package-design principles in one page. CCP listed alongside REP and CRP as the package-cohesion triad; useful for the family-tree framing.
  4. 04
    Mathias Hartl · 2018
    Apress book. Ch. 8 walks the CCP rule into worked examples; the “reasons to change” teaching framing is the sharpest version of the rule in print. The single best practitioner companion.
  5. 05
    DevLead.io · 2019
    Short tutorial that walks through CCP with a small worked .NET example. Useful as the one-paragraph entry point for readers who want the rule before they read the book chapter.

Sixteen sources, three stances. The supporters cluster on Martin's package-cohesion canon and the SRP-at-scale framing, with Hartl's Principles of Package Design grounding the day-to-day. The qualifiers further down carry the harder reading: axes-of-change are easier to name in retrospect than in prospect, and forcing co-location too early can fossilise a classification that the next requirement reveals to be wrong. The opposers carry the steelman: package boundaries should be drawn by aggregate boundaries or by architectural rings, not by what changes together this quarter.

§4

Examples

Viewing: TypeScript.
Avoid
File{routes,types,services}/...
// Before: payment files scattered across three folders. One rule change touches three.// types/payment-types.ts:export type Donation = { amountPence: number; sanctuary: string };// services/payment-processor.ts:import { Donation } from "@/types/payment-types";export const applyGiftAid = (d: Donation) => d.amountPence * 1.05;// routes/payment/route.ts (third folder, same change axis):import { applyGiftAid } from "@/services/payment-processor";
Prefer
Filepayment/...
// After: one payment/ folder. One reason to change, one folder to edit.// payment/payment.types.ts:export type Donation = { amountPence: number; sanctuary: string };// payment/apply-gift-aid.ts (same folder):import { Donation } from "./payment.types";export function applyGiftAid(donation: Donation): number {  return donation.amountPence * 1.05;}// payment/route.ts (same folder again):import { applyGiftAid } from "./apply-gift-aid";
§4b

Enforcement

Viewing: TypeScript.

Apply these rules in eslint.config.mjs. The full enforcement across every tenet lives on the implementation page.

RuleToolCatches
import/no-cycleeslint-plugin-importimport cycles between sibling components — the symptom that two components have grown into each other rather than past each other; almost always a missing extraction.
import/no-internal-moduleseslint-plugin-importdeep imports into a component's internals when only the public surface was meant to be reached — the import shape that quietly defeats CCP's axis-of-change boundary.
import/max-dependencieseslint-plugin-importfiles that import from too many places — a leading indicator that the component owning that file has grown a second reason to change.
jscpdjscpdduplicate code blocks across components — the third-occurrence trigger that says CCP's axis-of-change has been identified and the component split is overdue.
eslint.config.mjsconfiguration snippet
import tseslint from 'typescript-eslint';
import importPlugin from 'eslint-plugin-import';

export default tseslint.config({
  files: ['**/*.{ts,tsx}'],
  plugins: { import: importPlugin },
  rules: {
    'import/no-cycle': ['error', { maxDepth: 10 }],
    'import/no-internal-modules': ['error', {
      allow: ['**/index.ts', '**/*/public/**'],
    }],
    'import/max-dependencies': ['warn', { max: 12 }],
  }
});
§4c

AI rules

File.cursor/rules/a4-common-closure.mdc
---
description: Prickles A4 — Common Closure Principle
globs: "**/*.{ts,tsx,js,jsx,py,java,php}"
alwaysApply: false
---

## Prickles A4 — Common Closure Principle

Code that changes for the same reason belongs in the same component. Code that changes for different reasons belongs in different components. Group by axis of change.

A component should have one reason to change. When a single component starts appearing in PRs for two unrelated requirements, split it.

CCP is the SRP for components. Don't group by category (controllers, services, models). Group by what changes together (orders, billing, identity).

Wait for the third change-together signal before forcing the group. The wrong package boundary is more expensive than the duplication it would have eliminated.

Repo layout, CI, and ESLint wiring for these paths live on /implementation — not repeated on every tenet.

§5

Counter-argument

Counter

The strongest steelman is Sandi Metz combined with the DDD aggregate camp.3Sandi Metz“The Wrong Abstraction” (2016): “Duplication is far cheaper than the wrong abstraction.” Applied to packages: a grouping built on yesterday's axis-of-change is one that next quarter's requirement teaches you was wrong. “Things that change together” is a property the team can only see in retrospect; in prospect it is a guess, and a folder structure built on a guess is one the next requirement reveals to be wrong. The Wrong Abstraction tax applies to packages too: a component that grouped two features because they changed together in Q1 carries the cost of that grouping when Q2 says one of the features needs to ship separately. The DDD camp sharpens the same point: the right grouping is the aggregate, defined by transactional boundary, not by historical axis of change. CCP's rule, applied without that discipline, can produce components that respect change history but violate the model.

§6

Counter-argument retort

Reply

Metz is right that yesterday's grouping is a guess about tomorrow's requirements, and the carve-out is the same one Three-Tier Hoisting uses: don't group until you have seen the duplication.3Sandi Metz“The Wrong Abstraction” (2016): “Duplication is far cheaper than the wrong abstraction.” Applied to packages: a grouping built on yesterday's axis-of-change is one that next quarter's requirement teaches you was wrong. CCP doesn't fire on the first feature; it fires when two features that have always shipped together stop being two features and start being one component's worth of work. Until then, leave them in their own folders. Once the change history makes the grouping obvious, the rule says collect them. The Metz tax exists; the rule respects it by waiting for the third change-together signal before forcing the group.

The DDD aggregate counter is the more interesting one because it isn't wrong, it's a different decomposition.4Eric EvansDomain-Driven Design (Addison-Wesley, 2003). The aggregate-shape counter: Evans groups by transactional consistency boundary; CCP groups by reason-to-change. The two converge in a healthy domain; when they diverge the model boundary wins. Evans groups by transactional boundary, Vernon by consistency boundary, Khononov by subdomain category. CCP groups by reason-to-change. In a healthy domain those three groupings converge — an aggregate that protects an invariant changes when that invariant's rules change, which is the same axis-of-change CCP names. When they diverge, the team has a real architectural decision to make: the model boundary is the one to defend, and CCP's grouping has to bend around it.5Vaughn VernonImplementing Domain-Driven Design (Addison-Wesley, 2013), Ch. 10 “Aggregates”. The practitioner companion to Evans on aggregate design; sharpens the consistency-boundary rule that CCP's axis-of-change has to respect. CCP isn't a replacement for aggregate design; it's the rule for the part of the package boundary aggregate design hasn't already settled.

The OCP confusion is the genuine residue and the case file pins it: OCP and CCP are different rules at different scales with one accidentally-shared word.2Bertrand Meyer / Robert C. MartinOpen-Closed Principle: Meyer, Object-Oriented Software Construction (1988); Martin, “The Open/Closed Principle” (2014). The two “closures” are different: Meyer's is class-level (closed against modification); Martin's CCP is package-level (classes that change together). OCP is class-level, about extension without modification (Meyer's “closure”). CCP is package-level, about grouping by axis of change (Martin's “closure”). The two never overlap. They earn separate tenets for the same reason F1 (function-scale) and A4 (package-scale) both belong: scale matters, and the same instinct earns separate rules at separate altitudes. See F1 Single Responsibility for the function-scale form; this is the package-scale form.

The novelty challenge — that “package by feature” or “vertical slices” already cover this — is partially fair. The difference CCP adds is the negative half of the rule: not just “put feature things together” (vertical slices) but also “don't put together things that change for different reasons” — the rule that carves features apart when they grow into separate change axes. Vertical Slice Architecture handles the positive half; CCP handles both halves.

§7

Notes

  1. [1]Robert C. MartinClean Architecture (Pearson, 2017), Ch. 13 “Component Cohesion”. Pairs CCP with REP and CRP — a component should have one reason to change, one unit of release, and no operations its consumers don't use.
  2. [2]Bertrand Meyer / Robert C. MartinOpen-Closed Principle: Meyer, Object-Oriented Software Construction (1988); Martin, “The Open/Closed Principle” (2014). The two “closures” are different: Meyer's is class-level (closed against modification); Martin's CCP is package-level (classes that change together).
  3. [3]Sandi Metz“The Wrong Abstraction” (2016): “Duplication is far cheaper than the wrong abstraction.” Applied to packages: a grouping built on yesterday's axis-of-change is one that next quarter's requirement teaches you was wrong.
Disagree? Found a hole in the argument? Take issue with this tenet →
Last revised: 2026-04-27