The Grain of Software

Adding a new service to a microservices system: easy. Spin up a container, wire it in, done.

Converting that system to a monolith: brutal. Months of coordinated work, teams blocking each other, half the contracts need renegotiation.

Same codebase. What changed? The direction of the change.

This broke my mental model. I’d been thinking about coupling as a property of code — something you measure once, get a number, act on. But that number kept lying. A system could be “loosely coupled” for one kind of change and hopelessly tangled for another. The number depended on the question.

Coupling isn’t a scalar. It’s a field. Give it a direction, it returns resistance.

the field

Here’s what I mean concretely. Take the same codebase and ask different questions:

  • Add a new API endpoint following existing patterns? Easy. Low resistance.
  • Change the auth scheme? Touches dozens of files, breaks assumptions everywhere. High resistance.
  • Migrate from REST to GraphQL? Against every structural decision ever made. Enormous resistance.
  • Swap the database? Everything assumes SQL. Nearly infinite resistance.

Call the resistance X(d)X(d) — how hard it is to push the system toward direction dd. The codebase doesn’t have “coupling = 7.” It has a resistance profile. Easy in some directions, brutal in others.

A monolith with a clean domain model: adding a new field to the core entity touches everything, but adding a new report that reads existing data is clean and isolated. The architecture didn’t change. The direction of intent did.

This is why some small-seeming changes blow up into massive PRs, and some sweeping changes land in an afternoon. Diff size doesn’t tell you much. What matters is whether you’re moving with the resistance profile or against it.

the sculpture

A codebase is a sculpture.

Think about woodworking. A woodworker doesn’t fight the grain arbitrarily. The grain is the wood — it reflects how the tree grew, what it endured, where it was strong. Working with the grain produces clean cuts and strong joints. Working against it produces splinters and weakness.

A codebase’s grain works the same way. Past decisions carve grooves — adding another React component to a React app flows along existing grooves, almost effortless. Adding real-time WebSocket support to an app built around request-response cuts across them. Event sourcing where everything assumes mutable state? Against the grain entirely.

The grooves are fossilized intent.

Tight coupling between auth and billing means someone understood these as inseparable. Clean separation between API and storage means someone believed these should evolve independently. That god object? At some point, it was someone’s mental model of the world.

Every architectural decision, every abstraction boundary, every “we’ll do it this way” conversation — these carve grooves that persist long after the people who carved them have moved on. X(d)X(d) is a map of what the system believes about itself.

You can’t avoid grooves. Every decision creates them. The question is whether yours point toward where you need to go.

complection

Rich Hickey distinguished complexity (many things) from complection (things braided together). The resistance field makes this precise.

Complection isn’t a property. It’s the shape of the field.

Same codebase, five directions:

  • X(add new endpoint)=2X(\text{add new endpoint}) = 2
  • X(change auth scheme)=15X(\text{change auth scheme}) = 15
  • X(RESTGraphQL)=200X(\text{REST} \to \text{GraphQL}) = 200
  • X(change database)=500X(\text{change database}) = 500
  • X(rewrite in another language)=X(\text{rewrite in another language}) = \infty

A highly complected system has deep grooves — low resistance along established paths, enormous resistance everywhere else. An uncomplected system has a flatter profile. Neither is inherently better. Deep grooves encode what the system knows how to do. The resistance you feel when fighting them is the system telling you: “I wasn’t built for this.”

fluidity

If the field tells you resistance in one direction, you can integrate over all directions to get overall steerability. Call it fluidity:

φ=1X(θ)dθ\varphi = \int \frac{1}{X(\theta)} \, d\theta

High fluidity means gentle resistance in most directions — push the system wherever you need it to go. Low fluidity means deep grooves, easy in some directions, nearly impossible in others.

Some codebases feel light and responsive. Others feel like pushing through mud. That difference is fluidity. It’s real and it affects deadlines, budgets, whether engineers stay or leave.

And fluidity suggests a tradeoff. Deep grooves often mean shared patterns, conventions, frameworks — things that reduce comprehension cost because everything’s predictable. But they increase X(d)X(d) for any direction that doesn’t follow the pattern.

High fluidity means less structure and more independence, but comprehension cost rises because there’s less you can assume.

Comprehensibility×Fluidityconstant\text{Comprehensibility} \times \text{Fluidity} \approx \text{constant}

You can trade one for the other. Every architectural style has both advocates and critics because they’re choosing different points on this tradeoff curve.

what this gets us

We have informal words for all of this — technical debt, coupling, complexity. McKinsey calls technical debt “dark matter.” You can infer its impact, you can’t see it. That’s not a theory. That’s an admission of ignorance dressed up in a metaphor.

X(d)X(d) is a theory. It makes testable predictions. A ten-line change that cuts across the grain should take weeks. A thousand-line change that flows with it should take hours. Resistance depends on direction, not on size.

If this is right, it should explain things we already know. Why monorepos keep winning. Why DRY sometimes backfires. Why Conway’s Law holds. Why refactoring gets deferred even when everyone agrees it’s needed.

Next: what the grooves explain.