# The direction axis

> This section covers the direction axis, which says who may depend on whom, as shown in one way], while the classification described in [the layer spine says…

Page: Architecture · Model
Canonical: https://banes-lab.com/software-architecture#the-direction-axis

This section is stop 55 of 102 in the learning route. Previous: [03 - The layer spine](https://banes-lab.com/software-architecture/model/the-layer-spine.md). Next: [01 - Principles are typed](https://banes-lab.com/software-architecture/principles/principles-are-typed.md). It builds on [03 - The layer spine](https://banes-lab.com/software-architecture/model/the-layer-spine.md).

This section covers the direction axis, which says who may depend on whom, as shown in [D1·a one way](https://banes-lab.com/software-architecture#the-direction-axis-panel-a), while the classification described in [the layer spine](https://banes-lab.com/software-architecture/model/the-layer-spine.md) says what kind of thing a file is. The two axes are orthogonal, as typed in [D1·b two axes](https://banes-lab.com/software-architecture#the-direction-axis-panel-b). The canon's architecture styles are each one picture of the same direction rule, and a wrong-way edge has one repair, shown in [D1·c the repair](https://banes-lab.com/software-architecture#the-direction-axis-panel-c).

### Engine and consumer

Nothing refuses the import that crosses the wrong way, so the engine slowly learns about its consumers one convenient import at a time. A shared module gains one import from a page, the page changes, the module now breaks on every page, and the layering that was supposed to prevent that never had a rule behind it. A dependency rule that has no check behind it is a diagram, and the first import that crosses the wrong way is the one that was convenient that afternoon.

For this reason the dependency direction is an orthogonal axis that runs one way, from consumer to engine, and is held by its own check. The tier is declared as data a check reads, rather than inferred from what a file looks like. In practice, one tier is named the engine and the other the consumer. Each container is classified by prefix, and only the trees a prefix cannot decide are classified per file. Both classifications feed the check, which refuses an import that runs from the engine toward a consumer.

To check this, take any import and ask which tier each end is on. An import whose ends cannot be tiered is outside the model, and an import that runs from the engine toward a consumer is a move waiting to happen. The direction rule governs dependencies and says nothing about classification. A file is not on the engine tier because it is generic, and a consumer is not lower because it is specific. The tier is a fact declared about a tree, and the check reads the fact rather than inferring it.

### One rule, many pictures

The direction rule is the [dependency inversion principle](https://banes-lab.com/records/arch/dependency-inversion.md) drawn at the scale of a whole tree, and the canon's architecture styles are each one way of drawing it. [Hexagonal architecture](https://banes-lab.com/records/arch/hexagonal-architecture.md), [ports and adapters architecture](https://banes-lab.com/records/arch/ports-and-adapters-architecture.md) and [clean architecture](https://banes-lab.com/records/arch/clean-architecture.md) put the thing that knows nothing at the centre and let everything specific depend inward. [Layered architecture](https://banes-lab.com/records/arch/layered-architecture.md) draws the same arrow downward.

What they share is one direction and one rule. What they differ on is a picture, and the picture is not the mechanism. The mechanism is a tier declared for every file, a check that reads the [dependency graph](https://banes-lab.com/records/arch/dependency-graph.md) and refuses an edge from the engine toward a consumer, and a repair that is always a move.

[Inversion of control](https://banes-lab.com/records/arch/inversion-of-control.md) and [dependency injection](https://banes-lab.com/records/arch/dependency-injection.md) are the two techniques the rule pushes you toward. The only way an engine uses something specific without knowing it is to be handed it, and [extension points](https://banes-lab.com/records/arch/extension-points.md) with [runtime discovery](https://banes-lab.com/records/arch/runtime-discovery.md) are how the engine finds the consumers it must not import.

### Where a tier comes from

Most of a tree classifies by where it sits, because a container is built for one tier and everything under it inherits that. A few kinds of file resist that reading. Copy and type declarations serve whichever side names them, so their location says nothing about their tier, and those are classified one file at a time in data that starts empty.

What a file with no entry resolves to is a decision with a reason, not a default that fell out of the code. A type with no classification resolves to no tier, so the check treats it as unclassified rather than guessing a side. Copy with no classification resolves to the consumer tier, because copy is nearly always specific to one product. The two defaults differ because the cost of a wrong guess differs, and the general rule is that a default is chosen by which mistake is cheaper to discover.

### The repair is a move

The check reads a dependency graph derived from the tree, never the tree's claims about itself, and it fails closed, so a missing graph is a refusal rather than a pass over nothing. It refuses rather than repairs, because the only repair for a wrong-way import is to move the file.

A file that produces a [circular dependency](https://banes-lab.com/records/arch/circular-dependency.md), an upward dependency or a bypass of a declared entry point is in the wrong module. [Lazy evaluation](https://banes-lab.com/records/arch/lazy-evaluation.md) of an import, a direct path past the entry point, a re-export across modules and an import-sort trick each make the graph pass while the structure stays wrong. Those tricks preserve [concrete coupling](https://banes-lab.com/records/arch/concrete-coupling.md) and [inappropriate intimacy](https://banes-lab.com/records/arch/inappropriate-intimacy.md), because the engine still knows a consumer, only through a door no check watches. [Encapsulation](https://banes-lab.com/records/arch/encapsulation.md) and [information hiding](https://banes-lab.com/records/arch/information-hiding.md) are what the entry point protects.

D1·a one way

```mermaid
flowchart TB
engine["The engine tier · knows nothing about any consumer"]
consumer["The consumer tier · depends on the engine"]
consumer -- imports --> engine
engine -. never .-> consumer
prefix["Classified by container prefix"]
override["Two trees a prefix cannot decide · classified per file"]
prefix --> engine
prefix --> consumer
override --> engine
override --> consumer
```

D1·b two axes

```typescript
export const SPINE = ["domain", "application", "processing", "runtime", "infrastructure", "operations", "product"] as const;
export type Layer = (typeof SPINE)[number];

export const TIERS = ["engine", "consumer"] as const;
export type Tier = (typeof TIERS)[number];

export interface Classification {
readonly concern: Concern;
readonly layer: Layer;
}

export interface Direction {
readonly byPrefix: Readonly<Record<string, Tier>>;
readonly overrides: Readonly<Record<string, Tier>>;
}

export const tierOf = (path: string, direction: Direction): Tier | null =>
direction.overrides[path] ?? direction.byPrefix[prefixOf(path)] ?? null;

export const crossesUpward = (from: string, to: string, direction: Direction): boolean =>
tierOf(from, direction) === "engine" && tierOf(to, direction) === "consumer";
```

D1·c the repair

```mermaid
flowchart LR
edge["An edge from the engine toward a consumer"]
trick["A trick · lazy import, direct path, re-export, sort order"]
move["A move · to the module whose layer and entry point satisfy every dependency"]
lies["The graph passes and the structure stays wrong"]
holds["The graph and the structure agree"]
edge -. tempting .-> trick --> lies
edge --> move --> holds
```

## Links to

- [The layer spine](https://banes-lab.com/software-architecture/model/the-layer-spine.md)
- [Dependency Inversion Principle (DIP)](https://banes-lab.com/records/arch/dependency-inversion.md)
- [Hexagonal Architecture](https://banes-lab.com/records/arch/hexagonal-architecture.md)
- [Ports and Adapters Architecture](https://banes-lab.com/records/arch/ports-and-adapters-architecture.md)
- [Clean Architecture](https://banes-lab.com/records/arch/clean-architecture.md)
- [Layered Architecture](https://banes-lab.com/records/arch/layered-architecture.md)
- [Dependency Graph](https://banes-lab.com/records/arch/dependency-graph.md)
- [Inversion of Control (IoC)](https://banes-lab.com/records/arch/inversion-of-control.md)
- [Dependency Injection](https://banes-lab.com/records/arch/dependency-injection.md)
- [Extension Points](https://banes-lab.com/records/arch/extension-points.md)
- [Runtime Discovery](https://banes-lab.com/records/arch/runtime-discovery.md)
- [Circular Dependency](https://banes-lab.com/records/arch/circular-dependency.md)
- [Lazy Evaluation](https://banes-lab.com/records/arch/lazy-evaluation.md)
- [Concrete Coupling](https://banes-lab.com/records/arch/concrete-coupling.md)
- [Inappropriate Intimacy](https://banes-lab.com/records/arch/inappropriate-intimacy.md)
- [Encapsulation](https://banes-lab.com/records/arch/encapsulation.md)
- [Information Hiding](https://banes-lab.com/records/arch/information-hiding.md)

## Linked from

- [The layer spine](https://banes-lab.com/software-architecture/model/the-layer-spine.md)
- [This page is derived](https://banes-lab.com/anatomy/reading/this-page-is-derived.md)

## Evidence in the code

- [importsDiagram](https://banes-lab.com/source/tree/domain/converters/graph.converter.ts.md)
- [core](https://banes-lab.com/anatomy/tree/folder-core.md)
- [domain](https://banes-lab.com/anatomy/tree/folder-domain.md)
