# Computation and resource

> This section covers the split at the core of the canon, between what happens to data, computation, and where data lives, a resource.

Page: Architecture · Principles
Canonical: https://banes-lab.com/software-architecture/principles#computation-and-resource

This section is stop 59 of 102 in the learning route. Previous: [03 - The canon is grouped twice](https://banes-lab.com/software-architecture/principles/the-canon-is-grouped-twice.md). Next: [05 - Execution joins the halves](https://banes-lab.com/software-architecture/principles/execution-joins-the-halves.md). It builds on [01 - Principles are typed](https://banes-lab.com/software-architecture/principles/principles-are-typed.md), [03 - The canon is grouped twice](https://banes-lab.com/software-architecture/principles/the-canon-is-grouped-twice.md).

This section covers the split at the core of the canon, between what happens to data, [computation](https://banes-lab.com/records/layer/computation-core.md), and where data lives, a [resource](https://banes-lab.com/records/layer/resource-core.md). The split is the first question to ask of any unit, because the two halves answer the same question with different rules. Who releases a resource decides whether it leaks, as shown in [D1·a who releases](https://banes-lab.com/software-architecture/principles#computation-and-resource-panel-a), and a lifetime runs from one owner to one release, as shown in [D1·b a lifetime](https://banes-lab.com/software-architecture/principles#computation-and-resource-panel-b) and typed in [D1·c ownership as types](https://banes-lab.com/software-architecture/principles#computation-and-resource-panel-c).

### What happens to data, where data lives

Most code mixes the two, so data that should have been frozen is mutated and handles that should have been owned are shared, and the leaks are found in production. A cache with no capacity grows until the process dies, a listener that is never unsubscribed fires against a component that was removed an hour ago, and both were written correctly by their authors. A stateful thing treated as stateless leaks, because nothing owns its release, and a stateless thing treated as stateful drifts, because it acquires a lifetime nothing bounds.

For this reason computation is stateless and frozen, a resource has one owner and a bounded lifetime, and release is structural. Release is by structure rather than by discipline, and a unit is frozen rather than managed wherever it can be a computation. In practice, every unit is sorted into computation or resource before it is designed. What computation produces is frozen, each variable is assigned once, and a computation carries no persistent state, so it can be replayed and its [rollback](https://banes-lab.com/records/arch/rollback.md) restores meaning rather than bytes. Every resource has one owner and a lifetime bound to that owner's, every open is paired with a close and every start with a stop, and release happens by scope or by an explicit destroy rather than by the developer or the model remembering. Every cache and pool is bounded by a declared capacity and an eviction policy, and registered where it can be seen.

To check this, take any resource and name its owner and the structure that releases it. A resource with two owners, or with a release that depends on a developer, will leak, and the only open question is when. The split does not let one rule stand in for the other. A computation is stateless while a resource is snapshotted before mutation, computed data is immutable while resource state is mutable but managed, and a broken resource invariant halts while a computation's uncertainty is marked and carried on.

### The computation half

The computation half is the canon's [computation core](https://banes-lab.com/records/layer/computation-core.md) read as one rule. [Immutability](https://banes-lab.com/records/arch/immutability.md) freezes what a computation produces. [Pure functions](https://banes-lab.com/records/arch/pure-functions.md) give it no effect but its return value, and [referential transparency](https://banes-lab.com/records/arch/referential-transparency.md) lets any call be replaced by its result. [Determinism](https://banes-lab.com/records/arch/determinism.md) makes the same input give the same output, which is what makes [repeatability](https://banes-lab.com/records/arch/repeatability.md) and [reproducibility](https://banes-lab.com/records/arch/reproducibility.md) properties rather than hopes, and [testability](https://banes-lab.com/records/arch/testability.md) follows from all of them at once.

[Statelessness](https://banes-lab.com/records/arch/statelessness.md) is the same rule seen from the outside. Nothing is retained between calls, so a computation can run anywhere and be replayed. [Idempotency](https://banes-lab.com/records/arch/idempotency.md) is its consequence at the edge, because a retry of a stateless step has one effect however many times it lands. Validation and [verification](https://banes-lab.com/records/arch/verification.md) are then cheap, since a frozen output can be compared against an expected one without a running system around it.

### The resource half

Being reachable is not the same as being useful. A collector frees what nothing reaches and keeps what something still points at, so an architectural leak survives collection because it is reachable, as with a cache entry that will never be read, an observer on a dead subject, or a handle held by an injection container.

For this reason every non-owning reference is weak or ephemeral, hidden retention in injection, mapping and observer machinery is made observable and bounded, and anything that outlives a single call carries an initialise, a run and a shutdown. [Graceful shutdown](https://banes-lab.com/records/arch/graceful-shutdown.md) is the resource rule at the scale of a process. A long-lived component with no shutdown is a leak by construction, and the leak is a hole in the contract rather than a bug in the code.

### Where the halves meet

[Caching](https://banes-lab.com/records/arch/caching.md) is where the two halves meet most often and where the split is most often lost. A cache holds computed data, so its entries are immutable, and it is a resource, so it has one owner, a declared capacity and an eviction policy. [Cache poisoning by design](https://banes-lab.com/records/arch/cache-poisoning-by-design.md) is what happens when the first half is forgotten, and a cache with no capacity is what happens when the second is.

[Configuration externalization](https://banes-lab.com/records/arch/configuration-externalization.md) is the same split at the boundary of the process. What the process reads at boot is a resource with one source and a validation at the door, and what it computes from that is frozen for the run. [Environment parity](https://banes-lab.com/records/arch/environment-parity.md) follows from it, because the same computation over a different resource behaves the same, or the difference is in the resource and can be named.

D1·a who releases

```mermaid
flowchart TB
resource["A resource is acquired"]
who{"Who releases it?"}
discipline["The developer remembering · it leaks"]
structure["The structure · scope, a finally, an explicit destroy"]
owner{"Exactly one owner?"}
weak["Every other reference is weak or ephemeral"]
shared["Shared ownership · ambiguity, then a leak"]
resource --> who
who -- discipline --> discipline
who -- structure --> structure --> owner
owner -- yes --> weak
owner -- no --> shared
```

D1·b a lifetime

```mermaid
stateDiagram-v2
[*] --> Acquired : the owner opens it
Acquired --> InUse : initialise
InUse --> InUse : use · one owner writes
InUse --> Released : shutdown · by scope or explicit destroy
InUse --> Halted : invariant broken · fail fast
Halted --> Released : the owner still releases
Released --> [*]
```

D1·c ownership as types

```typescript
export interface Owned<Handle> {
readonly acquire: () => Handle;
readonly release: (handle: Handle) => void;
}

export interface Lifecycle {
readonly init: () => void;
readonly run: () => void;
readonly shutdown: () => void;
}

export const withOwned = <Handle, Result>(owned: Owned<Handle>, use: (handle: Handle) => Result): Result => {
const handle = owned.acquire();
try {
return use(handle);
} finally {
owned.release(handle);
}
};

export interface Bounded<Item> {
readonly capacity: number;
readonly evict: "lru" | "fifo";
readonly items: readonly Item[];
}
```

## Links to

- [Computation Core](https://banes-lab.com/records/layer/computation-core.md)
- [Resource Core](https://banes-lab.com/records/layer/resource-core.md)
- [Rollback](https://banes-lab.com/records/arch/rollback.md)
- [Immutability](https://banes-lab.com/records/arch/immutability.md)
- [Pure Functions](https://banes-lab.com/records/arch/pure-functions.md)
- [Referential Transparency](https://banes-lab.com/records/arch/referential-transparency.md)
- [Determinism](https://banes-lab.com/records/arch/determinism.md)
- [Repeatability](https://banes-lab.com/records/arch/repeatability.md)
- [Reproducibility](https://banes-lab.com/records/arch/reproducibility.md)
- [Testability](https://banes-lab.com/records/arch/testability.md)
- [Statelessness](https://banes-lab.com/records/arch/statelessness.md)
- [Idempotency](https://banes-lab.com/records/arch/idempotency.md)
- [Verification](https://banes-lab.com/records/arch/verification.md)
- [Graceful Shutdown](https://banes-lab.com/records/arch/graceful-shutdown.md)
- [Caching](https://banes-lab.com/records/arch/caching.md)
- [Cache Poisoning by Design](https://banes-lab.com/records/arch/cache-poisoning-by-design.md)
- [Configuration Externalization](https://banes-lab.com/records/arch/configuration-externalization.md)
- [Environment Parity](https://banes-lab.com/records/arch/environment-parity.md)

## Linked from

- [Execution joins the halves](https://banes-lab.com/software-architecture/principles/execution-joins-the-halves.md)
