Skip to content

Instantly share code, notes, and snippets.

@sergeliatko
Created June 20, 2026 13:36
Show Gist options
  • Select an option

  • Save sergeliatko/3556a1d57983484dc7090a450045057d to your computer and use it in GitHub Desktop.

Select an option

Save sergeliatko/3556a1d57983484dc7090a450045057d to your computer and use it in GitHub Desktop.
Architecture as Units, Environments, and Contracts

Architecture as Units, Environments, and Contracts

Purpose

Software architecture is written down in pieces today, scattered across diagrams, decision records, naming conventions, folder layouts, and people's memory. Each discipline keeps its own fragment, and the fragments do not line up, so the architecture rots quietly while the code keeps moving.

This document proposes one way to describe architecture that holds at every level, so the pieces stop drifting apart. It gives a small set of primitives and some guidance. Everything operational is left to you.

The whole thing in two smells

Keep these two even if you keep nothing else.

Your architecture smells when a relationship between two units is not written down anywhere.

Your architecture smells when you change the architecture without capturing why.

The most common form of the first smell is a connection that crosses a boundary with no contract for the crossing. When that happens, something is wrong in how the architecture is built or kept, and the rest of this document is a way to see it.

The core idea

Architecture is fractal. The same structure repeats at every level, from a single method to a whole company. Describe each level the same way and one set of rules covers all of them.

The model has three primitives: the unit, the contract, and the environment. A unit is a thing you isolate. A contract describes a unit. An environment is the law a unit sets for what lives inside it.

Units

A unit is anything you isolate from its surroundings and decide to treat as one thing. A unit can be a method, a module, a service, a data store, a team, a business, or a whole system. What a unit is made of does not matter; what matters is that you can tell it apart from what surrounds it.

A unit does something, lives at an address, and is described by three contracts.

Addresses

Every unit has an address, which is a path from the root, like a folder path. The system you choose to model is the root, and each step inward adds one segment to the path.

The depth of the path is the unit's level. Compare two units by their addresses: the shared prefix is their nearest common ancestor, and the remaining segments show whether they are siblings, ancestor and descendant, or cousins, and how many levels apart they sit.

Use stable keys as path segments, not positions. A positional index shifts when a neighbor is inserted, which makes unrelated units look as if they changed.

Keep the address a tree. Give each unit one home, and express a unit shared by two parents as a contract rather than a second address.

The root is not special. Its surroundings are the part of the world you chose not to model, and it holds a contract with that outside. There is no absolute root, only the level where you stopped.

Contracts

A contract describes a unit along one surface. A contract is flat: it has detail and length but no separate inside and outside view.

Each unit has three contracts: essence, behavior, and context.

Essence describes what the unit is. It states the unit's identity, what it is composed of, and the invariants that keep it itself. What a unit is composed of is the list of its child units, so opening the essence is how you move one level down.

Behavior describes how the unit interacts at its boundary. It states how you connect to the unit, what goes in, what comes out, what stays behind, and what side effects occur.

Context describes how the unit fits into the environment around it. It states what the unit needs from that environment, what it returns to it, and the rules it agrees to obey.

Each contract has two layers: a what and a why.

The what layer

The what layer states three things: what the surface provides, what it requires, and what it maintains.

Provides is what the surface guarantees to the outside. Requires is what the surface assumes from the outside in order to hold. Maintains is what stays true throughout.

Each line in the what layer is a clause. A clause carries a statement written to be checkable, a force, the evidence that verifies it, and a pointer to the check.

- id: stable-id
  statement: the claim, written to be checkable
  force: must | must_not | should | may
  evidence: static | runtime | policy | test | human | none_yet
  check: pointer to the rule or probe that verifies it

The why layer

The why layer is the intent: the reason the surface is shaped the way it is. Write the intent as answers to a fixed set of questions, so every unit answers the same questions and the answers stay comparable.

Some questions apply to every contract. Others apply to one surface only. The full intent of a unit appears only when the answers from all three contracts are read together.

Questions for every contract

  • Why does this exist in this form, at this level?
  • What alternatives were considered, and why were they not chosen?
  • What must stay true for this to remain the right choice?
  • What does this protect that must survive change?
  • How do you know it still holds?

Questions for the essence

  • Why is this isolated as one unit, and why this boundary?
  • What makes it itself, and what can change inside without changing what it is?
  • What is it composed of?

Questions for the behavior

  • How do you connect to it, and what goes in, comes out, stays, or happens as a side effect?
  • What does it expose, and what does it hide?
  • How is it allowed to fail?

Questions for the context

  • What is the law of the environment it lives in, and how does it conform?
  • What does it need to exist, and what does it return?
  • Who sustains it, and why in that way?

Environments

An environment is the law a unit sets for the units inside it. It states the rules those units operate under and the intent behind those rules.

An environment is what makes a unit distinct from its surroundings. A unit is what the unit does; an environment is the rules things are done under. Without an environment there is no law separating a unit from its level, and nothing to be distinct from.

State an environment once, on the enclosing unit, for the composition of its children. Do not copy it onto each child. Each child agrees to it through that child's context contract.

Write the environment first and shape units to fit it, or write the units first and read the environment from them. The direction does not matter once the architecture is fixed, because what is checked is whether the parts agree, not the order they were written.

A module shows the pattern. A module states its API as its behavior contract, and the environment of its level then states the rule that follows: reach this module only through its API, and let the module reach outside only through the contracts it is given.

A direct call that no rule permits is a violation. If the environment instead states that units at this level may call each other, and gives the contract for it, then they may.

Drift

Drift is any gap between what the architecture states and what is true, or any contradiction among the stated parts. Both smells are kinds of drift, and so are the cases below.

  • A unit's real behavior crosses a boundary that none of its contracts declare.
  • A unit reaches across levels, skipping the contracts in between.
  • A unit breaks a rule of the environment it lives in.
  • An environment and its enclosing unit contradict each other.
  • An environment's rule refers to a contract that nothing defines.
  • A requirement has no provider to satisfy it.
  • An invariant is broken in real behavior.
  • A stated reason no longer matches what the surface actually does.
  • A condition that made a choice valid no longer holds.

Where and how to write it down

You decide where all of this lives. The model cares about what you capture, not the file it sits in. Pick the granularity that fits the thing being described.

Write small things in place. For a method, a short comment next to it is enough, and it can be very simple.

Write larger things wherever they already belong. For a file, put the rules in a standalone document and let the file header point to it. For a module, put them in the module manifesto. For a folder or a feature, put them in the agents file that already sits there.

It does not matter where each piece lives. What matters is that you have tooling that can pull all the pieces together and read them as one architecture. The shape below is one way to write a unit and an environment, not the only way.

unit:
  id: orders
  address: [root, business, software, orders]
  essence:
    intent:    # answers to the common and essence questions
    provides:  # clauses
    requires:  # the child units this is composed of
    maintains: # clauses
  behavior:    # same four parts: intent, provides, requires, maintains
  context:     # same four parts: intent, provides, requires, maintains
environment:
  of: [root, business, software]
  members: [orders, payment, event-bus]
  intent:      # the reason this level is shaped this way
  rules:
    - id: reach-through-contract-only
      statement: a unit is reached only through its behavior contract, never its internals
      force: must
    - id: coordinate-via-bus
      statement: members coordinate only through the event-bus unit
      force: must_not

Detection, and what you build on it

This model is a detection approach, not a control system. It tells you where the architecture and the reality disagree. It does not decide what to do about it.

For each change, ask whether something changed, why it changed, whether a decision stands behind it, whether that decision was checked against the current architecture, and whether the decision changes the architecture. A change backed by a checked decision is a new state. A change with none is drift.

Because the tooling holds the whole state, an unexplained change can be reverted to the prior correct state. Detection and repair are the same store used two ways.

Checking new work becomes a lookup rather than a fresh proof. Asking whether a class you are about to write fits the architecture is a query against the store. Reasoning stays available for cases that need it, but the cheap path is retrieval, which is what lets this work on a large codebase.

Once you can detect drift, the rest is yours to design. Decide who resolves a drift when it appears. Decide which element is primary when two disagree, whether the environment wins, or the unit, or the intent. Decide how much of this you automate. Detection is the hard, shared part; the mechanics on top of it are local choices.

The rules in this document are guidance, not law. Build differently if you choose to. The only cost is that changing the approach constantly is itself a source of drift, so prefer to set the structure once and move it deliberately. Keep the whole architecture in one place that people, tools, and AI agents can all read, because a detector none of them can read cannot do its work.

Foundations

The model assembles ideas that already exist in the field.

  • Contracts use the provides, requires, and maintains shape from Design by Contract and assume-guarantee reasoning.
  • The rule that a unit is reached only through its surface, never its internals, is information hiding.
  • Addresses follow the path-from-a-root scheme of file systems, URIs, and package namespaces.
  • Environments as declared rules checked against reality follow policy engines such as Open Policy Agent.
  • Repair by reverting to a prior state follows version control and event sourcing.
  • Conformance as a query follows graph stores built over code.
  • Intent kept with the thing it explains follows architecture decision records, moved into the contracts so the rationale cannot drift away from the code.
  • The force words follow the must, should, and may convention of RFC 2119.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment